likes
comments
collection
share

手撸Webpack自定义Plugin

作者站长头像
站长
· 阅读数 1

前言

webpack-plugin 向开发者提供了 webpack 引擎中完整的能力。通过插件扩展 webpack,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力。

与 loader 相同,plugin 的本质也是一个模块(它包含一个apply函数),符合 webpack 的一切皆模块的理念。

工作原理

webpack 就像一条串行的生产线,要经过一系列处理流程后才能将源文件转换成输出结果。在这条生产线上webpack 会把一些关键的流程节点暴露给开发者,这些节点称为 hook(钩子),可以类比于 Vue 的生命周期钩子。

webpack-plugin 通过监听需要关注的 hook,在 hook 中引入自定义的构建行为,就能加入到这条生产线中,去改变生产线的运作。

webpack 通过 Tapable 来组织这条复杂的生产线,Tapable 是 webpack 的核心功能库,它提供了统一的插件hook(钩子)类型定义。同时,为 hook 提供了三个方法注册插件功能:

  • tap:注册同步钩子和异步钩子。
  • tapAsync:回调方式注册异步钩子。
  • tapPromise:Promise 方式注册异步钩子。

plugin示例

以下是一个官方的 plugin 示例:

// 一个 JavaScript 类
class MyExampleWebpackPlugin {
  // 在插件函数的 prototype 上定义一个 `apply` 方法,以 compiler 为参数。
  apply(compiler) {
    // 指定一个挂载到 webpack 自身的事件钩子。
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        console.log('这是一个示例插件!');
        console.log(
          '这里表示了资源的单次构建的 `compilation` 对象:',
          compilation
        );

        // 用 webpack 提供的插件 API 处理构建过程
        compilation.addModule(/* ... */);

        callback();
      }
    );
  }
}

一个 plugin 必须包含一个apply函数,它有一个参数compilercompiler是一个包含了完整的 webpack 配置的对象,每次启动 webpack 构建时它都是唯一存在的。

apply函数中,通过compiler对象监听 emit 这个 hook 上注册了一个异步的方法。

可以在 webpack api 中知道,emit 钩子是一个异步钩子,因此在示例中用到了tapAsync这个方法往里加入了插件功能。

emit hook 回调方法中注入了compilation实例,compilation实例能够访问当前构建时的所有模块和相应的依赖。

前面有提到,hook 的类型定义是由 Tapable 提供的,一共有十几种:

// https://github.com/webpack/tapable/blob/master/lib/index.js
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

webpack 工作流程

webpack 的执行流程涉及两个关键对象:compilercompilation,它们贯穿 webpack 打包构建的 整个生命周期。它们在 webpack 工作流程中在不同的工作节点提供 hook,以便让开发者注册插件。

以下是一个 webpack 工作流程的简图:

手撸Webpack自定义Plugin

更多 hooks 信息可查阅官网:compiler-hookscompilation-hooks

自定义 Plugin

现在,我们动手撸一个自定义的 loader。需求是,分析打包输出的文件大小并生成说明文档。

首先,我们在项目根目录下创建文件夹 plugins,并创建一个 plugin 文件 analyze-plugin.js,同时写入基本内容:

class AnalyzePlugin {
  apply(compiler) {
    ...
  }
}

module.exports = AnalyzePlugin;

我们需要获取最终输出的文件内容,因此选择compileremit hook。这是一个异步钩子,我们使用 tapAsync方法注册插件,这种注册方式需要在回调函数中注入callback方法作为插件执行完成的标识。

compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
  const { assets } = compilation
  let sources = []
  sources.push(`# 文件大小分析`)
  sources.push('| 文件 | 大小(KB) |')
  sources.push('| --- | --- |')
  for (const filePath in assets) {
    sources.push(`| ${filePath} | ${Number((assets[filePath].size() / 1024).toFixed(2))} |`)
  }
  // 添加输出资源
  const fileContent = sources.join('\n')
  const newAsset = {
    source: () => fileContent,
    size: () => fileContent.length
  };

  // 使用 compilation.emitAsset 方法添加新资源
  compilation.emitAsset('analyze.md', newAsset);
  callback()
});

通过compilation对象,获取到即将输出的 assets 内容,对其遍历并按markdown语法拼接分析文档的内容。而后,将分析文档使用添加到打包输出的文件里一并生成。

如果希望更灵活一些,比如可以将输出的文件名、文件标题等信息放在配置中,通过插件的构造函数读取。

new AnalyzePlugin({
  outputFile: 'analyze.md',
  title: '分析打包资源大小'
})
class AnalyzePlugin {
  constructor(options = {}) {
    // 获取指定分析文件名与文件标题
    const { outputFile, title } = options
    this.outputFile = outputFile
    this.title = title
  }
  apply(compiler) {
    ...
  }
}

最后,我们执行一下打包命令,查看生成的分析文档。

文件大小(KB)
static/51d58c07297bb973f805.jpg366.95
index.html7.79
static/css/app.6087d3e9100e1ed1b996.css0.13
static/js/app.769ebb778600c7d87e2f.js22.04
static/js/runtime.ef6603f9c6cf8071ccb3.js7.43

到这,就算是完成了一次自定义 plugin 的开发。如果你有更多的需求,可以继续尝试开发。

Github:webpack-template/plugins⭐⭐⭐

更多开发 plugin 的细节可参考官方网站:webpack.js.org/contribute/…