从 CLI 到 Rsbuild 的生命周期

一句话概括

Core 的运行链路是:CLI 或 API 收到用户意图,加载 Rslib 配置和 env,创建 RslibInstance,把 lib 数组转成 Rsbuild environments,再调用 Rsbuild 的 build、inspect 或 dev server 能力。

这条链路看起来直线,但维护时要注意两个事实:

  • CLI 和编程式 API 共享 createRslib 主路径,不能让 CLI 形成一条只在命令行生效的旁路。
  • Build、inspect、mf-dev 都会调用配置编排,但它们选择 environment 和设置 mode 的方式不同。

入口:runCLI

src/cli/index.ts 做的是启动前准备,不负责构建细节:

  1. initNodeEnv 根据命令设置默认 NODE_ENV
  2. setupLogLevel 在任何日志打印前处理 --log-level
  3. showGreeting 打印版本。
  4. setupCommands 注册和执行命令。

这里的维护重点是顺序。日志级别必须在 greeting 前设置,否则用户传 --log-level silent 也可能看到输出。NODE_ENV 必须在命令执行前设置,因为后续 Rsbuild 和 Rspack 配置可能读取它。

命令注册:setupCommands

src/cli/commands.ts 使用 cac 注册三个主命令:

命令默认 mode目的
build 或空命令production执行库构建
inspectproduction 或用户指定写出 Rslib、Rsbuild、Rspack 配置
mf-devdevelopment启动 MF format dev server

公共参数由 applyCommonOptions 注册,构建参数只挂在 build command 上。维护 CLI 参数时应明确它是通用参数还是 build 专用参数:

  • 通用参数应进入 CommonOptions,例如 rootconfigenvModeliblogLevel
  • 构建参数应进入 BuildOptions,例如 formatentrybundledtsexternalsdistPath

如果一个参数会影响配置加载本身,例如 --root--config--env-dir,必须在 init 之前生效。如果一个参数只影响 lib item,则应通过 applyCliOptions 覆盖配置。

CLI 初始化:init

src/cli/init.ts 是 CLI 和 core API 的桥。它做四件事:

  1. process.cwd()--root 得到项目 root。
  2. 调用 baseLoadConfig 加载配置。
  3. 在没有配置文件时填充默认 lib: [{}]
  4. 调用 createRslib,并传入 env 加载选项。

CLI 参数覆盖集中在 applyCliOptions。这个集中化很重要:如果每个命令自己改配置,build、inspect、mf-dev 很容易出现行为不一致。当前 inspect 和 mf-dev 只用通用参数,build 用完整 BuildOptions

配置加载:loadConfig

src/loadConfig.ts 使用 Rsbuild 的 loadConfig 作为底层加载器。Rslib 自己负责:

  • 解析显式 config path。
  • 在默认文件名列表中查找 config。
  • 暴露 defineConfig 类型辅助。
  • 将返回内容标记为 RslibConfig

默认文件名顺序偏向常用和性能:

  1. rslib.config.mjs
  2. rslib.config.ts
  3. rslib.config.js
  4. rslib.config.cjs
  5. rslib.config.mts
  6. rslib.config.cts

显式传 config 时,如果文件不存在,Rslib 会抛出带 [rslib:loadConfig] 前缀的错误。这类错误属于用户配置错误,应保持文案直接,不要让底层 loader 错误冒泡成难懂栈。

创建实例:createRslib

src/createRslib.ts 是 build、inspect、mf-dev 的共同核心。创建实例时会:

  • 根据 options.loadEnv 调用 Rsbuild 的 loadEnv
  • 如果配置是函数,则调用它得到最终配置。
  • 将 public env vars 合并到 config.source.define
  • 解析 config.root 为绝对路径。
  • 把 env 文件路径放到 _privateMeta.envFilePaths,供 restart 监听。
  • 暴露 onAfterCreateRsbuild 回调机制。

