声明文件系统

系统定位

声明文件生成由 packages/plugin-dts 提供,包名是 rsbuild-plugin-dts。@rslib/core 在 composeDtsConfig 中动态导入 pluginDts 并挂载到对应 environment。

这个系统解决的问题是:JavaScript 构建由 Rspack 处理,但 TypeScript 声明文件需要遵循 tsconfig、project references、路径别名、bundleless 路径重写和多格式扩展名。Rslib 将声明文件作为 Rsbuild 插件生命周期中的并行产物处理,而不是依赖 Rspack JS emit 自动完成。

核心文件

文件职责
packages/plugin-dts/src/index.tsRsbuild 插件入口,接入生命周期,选择 dts 后端
packages/plugin-dts/src/backend.ts校验和选择 isolated、tsc、tsgo 后端
packages/plugin-dts/src/dts.ts子进程侧主逻辑,准备上下文、生成声明、按需 bundle
packages/plugin-dts/src/tsc.ts使用 TypeScript Compiler API emit dts
packages/plugin-dts/src/tsgo.ts使用 tsgo emit dts
packages/plugin-dts/src/isolated.ts接入 Rspack isolated dts emit 后再做后处理
packages/plugin-dts/src/apiExtractor.ts使用 API Extractor 打包声明文件
packages/plugin-dts/src/utils.tstsconfig 加载、路径重写、清理、banner/footer、dts 后处理

从 Rslib 到 Plugin

packages/core/src/config.ts 中的 composeDtsConfig 将 lib.dts 转换为插件配置:

Rslib 配置Plugin 配置
dts.bundlebundle
dts.distPathdistPath
dts.buildbuild
dts.abortOnErrorabortOnError
dts.autoExtensiondtsExtension
dts.aliasalias
dts.isolatedisolated
dts.tsgotsgo
banner.dtsbanner
footer.dtsfooter
redirect.dtsredirect
autoExternalautoExternal

当 dts === true 时,Rslib 默认将其转换为 { bundle: false }。也就是说默认生成 bundleless declarations,而不是打包后的单个 .d.ts。

插件生命周期

后端选择

resolveDtsGenerationBackend 的规则很直接:

条件后端
isolated: trueisolated
tsgo: truetsgo
其他tsc

isolated: true 有额外约束:

  • 不能同时启用 tsgo。
  • 不能同时启用 build。
  • 不能设置 abortOnError: false。

这些约束来自 isolated dts 的实现方式:声明诊断由 Rspack 编译错误承载,插件只负责配置和后处理,因此不适合允许错误后继续。

上下文准备

prepareDtsContext 是所有后端共享的准备步骤:

  1. 合并 dts.alias 和 tsconfig compilerOptions.paths。
  2. 根据 tsconfig 推导 rootDir。若 tsconfig 设置 rootDir 则使用它;若 composite 为 true,默认使用 tsconfig 所在目录;否则取所有非声明输入文件的最长公共路径。
  3. 计算 dtsEmitPath。
  4. 如果 build: true,要求 tsconfig 中 declarationDir 或 outDir 与 dts 输出路径一致。
  5. 如果 bundle: true,使用 .rslib 临时声明目录,否则直接输出到目标 dts 目录。
  6. 如果 bundle,需要把 source entry 映射到临时声明入口,供 API Extractor 使用。

TSC 后端

packages/plugin-dts/src/tsc.ts 使用 TypeScript Compiler API,覆盖几种模式:

模式API适用场景
普通非 watch 且非 compositets.createProgram常规项目
非 watch 且 compositets.createIncrementalProgram增量项目
build: truets.createSolutionBuilderproject references
watch 且非 buildts.createWatchProgramwatch 模式
watch 且 buildts.createSolutionBuilderWithWatchproject references watch

TSC 后端会覆写 host 的 writeFile:

  • 根据 dtsExtension 重命名 .d.ts、.d.mts、.d.cts。
  • 更新 declaration map 中的扩展名。
  • emit 后调用 processDtsFiles 做 redirect、paths alias、banner/footer 等后处理。
  • 如果有 diagnostics,先处理文件再报错,保证输出目录状态可用于调试。

TSGo 后端

tsgo 后端用于实验性 TypeScript native preview。它仍共享 prepareDtsContext 和 processDtsFiles 的后处理路径,但 emit 阶段由 packages/plugin-dts/src/tsgo.ts 执行。

维护时要注意:generateDts 中 tsgo 和 tsc 的 bundle 时机不同。tsgo 路径会在 emit 完且无错误后立即 bundleDtsIfNeeded,而 tsc 非 watch 路径在 emit 完成后统一 bundle。

Isolated 后端

packages/plugin-dts/src/isolated.ts 依赖 Rspack 内置 RslibPlugin 的 isolated declaration emit。流程为:

  1. createIsolatedDtsContext 准备 dts 上下文。
  2. applyIsolatedDtsOptions 在 bundler config 中找到 Rspack RslibPlugin 实例。
  3. 将 emitDts.rootDir 和 emitDts.declarationDir 写入插件参数。
  4. Rspack 编译时产出 .d.ts。
  5. processIsolatedDts 将 .d.ts 重写为需要的扩展名,并执行统一后处理。
  6. 如果启用 bundle,再调用 API Extractor。

如果找不到内置 RslibPlugin,isolated 后端会报错。这通常意味着 format 或插件组合不满足 Rslib 内置插件的前提。

Bundle 声明文件

bundleDtsIfNeeded 在 bundle: true 时调用 apiExtractor.ts。API Extractor 的输入是已经生成到临时目录的 declaration entry,输出是目标 distPath 下的单个 entry dts 文件。

calcBundledPackages 用“非 external 的依赖需要被打进 dts bundle”的思路计算 bundledPackages:

  • 默认 external dependencies、peerDependencies、optionalDependencies,不 external devDependencies。
  • 用户 output.externals 中的依赖不 bundle。
  • @types/react 默认排除。
  • 用户可用 dts.bundle.bundledPackages 覆盖计算结果。

清理策略

pluginDts 在 onBeforeEnvironmentCompile 中执行清理:

清理对象条件
dts 输出文件output.cleanDistPath !== false
.rslib 临时目录bundle: true
tsbuildinfotsc 或 tsgo 后端,并且 composite、incremental 或 build: true

这保证每次构建不会混入旧声明文件,但也意味着修改清理逻辑时必须关注 watch 和多 environment 并行构建。

错误处理

插件默认 abortOnError = true。在 onAfterBuild 第二阶段:

  • 如果 dts 子任务失败且 abortOnError 为 true,抛出错误使构建失败。
  • 如果 abortOnError 为 false,记录错误和警告,但不终止构建。
  • isolated 后端不允许 abortOnError: false。

子进程模式下,dts.ts 会监听 process.on("message") 接收 DtsGenOptions,成功发送 success,失败发送 error 并退出。父进程在 close build 或 close dev server 时会 kill 剩余子进程。

维护检查点

  • 修改后端选择时必须更新 backend.ts 的约束和对应测试。
  • 修改路径重写时应检查 bundleless JS redirect 和 dts redirect 是否一致。
  • 修改 bundle 行为时应检查 API Extractor 多 entry、bundledPackages 和临时目录清理。
  • 修改 watch 行为时要确认子进程不泄漏,且 tsc watch 的 diagnostics 和 post-process 顺序仍正确。
  • 修改 isolated 行为时要同时检查 Rspack RslibPlugin 参数结构是否仍可用。