实验性 exe 接入点
Core 层关注什么
experiments.exe 的实现分布在 src/exe,但从 core 角度看,它是一个在 JS 构建成功后运行的附加产物生成流程。Core 层主要负责:
- 校验用户配置是否满足 exe 前提。
- 解析 target 配置。
- 在 Rsbuild environment 中挂载
ExePlugin。 - 调整 Rspack 配置,保证输出适合作为 Node SEA main。
真正的 binary 下载、checksum、SEA 配置、--build-sea 调用由 exe 子模块内部完成。
为什么 exe 要求严格
生成单文件可执行程序比普通 JS 产物限制更多。Core 明确校验:
- 只支持
format: "esm"和format: "cjs"。 - 必须
bundle: true。 - 必须
output.target = "node"。 - 每个 lib 只能有一个 entry。
- 当前 Node runtime 必须支持 SEA。
- ESM exe 不能使用 snapshot。
这些限制都来自 Node SEA 和 Rspack bundle 输出的现实约束。如果放宽限制,用户可能得到一个构建成功但无法运行的二进制文件。
接入位置
composeLibRsbuildConfig 在 entry、format、target 等配置计算后调用 composeExeConfig。这是必要的,因为 exe 校验需要知道:
- 当前 format。
- 当前 bundle 值。
- 当前 source entry。
- 当前 target。
- 项目 root。
composeExeConfig 返回一个 environment config,包含:
plugins: [ExePlugin(...)]optimization.runtimeChunk = falseoptimization.splitChunks = falseoutput.asyncChunks = false
这些 Rspack 配置确保最终 JS 产物适合作为单入口 SEA main。
Target 解析
用户可以通过 experiments.exe.targets 指定多个目标。Core 会把它们规范化为 NormalizedExeTarget:
- 没写 platform 时使用当前平台。
- 没写 arch 时使用当前架构。
- 没写 nodeVersion 时使用当前 Node 版本。
- 字符串 target 视为自定义 Node binary 路径。
- 多 target 时添加 suffix,避免输出覆盖。
- 跨平台 target 自动关闭 snapshot 和 code cache。
维护这里时要区分“目标 binary”和“构建 binary”。目标 binary 是最终注入 SEA blob 的模板;构建 binary 必须能在当前机器执行 --build-sea。
ExePlugin 的生命周期
ExePlugin 运行在 onAfterEnvironmentCompile。它会先检查 Rspack stats:
- 如果没有 stats,直接返回。
- 如果 compilation 有错误,直接返回。
- 如果 entrypoint 数量不是 1,抛错。
然后它会:
- 找到构建后的 main file。
- 解析所有目标 binary。
- 为每个 target 计算 executable 输出路径。
- 对自定义 binary 做版本一致性最终校验。
- 调用
buildExecutable。 - 打印生成文件路径、大小和耗时。
这个生命周期意味着 exe 不会在 JS 构建失败时继续执行,避免拿不完整产物生成二进制。
输出路径
exe 输出路径由 resolveExecutableOutputPath 计算。它需要综合:
- environment output path。
- JS main file。
- 用户
fileName。 - 用户
outputPath。 - target suffix。
- Windows
.exe后缀。
多 target 输出路径尤其需要注意。没有 suffix 时,不同平台产物可能互相覆盖;有 suffix 时,用户仍可能通过 fileName 或 outputPath 产生冲突,需要测试覆盖。
Binary 下载和缓存
当 target 不是当前 runtime 时,Rslib 会下载 Node release。下载流程包括:
- 获取
SHASUMS256.txt。 - 找到 archive checksum。
- 下载 archive。
- 校验 sha256。
- 解压。
- chmod。
- 缓存到 exe cache dir。
这里 checksum 是安全边界,不能为了简化逻辑移除。并发下载同一 archive 时,inFlightBinaryDownloads 会复用 promise,避免重复下载和解压。
SEA 构建
buildExecutable 会创建临时目录,写入 sea-config.json,再运行:
SEA config 包括:
mainmainFormatoutputexecutabledisableExperimentalSEAWarninguseSnapshotuseCodeCacheexecArgvexecArgvExtensionassets
构建完成后,macOS 会尝试签名。临时目录在 finally 中删除。
为什么这是 experimental
这个能力依赖 Node SEA,而 SEA 本身还在演进。维护时要保持保守:
- 不要默认扩大平台支持。
- 不要吞掉 Node binary 版本不一致。
- 不要在 cross-platform 时启用 snapshot 或 code cache。
- 不要让 exe 逻辑影响普通 JS 构建。
它应作为 lib item 的附加能力,而不是改变整个 Rslib 构建模型。
测试建议
exe 改动测试成本较高,但至少要覆盖:
- 不支持 format 时的错误。
bundle: false错误。- 非 node target 错误。
- 多 entry 错误。
- 默认当前 runtime target。
- 自定义 fileName 和 outputPath。
- 多 target suffix。
- custom binary 版本读取失败。
仓库把 exe integration 拆成 integration-exe project,就是为了让慢速 SEA case 可以单独调度。