news 2026/9/12 11:00:27

Nx 23.0 迁移指南:将 `@nx/remix` 插件的 `createNodesV2` 导入重命名为 `createNodes`

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nx 23.0 迁移指南:将 `@nx/remix` 插件的 `createNodesV2` 导入重命名为 `createNodes`

Nx 23.0 迁移指南:将@nx/remix插件的createNodesV2导入重命名为createNodes

【免费下载链接】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/remix插件推断功能(inferred plugin)的开发者,讲解 Nx 23.0 引入的自动化迁移:把@nx/remix/plugin的主导出createNodesV2重命名为规范的createNodes,并保留旧的createNodesV2作为已弃用别名。读完本文,你将掌握这条迁移的完整行为边界(会重写什么、明确跳过什么)、其源码级实现原理(基于 TypeScript AST 的文本重写机制),以及如何通过nx migrate在真实工作区中落地该变更。

迁移背景:为什么createNodesV2要改名

Nx 的插件体系通过「推断插件」在无需手写project.json的情况下,从文件系统自动发现项目并推断出 targets。@nx/remix作为 Remix 官方集成插件,其主推断导出此前命名为createNodesV2

在 Nx 23.0.0 中,@nx/remix将这一主导出更名为createNodes,使之与 Nx 生态其他插件的命名保持一致。createNodesV2这个名字作为已弃用别名暂时保留,但新代码应当统一使用createNodes

从源码可以确认这一「同一函数、两个名字」的实现方式:packages/remix/src/plugins/plugin.ts 中,createNodes是真正的推断插件实现(匹配**/{remix,vite}.config.{js,cjs,mjs,ts,cts,mts}配置文件并生成builddevstartserve-statictypecheck等 target),而紧随其后的export const createNodesV2 = createNodes;只是对同一对象的别名引用。公共入口 packages/remix/plugin.ts 同时导出createNodescreateNodesV2,因此在运行时两者完全等价,任何通过旧名字进行的调用(包括命名空间访问、动态导入等)都继续可用。

正因二者是同一引用,这次变更本质上是「更名」而非「替换实现」,所以迁移工具可以放心地把createNodesV2的全部静态命名导入重写为createNodes,而不会产生行为差异。

迁移的行为范围:会被重写的内容

该迁移由 packages/remix/migrations.json 注册,generator 名为update-23-0-0-migrate-create-nodes-v2-import,对应版本23.0.0-beta.24,实现位于 packages/remix/src/migrations/update-23-0-0/migrate-create-nodes-v2-to-create-nodes.ts。

扫描范围

迁移会遍历工作区中所有未被忽略的文件,但只处理以下四种 TypeScript 扩展名:

  • .ts
  • .tsx
  • .cts
  • .mts

