Nx 23.2 配置迁移:将 Vite/Vitest 配置文件中的__dirname替换为import.meta.dirname
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
本文围绕 Nx 仓库中@nx/vitest插件在 23.2.0 引入的自动迁移(update-23-2-0-use-import-meta-dirname)展开,讲解它为什么存在、覆盖哪些文件、底层如何借助 TypeScript AST 安全改写代码,以及哪些场景会被刻意跳过。读完本文,你将理解 ViteconfigLoader: 'native'对 ESM 语法的新要求,掌握__dirname→import.meta.dirname的迁移边界,并能判断自己工作区中的配置文件是否还需要手工处理。
迁移背景:Vite 原生配置加载器不认__dirname
Vite 计划在未来主版本中把configLoader: 'native'设为默认的配置加载方式。在这种原生加载模式下,Vite 使用 Node.js 自身的 ESM 机制去加载配置文件,而__dirname是 CommonJS 的运行时全局变量——在 ESM 模块中它并不存在。因此当配置里用到__dirname时,Vite 8 会给出类似如下的警告:
(!) Your Vite config uses features that are unsupported by `configLoader: 'native'`, which is planned to become the default in a future major version of Vite: - `__dirname` (packages/utils/vitest.config.mts:4:9). Use `import.meta.dirname` instead也就是说:虽然当前默认的configLoader: 'bundle'还能兼容__dirname,但配置一旦迁移到原生加载模式就会失败。Node.js 20.11+ 提供了与__dirname语义等价的 ESM 替代品import.meta.dirname,这就是本次迁移要做的替换。
迁移覆盖范围:只处理 ESM 专属扩展名的配置文件
这条迁移会扫描工作区中所有vite.config.mts、vite.config.mjs、vitest.config.mts、vitest.config.mjs文件,把其中的__dirname替换为import.meta.dirname。
官方文档给出的替换前后示例:
// Before export default defineConfig(() => ({ root: __dirname, }));// After export default defineConfig(() => ({ root: import.meta.dirname, }));实现中对文件匹配的正则定义在 use-import-meta-dirname.ts:
// Only ESM-only extensions. A `.ts`/`.js` config can still be loaded as CJS, // where `import.meta` is a syntax error. const CONFIG_FILE_PATTERN = /(^|\/)(vite|vitest)\.config\.(mts|mjs)$/;注意两个关键设计:
- 只匹配
.mts/.mjs:只有这两个扩展名能保证文件以 ESM 方式加载,import.meta语法必然合法; - 路径匹配不限层级:
(^|\/)前缀允许packages/utils/vitest.config.mts、apps/web/vite.config.mjs这类子目录中的配置被命中,根目录配置同样覆盖。
迁移通过@nx/devkit的visitNotIgnoredFiles(tree, '.', ...)遍历整个工作区树(尊重.gitignore等忽略规则),并对每个命中的文件先做快速字符串判断(original?.includes('__dirname')),只有确实包含该标识符时才进入 AST 改写流程;全部处理完成后调用formatFiles(tree)统一格式化,保证改动后的文件风格与工作区一致。
如何触发这条迁移
该迁移已注册在@nx/vitest的 migrations.json 中:
"update-23-2-0-use-import-meta-dirname": { "version": "23.2.0-beta.6", "description": "Replace `__dirname` with `import.meta.dirname` in `vite.config.mts`/`vitest.config.mts` files so they work with Vite's `configLoader: 'native'`.", "implementation": "./dist/src/migrations/update-23-2-0/use-import-meta-dirname", "documentation": "./dist/src/migrations/update-23-2-0/use-import-meta-dirname.md" }它属于标准的 Nx 自动迁移(migration generator),当你把工作区的@nx/vitest从旧版本升级到 23.2.0 时,会随nx migrate流程生成对应的迁移文件并被自动执行。运行迁移后,控制台会输出类似信息:Replaced \__dirname` with `import.meta.dirname` in N Vite config file(s).(来自 [use-import-meta-dirname.ts](https://link.gitcode.com/i/128ef2522e2313fa05da700314db97d7) 的logger.info`),其中 N 是被实际改写的配置文件数量;若一个文件都没改动,则不会输出任何日志。
源码级原理:AST 分类与三种处理策略
迁移的核心是 rewriteDirname。它不是粗暴的字符串替换,而是先用 TypeScript 编译器把文件内容解析成 AST,再遍历每一个__dirname标识符并为其分类,根据分类决定动作:
bail(整体放弃):只要文件里出现一个bail类型的__dirname,整个文件都不做任何改写;reference(改写):确认是引用 CommonJS 全局__dirname,收集起来统一替换;skip(跳过单个):该标识符只是某个声明里的名字位,不是真正的引用,仅跳过它本身。
分类逻辑在 classify 函数 中实现,同时配套 bindsDirname 函数 判断一个声明是否把新的__dirname引入了作用域(即发生了遮蔽,此时__dirname不再是 CJS 全局变量):
- 出现在变量声明、函数参数、解构绑定元素、函数声明、类声明、import/export 子句的名字位时 → 判定为遮蔽声明,
bail整个文件; - 出现在简写属性赋值(
{ __dirname })中 →bail,因为此时 key 和 value 是同一个 token,改写会破坏语义; - 出现在父节点的
name/propertyName槽位 →skip。如源码注释所述,这个“反向”的名字位测试是刻意设计的:凡是没被显式枚举到的节点类型(类字段、访问器、枚举成员等),默认都进不了reference,从而不可能被改写成非法语法; - 其余情况 →
reference,即真正的全局__dirname引用。
改写时按 AST 节点位置从后往前逐个替换文本(避免位置偏移相互影响),每个引用点被精确替换为import.meta.dirname。另外,如果文件本身存在 TypeScript解析错误(parseDiagnostics非空),迁移会直接原样返回、绝不盲目改写——源码注释明确说明:一个 TypeScript 都只能错误恢复解析的配置,其 AST 表达的含义不可信,应当留给人工处理。
什么不会被重写:五类边界情况
这是本次迁移设计上最值得注意的部分,也是避免迁移引入新 bug 的关键。
1..ts与.js配置文件一律不动
vite.config.ts、vitest.config.js这类文件依然可以被当作 CommonJS 加载(Vite 默认的 bundle 模式可以处理),而在 CJS 中import.meta是语法错误。所以迁移对它们保持原样。相关断言见 use-import-meta-dirname.spec.ts。
2. 自行声明了__dirname的配置
如果配置里自己定义了__dirname(通常是const __dirname = path.dirname(fileURLToPath(import.meta.url))这个惯用写法),那么它已经与import.meta语义等价,在原生配置加载器下也能正常工作,迁移不会画蛇添足。对应的测试用例如 use-import-meta-dirname.spec.ts。
3. 解构重命名、import 别名、re-export
以下场景中__dirname都不是全局引用,迁移全部跳过:
// 解构重命名:只改后面的真实引用,不解构键名 const { __dirname: dir } = someOptions; export default { root: __dirname, dir }; // import 别名:整体不动 import { __dirname as dir } from './paths.mjs'; export default { root: dir }; // 再导出:整体不动 export { __dirname } from './paths.mjs';这三个用例分别对应 use-import-meta-dirname.spec.ts 与第 L171-L178 行的测试。
4. 类成员、访问器、枚举成员
即使类字段、getter、枚举成员恰好也叫__dirname,它们也不会被误改,而同一文件里真正的全局引用仍会正常替换——这得益于classify中“名字位默认排除”的反向测试设计,具体见测试 use-import-meta-dirname.spec.ts。
5. 无法解析的配置文件
语法不完整的配置(例如缺少闭合括号)会被整体跳过,等待人工修复,见 use-import-meta-dirname.spec.ts。
此外,非配置类文件(如src/other.mts)不在匹配范围内,天然不会被触碰(见 use-import-meta-dirname.spec.ts);迁移也是幂等的——对已改写过的文件再次运行不会产生任何变化(见 use-import-meta-dirname.spec.ts)。
未被迁移的.ts配置:仍有一个 CJS/ESM 警告需要你处理
迁移不会把现有配置重命名成.mts,因为其他工具链可能通过路径引用这些配置文件,擅自改名会破坏引用关系。因此,一个.ts配置会继续保留“ESM 语法出现在 CommonJS 加载文件里”的伴随警告。要消除它,有两种方式(按 use-import-meta-dirname.md 的说明):
- 手动把配置重命名为
.mts(vite.config.ts→vite.config.mts),同时记得同步更新任何按路径引用它的工具配置; - 在最近的
package.json中设置"type": "module",让该目录下的文件默认按 ESM 解析。
而新生成的配置不受影响——@nx/vitest的 configuration 生成器 在生成根级聚合配置时明确选择.mts扩展名,源码注释指出:.mts保证无论根package.json的type字段是什么都按 ESM 加载,而 CommonJS 加载的配置会触发 ViteconfigLoader: 'native'警告。
一个特殊的例外:@nx/nuxt在 eslintrc 工作区中的.ts回退
有一个自动生成的场景确实会把import.meta.dirname放进.ts配置:@nx/nuxt的 Vitest 配置生成器在仍使用旧版 eslintrc(.eslintrc.json)的工作区中会回退到vitest.config.ts,原因是旧版@nuxt/eslint-config(约~0.5.6)的解析器配置不认识.mts文件。相关逻辑见 packages/nuxt/src/generators/application/lib/add-vitest.ts:
// Only the legacy @nuxt/eslint-config (~0.5.6) leaves .mts out of its // parser configuration, so fall back to .ts just for it. A .ts config in // a CommonJS package trips Vite's `configLoader: 'native'` warning. useEsmExtension: options.linter !== 'eslint' || useFlatConfig(tree),这种情况下生成的vitest.config.ts今天还能通过 Vite 默认配置加载器(bundle 模式)正常工作——它自己的import语句本来就依赖该模式。但该文件在configLoader: 'native'下是不可加载的。换句话说,如果你正处于这个例外场景,升级 Vite 主版本前需要先迁移到 ESLint flat config,或按上一节的方式处理该.ts配置。
结语
use-import-meta-dirname是一条小而精的 Nx 自动迁移:它精准锁定.mts/.mjs配置、以 AST 级粒度区分“真正的__dirname引用”与“同名但无关的标识符”,并主动放弃所有存在歧义或解析异常的文件。理解它的边界设计,不仅能帮你顺利度过 Vite 原生配置加载器的过渡期,也能为你在自己工具链中编写“安全优先”的代码改写迁移提供一个可参考的范本。
如果想深入验证或学习,可以继续阅读仓库内的三份关键文件:迁移说明 use-import-meta-dirname.md、迁移实现 use-import-meta-dirname.ts、以及覆盖全部边界场景的测试 use-import-meta-dirname.spec.ts。
【免费下载链接】nxThe Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the time.项目地址: https://gitcode.com/GitHub_Trending/nx/nx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考