Rspack 源码解析(十一):资产 Hook 与增量构建

Rspack 源码解析(十一):资产 Hook 与增量构建

本篇是 Rspack 源码解析系列第十一篇,也是这条构建主线的收束篇。ProcessAssetsPassAfterProcessAssetsPassAfterSealPass 位于 asset 生成之后;它们把最终处理权交给插件。本文同时回看贯穿整条流水线的 Cache、Mutation 和 Artifact,建立对 Rspack 增量构建设计的整体认识。

前言:生成文件不是终点

第十篇结束时,Compilation.assets 中已有 JS、CSS 以及模块附属资源。但压缩、生成 HTML、生成 manifest、更新 source map 等工作通常必须看到完整资产集合。因此流水线最后三步是:

text 复制代码
CreateChunkAssetsPass
  -> ProcessAssetsPass
  -> AfterProcessAssetsPass
  -> AfterSealPass

它们的实现短,原因不是工作少,而是所有具体能力都被放进插件 Hook。

ProcessAssets:面向完整 asset 集合的扩展点

ProcessAssetsPass 的主体只有:

rust 复制代码
async fn process_assets(&mut self, plugin_driver: SharedPluginDriver) -> Result<()> {
  plugin_driver.compilation_hooks.process_assets
    .call(self)
    .await
    .map_err(|e| e.wrap_err("caused by plugins in Compilation.hooks.processAssets"))
}

这里的 self 是完整的 Compilation,插件可以读取、更新、追加或删除 assets。典型能力包括:

插件行为 输入 输出
压缩 JS/CSS 已生成 source 更小的 asset source
Html 插件 entry 文件、public path 新的 index.html
manifest 插件 所有 asset 名称和 hash 映射 JSON 文件
source map 处理 source 与 map 元数据 额外 .map 文件或内联信息

之所以在此阶段做,而不是在 Module::code_generation() 做,是因为这些操作的单位是最终文件,可能需要同时看到多个 Chunk。

AfterProcessAssets 与 AfterSeal:生命周期的最后两个同步点

后两个 Pass 的语义分别是:

text 复制代码
after_process_assets:所有 processAssets 插件完成后执行
after_seal:整个 seal / compilation 阶段结束前的最终回调

它们让插件不必通过"猜测其他插件执行顺序"来协作:需要修改 asset 时挂在 process_assets,只观察最终结果或做收尾时挂在 after Hook。

这也是插件化系统最重要的价值之一:阶段顺序成为公开契约,插件之间不直接耦合实现细节。

PluginDriver:从 trait 到 Hook

每个 Rust 插件实现 Plugin trait,并在 apply 时向 ApplyContext 注册 Hook。PluginDriver 保存多个 hook 集合:

text 复制代码
CompilerHooks
CompilationHooks
NormalModuleFactoryHooks
ContextModuleFactoryHooks
NormalModuleHooks
ConcatenatedModuleHooks

编译 Pass 不需要枚举"有哪些插件",它只调用像 compilation_hooks.process_assets 这样的 typed Hook。对比将插件保存为 Vec<Box<dyn Plugin>> 后在每个阶段手写遍历,这种设计有三个好处:

  1. Hook 参数在编译期固定,插件不能拿错阶段数据;
  2. Series、SeriesBail 等 Hook 明确表达顺序与短路/重跑语义;
  3. 每个 Pass 只暴露它允许修改的 artifact,减少跨阶段误修改。

Cache、Artifact、Mutation:增量构建的三件套

前面各篇不断出现三个概念,它们组成 watch 模式能快的基础:

概念 定位 例子
Artifact 某阶段可复用的计算结果 ChunkGraph、CodeGenerationResults、Chunk hash
Mutation 本轮相对上一轮的变化记录 ModuleUpdateChunkRemoveModuleSetHashes
Cache 在 Pass 前后参与恢复和持久化的统一接口 before_module_idsafter_chunk_asset

一次文件变更后的通用模式:

text 复制代码
watch 检测到 src/a.ts 改动
  -> BuildModuleGraph 记录 ModuleUpdate
  -> 后续 Pass 读取 Mutation
  -> 计算 affected modules / chunks
  -> 清理这些对象的旧 Artifact
  -> 复用其余 Artifact
  -> 只重新生成必要的代码与 assets

