Core 维护者心智模型
Core 到底负责什么
packages/core 的职责不是执行一个普通的 bundler 配置文件,而是把“库构建”这个领域问题翻译成 Rsbuild 和 Rspack 能执行的多 environment 构建。维护时可以把 core 看成三层:
第一层是用户入口。用户可以通过 CLI 调用 rslib build、rslib inspect、rslib mf-dev,也可以通过 @rslib/core 的编程式 API 调用 createRslib、loadConfig、defineConfig。这一层要保证参数稳定、错误信息明确、默认行为符合文档。
第二层是配置解释。Rslib 的用户配置继承 Rsbuild 配置,但额外引入 lib 数组。每个 lib item 代表一个库构建目标。core 的核心工作,是把每个 lib item 解释成一个 Rsbuild environment,并把共享配置、内置默认值、用户覆盖和 Rslib 特有配置合并成最终 bundler 配置。
第三层是产物适配。库构建和应用构建的差异很大:库通常需要保留外部依赖、保留资源相对路径、支持 bundleless、生成声明文件、兼容 ESM/CJS/UMD/IIFE/MF、多入口输出,以及在某些场景下生成单文件可执行程序。core 通过一组 compose 函数和 Rsbuild/Rspack 插件把这些产物规则接入构建生命周期。
最重要的抽象:lib item
维护 core 时最重要的抽象不是“项目”,而是 “lib item”。一个 rslib.config.ts 可以包含多个 lib:
每个 item 最终都会变成一个 Rsbuild environment。这个设计带来几个维护后果:
- 每个 format 的配置不能只考虑单构建;它可能和其他 format 并行存在。
- 输出文件名、runtime chunk、externals、dts 输出路径都要避免多 environment 冲突。
- CLI 的
--lib不是 format 名本身,而是 environment id 选择器。 - 用户共享配置会先和每个 lib 合并,之后再由 Rslib 派生配置读取这些字段并生成最终配置。
Core 中的“用户配置”和“派生配置”
Rslib 的配置不是简单 merge。维护时要区分三类配置:
维护 config.ts 时,常见错误是把某个逻辑当作“用户配置覆盖”处理,但实际它应该在派生配置中影响多个子系统。例如 autoExtension 不只是输出文件名,它还影响 bundleless JS redirect、dts extension、asset redirect 以及用户 import 路径是否能被运行时解析。
为什么 config.ts 会很大
config.ts 集中了库构建的领域规则。它大,不是因为所有逻辑都应该随意堆在一起,而是因为这些规则高度耦合:
- format 会影响 library type、parser、runtimeChunk、externalsType、默认 autoExternal、默认 target。
- target 会影响 Node builtins、moduleIds、browserslist、Rspack target。
- bundle 会影响 entry 校验、bundleless redirect、CSS 插件、printFileSize、dts 默认形态。
- entry 会影响 runtime chunk、bundleless outBase、dts entry、exe 单入口校验。
- output extension 会影响 JS 文件、chunk 文件、dts 文件、CSS modules import 和 asset redirect。
这些规则如果拆得太碎,容易丢失顺序语义;如果完全不拆,又难维护。当前实现采取的是“一个主编排函数加多个 compose 子函数”的折中:composeLibRsbuildConfig 负责顺序,子函数负责局部规则。
Core 的关键数据流
在这个数据流中,core 最容易出错的地方是 LibConfig item -> 派生 Rsbuild 配置。它不仅是字段映射,而是多组规则的组合。评审 core PR 时,应重点看:
- 新字段是否同时出现在类型、运行时、文档和测试里。
- 新逻辑放在 compose 顺序中的位置是否正确。
- 是否影响
bundle: false。 - 是否影响多 environment。
- 是否影响 dts 或 CSS/asset 的路径。
Core 的边界
Core 不应该承担所有事情。边界可以这样理解:
packages/plugin-dts 单独成包就是这个边界的例子。Core 只负责把 lib.dts 映射为 pluginDts 配置,并保证 dts 扩展名、externals、banner/footer、redirect 等与 JS 构建规则一致。具体 tsc、tsgo、isolated、API Extractor 后端由插件包维护。
读源码的推荐顺序
如果是第一次维护 core,不建议从 config.ts 第一行读到最后。更有效的顺序是:
- 读
src/index.ts,确认公共 API。 - 读
src/cli/index.ts和src/cli/commands.ts,理解 CLI 如何进入系统。 - 读
src/cli/init.ts和src/loadConfig.ts,理解配置来源。 - 读
src/createRslib.ts,理解 build、inspect、mf-dev 如何创建 Rsbuild 实例。 - 回到
src/config.ts,先读composeRsbuildEnvironments和composeCreateRsbuildConfig。 - 再读
composeLibRsbuildConfig,按它调用的子 compose 函数逐个展开。 - 最后按需要读 CSS、asset、exe、plugins、utils。
这样读能先建立调用图,再进入细节,避免把所有函数都看成互不相关的工具。
维护时的基本判断
遇到一个 core 改动,先问以下问题:
- 这是用户配置层改动,还是派生配置层改动?
- 这个行为是否只适用于某些 format?
- 这个行为是否只适用于 bundle 或 bundleless?
- 这个行为是否影响 dts、CSS、asset 或 exe?
- 这个行为是否要反映在 inspect 输出里?
- 这个行为是否需要 CLI 参数覆盖?
- 这个行为是否会改变已有默认值,是否属于 breaking change?
这些问题比逐行读代码更重要,因为它们决定改动的测试面和文档面。