Core 维护者心智模型

Core 到底负责什么

packages/core 的职责不是执行一个普通的 bundler 配置文件,而是把“库构建”这个领域问题翻译成 Rsbuild 和 Rspack 能执行的多 environment 构建。维护时可以把 core 看成三层:

第一层是用户入口。用户可以通过 CLI 调用 rslib buildrslib inspectrslib mf-dev,也可以通过 @rslib/core 的编程式 API 调用 createRslibloadConfigdefineConfig。这一层要保证参数稳定、错误信息明确、默认行为符合文档。

第二层是配置解释。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

export default defineConfig({
  lib: [
    { id: 'esm', format: 'esm', output: { distPath: './dist/esm' } },
    { id: 'cjs', format: 'cjs', output: { distPath: './dist/cjs' } },
  ],
});

每个 item 最终都会变成一个 Rsbuild environment。这个设计带来几个维护后果:

  • 每个 format 的配置不能只考虑单构建;它可能和其他 format 并行存在。
  • 输出文件名、runtime chunk、externals、dts 输出路径都要避免多 environment 冲突。
  • CLI 的 --lib 不是 format 名本身,而是 environment id 选择器。
  • 用户共享配置会先和每个 lib 合并,之后再由 Rslib 派生配置读取这些字段并生成最终配置。

Core 中的“用户配置”和“派生配置”

Rslib 的配置不是简单 merge。维护时要区分三类配置:

类型来源例子特点
内置常量配置createConstantRsbuildConfigchunkSplit.strategybuildCacheextensionAlias提供库构建基础默认值
派生配置composeLibRsbuildConfigformat output、externals、entry、CSS、dts、exe根据 Rslib 特有字段计算
用户配置rslib.config.ts 和 CLI overridesource.entryoutput.externalstools.rspack优先级最高,但部分字段会先被读取再重置

维护 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 不应该承担所有事情。边界可以这样理解:

Core 应该做Core 不应该做
定义 Rslib 用户配置如何转成 Rsbuild 配置重新实现 Rsbuild 或 Rspack
为库构建提供默认值和兼容修补在 core 中写业务框架专用逻辑
接入 dts、CSS、asset、exe 等产物流程把 dts 后端实现塞进 core
提供 CLI 和编程式 API在 CLI 中绕过 createRslib 主路径
维护错误信息和 inspect 能力让用户只能从 Rspack 底层错误猜原因

packages/plugin-dts 单独成包就是这个边界的例子。Core 只负责把 lib.dts 映射为 pluginDts 配置,并保证 dts 扩展名、externals、banner/footer、redirect 等与 JS 构建规则一致。具体 tsc、tsgo、isolated、API Extractor 后端由插件包维护。

读源码的推荐顺序

如果是第一次维护 core,不建议从 config.ts 第一行读到最后。更有效的顺序是:

  1. src/index.ts,确认公共 API。
  2. src/cli/index.tssrc/cli/commands.ts,理解 CLI 如何进入系统。
  3. src/cli/init.tssrc/loadConfig.ts,理解配置来源。
  4. src/createRslib.ts,理解 build、inspect、mf-dev 如何创建 Rsbuild 实例。
  5. 回到 src/config.ts,先读 composeRsbuildEnvironmentscomposeCreateRsbuildConfig
  6. 再读 composeLibRsbuildConfig,按它调用的子 compose 函数逐个展开。
  7. 最后按需要读 CSS、asset、exe、plugins、utils。

这样读能先建立调用图,再进入细节,避免把所有函数都看成互不相关的工具。

维护时的基本判断

遇到一个 core 改动,先问以下问题:

  1. 这是用户配置层改动,还是派生配置层改动?
  2. 这个行为是否只适用于某些 format?
  3. 这个行为是否只适用于 bundle 或 bundleless?
  4. 这个行为是否影响 dts、CSS、asset 或 exe?
  5. 这个行为是否要反映在 inspect 输出里?
  6. 这个行为是否需要 CLI 参数覆盖?
  7. 这个行为是否会改变已有默认值,是否属于 breaking change?

这些问题比逐行读代码更重要,因为它们决定改动的测试面和文档面。