这里有一个重要设计:createRslib 不立即创建 Rsbuild 实例,而是在 build、inspect、mf-dev 被调用时再创建。原因是不同操作需要不同 mode、不同 environment 裁剪和不同 Rsbuild API。

创建 Rsbuild 实例

createRslib 内部的 createRsbuildInstance 会把 Rslib 配置中的共享字段传给 Rsbuild:

  • cwd
  • callerName: "rslib"
  • mode
  • root
  • plugins
  • dev
  • server
  • logLevel
  • environments

它还会:

  • 在 env 加载开启时注册 cleanup。
  • 在 debug 模式下挂载 inspect 插件。
  • 执行 onAfterCreateRsbuild 回调。

维护这里时要特别小心 pluginsdevserver 的来源:这些是顶层共享 Rsbuild 配置,不属于单个 lib item。单个 lib 的插件和 output 则在 environment config 中处理。

Build 生命周期

Build 的具体流程:

Watch 模式下,build 还会向 config.plugins 追加一个 rslib:on-after-build 插件,用于首次编译后输出 watch 状态。CLI 层会调用 watchFilesForRestart 监听配置文件和 env 文件变化,变更时重新执行 cliBuild

这里有一个维护陷阱:watch restart 是 CLI 层循环,不是 Rsbuild 自己的普通 watch。配置文件或 env 文件变化后需要关闭旧 build 实例,否则可能出现多个 watcher 和 server 同时存在。

Inspect 生命周期

Inspect 不是构建,但它必须走同一套配置编排。它会:

  1. 设置 mode,默认 production。
  2. 调用 composeRsbuildEnvironments
  3. 创建 Rsbuild 实例。
  4. 调用 rsbuildInstance.inspectConfig
  5. extraConfigs 中附带 Rslib 原始配置。
  6. 返回格式化后的 rslibConfig 字符串和 Rsbuild inspect 结果。

维护 inspect 时要坚持“所见即所得”:inspect 应尽量反映真实 build 会使用的 config,而不是另一份手写配置。

MF Dev 生命周期

startMFDevServer 只选择 format: "mf" 的 environment。原因是普通 library environment 不应该启动应用 dev server。

选择逻辑是:

  • 没传 lib 时,选择所有 MF environment。
  • 传了 lib 时,只选择 id 在列表中的 MF environment。
  • 如果没有选中任何 MF environment,抛出明确错误。

MF dev server 创建后,会把 server close 注册到 onBeforeRestart,这样配置变化重启时能先关闭旧 server。

Restart 系统

src/restart.ts 做的是配置级重启,不是普通源码热更新。它监听:

  • Rslib config file。
  • env files。

getWatchFilesForRestartrslib.getRslibConfig()._privateMeta 读取这些路径。watchFilesForRestart 使用 chokidar 动态导入,并通过 debounce 合并快速连续变更。

重启前会执行所有 onBeforeRestart 注册的 cleaner,并清空控制台重新打印启动信息。这里的目标是让 watch 模式在配置变化后像一次干净启动,而不是继续沿用旧 Rsbuild 实例。

错误处理模型

CLI 的错误处理有三个层次:

层次处理方式
Rspack 已格式化构建错误避免重复打印 “Failed to build.”
AggregateError逐个打印内部 error
普通 error打印统一失败前缀和错误对象

最后都会 process.exit(1)。这对 CLI 是合理的,但编程式 API 不应随意退出进程。维护时要确认错误是在 CLI 层退出,还是在底层配置函数中直接 process.exit。后者需要更谨慎,只适合确实不可恢复且已有先例的场景。

维护建议

  • 新增 CLI 参数时,先决定它属于 CommonOptions 还是 BuildOptions。
  • 修改 config 加载时,同时检查 CLI、API、watch restart 和 env file paths。
  • 修改 createRslib 时,同时检查 build、inspect、mf-dev 三条路径。
  • 修改 watch 行为时,确认旧实例会关闭,避免资源泄漏。
  • 修改错误文案时,跑对应 CLI 测试,因为测试可能断言 stdout 或 stderr。