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

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

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

Rspack 内部会使用 ModuleIdentifierChunkUkey 这样的 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_idsmodule_ids Hook。前者允许插件预先保留自定义 ID,后者执行完整策略。ModuleIdsArtifact 单独保存分配结果,因此增量编译可复用未受影响模块的 ID。

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

ChunkIdsPass 的结构几乎相同,只是处理 chunk_by_ukeynamed_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 仍然把 BuildModuleGraphArtifactExportsInfoArtifact 取出交给 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。

相关推荐
彭于晏分晏1 小时前
深入理解 Vue 3 响应式原理:从 Proxy 到依赖收集
前端
晚安日记wanna1 小时前
Vue3 script setup 的四层追问答到第三层才算过关
前端·vue.js·面试
前端柱子1 小时前
Chaikin‘s Corner Cutting 算法 应用canvas绘制平滑的曲线
前端
gnip1 小时前
uts 插件示例:获取设备电量信息
前端·javascript
码上成长1 小时前
Mapbox 上用 Turf 裁多边形:屏幕贴边了,接口却说越界
前端·前端框架
计算机魔术师1 小时前
纽约时报告了OpenAI和微软:820万条聊天记录背后,是AI时代最大的版权保卫战
前端
码事漫谈2 小时前
在 Kubernetes 上管好数据库:金仓 KES-Operator 正式落地
前端·后端
用户1880687493522 小时前
手上全是油,我怎么让rokid眼镜在厨房里教我炒菜
前端
gnip2 小时前
uni-app 原生插件
前端·javascript·uni-app