声明文件接入点

Core 为什么只做接入

声明文件生成的具体实现位于 packages/plugin-dts。Core 不直接调用 TypeScript Compiler API,也不直接运行 API Extractor。Core 的职责是把 lib.dts 放到正确的 environment 中,并把 JS 构建侧已经确定的上下文传给插件。

这种分工有两个好处:

  • dts 后端可以独立演进,不让 core 直接承担 TypeScript 编译器复杂度。
  • core 可以专注保证 JS 输出和 dts 输出的一致性。

composeDtsConfig

Core 中 dts 接入的中心是 composeDtsConfig。它读取:

  • libConfig.dts
  • format
  • dtsExtension
  • autoExternal
  • banner.dts
  • footer.dts
  • redirect.dts

然后动态导入 rsbuild-plugin-dts 并返回 Rsbuild plugin 配置。

dtsfalseundefined,不挂载插件。当 dts === true,core 会把它解释成 { bundle: false },即默认生成非打包声明文件。

为什么需要 dtsExtension

JS format 会影响声明文件扩展名。典型情况:

  • ESM 产物可能需要 .d.mts
  • CJS 产物可能需要 .d.cts
  • 普通情况使用 .d.ts

Core 在 composeOutputFilenameConfig 中计算 dtsExtension,再传给 pluginDts。如果用户没有开启 dts.autoExtension,插件仍使用 .d.ts

这保证 dts 文件扩展名与 JS 产物模块语义一致。维护扩展名逻辑时,必须同时看 JS output 和 dts output。

Auto external 与 dts bundle

Core 会把 autoExternal 的 resolved 默认值传给 dts 插件。原因是 dts bundle 需要知道哪些依赖应被打进声明文件,哪些依赖应该保持 external。

JS 和 dts 的 external 策略如果不一致,会出现两类问题:

  • JS 产物 external 了依赖,但 dts 把依赖类型打进去,导致类型体积和边界不符合预期。
  • JS 产物 bundle 了依赖,但 dts 仍引用外部类型,导致消费者缺类型依赖。

因此 dts 插件中的 bundledPackages 计算要和 core 的 autoExternal 语义保持一致。

Core 把 banner.dtsfooter.dts 传给 dts 插件,而 JS 和 CSS banner/footer 由 composeBannerFooterConfig 处理。

这意味着 banner/footer 是按产物类型分流的:

产物处理位置
JSRspack BannerPlugin
CSSRspack BannerPlugin 或 CSS loader/plugin
DTSplugin-dts 后处理

维护时不要假设 banner 一个配置能通过同一机制覆盖所有产物。

Redirect 与 dts

Bundleless 下,JS import 会被 rewrite;dts import 也需要相应 rewrite。Core 把 redirect.dts 传给插件,插件在处理声明文件时执行路径重写。

默认 redirect.dts.extension 是 false,和 JS 默认不同。这是因为 TypeScript 声明文件的扩展名引用语义和 JS runtime import 不完全一样。修改这个默认值要非常谨慎。

dts 与 entry

dts 插件通过 environment config 的 source.entry 计算 dts entry。Core 中 entry 的处理顺序会影响 dts:

  • Bundle 模式 entry 是显式文件或 Rsbuild 默认 entry。
  • Bundleless 模式 entry 是 glob 展开后的动态 Rspack entry。
  • dts bundle 时,插件需要把 source entry 映射到临时 declaration entry。

如果 entry 被用户配置在最终 merge 阶段覆盖,就可能让 dts entry 和 JS entry 不一致。因此 core 在派生 entry 后会重置用户 config 中的 source.entry,避免后续 merge 打乱结果。

多 environment 的 dts

每个 lib item 都会变成一个 environment。若多个 environment 都开启 dts,就会各自挂载 dts 插件。维护时需要考虑:

  • 多个 format 是否写到同一个 dts distPath。
  • 多个 environment 是否同时清理同一 dts 输出目录。
  • dts autoExtension 是否区分 .d.mts.d.cts
  • watch 模式下子进程是否会泄漏。

通常建议测试多 format 同时开启 dts 的情况,尤其是 ESM + CJS。

dts.build

dts.build 对应 TypeScript project references 的 build 模式。Core 只传递配置,具体校验在 plugin-dts 中完成。维护 core 时要知道这个模式对输出路径有更严格要求:tsconfig 中的 declarationDiroutDir 必须与 Rslib dts 输出路径一致。

这类错误应在插件中给出清晰提示,因为用户需要修改 tsconfig,而不是 Rspack 配置。

dts.isolated

dts.isolated 由 plugin-dts 通过 Rspack 内置 RslibPlugin 接入。Core 的责任是确保对应 environment 中确实有内置 RslibPlugin。当前 ESM/CJS/UMD/IIFE 会通过 format config 挂载 RslibPlugin,MF 例外。

如果未来改变 format 与 RslibPlugin 的关系,必须重新审视 isolated dts 是否还能工作。

dts 改动的测试面

Core 层 dts 改动至少应考虑:

  • dts: true 默认 bundleless。
  • dts.bundle: true
  • dts.autoExtension 与 ESM/CJS。
  • redirect.dts
  • banner.dtsfooter.dts
  • 多 lib 多 format。
  • bundle: false 下 JS 和 dts 路径一致性。
  • 用户 output.externals 与 autoExternal。

不要只跑 plugin-dts 单元测试。Core 接入问题通常只有在 Rslib integration case 中才能暴露。