乐于分享
好东西不私藏

Rspack Webpack 插件的核心钩子:Compiler 与 Compilation 的生命周期,如何精准干预打包流程

Rspack Webpack 插件的核心钩子:Compiler 与 Compilation 的生命周期,如何精准干预打包流程

插件不是“配一下就能用”,它是一段在构建流程的特定时间点执行的代码

写过 Webpack 插件的人,大概都见过这段模板代码:

这段代码能跑,但你有没有想过:compiler是什么?compilation是什么?emit这个钩子为什么能拿到产物?如果我想在模块解析阶段动手,该用哪个钩子?

今天从 Compiler、Compilation、以及一条完整构建时间线三个角度,拆解 Rspack / Webpack 插件机制。理解了这些钩子,你才知道“在哪个阶段做什么事”。

一、Compiler 和 Compilation:两个核心对象

Compiler 是整个构建流程的“总控”。它包含了完整的 Webpack / Rspack 环境配置,从启动到结束,全程只有一个 Compiler 实例。它负责触发全局级别的生命周期事件:beforeRunrunwatchRundonefailed 等。

Compilation 是每次构建的“现场”。每次重新编译(比如文件变更触发的增量构建)都会生成一个新的 Compilation 实例,负责当前这次构建的所有细节:模块解析、依赖收集、优化、分块、产物生成。

两者的关系可以这样理解:Compiler 是“工厂”,Compilation 是“产品线”。Compiler 启动一次,可能触发多次 Compilation(比如 watch 模式下文件变了就重新跑一次构建)。

二、构建时间线:钩子在什么时机触发?

一条典型的构建流程,钩子的触发顺序大致如下:

启动阶段

beforeRunrun(非 watch 模式)或 watchRunwatch 模式)。beforeRun 适合做构建前的准备工作,比如清理临时目录。watchRun 可以通过compiler.modifiedFiles 拿到变更的文件列表。

编译阶段

beforeCompile → compile → thisCompilation compilation → make → finishMake → afterCompilemake 阶段是构建模块依赖图的核心——以 entry 为起点,递归解析所有依赖,用 loader 处理每个模块。

产出阶段

shouldEmit → emit → afterEmit  doneemit 钩子是在产物写入磁盘之前触发的,这是修改产物的最后机会。

注意beforeRun run 只在命令行build 时触发,watch 模式用 watchRun 替代。

三、几个关键钩子的用途

compilation thisCompilation

这两个钩子都在 Compilation 创建时触发,区别在于:thisCompilation 不会被复制到 child compiler,compilation 会被复制。如果你写的插件只关心主构建流程,用thisCompilation 就够了;如果插件需要和子编译器(比如 worker 或 child 构建)交互,用 compilation

用法示例(Rspack 官方):

拿到compilation之后,你可以在模块构建、优化、产物生成等更细粒度的节点上做操作。

processAssets

Rspack 和 Webpack 5 推荐用processAssets 替代直接在emit 里操作 compilation.assets。它有明确的 stage 参数,可以控制插件在哪个阶段处理产物:PROCESS_ASSETS_STAGE_OPTIMIZE 做优化,PROCESS_ASSETS_STAGE_ADDITIONS 加内容,PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE 压缩。

make afterCompile

make 是构建模块依赖图的核心阶段,如果你需要干预模块解析过程,这是最合适的时机。afterCompile 在模块构建完成、进入 seal(封存)阶段之前触发,适合做模块级别的收尾处理。

四、Rspack 的特殊之处

Rspack 的核心编译逻辑跑在 Rust 侧,因此大部分 Compilation 钩子在 JavaScript 侧是只读的。对 JavaScript 侧的修改不会同步到 Rust 侧。这意味着某些在 Webpack 里惯用的操作方式,在 Rspack 里可能不生效。

建议:优先使用 Rspack 官方文档里标记了可用的钩子和操作方式,比如processAssets这类通过官方 API 操作产物的方式。

最后

理解 Compiler 和 Compilation 的生命周期,是写插件的基本功。知道钩子在什么时候触发、能拿到什么数据,你才能精准地“插入”到构建流程里——而不是靠试错。

下次写插件,先问自己:我要干预的是“全局流程”还是“单次构建”?这个操作应该发生在模块解析阶段、优化阶段、还是产物生成阶段?答案会告诉你该用哪个钩子。

如果这篇文章对你有帮助,欢迎点赞、在看、转发。