airi 多包工程中的 tsdown `platform` 选项实战:node、browser、neutral 三平台构建决策 airi 多包工程中的 tsdownplatform选项实战node、browser、neutral 三平台构建决策【免费下载链接】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 是 airi 仓库内各共享包packages/*、plugins/*、integrations/*统一使用的库打包器其platform选项决定了构建产物的目标运行时环境并直接影响模块解析规则、Node.js 内置模块的处理方式和默认优化策略。本文以仓库内维护的 option-platform 参考文档 为主体完整覆盖三种平台的特性、CJS 格式限制、mainFields模块解析差异与常见故障排查并结合 airi monorepo 中真实存在的 tsdown 配置文件 展示 node / browser / neutral 三平台如何落到同一个包里。一、platform是什么决定运行时环境的总开关platform指定打包代码的目标运行时环境Target runtime environment for bundled code。它的影响体现在三个层面模块解析module resolution不同平台检查package.json中不同的字段详见第四节内置模块处理built-in handlingNode.js 内置模块fs、path等在node平台下被自动解析在browser平台下会触发告警优化行为optimizations每个平台有自己的默认mainFields进而决定依赖解析路径。可用平台一览平台运行时内置模块处理典型用途nodeNode.js默认值自动解析Resolved automatically服务端、CLI 工具、工程化工具browserWeb 浏览器使用时发出告警Warning if used前端应用neutral平台无关不做任何假设No assumptions通用库两种指定方式CLI 方式取值只能是node、browser、neutral三者之一CLI 参考见 reference-cli.md 中--platform platform一节tsdown --platform node # 默认值 tsdown --platform browser tsdown --platform neutral配置文件方式airi 仓库中各包使用的tsdown.config.ts均为这种形态export default defineConfig({ entry: [src/index.ts], platform: browser, })适用前提tsdown本身要求构建环境为 Node.js 22.18.0 以上仅构建期要求产物可用 target 选项降级这一约束说明见 SKILL.md。二、三大平台特性详解Node 平台服务端与工具类场景的默认平台export default defineConfig({ entry: [src/index.ts], platform: node, })特性Node.js 内置模块fs、path等被自动解析不会进产物也不报错针对 Node.js 运行时做优化兼容 Deno 与 Bun默认mainFields为[main, module]即按 main → module 的顺序解析依赖包。Browser 平台面向浏览器中运行的 Web 应用export default defineConfig({ entry: [src/index.ts], platform: browser, format: [esm], })特性代码中若使用 Node.js 内置模块构建时会发出告警Node API 在浏览器中运行可能需要 polyfill针对浏览器环境做优化默认mainFields为[browser, module, main]即优先读取依赖包的browser字段这是多数为 Node 写的库提供浏览器替身的标准位置。Neutral 平台面向平台无关的通用库export default defineConfig({ entry: [src/index.ts], platform: neutral, format: [esm], })特性不做任何运行时假设不会自动解析Node.js 内置模块只依赖exports字段解析依赖默认mainFields为[]空数组意味着不会回退到main/module等字段运行时行为完全由你自己控制。airi 仓库实证neutral平台并非理论选项。packages/electron-screen-capture/tsdown.config.ts 用defineConfig数组一次性构建了三份不同平台的产物其中中间那份就是 neutral 平台export default defineConfig([ { ...sharedConfig, platform: node, entry: { main: src/main/index.ts, }, inlineOnly: false, }, { ...sharedConfig, platform: neutral, entry: { index: src/index.ts, }, inlineOnly: false, }, { ...sharedConfig, unbundle: true, platform: browser, entry: { vue: src/vue/index.ts, renderer: src/renderer.ts, }, inlineOnly: false, }, ])这份配置把同一个屏幕采集包拆成三种构建目标Electron 主进程入口走node平台src/index.ts作为跨运行时的公共 API 走neutral平台既不解析 Node 内置模块、也不做浏览器特化而src/vue/index.ts与src/renderer.ts走browser平台供渲染进程使用。对照同目录的 package.json 可以确认exports字段恰好按平台产物一一映射exports: { .: ./dist/index.js, ./main: ./dist/main.mjs, ./renderer: ./dist/renderer.js, ./vue: ./dist/vue.js }这正是 neutral 平台只依赖exports字段特性的实际落地消费方airi 的 Electron 桌面端按子路径导入时拿到的是对应平台构建的产物。三、CJS 格式限制CJS 输出恒等于 node 平台这是一个容易踩坑的硬约束cjs格式始终使用node平台且无法更改。以下配置中的platform: browser在 CJS 构建下会被忽略export default defineConfig({ entry: [src/index.ts], format: [cjs], platform: browser, // 对 CJS 无效被忽略 })该限制源于 tsdown 底层打包引擎 rolldown 的实现决策原文档引用了 rolldown 侧的 PR 讨论作为出处。实践上的结论是只有 ESM 等格式才能真正区分 browser / neutral 平台需要浏览器目标的 CJS 场景本质上不存在。airi 仓库实证integrations/vscode/vscode-airi/tsdown.config.ts 中的 VS Code 插件构建配置同时写了format: cjs与platform: node{ entry: [./src/extension.ts], format: cjs, platform: node, external: [vscode], sourcemap: true, clean: true, dts: false, inlineOnly: false, }两者看似重复实际互为核心逻辑format: cjs已经决定了平台必然是 node显式写出platform: node是让配置意图对读者自明——VS Code 扩展宿主只接受 CommonJS 模块因此这里必须 CJS而 CJS 又天然落在 node 平台上。四、模块解析mainFields差异与 neutral 的解析告警各平台的 mainFields 优先级不同平台在解析依赖包的package.json时检查的字段不同平台mainFields优先级顺序node[main, module]main → modulebrowser[browser, module, main]browser → module → mainneutral[]仅exports字段这意味着同一个依赖包在browser平台下可能解析到带browser字段的替身实现而在node平台下解析到main指向的实现——这就是切换平台会改变构建结果的底层机制。Neutral 平台的典型解析失败与修复使用neutral时如果某个依赖包没有exports字段只有main/module由于 neutral 默认mainFields为空构建会失败并给出如下提示Help: The main field here was ignored. Main fields must be configured explicitly when using the neutral platform.解决方案是显式配置mainFieldsexport default defineConfig({ platform: neutral, inputOptions: { resolve: { mainFields: [module, main], }, }, })inputOptions.resolve是透传给底层打包器的原生解析选项mainFields直接指定字段优先级数组。对于需要兼容 Node 与浏览器两类消费者的库可以放宽为三字段并补充条件见下文 Troubleshooting 的完整示例。五、常见模式Common Patterns原文档给出五组可直接复制的配置模板这里完整保留并结合适用场景说明。1. Node.js CLI 工具export default defineConfig({ entry: [src/cli.ts], format: [esm], platform: node, shims: true, })shims: true会为 ESM 产物补齐__dirname、__filename等 CJS 兼容垫片详见 Shims 参考。airi 仓库中 plugins/airi-plugin-claude-code/tsdown.config.ts 就是一个真实的 CLI 类构建{ entry: [./src/run.ts], inlineOnly: [], platform: node, dts: true, unused: true, publint: true, }2. 浏览器库IIFEexport default defineConfig({ entry: [src/index.ts], format: [iife], platform: browser, globalName: MyLib, minify: true, })globalName指定挂到window上的全局变量名配合format: [iife]可直接用script标签引入IIFE/UMD 等格式的完整语义见 Output Format 参考。3. 通用库Universal Libraryexport default defineConfig({ entry: [src/index.ts], format: [esm], platform: neutral, inputOptions: { resolve: { mainFields: [module, main], }, }, })这是第四节 neutral 修复方案的标准姿势显式声明mainFields让没有exports字段的依赖也能被解析。4. React 组件库export default defineConfig({ entry: [src/index.tsx], format: [esm, cjs], platform: browser, deps: { neverBundle: [react, react-dom], }, })deps.neverBundle将react、react-dom标记为外部依赖不打包避免两份 React的经典问题依赖处理的完整选项neverBundle/alwaysBundle/onlyBundle/自动外部化见 Dependencies 参考。5. Node Browser 双平台构建export default defineConfig([ { entry: [src/index.ts], format: [esm, cjs], platform: node, }, { entry: [src/browser.ts], format: [esm], platform: browser, }, ])defineConfig接受数组即为多配置模式每个配置对象独立指定平台与入口最后通过package.json的exports条件如node/browser条件分发给不同运行时的消费者。airi 的 electron-screen-capture 配置见第二节示例是该模式在三平台下的扩展版本。六、Troubleshooting两类高频故障故障 1Browser 构建中出现 Node 内置模块告警当代码在 browser 平台构建时使用了 Node.js API会看到Warning: Module fs has been externalized for browser compatibility含义是fs等内置模块被外部化externalized以兼容浏览器——构建能过但运行时该模块是空实现。解决路径按优先级排列若产物并非浏览器专属改用platform: node为所需 Node API 添加 polyfill从浏览器侧代码中剥离对 Node.js 内置模块的依赖最干净的做法通常意味着拆分src/node.ts/src/browser.ts两套入口即第五节模式 5;使用platform: neutral并仔细管理依赖边界。故障 2Neutral 平台下依赖包无法解析当neutral平台下某些包解析失败时可以显式同时放宽mainFields与解析条件conditionsexport default defineConfig({ platform: neutral, inputOptions: { resolve: { mainFields: [module, browser, main], conditions: [import, require], }, }, })conditions数组声明参与exports条件匹配的运行时条件同时给出import与require两个条件可让同一个产物同时满足 ESM 导入方与 CJS 导入方对exports的条件查询。七、实践要点小结Tips原文档给出的六条经验结合 airi 仓库的现状逐条对应服务端与 CLI 用node默认值——如 airi-plugin-claude-code、vscode-airi前端应用用browser——如 electron-screen-capture 中vue/renderer入口通用库用neutral——同上配置中的index入口产物经exports分发给 Node 与浏览器两侧消费者使用 neutral 平台时必须显式配置mainFields否则无exports字段的依赖会解析失败CJS 恒等于 node 平台——其他平台目标请使用 ESM在目标环境中实测验证兼容性——构建期平台声明不能替代运行时验证多目标包如同 electron-screen-capture 这种 main/renderer/index 三分产物尤其应在各自运行时里跑一遍冒烟检查。八、相关选项platform与以下选项协同工作完整选项文档位于 tsdown skills 参考目录Output Formatesm/cjs/iife/umd等模块格式决定第三节 CJS 限制是否生效TargetJavaScript 语言版本目标es2020、node18、chrome100等与平台共同决定产物运行下限ShimsESM/CJS 兼容垫片__dirname、__filename等Dependencies外部依赖控制neverBundle、alwaysBundle、自动外部化。阅读建议本文所有命令行与配置示例均来自当前仓库内 option-platform.md 的既有内容airi 仓库为只读环境实际落地时请在自己的项目中复制这些配置并对照本文第六节做故障排查。【免费下载链接】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),仅供参考