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

本篇是 Rspack 源码解析系列第十篇。经过 runtime requirements 阶段后,Rspack 已经拥有生成 asset 所需的全部语义信息。本篇追踪 CreateHashPassCreateModuleAssetsPassCreateChunkAssetsPass,理解 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.jslazy.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 增量编译的关系。

相关推荐
福兮说1 小时前
用 IndexedDB 存用户的文件,我踩过的五个坑
前端·javascript
闪耀之光M781 小时前
前端依赖自动导入:unplugin-auto-import
前端
柯南46681 小时前
【AI开发之Rust】第 12 课:并发模型与同步原语
rust·编程语言
Ricon组态薄荷糖1 小时前
Ricon组态系统:工业组件开发指南与实践
前端·后端·物联网
flash俊杰2 小时前
Electron 桌面应用的进程模型:为什么 fork Next.js standalone
前端
沙蒿同学2 小时前
我把架构约定编译成了会变红的测试:Wails v2 + Go + Vue3 桌面脚手架实战
前端·后端·github
cpolar技术支持2 小时前
本地 Playwright 测试报告怎么远程复盘?Trace Viewer 跑起来后,用 cpolar 分享失败现场
前端·自动化测试·测试工具·cpolar·playwright
wordbaby2 小时前
企业级后台管理系统路由设计与最佳实践指南
前端
胡志辉的博客2 小时前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium