Rspack 源码解析(八):Module、Chunk 与 Runtime ID

本篇是 Rspack 源码解析系列第八篇。经过图优化后,Rspack 依次执行 ModuleIdsPass、ChunkIdsPass、AssignRuntimeIdsPass 与 OptimizeCodeGenerationPass。它们将内部图里的稳定 key 转换为生成代码可使用的 ID,并在真正 code generation 前完成最后一轮面向生成的优化。

前言:内部 key 不等于输出 ID

Rspack 内部会使用 ModuleIdentifier、ChunkUkey 这样的 key 来索引结构。它们服务于 Rust 内存中的图操作;最终 JavaScript 却需要能序列化的模块 ID、Chunk ID 和 runtime ID:

text 复制代码
ModuleIdentifier("/abs/src/a.ts") --内部索引-->
ModuleId("123")                 --写入 bundle-->
__webpack_require__(123)

把两者分开有两个好处:内部索引不受输出命名策略约束;生产构建可以选择 deterministic、named 等不同 ID 插件,而图算法无需改动。

ModuleIdsPass:只给真正需要的模块分配 ID

源码先筛选:

rust 复制代码
fn get_modules_needing_ids(compilation: &Compilation, ids: &ModuleIdsArtifact) -> IdentifierSet {
  compilation.get_module_graph().modules()
    .map(|(_, module)| module)
    .filter(|m| {
      m.need_id()
        && ChunkGraph::get_module_id(ids, m.identifier()).is_none()
        && chunk_graph.get_number_of_module_chunks(m.identifier()) != 0
    })
    .map(|m| m.identifier())
    .collect()
}

三个条件很关键:模块本身需要 ID、尚未分配过、并且确实属于至少一个 Chunk。被 Tree Shaking 移除的模块不会占用输出 ID。

随后会依次调用 before_module_ids 和 module_ids Hook。前者允许插件预先保留自定义 ID,后者执行完整策略。ModuleIdsArtifact 单独保存分配结果,因此增量编译可复用未受影响模块的 ID。

ChunkIdsPass:给将要加载的文件身份

ChunkIdsPass 的结构几乎相同,只是处理 chunk_by_ukey 和 named_chunk_ids_artifact:

rust 复制代码
compilation.compilation_hooks.chunk_ids.call(
  compilation,
  &mut chunk_by_ukey,
  &mut named_chunk_ids_artifact,
  &mut diagnostics,
).await?;

Chunk ID 往往会进入文件名与异步加载表达式。例如 import() 的 runtime 代码需要根据一个 Chunk ID 查找对应资源。稳定 ID 是长期缓存的重要前提:无关模块变化时,未变 Chunk 的名称和内容不应无故变化。

AssignRuntimeIdsPass:入口 runtime 如何找到自己

runtime 是一组"执行这个入口所需的运行时能力"的标识,并不等同于 Chunk。实现会遍历普通入口和异步入口:

rust 复制代码
let runtime = entrypoint.kind.get_entry_options()
  .and_then(|options| match &options.runtime {
    Some(EntryRuntime::String(name)) => Some(name.to_owned()),
    _ => None,
  })
  .or_else(|| entrypoint.name().map(str::to_string));

chunk_graph.set_runtime_id(runtime, chunk.id().map(str::to_string));

优先使用 entry.runtime 配置,未设置时回退为 entrypoint name;随后取该 entrypoint 的 runtime chunk,并在 ChunkGraph 中保存映射。

text 复制代码
Entrypoint(main)
  runtime name: "main"
  runtime chunk id: "runtime~main"
  -> ChunkGraph.runtime_id["main"] = "runtime~main"

这份映射会在生成 runtime 和 asset 名称时被消费。

OptimizeCodeGeneration:代码生成前的最后一次图调整

最后一个 Pass 仍然把 BuildModuleGraphArtifact 与 ExportsInfoArtifact 取出交给 Hook:

rust 复制代码
let mut module_graph = compilation.build_module_graph_artifact.steal();
let mut exports = compilation.exports_info_artifact.steal();
compilation.compilation_hooks.optimize_code_generation.call(
  compilation, &mut module_graph, &mut exports, &mut diagnostics
).await?;

和前面的图优化不同,这里关注"如何生成代码"而不是"模块归属在哪个 Chunk"。比如最终导出使用状态、模块拼接的生成细节,都必须在 Module::code_generation() 之前确定。代码一旦生成,改变这些图信息就会导致缓存失效或生成结果不一致。

Rust 角度:策略与存储分离

这四个 Pass 有一个重复的结构:

text 复制代码
Core:筛选对象、持有 Artifact、定义 Hook 时序
Plugin:决定 named / deterministic / natural 等具体策略
Artifact:保存本轮或跨增量构建可复用的结果

这种分离避免 Compilation 依赖每种 ID 算法,也使 JS 侧的兼容插件可以映射到 Rust Hook。对 Rust 学习而言,这是典型的"稳定 trait/Hook 边界 + 可替换策略实现"。

这一篇应该带走什么

  1. Identifier/Ukey 是内部图 key,Module/Chunk ID 是最终输出身份;
  2. ID 分配会跳过已移除或不在任何 Chunk 中的模块;
  3. runtime ID 将入口名(或配置)映射到 runtime chunk 的输出 ID;
  4. OptimizeCodeGenerationPass 是图状态进入代码生成前的最后稳定点;
  5. Artifact 保存状态,Hook 提供策略,Core 负责调度。

写在最后

第五篇已经介绍过 CodeGenerationPass 怎样生成模块源码。模块生成完后,还不知道每个 Chunk 需要哪些 runtime helper。下一篇进入 RuntimeRequirementsPass,看 Rspack 如何把模块声明的能力需求向上汇总,并最终加入 RuntimeModule。

相关推荐
小李科技17 分钟前
【性能优化】事件委托:目标定位与 React/Vue 实战
前端·vue.js·react.js·面试·性能优化·前端框架
QianhangQianping24 分钟前
一块大屏的数据在浏览器里怎么变成画面:背景、子屏、前景三层结构
前端·低代码·架构·编辑器·数据可视化·大屏可视化
梦想很大很大35 分钟前
让 Codex 自由发挥,给 nvm-desktop 换个 UI
前端·人工智能·设计
蓝莓味的口香糖39 分钟前
【VUE】【CSS】Vue移动端适配主流方案总结
前端·css·vue.js
shennv88840 分钟前
接入大模型流式输出后,文字偶尔乱码?问题可能出在前端
前端
csj5041 分钟前
前端基础之《React(16)—Modal组件封装》
前端·react.js
雨季mo浅忆1 小时前
Vue关于前端XSS漏洞修复方案
前端·xss
切糕师学AI1 小时前
el-select-v2 深度解析:用虚拟滚动解决海量数据下拉选择性能瓶颈
前端·vue.js·elementui·el-select-v2
徐小夕1 小时前
JitOffice:把办公、AI 与审批装进同一个平台
前端·vue.js·github
徐小夕1 小时前
万字分享:为什么我们要自研Word解析引擎?JitWord 解析引擎 4.0 深度复盘
前端·vue.js·github