airi × tsdown:cjsDefault 选项深度解析——掌控 CJS 输出的默认导出形态 airi × tsdowncjsDefault 选项深度解析——掌控 CJS 输出的默认导出形态【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airitsdown 是基于 Rolldown 与 Oxc 构建的 TypeScript/JavaScript 库打包器在 airi 仓库中作为约 29 个 packages 的标准构建工具版本由 pnpm workspace 统一锁定在 pnpm-workspace.yaml 的catalog:中为tsdown: ^0.22.14各子包的package.json以build: tsdown脚本调用。本文聚焦 tsdown 参考文档中的 cjsDefault 选项完整讲解它如何控制 CommonJS 产物中默认导出的落地形态module.exports还是exports.default、两种取值下生成代码与声明文件的差异以及如何结合仓库中真实的tsdown.config.ts配置判断自己是否需要显式设置该选项。读完本文你可以为双格式ESM CJS库精确设计require()消费方的接入体验并理解 airi 各子包为何普遍选择纯 ESM 从而绕过这一问题。一、cjsDefault 解决什么问题当库同时构建出 ESM 与 CJS 两种产物时format: [esm, cjs]CJS 产物中默认导出如何落地存在两种流派模式CJS 产物写法消费方 require 方式cjsDefault: true默认module.exports greetconst greet require(your-module)cjsDefault: falseexports.default greetconst { default: greet } require(your-module)选项定义如下摘自 option-cjs-default.mdcjsDefault?: boolean // default: true其生效条件有两个前提一是产物格式为cjs即 输出格式 中包含 CJS二是入口模块只有单个 default 导出。满足这两点时tsdown 会将默认导出直接挂到module.exports上而不是保留 ESM 语义的exports.default命名空间形态。二、配置写法启用与禁用两种取值的完整配置示例与原文档一致可直接复制到任意tsdown.config.ts启用默认行为import { defineConfig } from tsdown export default defineConfig({ entry: [src/index.ts], format: [cjs], cjsDefault: true, // default behavior })禁用import { defineConfig } from tsdown export default defineConfig({ entry: [src/index.ts], format: [cjs], cjsDefault: false, })需要注意的隐含前提cjsDefault只影响format包含cjs的构建目标若配置中只有format: esm这是 airi 仓库的普遍做法见第五节该选项实际上不会被触发。选项作用于仅含单一默认导出的入口。若入口同时存在默认导出与命名导出tsdown 不会将module.exports整体替换为默认值——这既是安全边界也是文档建议既有默认导出又有命名导出时显式禁用的原因。三、工作机制源码、CJS 产物与声明文件的三方对照cjsDefault: true默认时的转换源码src/index.tsexport default function greet() { console.log(Hello, world!) }生成的 CJS 产物dist/index.cjsfunction greet() { console.log(Hello, world!) } module.exports greet生成的声明文件dist/index.d.ctsdeclare function greet(): void export greet注意声明文件中的export 语法这是 TypeScript 对 CJS 整模块即值语义的正式表达保证类型检查与运行期行为一致——require()拿到的就是函数本身调用方无需解包.default。cjsDefault: false 时的转换默认导出保留为exports.default属性// dist/index.cjs function greet() { console.log(Hello, world!) } exports.default greet此时 CJS 消费方必须写require(your-module).default才能拿到函数本体。消费方视角的差异总结场景cjsDefault: truecjsDefault: falserequire()返回值函数本体模块命名空间对象调用写法const greet require(your-module); greet()require(your-module).default()声明文件export greetexport default greet与 ESMimport greet from的直觉一致都直接拿到本体一致都需处理 default 包裹四、何时应该禁用 cjsDefault原文档给出三条禁用场景展开说明如下模块同时存在默认导出和命名导出一旦module.exports被整体替换为默认值命名导出将无法以常规方式附着行为会变得不一致显式cjsDefault: false可让所有导出统一走exports.*属性CJS 消费方行为可预期。需要一致的exports.default行为多入口库中若部分入口是纯默认导出、部分不是保持全部走exports.default可以让消费方使用统一的解包逻辑例如mod.default ?? mod的垫片只写一处。消费方全部使用 ESMimport当 CJS 产物只是兼容兜底、实际无人require()时禁用cjsDefault反而能避免看起来可直接require()调用造成的误用。配套的最佳实践Tips与原文档一致大多数库保持默认true即可同时存在默认与命名导出、且需要一致行为时禁用发布前用真实的 CJS 消费方脚本测试产物兼容性例如临时写一个require(dist/index.cjs)的.cjs探针文件运行验证不要只靠 ESM 侧的类型检查推断 CJS 行为。五、在 airi 仓库中的实践观察airi 仓库是 tsdown 的大规模使用现场通过 find tsdown.config.ts 可确认共有 29 个配置文件分布在packages/、integrations/、plugins/、services/、server/packages/等处构建命令统一为build: tsdownwatch 场景如 integrations/vscode/vscode-airi/package.json 使用tsdown --watch。从源码结构看这些子包对cjsDefault的实际依赖极低原因在于它们的format选择纯 ESM 显式声明packages/plugin-sdk/tsdown.config.ts、integrations/vscode/airi-plugin-vscode/tsdown.config.ts、packages/electron-vueuse/tsdown.config.ts 等均写有format: esm。ESM 产物中默认导出本来就是export default语义cjsDefault不参与。未显式指定 format 的默认行为packages/audio/tsdown.config.ts多入口 unbundle: true、packages/better-ws/tsdown.config.ts、packages/stream-kit/tsdown.config.ts 未写format字段按 输出格式文档 的说明tsdown 默认格式为 ESM同样不触发 CJS 默认导出转换。仅声明入口差异格式一致如 packages/cap-vite/tsdown.config.ts 使用对象式多入口index/bin/run/vite-plugin/vite-wrapper-config并设置target: node18但同样未声明cjs格式。可以推断airi 作为以 ESM 为主的现代 workspacepnpm-workspace.yaml中 TypeScript 已升至 6.x 目录将 CJS 兼容负担整体交给了上游工具链而非库产物因此没有任何子包需要为cjsDefault写显式配置。这正是原文档 Tips 第 1 条大多数库保持默认即可在真实 monorepo 中的印证——选项的默认值true在不产出 CJS的项目里甚至零成本。反过来当你在 airi 风格的项目中为某个子包新增format: [esm, cjs]例如给旧版 Node.js 生态或 VS Code 扩展宿主提供require()入口时cjsDefault才是需要主动决策的选项入口是纯默认导出且希望require()拿到本体 → 保持默认入口混合了命名导出且希望 CJS/ESM 两侧行为对称 → 显式cjsDefault: false。六、与相邻选项的配合关系cjsDefault不是孤立开关它与以下两个选项共同决定双格式库的兼容面对应原文档的 Related Options 章节输出格式formatcjsDefault仅在产物包含cjs时有意义。airi 各子包以format: esm或省略默认 ESM为主故该选项在这些配置中处于休眠状态若按该文档的Node.js 包CJS ESM模式补上format: [esm, cjs]cjsDefault才进入生效范围。Shimsshims两者解决的是同一兼容问题的不同侧面——cjsDefault决定CJS 产物里默认导出如何暴露shims决定ESM/CJS 之间缺失的运行时变量__dirname、__filename、import.meta.*如何补齐。构建双格式 Node.js 库时典型组合是format: [esm, cjs]platform: nodeshims: true再按第四节的判断决定是否追加cjsDefault: false。七、快速决策清单综合原文档与 airi 仓库的实际配置给出一条可执行的决策路径产物是否包含cjs格式否 → 无需关心cjsDefaultairi 当前 29 个子包均落在这一侧是 → 入口是否只有单个默认导出是且希望require()直接拿到本体 → 保持默认true入口混合默认与命名导出、或消费方以 ESM 为主、CJS 仅作兜底 → 显式cjsDefault: false无论哪种选择用真实的 CJS 探针require产物并调用验证再结合 shims 检查__dirname/import.meta等变量在双格式下的可用性。更多 tsdown 选项entry、dts、deps、unbundle、workspace等与 CLI 用法可继续参考 tsdown 技能索引。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考