webpack plugin

前言

Plugin 是 webpack 中最核心的机制,且是 webpack 生态中最关键的一部分,它让社区用户可以自定义 Plugin 来控制 webpack 的编译过程。

该篇文章将会讲解 Plugin 的相关机制。

Plugin

webpack 在编译过程中,会初始化不同的 Hook,用户可以在 Plugin 通过这些 Hook 去注册相关的事件,在打包过程过会在不同阶段去触发这些事件从而实现自定义行为。

该逻辑是由 Tapable 机制实现的,具体可以查看该文章

Plugin 的主要构成

class DonePlugin {
  apply(compiler) {
    // 调用 Compiler Hook 注册额外逻辑
    compiler.hooks.done.tap('Done Plugin', () => {
      console.log('compilation done');
    });
  }
}

module.exports = DonePlugin;

上述 Plugin 主要实现了在编译完成后输出”compilation done”

  • Plugin 是一个 class,也可以是一个 Function
  • Plugin 对象上要有一个 apply 方法,其接受 compiler 参数,在 webpack 创建 compiler 的时候会调用 Plugin 上的 apply 方法
  • 在 apply 中调用 Hook 方法,通过 hook.tap 注册对应的事件函数
  • 根据不同 Hook 类型在 webpack 不同阶段执行

Plugin 主要对象

compiler

compiler 对象包含了当前运行 webpack 的配置,包括 entry、output、loaders 等配置,该个对象在启动 webpack 时被实例化,而且是全局唯一的。Plugin 可以通过该对象获取到 webpack 的配置信息进行处理。

常用的 hook 如下:

钩子类型什么时候调用
runAsyncSeriesHook在编译器开始读取记录前执行
compileSyncHook在一个新的 compilation 创建之前执行
compilationSyncHook在一次 compilation 创建后执行插件
makeAsyncParallelHook完成一次编译之前执行
emitAsyncSeriesHook在生成文件到 output 目录之前执行,回调参数:compilation
afterEmitAsyncSeriesHook在生成文件到 output 目录之后执行
assetEmittedAsyncSeriesHook生成文件的时候执行,提供访问产出文件信息的入口,回调参数:file,info
doneAsyncSeriesHook一次编译完成后执行,回调参数:stats

更多钩子可以点击查看

compilation

compilation 对象代表一次资源构建,其职责就是构建模块和 chunk,并利用插件优化构建过程

compiler 代表了整个 webpack 从启动到关闭的生命周期,而 compilation 只是代表了一次新的编译,只要文件有改动,compilation 就会被重新创建

常用的 hook 如下:

钩子类型什么时候调用
processAssetsAsyncSeriesHookasset 处理
buildModuleSyncHook在模块开始缉译之前触发,可以用于修改模块
succeedModuleSyncHook当一个模块被成功编译,会执行这个钩子
finishModulesAsyncSeriesHook当所有模块都缉译成功后被调用
sealSyncHook当一次 compilation 停止接收新模块时发
optimizeDependenciesSyncBailHook在依赖优化的开始执行
optimizeSyncHook在优化阶段的开始执行
optimizeModulesSyncBailHook在模块优化阶段开始时执行,插件可以在这个钩子里执行对模块的优化,回调参数:modules
optimizeChunksSyncBailHook在代码块优化阶段开始时执行,插件可以在这个钩子里执行对代码块的优化,回调参数:chunks
optimizeAssetsAsyncSeriesHook优化存储在 compilation.assets 中的所有 asset,回调参数:asset

更多钩子可以点击查看

手写 Plugin

fileListPlugin

需求:每次打包完成之后,需要输出一个文件清单的 markdown 文件到 dist 目录

思路如下:

  • 需要输出将 markdown 文件输出到 output 文件中,选择合适的钩子 compiler.hooks.compilation/compilation.hooks.processAssets 处理 assets
  • 通过 asset 获取到文件数量,自定义 md 文件写入相关的 content
  • 给 dist 文件夹里添加一个资源名称为 fileListName 的变量,写入资源的内容和文件大小
  • 执行回调,让 webpack 继续执行
class FileListPlugin {
  constructor(opts) {
    this.fileName = opts?.fileName || 'fileList.md';
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('FileListPlugin', (compilation) => {
      compilation.hooks.processAssets.tapAsync(
        {
          name: 'FileListPlugin',
          stage: 'PROCESS_ASSETS_STAGE_ADDITIONAL',
        },
        (assets, callback) => {
          const length = Object.keys(assets).length;

          let content = `# ${length} file${
            length > 1 ? 's' : ''
          } emitted by webpack\n\n`;

          for (let filename in assets) {
            content += `- ${filename}\n`;
          }

          assets[this.fileName] = {
            source: () => content,
            size: () => content.length,
          };
          callback();
        },
      );
    });
  }
}

如果通过 compiler.hooks.emit 来获取 compilation.assets 会有警告,该 hook 会被遗弃掉,因此改为使用 compilation.hooks.processAssets

autoPolyfillsWebpackPlugin

需求:根据 browserslistrc 和 coreJS 生成对应的 polyfill

思路如下:

  • 获取 browserslistrc 通过 core-js-builder 在当前目录下生成 polyfill 文件
  • 需要通过 html-webpack-plugin 的 beforeAssetTagGeneration 在生成 html 之前插入该 polyfill 文件

具体代码请查看

Untitled