tsdown shims 选项完全指南:在 airi 仓库中优雅打通 ESM 与 CommonJS 模块系统
tsdown shims 选项完全指南在 airi 仓库中优雅打通 ESM 与 CommonJS 模块系统【免费下载链接】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 的shims选项用于解决 TypeScript 库打包中最常见的一类痛点ESMECMAScript Modules与 CommonJSCJS两套模块体系之间运行时全局变量缺失的兼容问题。本文以 option-shims.md 为主体结合 airi 开源仓库内大量真实tsdown.config.ts配置进行佐证帮助你彻底理解__dirname、__filename、require、import.meta.*在不同输出格式下的补齐机制并学会在 CLI 工具、双格式库、服务端代码、浏览器包等场景中正确地开关 shims。什么是 ShimsShims垫片是 tsdown 在构建产物中注入的一小段运行时辅助代码用于弥合 CJS 与 ESM 两套模块系统之间变量能力不对等的缝隙实现跨模块系统的兼容。这两套模块体系各自拥有对方不存在的全局能力CommonJS天生提供__dirname当前目录绝对路径、__filename当前文件绝对路径、require、module、exports但没有import.metaESM天生提供import.meta.url当前模块的file://形式 URL但没有__dirname、__filename且关键字require未定义此外CJS 模块内的__dirname是文件系统路径而 ESM 的import.meta.url是 URL 形式file:///...二者还需要相互转换。当你用 tsdown 将同一份源码编译成多种格式或从 CJS 向 ESM 迁移时shims 会自动把这些缺失的全局能力补上让同一套源码在不同格式下都能直接运行。从 tsdown 的设计看shims 分为显式开启与自动注入两类。下面逐一展开。三类 Shims 与各自的作用显式开启ESM 输出中的__dirname/__filename当配置shims: true且输出为 ESM 时tsdown 会向产物注入__dirname与__filename两个 CommonJS 变量的定义方便在 ESM 下做相对于当前模块的文件路径计算__dirname当前文件所在目录路径__filename当前文件路径自动注入ESM 中使用require只要 tsdown 检测到源码在 ESM 输出中使用了require且运行平台为 Node.js就会自动通过 Node 标准库module.createRequire构造一个合法的require函数无需任何配置require经由createRequire(import.meta.url)创建其解析基准与当前模块一致自动注入CJS 输出中的import.meta.*tsdown 总是会为 CommonJS 输出补充以下三项import.meta属性保证源码中无论使用import.meta.url、import.meta.dirname还是import.meta.filename在 CJS 产物中都能正常工作import.meta.url通过pathToFileURL(__filename).toString()得到import.meta.dirname即__dirnameimport.meta.filename即__filename也就是说CJS 方向的三项补齐是完全自动、无需配置的而 ESM 方向只有require是自动的__dirname/__filename需要显式打开shims: true。如何开启CLI 与配置文件两种用法CLI 方式tsdown --shims仅需一个--shims标志即可对本次构建开启 ESM 输出下的__dirname/__filename注入。配置文件方式export default defineConfig({ entry: [src/index.ts], format: [esm], shims: true, })shims是与entry、format、platform平级的顶层构建选项类型为布尔值。值得注意的是它只影响 ESM 输出的显式补齐CJS 输出的import.meta.*与 ESM 输出的require属于自动行为不受此开关约束也因此本文后面 CJS 场景的配置示例中都不需要写shims: true。三种典型场景下的产物对比理解 shims 最快的方式是直接对照源码 → 产物的变换关系。ESM shims: true__dirname/__filename源码console.log(__dirname) console.log(__filename)产物shims: trueimport { fileURLToPath } from node:url import { dirname } from node:path const __filename fileURLToPath(import.meta.url) const __dirname dirname(__filename) console.log(__dirname) console.log(__filename)可以看到tsdown 生成的垫片非常标准先用fileURLToPath(import.meta.url)把 ESM 的 URL 形式转成文件系统路径赋给__filename再用dirname()推得__dirname。这两行代码本身就是 ESM/CJS 互操作中最常被手写的样板tsdown 只是替你自动写好了。ESM 使用requireNode.js 上自动源码const mod require(some-module)产物Node.js 平台下自动注入import { createRequire } from node:module const require createRequire(import.meta.url) const mod require(some-module)createRequire(import.meta.url)创建的require以当前 ESM 模块为解析起点因此对依赖的查找行为与 CJS 下保持一致源码里遗留的require调用无需逐一手动改写。CJS 输出 使用import.meta自动补齐源码console.log(import.meta.url) console.log(import.meta.dirname)产物CJS 下自动const import_meta { url: require(url).pathToFileURL(__filename).toString(), dirname: __dirname, filename: __filename } console.log(import_meta.url) console.log(import_meta.dirname)tsdown 会把源码中的import.meta替换为局部对象import_meta其中url由pathToFileURL(__filename).toString()还原为 URL 字符串dirname/filename则直接复用 CJS 本就拥有的__dirname/__filename。常见配置模式shims极少单独出现通常与format、platform、deps组合使用。以下四类模式覆盖了绝大多数真实工程。Node.js CLI 工具CLI 工具几乎必然要访问自身路径如读取模板、定位资源文件建议对platform: nodeformat: esm开启 shimsexport default defineConfig({ entry: [src/cli.ts], format: [esm], platform: node, shims: true, // Add __dirname, __filename })双格式ESM CJS库需要同时发布 ESM 与 CJS 两种产物时开启shims: true即可让源码中的__dirname/__filename在 ESM 产物中可用而 CJS 产物会自动获得import.meta.*二者互补一套源码无需条件分支export default defineConfig({ entry: [src/index.ts], format: [esm, cjs], platform: node, shims: true, // ESM gets __dirname/__filename // CJS gets import.meta.* (automatic) })服务端代码服务端代码常常需要外部化全部依赖保留运行时的node_modules解析。此时把deps.neverBundle设为匹配所有包的正则即可避免把fs、path、node:url等内置模块和第三方依赖打进产物export default defineConfig({ entry: [src/server.ts], format: [esm], platform: node, shims: true, deps: { neverBundle: [/.*/], // External all deps }, })文件系统操作shims 最典型的使用场景是读取与当前模块同目录的资源文件。注意源码与配置要成对出现——源码依赖__dirname配置必须打开shims源码import { readFileSync } from fs import { join } from path // Read file relative to current module const content readFileSync(join(__dirname, data.json), utf-8)tsdown 配置export default defineConfig({ entry: [src/index.ts], format: [esm], shims: true, // Enables __dirname })何时该用、何时不该用应当开启shims: true的场景✅ 构建 Node.js 工具 / CLI✅ 源码中使用了__dirname或__filename✅ 需要相对于当前模块的文件系统操作✅ 正在从 CommonJS 向 ESM 迁移迁移存量代码常残留 CJS 写法✅ 需要跨格式ESM 与 CJS的兼容产物不需要 shims 的场景❌ 纯浏览器代码浏览器既无__dirname也无require❌ 不涉及任何文件系统操作❌ 只使用import.meta.urlESM 原生支持无需垫片❌ 纯 ESM、从不引用 CJS 变量性能影响与 Tree Shaking运行时开销shims 注入的运行时开销极小本质上只是在模块顶层求值两条 Node 内置 API// Added to output when shims enabled import { fileURLToPath } from node:url import { dirname } from node:path const __filename fileURLToPath(import.meta.url) const __dirname dirname(__filename)Tree Shaking 自动消解tsdown 的底层打包器会做依赖分析如果产物中根本没有用到__dirname/__filename这段垫片代码会在打包阶段被自动剔除不会给未使用该特性的包带来任何多余体积。因此对纯 ESM 库而言即使误开了shims: true只要源码不引用这两个变量产物也不会有额外负担——这也是可以在双格式库中放心全局开启的原因之一。平台考量node / browser / neutralshims 的取舍与目标平台强相关。tsdown 的platform选项详见 option-platform.md决定了产物的运行环境假设进而决定哪些变量有意义。Node.js 平台export default defineConfig({ platform: node, format: [esm], shims: true, // Recommended for Node.js })require垫片自动添加Node.js 原生提供createRequire__dirname与__filename在shims: true下可用Browser 平台export default defineConfig({ platform: browser, format: [esm], shims: false, // Not needed for browser })浏览器环境不存在 Node.js 变量不需要 shims若在 browser 产物中使用 Node.js APItsdown 会给出警告Neutral中立平台export default defineConfig({ platform: neutral, format: [esm], shims: false, // Avoid platform-specific code })中立平台面向不绑定 Node.js / 浏览器的通用包为了最大化可移植性应避免注入平台相关代码CLI 速查示例# Enable shims tsdown --shims # ESM with shims for Node.js tsdown --format esm --platform node --shims # Dual format with shims tsdown --format esm --format cjs --shimsTroubleshooting三个高频报错__dirname is not defined原因很直接ESM 输出下__dirname并非原生变量。开启 shims 即可export default defineConfig({ shims: true, })ESM 中require is not defined在 Node.js 平台上本应自动注入。若仍报错请确认platform明确为 node因为createRequire依赖 Node 环境浏览器/中立平台不会也无法注入export default defineConfig({ platform: node, // Ensure Node.js platform })CJS 中import.meta不可用import.meta.*在 CJS 输出中是自动补齐的无需配置。如果仍然失败检查输出格式是否确实为 CJSexport default defineConfig({ format: [cjs], // Shims added automatically })使用建议速览Node.js 工具务必开启——CLI 与服务的文件路径访问几乎都依赖__dirname/__filename浏览器代码直接跳过——没有 Node 变量垫片无意义未使用即零开销——Tree Shaking 会自动剔除无引用的垫片require垫片全自动——ESM 中残留的require无需手工改造CJS 的import.meta.*全自动——CJS 产物里始终可用airi 仓库中的实际印证airi 是一个横跨 Electron 桌面端、Web、移动端Capacitor、服务端与各插件 SDK 的大型 monorepo几乎全部产物都用 tsdown 构建仓库根目录下可检索到近 30 个tsdown.config.ts。这些真实配置恰好能印证本文不同模式的分工纯 ESM、偏向浏览器/中立平台的组件库如 packages/audio/tsdown.config.tsunbundle: true保留目录结构、packages/better-ws/tsdown.config.ts、packages/plugin-sdk-tamagotchi/tsdown.config.ts、packages/electron-vueuse/tsdown.config.ts均只声明format: esm与dts: true源码运行时不依赖 CJS 全局变量因此这些配置中都不需要出现shims: true——这与纯 ESM、只用import.meta时无需垫片的结论一致。按运行环境拆分产物的多平台包packages/electron-screen-capture/tsdown.config.ts 用defineConfig([...])分别声明platform: node、platform: neutral与platform: browser三套配置对应 Electron 主进程、通用入口与渲染进程三个环境。若主进程 ESM 代码需要读写自身安装目录下的资源只需在该段配置追加shims: truebrowser 段则保持关闭避免注入 Node 变量。强制 CJS 的宿主约束integrations/vscode/vscode-airi/tsdown.config.ts 是一个反例——VS Code 扩展的加载机制要求插件以 CommonJS 发布因此该配置显式指定format: cjs与platform: node。按本文的机制这类 CJS 产物会自动获得import.meta.url/import.meta.dirname/import.meta.filename三项补齐扩展内即使书写 ESM 风格的元信息也能安全运行无需手动配置。服务端/命令行入口plugins/airi-plugin-claude-code/tsdown.config.ts 将入口定位为src/run.ts的命令行 runner 并声明platform: node、publint: trueintegrations/vscode/airi-plugin-vscode/tsdown.config.ts 同样面向 Node 运行环境。凡是这类在 Node 进程里真正跑起来的产物若内部需要相对模块路径读取资源就属于应当补充shims: true的候选。从源码结构可以推断airi 仓库中的绝大多数包是组件/协议/纯逻辑库天然不触碰__dirname、require等 CJS 专属能力因而整体上无需显式开启shims真正需要它的是那些会读取自身资源文件、以 Node 进程方式被启动的服务端模块与 CLI。理解了这一判断逻辑你在自己项目中遇到__dirname is not defined、require is not defined一类报错时也能迅速定位到正确的 tsdown 配置。关联选项Platform目标运行环境node / browser / neutral决定哪些变量有意义Output FormatESM / CJS / IIFE 等模块格式决定垫片注入方向Target语法降级目标与运行时变量的补齐相互独立本文对应技能文档原文位于 .agents/skills/tsdown/references/option-shims.md可结合 tsdown 技能总览 与其他 option 文档交叉阅读。【免费下载链接】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),仅供参考