Compilation 钩子

INFO

Rspack 主要编译逻辑运行在 Rust 侧。出于稳定性、性能、架构等因素,在使用钩子时 Rust 侧编译对象传输到 JavaScript 侧后,对 JavaScript 侧的修改不会被同步到 Rust 侧。因此绝大部分钩子为“只读”。

Overview

compilation.addEntry()

创建模块图

true

false

success

failed

failed

success

EntryPlugin

compilation.addEntry(callback)

hooks.addEntry

ModuleFactory.create()

解析模块路径

compilation.buildModule()

hooks.stillValidModule

处理模块依赖

module.build()

运行 Loader 编译模块

解析模块依赖

hooks.failedModule

hooks.failedEntry

hooks.succeedEntry

callback()

compilation.finish()

compiler.compile()

compilation.finish(callback)

callback()


Stats

stats.toString()

compilation.createStatsOptions

生成 Stats 字符串


compilation.seal()

additional seal

End

true

false

HookNeedAdditionalSeal

hooks.unseal

compilation.seal()

callback()

生成产物信息

加工产物

shouldRecord=true

hooks.record

生成模块产物

true

false

shouldRecord

hooks.recordHash

hooks.beforeModuleAssets

生成模块产物

hooks.moduleAsset

生成 Chunk 产物

true

false

hooks.shouldGenerateChunkAssets

hooks.beforeChunkAssets

生成 Chunk 产物

hooks.renderManifest

生成模块和 Chunk 代码

生成 Compilation 哈希

hooks.beforeHash

生成 Compilation 哈希

hooks.fullHash

hooks.afterHash

生成模块哈希

hooks.beforeModuleHash

生成模块哈希

hooks.afterModuleHash

生成模块代码

hooks.beforeModuleCodeGeneration

生成模块代码

hooks.afterModuleCodeGeneration

收集运行时模块依赖

hooks.beforeRuntimeRequirements

hooks.runtimeRequirementInModule

hooks.additionalChunkRuntimeRequirements

hooks.runtimeRequirementInChunk

hooks.afterRuntimeRequirements

生成 ID 信息

生成编译记录

true

false

shouldRecord

hooks.recordModules

hooks.optimizeCodeOptions

hooks.recordChunks

生成模块 ID

hooks.reviveModules

hooks.beforeModuleIds

hooks.moduleIds

hooks.optimizeModuleIds

hooks.afterOptimizeModuleIds

生成 Chunk ID

hooks.reviveChunks

hooks.beforeChunkIds

hooks.moduleIds

hooks.optimizeChunkIds

hooks.afterOptimizeChunkIds

优化模块和 Chunk

优化 Chunk 内的模块

hooks.afterOptimizeChunkModules

hooks.shouldRecord

优化模块

hooks.optimize

OptimizeChunks

hooks.optimizeChunks

hooks.afterOptimizeChunks

优化 Chunk 组

hooks.afterOptimizeTree

创建 Chunk 图

生成 Chunk 图

hooks.beforeChunks

创建入口 Chunk

从入口出发生成 Chunk 图

hooks.afterChunks

优化模块图

hooks.optimizeDependencies

hooks.afterOptimizeDependencies

Start

compiler.compile()

compilation.seal(callback)

buildModule

只读

在模块被构建之前调用。

  • 类型: SyncHook<[Module]>
  • 参数:
    • Module:模块实例

executeModule

只读

若存在编译期执行模块,将在模块被执行时调用。

  • 类型: SyncHook<[ExecuteModuleArgument, ExecuteModuleContext]>
  • 参数:
    • ExecuteModuleArgument:模块运行参数
    • ExecuteModuleContext:模块运行上下文

succeedModule

只读

在模块成功构建后调用。

  • 类型: SyncHook<[Module]>
  • 参数:
    • Module:模块实例

finishModules

只读

当所有模块都没有错误地构建完成时调用。

  • 类型: AsyncSeriesHook<[Module[]]>
  • 参数:
    • Module[]:所有模块列表

optimizeModules

只读

在模块优化阶段开始时调用。

  • 类型: SyncBailHook<[Module[]]>
  • 参数:
    • Module[]:所有模块列表

seal

停止接收新模块并开始优化前触发。

  • 类型: SyncHook<[]>

afterOptimizeModules

只读

在模块优化完成之后调用。

  • 类型: SyncBailHook<[Module[]]>
  • 参数:
    • Module[]:所有模块列表

optimizeTree

只读

在优化依赖树之前调用。

  • 类型: AsyncSeriesHook<[Chunk[], Module[]]>
  • 参数:
    • Chunk[]:Chunk 列表:
    • Module[]:模块列表

optimizeChunkModules

只读

在树优化之后,chunk 模块优化开始时调用。

  • 类型: AsyncSeriesBailHook<[Chunk[], Module[]]>
  • 参数:
    • Chunk[]:Chunk 列表:
    • Module[]:模块列表

additionalTreeRuntimeRequirements

