Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2018/11/20 - webpack原理与实践(二):实现一个webpack插件 #25

Open
lance10030 opened this issue Nov 20, 2018 · 1 comment

Comments

@lance10030
Copy link
Contributor

[TOC]

webpack原理与实践(二):实现一个webpack插件

关于 loaderplugin

  1. webpack 本身只能处理js文件。那如何处理如 css内联图像html 等这些文件了呢。这就需要用 loader 来进行转化。
  2. 通常 loader 功能比较单一,只专注于语言的转化。但是我们会有像压缩,分离文件这样的需求,这就需要通过插件来实现

实现一个插件

插件本身为一个构造函数,除了自己定义的方法外,会有一个 apply 方法 , apply 方法中传入全局唯一的 compiler 对象。

基本结构

class FileFilterPlugin {
    constructor(options){
        this.options = options;
    }
    apply(compiler) {
        
    }
}
  1. 插件 options 是你在使用插件的时候new 一个插件时传入的配置。通常做法是你可以有一些默认的配置,通过 Object.assign 来合并传入的配置和默认的配置,来得到最终的配置项。
  2. 第二个重点就是 apply 方法,该方法会传入一个 compile 对象。compile 对象和 compilation 对象是 webpack 打包过程中最重要的两个对象。他们都继承自 Tapable 关于,通过 Tapable 注入钩子进行流程管理。compile 对象每一次编译全局唯一,并且包含了compilation对象。一个 compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。compilation 对象也提供了很多事件回调供插件做扩展。在开发模式下,每次热更新都会生成一个新的compilation对象。

准备工作

前一篇文章中已经讲过 webpack 执行的一个流程了。我们需要知道插件应该挂载在执行过程中的哪个阶段,各个阶段都做了什么。这里就给大家贴几张总结的比较到位的图(来自《深入浅出webpack》):

image
image
image

然后你需要了解:

  • 你需要做的操作对应挂载在 compile 的哪个钩子以;
  • 这个钩子的触发方式有哪些需要选择哪一种, 注册在这个钩子下的插件时如何执行的
  • 下面这段代码截取compile对象的 hooks ,挂载在这些钩子上的插件如何执行取决于 Tapable, 关于 Tapable 的各种行为和原理, 仍然推荐这篇文章: 传送门
class Compiler extends Tapable {
	constructor(context) {
		super();
		this.hooks = {
			/** @type {SyncBailHook<Compilation>} */
			shouldEmit: new SyncBailHook(["compilation"]),
			/** @type {AsyncSeriesHook<Stats>} */
			done: new AsyncSeriesHook(["stats"]),
			/** @type {AsyncSeriesHook<>} */
			additionalPass: new AsyncSeriesHook([]),
			/** @type {AsyncSeriesHook<Compiler>} */
			beforeRun: new AsyncSeriesHook(["compiler"]),
			/** @type {AsyncSeriesHook<Compiler>} */
			run: new AsyncSeriesHook(["compiler"]),
			/** @type {AsyncSeriesHook<Compilation>} */
			emit: new AsyncSeriesHook(["compilation"]),
			/** @type {AsyncSeriesHook<Compilation>} */
			afterEmit: new AsyncSeriesHook(["compilation"]),

			/** @type {SyncHook<Compilation, CompilationParams>} */
			thisCompilation: new SyncHook(["compilation", "params"]),
			/** @type {SyncHook<Compilation, CompilationParams>} */
			compilation: new SyncHook(["compilation", "params"]),
			......
			}
	    
	}
    
}

在插件apply 方法的回调中我们可以传入 compilation 对象. 如果是异步, 通常还会传入一个回调函数,对资源进行处理.

例子

下面会根据实际开发中的问题举一个完整的例子.

  • 问题: 在开发小程序的 webpack 脚手架过程中我们遇到一个问题: 我们使用了 scss 来编写我们的样式, 但是在输出的文件中我们需要的是 wxss 文件. 首先我们通过 file-loader 已经完成了 css 文件到wxss 文件的转化,但是还有一个问题: 我们输出的文件中包含了 scss 文件, 需要在输出的时候去掉这个文件.

首先是第一中解决方案.我们找到了一个暴露 webpack 钩子的插件: event-hooks-webpack-plugin
该插件通过传入调用的钩子名称和相应的回调函数,在回调函数中执行钩子对应阶段可以执行的操作.插件本身很强大,并且代码很简单,但是需要你了解webpack的原理才能使用,也就是有一定的门槛.我先把我们的用法粘贴出来:

new EventHooksPlugin({
  emit: (compilation) => {
    // compilation.chunks 存放所有代码块,是一个数组
    compilation.chunks.forEach(function(chunk) {
      // chunk 代表一个代码块
      chunk.files.forEach(function(filename) {
        // compilation.assets 存放当前所有即将输出的资源,是一个对象
        let regex = /\.scss$/;
        if (regex.test(filename)) {
          delete compilation.assets[filename];
        }
      });
    });
  }
})

这里我们调用的钩子是 emit 从上文粘贴的对该钩子的介绍我们知道这是最后一个可以改变输出文件的钩子.传入的回调中我们对 scss 文件做了删除处理.到这里我们的目标实现了.但是同时也在思考: 本身这个插件使用时有门槛的,我们能不能自己写一个简单的插件来替代他,
让配置变得简单,不用了解webpack内部的实现就能使用.于是我们自己写了一个插件:

module.exports = class FileFilerPlugin {
    constructor(options) {
        this.options = options;
    }

    apply(compiler) {
        compiler.hooks.emit.tap(' FileFilerPlugin', compilation => {
        // compilation.chunks 存放所有代码块,是一个数组
        compilation.chunks.forEach((chunk) => {
        // chunk 代表一个代码块
          chunk.files.forEach(function(filename) {
            // compilation.assets 存放当前所有即将输出的资源,是一个对象
            // let regex = /\.scss$/;
            let regex = this.options.deleteFileReg
            if (regex.test(filename)) {
              delete compilation.assets[filename];
            }
          });
        });
      })
    }
};

如上,我们只需要在配置中传入需要删除的文件的正则表达式就能实现目标:

 new FileFilterPlugin({
      deleteFileReg: /\.scss$/
    }),

这个插件的实际上就是把我们第一个插件的配置转移到了插件源码中进行实现.但是针对这个场景的使用就变得简单了很多.

参考

  • <<深入浅出webpack>>
  • 官方文档(注意文档目前采用还是老版本插件的写法, 新版钩子都放到了hooks里面,大多数插件也对两种写法做了兼容)

广而告之

本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。

欢迎讨论,点个赞再走吧 。◕‿◕。 ~

@heboliufengjie
Copy link

有点简单了,继续

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants