Skip to content
On this page

插件向第三方开发者提供了 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 的一些钩子

序号钩子名称钩子类型参数执行时机
1entryOptionSyncBailHookcontext,entry在 webpack 中的 entry 配置处理过之后
2afterPluginsSyncHookcompiler初始化完内置插件之后
3beforeRunAsyncSeriesHookcompiler开始正式编译之前
4runAsyncSeriesHookcompiler开始编译之后,读取 records 之前;监听模式触发 watch-run
5normalModuleFactorySyncHooknormalModuleFactoryNormalModuleFactory 创建之后
6beforeCompileAsyncSeriesHookparamscompilation 实例化需要的参数创建完毕之后
7compileSyncHookparams一次 compilation 编译创建之前
8compilationSyncHookcompilation,paramscompilation 创建成功之后
9makeAsyncParallelHookcompilation完成编译之前
10afterCompileAsyncSeriesHookcompilation完成编译和封存(seal)编译产出之后
11emitAsyncSeriesHookcompilation生成资源到 output 目录之前
12doneAsyncSeriesHookstatscompilation 完成之后

compilation 的一些钩子

序号钩子名称钩子类型参数执行时机
1chunkAssetSyncHookchunk,filename一个 chunk 中的一个资源被添加到编译中
2processAssetsAsyncSeriesHookassets
3afterHashSyncHook在编译被哈希(hashed)之后

手写 plugin

编写插件需要知道以下几点:

  1. compiler 和 compilation 有哪些钩子
  2. 这些钩子的执行时机
  3. 钩子的参数
  4. 钩子的类型

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
// 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

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

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

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

MIT Licensed