在树运行时依赖计算完成后调用。

  • 类型: SyncHook<[Chunk, Set<RuntimeGlobals>]>
  • 参数:
    • Chunk:Chunk 实例
    • Set<RuntimeGlobals>:运行时依赖集合

可通过修改运行时依赖集合以添加额外的内置运行时模块:

rspack.config.mjs
export default {
  entry: './index.js',
  plugins: [
    {
      apply(compiler) {
        const { RuntimeGlobals } = compiler.webpack;
        compiler.hooks.thisCompilation.tap('CustomPlugin', compilation => {
          compilation.hooks.additionalTreeRuntimeRequirements.tap(
            'CustomPlugin',
            (_, set) => {
              // 添加访问 compilation 哈希值的运行时模块
              set.add(RuntimeGlobals.getFullHash);
            },
          );
        });
      },
    },
  ],
};
index.js
// 将打印 compilation 的哈希值
console.log(__webpack_require__.h);

runtimeRequirementInTree

在根据运行时依赖以添加运行时模块时调用。

  • 类型: HookMap<SyncBailHook<[Chunk, Set<RuntimeGlobals>]>>
  • 参数:
    • Chunk:Chunk 实例
    • Set<RuntimeGlobals>:运行时依赖集合

可通过修改运行时依赖集合以添加额外的内置运行时模块,也可通过 compilation.addRuntimeModule 添加自定义运行时模块:

rspack.config.mjs
export default {
  entry: './index.js',
  plugins: [
    {
      apply(compiler) {
        const { RuntimeGlobals, RuntimeModule } = compiler.webpack;
        class CustomRuntimeModule extends RuntimeModule {
          constructor() {
            super('custom');
          }

          generate() {
            const compilation = this.compilation;
            return `
            __webpack_require__.mock = function(file) {
              return ${RuntimeGlobals.publicPath} + "/subpath/" + file;
            };
          `;
          }
        }

        compiler.hooks.thisCompilation.tap('CustomPlugin', compilation => {
          compilation.hooks.runtimeRequirementInTree
            .for(RuntimeGlobals.ensureChunkHandlers)
            .tap('CustomPlugin', (chunk, set) => {
              // 添加访问 publicPath 的运行时模块
              set.add(RuntimeGlobals.publicPath);
              // 添加自定义运行时模块
              compilation.addRuntimeModule(chunk, new CustomRuntimeModule());
            });
        });
      },
    },
  ],
};
index.js
// 将打印 "/subpath/index.js"
console.log(__webpack_require__.mock('index.js'));

runtimeModule

在运行时模块被添加后调用。

  • 类型: SyncHook<[RuntimeModule, Chunk]>
  • 参数:
    • RuntimeModule:运行时模块
    • Chunk:Chunk 实例

可通过 source 字段修改该运行时模块生成的代码。

rspack.config.mjs
export default {
  plugins: [
    {
      apply(compiler) {
        const { RuntimeGlobals } = compiler.webpack;
        compiler.hooks.compilation.tap('CustomPlugin', compilation => {
          compilation.hooks.runtimeModule.tap(
            'CustomPlugin',
            (module, chunk) => {
              if (module.name === 'public_path' && chunk.name === 'main') {
                const originSource = module.source.source.toString('utf-8');
                module.source.source = Buffer.from(
                  `${RuntimeGlobals.publicPath} = "/override/public/path";\n`,
                  'utf-8',
                );
              }
            },
          );
        });
      },
    },
  ],
};
index.js
// 将打印 "/override/public/path"
console.log(__webpack_require__.p);

processAssets

在产物输出之前进行修改产物。

  • 类型: AsyncSeriesHook<Assets>
  • hook 参数:
  • 参数:
    • Assets: Record<string, Source>: 一个对象,其中 key 是 asset 的路径名,值是由 Source 表示的 asset 数据。

Process assets 示例

  • PROCESS_ASSETS_STAGE_ADDITIONAL 阶段输出一个新的 asset:
compiler.hooks.thisCompilation.tap('MyPlugin', compilation => {
  const { Compilation } = compiler.webpack;
  compilation.hooks.processAssets.tap(
    {
      name: 'MyPlugin',
      stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
    },
    assets => {
      const { RawSource } = compiler.webpack.sources;
      const source = new RawSource('This is a new asset!');
      compilation.emitAsset('new-asset.txt', source);
    },
  );
});
  • 更新一个已经存在的 asset:
compiler.hooks.thisCompilation.tap('MyPlugin', compilation => {
  const { Compilation } = compiler.webpack;
  compilation.hooks.processAssets.tap(
    {
      name: 'MyPlugin',
      stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
    },
    assets => {
      const asset = assets['foo.js'];
      if (!asset) {
        return;
      }

      const { RawSource } = compiler.webpack.sources;
      const oldContent = asset.source();
      const newContent = oldContent + '\nconsole.log("hello world!")';
      const source = new RawSource(newContent);

      compilation.updateAsset(assetName, source);
    },
  );
});
  • 移除一个 asset:
