Appearance
插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。创建插件比创建 loader 更加高级,因为你将需要理解一些 webpack 底层的内部特性来做相应的钩子。配置的插件在 compiler 执行 run 方法后、编译前就挂载了。之后在编译的各个阶段执行对应钩子。
plugin 本质上就是 class 类或构造函数,都有 apply 方法。注意:new plugin()只是把构造函数执行一遍,webpack 不会执行这个构造函数,而是执行构造函数的原型上的 apply 方法。
compiler 和 compilation
在插件开发中最重要的两个就是 compiler 和 compilation 对象。
- compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。 
- compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。 
compiler 的一些钩子
| 序号 | 钩子名称 | 钩子类型 | 参数 | 执行时机 | 
|---|---|---|---|---|
| 1 | entryOption | SyncBailHook | context,entry | 在 webpack 中的 entry 配置处理过之后 | 
| 2 | afterPlugins | SyncHook | compiler | 初始化完内置插件之后 | 
| 3 | beforeRun | AsyncSeriesHook | compiler | 开始正式编译之前 | 
| 4 | run | AsyncSeriesHook | compiler | 开始编译之后,读取 records 之前;监听模式触发 watch-run | 
| 5 | normalModuleFactory | SyncHook | normalModuleFactory | NormalModuleFactory 创建之后 | 
| 6 | beforeCompile | AsyncSeriesHook | params | compilation 实例化需要的参数创建完毕之后 | 
| 7 | compile | SyncHook | params | 一次 compilation 编译创建之前 | 
| 8 | compilation | SyncHook | compilation,params | compilation 创建成功之后 | 
| 9 | make | AsyncParallelHook | compilation | 完成编译之前 | 
| 10 | afterCompile | AsyncSeriesHook | compilation | 完成编译和封存(seal)编译产出之后 | 
| 11 | emit | AsyncSeriesHook | compilation | 生成资源到 output 目录之前 | 
| 12 | done | AsyncSeriesHook | stats | compilation 完成之后 | 
compilation 的一些钩子
| 序号 | 钩子名称 | 钩子类型 | 参数 | 执行时机 | 
|---|---|---|---|---|
| 1 | chunkAsset | SyncHook | chunk,filename | 一个 chunk 中的一个资源被添加到编译中 | 
| 2 | processAssets | AsyncSeriesHook | assets | |
| 3 | afterHash | SyncHook | 在编译被哈希(hashed)之后 | 
手写 plugin
编写插件需要知道以下几点:
- compiler 和 compilation 有哪些钩子
- 这些钩子的执行时机
- 钩子的参数
- 钩子的类型
ZipPlugin
将输出文件统一压缩成一个 zip 文件
// /plugins/zip-plugin.js
const { RawSource } = require('webpack-sources');
const JSZip = require('jszip');
class ZipPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    let that = this;
    compiler.hooks.compilation.tap('ZipPlugin', compilation => {
      compilation.hooks.processAssets.tapAsync(
        'ZipPlugin',
        (assets, callback) => {
          const zip = new JSZip();
          for (let filename in assets) {
            zip.file(filename, assets[filename].source());
          }
          zip.generateAsync({ type: 'nodebuffer' }).then(data => {
            assets[that.options.zipName] = new RawSource(data);
            callback();
          });
        }
      );
    });
  }
}
module.exports = ZipPlugin;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// webpack.config.js
const ZipPlugin = require('./plugins/zip-pluign');
module.exports = {
  plugins: [
    new ZipPlugin({
      zipName: 'main.zip'
    })
  ]
};
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
style-loader
function styleLoader(source) {
  let script = `
    let style = document.createElement('style')
    style.innerHTML = ${JSON.stringify(source)}
    document.head.appendChild(style);
  `;
  return script;
}
module.exports = styleLoader;
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
sass-loader
const sass = require('node-sass');
function sassLoader(source) {
  //异步
  const callback = this.async();
  sass.render(
    {
      data: source
    },
    (err, res) => {
      if (err) {
        console.log(err);
      } else {
        // console.log(res.css.toString());
        callback(null, res.css); //res.css 是一个Buffer
      }
    }
  );
  /*  同步
   * const result = sass.renderSync({data: source})
   * return result.css */
}
module.exports = sassLoader;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
less-loader
const less = require('less');
function lessLoader(source) {
  const callback = this.async();
  less.render(
    source,
    {
      filename: 'index.js'
    },
    (err, res) => {
      callback(null, res.css);
    }
  );
}
module.exports = lessLoader;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
 Cmq Webpack
Cmq Webpack