Rspack 源码解析(十):Hash 与 Asset 生成

本篇是 Rspack 源码解析系列第十篇。经过 runtime requirements 阶段后,Rspack 已经拥有生成 asset 所需的全部语义信息。本篇追踪 CreateHashPass、CreateModuleAssetsPass 和 CreateChunkAssetsPass,理解 CodeGenerationResults 如何成为带 hash 的 main.js、异步 chunk 和附属资源。

前言:源码变成文件之前还有两层

第五篇的结果是每个 Module 在某个 runtime 下的 source;第九篇补上 runtime modules。距离 dist/main.js 仍需两步:

text 复制代码
中间结果
  -> 计算 module / chunk / compilation hash
  -> 由插件提供 render manifest
  -> emit_asset 写入 Compilation.assets

源码中的执行顺序为:

text 复制代码
CreateHashPass
  -> CreateModuleAssetsPass
  -> CreateChunkAssetsPass

CreateHashPass:hash 不是一个值

Pass 入口做两件事:

rust 复制代码
async fn run_pass(&self, compilation: &mut Compilation) -> Result<()> {
  compilation.create_hash(plugin_driver).await?;
  compilation.runtime_modules_code_generation().await?;
  Ok(())
}

第一步计算 hash,第二步为第九篇已选择的 RuntimeModule 生成 source。之所以 runtime module 的生成放在 hash 后面,是因为它的内容和名字也必须参与正确的缓存失效关系。

至少要区分三类 hash:

名称 代表什么 常见用途
Module hash 单个模块生成结果的内容/语义 复用 code generation cache
Chunk content hash 一个 Chunk 中特定 source type 的内容 [contenthash] 文件名
Full hash 整次 compilation 的整体标识 [fullhash] 与全局关联内容

如果某个 Chunk 依赖 full hash,局部变化可能影响全局命名。源码会通过 dependent_full_hash Hook 找到这类 Chunk,并禁用相关增量 Pass,因为它已经不是局部可重算的问题。

Hash 的并行与增量边界

create_hash() 会并行检查 Chunk 是否依赖 full hash,并根据 Mutation 选取受影响 Chunk。这个模式和前文相同:

text 复制代码
没有可用 artifact:遍历全部 Chunk
有可用 artifact + Mutation:只计算 affected chunks
发现 full hash 全局依赖:禁用局部缓存路径

这里不能简单地"每个 Chunk 各算各的":异步加载文件名、runtime manifest、library 格式等都可能把 Chunk 联系起来。Rspack 明确检测这种关系,而不是错误复用局部结果。

CreateModuleAssets:处理模块自带的附属文件

CreateModuleAssetsPass 扫描所有模块的 build_info().assets:

rust 复制代码
for (identifier, module) in mg.modules() {
  let assets = &module.build_info().assets;
  for (name, asset) in assets.as_ref() {
    module_assets.push((name.clone(), asset.clone()));
  }
  for chunk in chunk_graph.get_module_chunks(*identifier) {
    chunk_asset_map.push((chunk, name.clone()));
  }
}

典型来源是 asset module、loader 或插件在构建模块时产出的图片、字体、source map 等。Pass 做两件事:

  1. 调用 emit_asset(name, asset) 把文件放进 compilation 的 asset 集合;
  2. 若模块属于某个 Chunk,则在 Chunk 上标记该文件为 auxiliary file。

辅助文件不会作为 JS Chunk 的主体内容,却必须在 stats、清理旧文件和发布产物时与 Chunk 建立关联。

CreateChunkAssets:用 render manifest 产出 bundle

核心调用不是直接遍历 CodeGenerationResults 拼字符串,而是:

rust 复制代码
plugin_driver.compilation_hooks.render_manifest
  .call(compilation, chunk, &mut manifests, &mut diagnostics)
  .await?;

每个 FileManifest 包含:

text 复制代码
filename
source
asset info
auxiliary 标记

内置的 JavaScript、CSS、source map 等插件各自向 manifest 添加条目。这一点解释了为什么 Rspack 可以支持不同输出格式和 target:Core 负责"对每个 Chunk 请求一份渲染清单",插件负责"如何渲染这个 Chunk"。

生成结果随后写回:

rust 复制代码
current_chunk.set_rendered(true);
current_chunk.add_file(filename.clone());
compilation.emit_asset(filename, CompilationAsset::new(Some(source), info));

这时 Compilation.assets 才出现真正的 main.js、lazy.js 等文件。

为什么 Chunk 资产可以并发生成

源码使用 rspack_futures::scope 为多个 Chunk 建立任务。前提是图、ID、code generation result 和 hash 都已稳定;每个任务只为一个 Chunk 收集 manifest,最终在主流程集中写回 artifact 和 assets。

text 复制代码
稳定的 Compilation 视图
  -> 多个 chunk render_manifest 并发运行
  -> 收集 (ChunkUkey, ChunkRenderResult)
  -> 顺序 emit_asset / 更新 Chunk 元数据

这与第三篇的"后台构建、主队列改图"是同一原则:可并发的纯计算并行;共享状态写入保持可控。

用 import() 再走一遍

text 复制代码
CodeGenerationResults
  main: index.ts + math.ts
  lazy: lazy.ts
       |
       v
Runtime requirements
  main 需要加载 lazy 的 helper
       |
       v
CreateHash
  main content hash、lazy content hash
       |
       v
render_manifest
  main -> main.[hash].js
  lazy -> lazy.[hash].js
       |
       v
Compilation.assets

这一篇应该带走什么

  1. hash 有 Module、Chunk content、Full compilation 等不同粒度;
  2. full hash 依赖会破坏局部增量计算,Rspack 会显式降级;
  3. Module assets 是模块构建副产物,通常作为 Chunk 的 auxiliary file;
  4. Chunk 的实际渲染由 render_manifest Hook 驱动,而不是由 Core 固定拼接;
  5. 图稳定后,Chunk 级渲染可以并行,最终 asset 写入统一收束。

写在最后

到这里文件已经进入 Compilation.assets,但还不是编译真正结束。插件仍可在 process_assets 中压缩、生成 manifest、修改 source map 或注入额外文件。下一篇会讲整个收尾 Hook 链,以及它与缓存和 watch 增量编译的关系。

相关推荐
小兔子2 小时前
Python 的 GIL 与 free-threading:3.13 之后「去 GIL」走到哪一步了
前端
IT_陈寒2 小时前
SpringBoot自动配置差点让我加班到凌晨
前端·人工智能·后端
guslegend3 小时前
脚手架原理与本地调试:从 bin 软链接到 npm link
前端·npm·node.js·脚手架·前端工程化
海码事务所4 小时前
Google Play 新个人开发者账号上架指南:12 人连续 14 天封闭测试怎么做?
前端
田威AI4 小时前
图片内文字翻译的规格:输入输出、保真、自动化、时间与费用
前端·计算机视觉
不可能片场4 小时前
命令行中文变问号 我用环境变量救了场
前端·electron
lerhxx4 小时前
AI 应用中的上下文管理 —— 从"上下文窗口"到"分层压缩"的工程实践
前端·javascript
骑着蜗牛撵大象3274 小时前
多 Agent 分治协作:MapReduce 模式下的并行扇出与结果归并
前端
用户55318297324 小时前
Flutter iOS 热更新深入 Dart VM:读懂函数入口、解释执行与调用桥接
前端
前端snow4 小时前
为什么大厂要用Postgres + mogodb的框架?
前端