compiler.hooks.thisCompilation.tap('MyPlugin', compilation => {
  const { Compilation } = compiler.webpack;
  compilation.hooks.processAssets.tap(
    {
      name: 'MyPlugin',
      stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE,
    },
    assets => {
      const assetName = 'unwanted-script.js';
      if (assets[assetName]) {
        compilation.deleteAsset(assetName);
      }
    },
  );
});

Process assets stages

下面是支持的 stage 列表,Rspack 会按由上至下的顺序依次执行这些 stages,请根据你需要进行的操作来选择合适的 stage。

  • PROCESS_ASSETS_STAGE_ADDITIONAL — 在编译中添加额外的 asset。
  • PROCESS_ASSETS_STAGE_PRE_PROCESS — asset 进行了基础的预处理。
  • PROCESS_ASSETS_STAGE_DERIVED — 从现有 asset 中派生新的 asset。
  • PROCESS_ASSETS_STAGE_ADDITIONS — 为现有的 asset 添加额外的内容,例如 banner 或初始代码。
  • PROCESS_ASSETS_STAGE_OPTIMIZE — 以通用的方式优化现有 asset。
  • PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT — 优化现有 asset 的数量,例如,进行合并操作。
  • PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY — 优化现有 asset 的兼容性,例如添加 polyfills 或者 vendor prefixes。
  • PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE — 优化现有 asset 的大小,例如进行压缩或者删除空格。
  • PROCESS_ASSETS_STAGE_DEV_TOOLING — 为 asset 添加开发者工具,例如,提取 source map。
  • PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE — 将 asset 内联到其他 asset 中来优化现有 asset 数量。
  • PROCESS_ASSETS_STAGE_SUMMARIZE — 整理现有 asset 列表。
  • PROCESS_ASSETS_STAGE_OPTIMIZE_HASH — 优化 asset 的 hash 值,例如,生成基于 asset 内容的真实 hash 值。
  • PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER — 优化已有 asset 的转换操作,例如对 asset 进行压缩,并作为独立的 asset。
  • PROCESS_ASSETS_STAGE_ANALYSE — 分析已有 asset。
  • PROCESS_ASSETS_STAGE_REPORT — 创建用于上报的 asset。

afterProcessAssets

只读

processAssets hook 无错误执行后调用。

  • 类型: SyncHook<Assets>
  • 参数:
    • Assets: Record<string, Source>:产物资源映射表
  • 示例:
compilation.hooks.afterProcessAssets.tap('MyPlugin', assets => {
  console.log('assets', Object.keys(assets));
});

afterSeal

只读

在 seal 阶段结束后调用。

  • 类型: AsyncSeriesHook<[]>

chunkHash

只读

触发来为每个 chunk 生成 hash。

  • 类型: SyncHook<[Chunk, Hash]>
  • 参数:
    • Chunk:Chunk 实例
    • Hash:Chunk 哈希实例

chunkAsset

只读

一个 chunk 中的一个 asset 被添加到 compilation 时调用。

  • 类型: SyncHook<[Chunk, string]>
  • 参数:
    • Chunk:Chunk 实例
    • string:产物文件名

childCompiler

只读

创建子 compiler 之后调用。

  • 类型: SyncHook<[Compiler, string, number]>
  • 参数:
    • Compiler:子编译实例:
    • string:子编译名称
    • number:子编译索引

statsPreset

只读

当使用预设 stats 配置时触发。接收一个 stats 配置对象,当插件管理 stats 预设配置时,它应当在配置对象上仔细地修改,而非直接替换整个配置对象。

  • 类型: SyncHook<[Partial<StatsOptions>, CreateStatsOptionsContext]>
  • 参数:
    • Partial<StatsOptions>:Stats 配置
    • CreateStatsOptionsContext:Stats 上下文

以如下插件为例:

compilation.hooks.statsPreset.for('my-preset').tap('MyPlugin', options => {
  if (options.all === undefined) options.all = true;
});

该插件确保对于预设 "my-preset",如果 all 选项未定义,则默认为 true

statsNormalize

只读

此钩子用于将选项对象转换为便于后续钩子使用的格式。它还确保缺失的选项被设置为默认值。

  • 类型: SyncHook<[Partial<StatsOptions>, CreateStatsOptionsContext]>
  • 参数:
    • Partial<StatsOptions>:Stats 配置
    • CreateStatsOptionsContext:Stats 上下文

以如下插件为例:

compilation.hooks.statsNormalize.tap('MyPlugin', options => {
  if (options.myOption === undefined) options.myOption = [];

  if (!Array.isArray(options.myOption)) options.myOptions = [options.myOptions];
});

在这个插件中,如果 myOption 缺失,会将其设置为 []。此外,它确保 myOption 始终是一个数组,即使它最初被定义为单个值。

statsFactory

只读

此钩子提供了对 StatsFactory 的访问,以调用其钩子。该类用于构造 Stats 对象。

  • 类型: SyncHook<[StatsFactory, StatsOptions]>
  • 参数:

statsPrinter

只读

此钩子提供了对 StatsPrinter 的访问,以调用其钩子。该类用于打印 Stats 信息。

  • 类型: SyncHook<[StatsPrinter, StatsOptions]>
  • 参数: