implemented(除 4 篇超长文档随后补)、proposed、rejected 全树配对; 同一流水线 + 二遍校验(paraphrase-back + 仓库上下文一致性)产出。 docs/rfc/implemented/AGENTS.md 与其 CLAUDE.md 符号链接列入排除 (agent 指令文件,与根 AGENTS.md 同策略)。
3.2 KiB
RFC:用 tsdown 替代 dumble 进行 JS 打包
Status: implemented
English | 中文
问题
初始构建使用 dumble——cordiverse 的零配置 esbuild 包装层,上游 Cordis 本身也用它构建——与 vendor 包的约定最大程度对齐(它读取每个 package.json,从 exports 字段推断入口和格式)。但 dumble 作为本仓库的承重工具是一个隐患:v0.2.x,每周约 530 次 npm 下载,实质上只有一位维护者,而且由于它没有 workspace 模式,我们不得不通过一个自定义编排脚本(scripts/build.ts)来调用它。
目前构建产物只对 pnpm run build + publint 有意义(尚无包发布;开发/测试/演示通过 tsx 直接运行未打包的源码),因此切换成本现在最低,一旦包开始发布就只会更高。
决策
用 tsdown(基于 rolldown,每周约 250 万次下载,VoidZero 支持,活跃发布)替代 dumble:
- 根目录
tsdown.config.ts,配置workspace: ['vendor/*', 'packages/*/*'](显式 glob 将打包范围限定在 vendor Cordis 和 TypeScript 包树;workspace: true还会发现示例 manifest 和不需要打包的 workspace 成员)。 - 共享形态:入口
lib/types/index.js,outDir: 'lib',ESM,platform: node,target: es2024,fixedExtension: false(对"type": "module"的包保持.js扩展名),dts: false(声明文件由 tsc -b 负责),clean: false(lib/ 同时存放 TSC 的lib/types中间产物树)。入口最初是src/index.ts;TSC 优先构建 RFC 后来将 tsdown 改为打包 TSC 输出的 JS,使 TypeScript 转换行为来自同一个编译器。 - vendor/ 中有两个逐包覆盖配置(属于我们的修改,与重新生成的 tsconfig 一样;记录在 vendor/README.md 中):schemastery(通过
outExtensions输出双格式.mjs/.cjs)、logger-console(两次单入口 pass,使共享基类内联到每个入口而非生成 hash 命名的 chunk,与上游发布形态一致)。 - 删除
scripts/build.ts;pnpm run build=tsc -b tsconfig.build.json && tsdown。
曾考虑的替代方案
- 直接编写 esbuild 脚本:最成熟的引擎,零包装层风险,但需要手动维护 tsdown workspace 模式自动提供的逐包规格表。
- pkgroll:理念上最接近的直接替代品,但每周仅 78k 下载且基于 Rollup:维护前景严格弱于 tsdown。
- 保留 dumble:与上游完美对齐,但 bus factor 不可接受。
后果
运行时打包产物仍遵循 dumble 时代的公开入口形态(lib/index.js,加上包特有的变体如 schemastery 的 lib/index.mjs/lib/index.cjs 和 logger-console 的 lib/browser.js);声明文件现在按 TSC 优先构建 RFC 放在 lib/types 下。外部依赖仍来自各包的 dependencies/peerDependencies。我们放弃了 dumble 的 exports 字段推断能力:入口形态非默认的新包需要一个逐包的 tsdown.config.ts,而不能仅靠 package.json 字段。未来选项:如果 tsc -b 成为瓶颈,tsdown 也可以接管声明文件打包(isolatedDeclarations);那将是一个新的 RFC。