对每个文件,先做一次快速文本检查(是否包含createNodesV2),不包含则直接跳过,避免不必要的解析开销(见 migrate-create-nodes-v2-to-create-nodes.ts#L39-L52)。测试用例也验证了非 TS 文件(如docs/example.md)不会被改动。

唯一命中的模块说明符

只有从@nx/remix/plugin导入或再导出的createNodesV2才会被重写(TARGET_SPECIFIERS集合中目前只有这一项)。以下来源的createNodesV2不会被触碰

  • 来自其他模块,如import { createNodesV2 } from '@nx/other-plugin/plugin';
  • 来自相对路径,如import { createNodesV2 } from './plugin';

这两条边界均有对应单元测试覆盖(见 migrate-create-nodes-v2-to-create-nodes.spec.ts#L103-L116)。

具体的改写规则

1. 普通命名导入

// Before import { createNodesV2 } from '@nx/remix/plugin'; // After import { createNodes } from '@nx/remix/plugin';

2. 混合命名导入:其他导出保持不动,仅重命名createNodesV2

// Before import { createNodesV2, SomeOtherExport } from '@nx/remix/plugin'; // After import { createNodes, SomeOtherExport } from '@nx/remix/plugin';

3. 别名导入:别名(本地绑定名)保留,只重写「原导出名」一侧:

// Before import { createNodesV2 as cn } from '@nx/remix/plugin'; // After import { createNodes as cn } from '@nx/remix/plugin';

4. 冗余别名折叠createNodesV2 as createNodes会被折叠为单个createNodes

// Before import { createNodesV2 as createNodes } from '@nx/remix/plugin'; // After import { createNodes } from '@nx/remix/plugin';

5. 重复绑定去重:若同一文件同时导入了createNodescreateNodesV2,无论先后顺序,冗余绑定都会被删除:

// Before import { createNodes, createNodesV2 } from '@nx/remix/plugin'; // After import { createNodes } from '@nx/remix/plugin';

多行命名导入同样会被重排为单行形式(见 spec#L61-L66)。

6. 默认导入与 type 导入:默认导入保留;import type、内联type修饰符也被正确保留:

// Before import def, { createNodesV2 } from '@nx/remix/plugin'; import type { createNodesV2 } from '@nx/remix/plugin'; import { type createNodesV2 } from '@nx/remix/plugin'; // After import def, { createNodes } from '@nx/remix/plugin'; import type { createNodes } from '@nx/remix/plugin'; import { type createNodes } from '@nx/remix/plugin';

7. 命名再导出export { ... } from '@nx/remix/plugin'与导入同样处理,别名规则一致:

// Before export { createNodesV2 } from '@nx/remix/plugin'; export { createNodesV2 as cn } from '@nx/remix/plugin'; // After export { createNodes } from '@nx/remix/plugin'; export { createNodes as cn } from '@nx/remix/plugin';

8. 文件体内对绑定名的值引用:当一个本地绑定确实被重命名(无别名的{ createNodesV2 },或被去重合并进已有createNodes)时,文件体内所有对该绑定的值引用也会同步重写,否则会留下悬空引用。测试覆盖了变量赋值、函数调用实参(如addPlugin(graph, '@nx/remix/plugin', createNodesV2, {}))、对象简写属性等场景:

// Before import { createNodesV2 } from '@nx/remix/plugin'; export const plugin = createNodesV2; // After import { createNodes } from '@nx/remix/plugin'; export const plugin = createNodes;

9. 对象简写属性的展开{ createNodesV2 }简写会被展开为{ createNodesV2: createNodes },从而保留属性键名:

// Before import { createNodesV2 } from '@nx/remix/plugin'; export const plugins = { createNodesV2 }; // After import { createNodes } from '@nx/remix/plugin'; export const plugins = { createNodesV2: createNodes };

不会被重写的内容(及原因)

这是本次迁移最关键的行为边界。以下形式的createNodesV2全部保持原样,因为它们不依赖静态命名绑定,且能通过运行时别名继续正常工作:

  • 命名空间导入import * as plugin from '@nx/remix/plugin'后通过plugin.createNodesV2访问;
  • 动态导入import('@nx/remix/plugin')之后解构;
  • CommonJS 解构const { createNodesV2 } = require('@nx/remix/plugin');(有专门测试验证其保持不变);
  • 属性访问 / 限定类型名config.createNodesV2PluginTypes.createNodesV2
  • export * from再导出export * from '@nx/remix/plugin';没有可重命名的具名绑定;
  • 对象字面量键{ createNodesV2: something }中的键名不是绑定引用;
  • 遮蔽声明:文件中若存在名为createNodesV2的变量、参数或函数声明,属于遮蔽(shadowing),不会被误改;
  • 字符串与注释"createNodesV2"// import { createNodesV2 } ...一律不动。

对于这些形式,若你希望彻底移除弃用名字,需要手工更新。由于createNodesV2只是运行时别名,即使不改,代码依然正常工作(相关边界判定逻辑见 migrate-create-nodes-v2-to-create-nodes.ts#L280-L316)。

迁移的源码实现原理

实现 migrate-create-nodes-v2-to-create-nodes.ts 的机制值得了解,便于理解上述所有行为:

  1. 整体流程:入口migrateCreateNodesV2ToCreateNodes(tree)通过visitNotIgnoredFiles遍历工作区,过滤出 TS 扩展名文件,做createNodesV2文本预检,命中后调用核心函数rewriteCreateNodesV2Imports;若有文件实际发生变化,通过logger.info输出改动文件数,最后调用formatFiles统一格式化。累计改动文件数统计逻辑见 migrate-create-nodes-v2-to-create-nodes.ts#L34-L61。

  2. AST 解析rewriteCreateNodesV2Imports使用typescriptcreateSourceFile将源码解析为 AST(ScriptKind.TSX),遍历顶层语句,区分ImportDeclarationExportDeclaration分别处理。

  3. 字符串补丁而非整文件重写:核心的rewriteNamedBindings会重新渲染{ ... }绑定列表——重命名createNodesV2createNodes、丢弃重复项、保留每个绑定上的type修饰符与别名——然后通过applyChangesToString以「删除旧区间 + 插入新文本」的方式生成补丁(见 migrate-create-nodes-v2-to-create-nodes.ts#L173-L212)。这意味着模块说明符、import/export关键字、默认导入、type修饰符等周边内容全部原样保留,只动绑定列表本身。

  4. 条件性的值引用重写:只有当本地绑定名真正发生变化(无别名导入或被去重)时,才会触发collectValueUsageRewrites重写文件体内的标识符引用,并配合isRenamableValueUsage排除属性访问、限定名、对象键、遮蔽声明等不可重写的语法位置。

如何在你的工作区执行这条迁移

@nx/remix的迁移注册于 packages/remix/migrations.json,由 Nx 迁移机制统一驱动。在升级到 Nx 23 时:

  1. 升级 Nx 相关依赖到 23.x(该迁移对应的version字段为23.0.0-beta.24);
  2. 在仓库根目录运行迁移命令,Nx 会读取各插件的migrations.json并自动应用update-23-0-0-migrate-create-nodes-v2-import
    nx migrate @nx/remix

    (按 Nx 常规流程,先nx migrate更新 package.json 并安装依赖,再运行nx migrate --run-migrations执行迁移文件。)

  3. 迁移完成后,检查migrations.json生成的迁移文件输出日志(Renamed \createNodesV2` imports to `createNodes` in N file(s).`),并在各应用/库下运行类型检查与测试,确认无悬空引用。

迁移后的代码该长什么样

迁移完成后,你的推断插件注册代码应使用规范名。例如,一个手写插件配置或入口文件应呈现为:

import { createNodes } from '@nx/remix/plugin'; // 在 nx.json / 插件配置中使用 createNodes export default { name: '@nx/remix/plugin', createNodes, options: { buildTargetName: 'build', devTargetName: 'dev', startTargetName: 'start', typecheckTargetName: 'typecheck', serveStaticTargetName: 'serve-static', }, };

同时,仓库内部源码也已全面转向新命名,可作为「迁移后的正确写法」的参考:@nx/remix自己的生成器在注册插件时使用createNodesV2(因为内部运行时别名仍存在,见 init.ts#L11、convert-to-inferred.ts#L7),而插件入口 packages/remix/plugin.ts 同时导出两个名字,其中createNodes已排在首位。

常见问题

Q:迁移后nx命令还能识别旧的createNodesV2吗?能。createNodesV2作为已弃用别名保留(export const createNodesV2 = createNodes;),运行时行为完全一致,只是新代码不应再使用。

Q:为什么我的require('@nx/remix/plugin')没被改?迁移只重写静态import/export的具名绑定。require解构、命名空间访问、动态import()等通过运行时别名继续工作,不在自动重写范围内,需要的话请手工更新。

Q:迁移会不会误改其他包导出的同名符号?不会。TARGET_SPECIFIERS只匹配@nx/remix/plugin,来自其他模块或相对路径的createNodesV2一律跳过。

Q:如果我同时导入了createNodescreateNodesV2会怎样?冗余绑定会被自动删除,只保留createNodes,且文件体内引用同步更新。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 11:00:16

移动储能在配电网韧性提升中的优化策略与Matlab实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 10:58:59

AI技术如何突破跨境电商语言壁垒

1. 义乌防雾面罩的16秒神话背后:AI如何击穿跨境语言壁垒去年冬天,一款来自义乌的防雾面罩在TikTok上突然爆火。从第一个测评视频发布到登上亚马逊美区运动防护类目榜首,只用了16秒。这个看似偶然的案例背后,隐藏着跨境商家用AI技术…

作者头像 李华
网站建设 2026/9/12 10:58:48

线上接单5-8元/单的高效运营指南

1. 项目概述"在线接单 5-8/一单"这个标题描述的是一个典型的线上服务交易场景。作为从业多年的自由职业者,我理解这指的是通过互联网平台接收任务订单,每单报酬在5-8元之间的服务模式。这种模式常见于文案写作、数据标注、简单设计等标准化程度…

作者头像 李华
网站建设 2026/9/12 10:57:20

AI邮件助手误删事故:权限控制与数据恢复的教训

1. 事件背景:AI失控引发的数据灾难那天早上,我像往常一样测试新开发的AI邮件助手。这个基于大语言模型的智能系统被设计用来帮助用户自动分类、归档和回复邮件。在测试环境中运行两周后表现良好,于是我决定用个人邮箱账号做真实场景测试——这…

作者头像 李华