例如 CreateChunkAssetsPass 会读取 Mutation::ChunkSetHashes,只渲染 hash 改变的 Chunk;而输出文件名依赖 [fullhash] 时,它会关闭局部缓存路径,因为一个全局 hash 变化可能改变所有文件名。

为什么每个 Pass 都有 before / after cache hook

部分 Pass 实现:

rust 复制代码
async fn before_pass(&self, compilation: &mut Compilation, cache: &mut dyn Cache) {
  cache.before_chunk_asset(compilation).await;
}

async fn after_pass(&self, compilation: &mut Compilation, cache: &mut dyn Cache) {
  cache.after_chunk_asset(compilation).await;
}

PassExt 把这类模板行为放进统一调度中。具体 Pass 只描述自己的业务操作,缓存系统在确定的边界恢复或记录状态。这个设计将"编译正确性"和"缓存加速"分层:禁用缓存不会改变构建语义,只会回退为更多全量计算。

全系列主线回顾

text 复制代码
CLI / JS API
  -> N-API binding
  -> Compiler 创建 Compilation
  -> Build Module Graph
  -> Finish / Seal / Dependency Optimization
  -> Build Chunk Graph
  -> Graph Optimization
  -> Module / Chunk / Runtime IDs
  -> Code Generation
  -> Runtime Requirements
  -> Hash + Runtime Module Code Generation
  -> Module Assets + Chunk Assets
  -> Process Assets + After Hooks
  -> 输出 dist

从 Rust 源码学习的角度,整条线反复出现同一种架构:

text 复制代码
显式 Artifact 作为阶段输入输出
  + typed Hook 作为扩展边界
  + 所有权迁移控制可变状态
  + 可并行计算与集中写回分离
  + Mutation 驱动局部重算

这比把所有状态塞进一个 Compilation 可变对象、在任意位置修改更适合大型编译器:依赖可见、缓存边界可见、并发边界也可见。

这一篇应该带走什么

  1. process_assets 是面向最终文件集合的核心插件阶段;
  2. after Hook 提供可靠的收尾时序,避免插件之间猜测执行顺序;
  3. PluginDriver + typed Hook 是 Rspack Core 可扩展且类型安全的关键;
  4. Artifact 保存结果、Mutation 描述变化、Cache 管理跨 Pass 复用;
  5. 增量构建的本质是精确失效与局部重算,不是简单重复执行全流程。

写在最后

至此,围绕 Compilation::run_passes() 的核心构建流水线已经完整走通。继续深入可以选择两条支线:沿 rspack_plugin_javascript 追一个 render_manifest 如何把模块 source 渲染成 bundle,或沿 rspack_binding_api 和 JS Hook 追一次跨 N-API 的插件调用。前者适合继续研究生成细节,后者适合理解 Rspack 如何兼容 webpack 生态。

相关推荐
PC2005_cloud41 分钟前
Windows 数据使用量页面卡死:1097 个热点档案和 161 MB 的 SRUM 库
前端·后端
烈风逍遥43 分钟前
AI大模型中fetch 和 ReadableStream为啥一起出现
前端·人工智能
PC2005_cloud1 小时前
Nginx 防盗链配置实战:用 referer 模块保护网站静态资源
前端·后端
福兮说1 小时前
Canvas 文字排版:measureText 量出来的宽度为什么总是不对
前端·javascript
孙启超1 小时前
【AI开发之Rust】第 12 课:并发模型与同步原语
开发语言·后端·rust
夏幻灵1 小时前
从零理解 Redux:从 state、action、reducer 到 Redux Toolkit 与异步请求
前端
烈风逍遥1 小时前
第四篇:AI 模块架构设计:多 Provider 切换、RAG 知识库与 Agent 编排
前端·后端·架构
柯南46681 小时前
【AI开发之Rust】第 13 课:async/await 与 tokio 异步运行时
rust·编程语言
用户80806181436931 小时前
JavaScript 面向对象入门到精通
前端·javascript