Nx 迁移编写避坑指南:废弃模式(Deprecated Patterns)识别与现代形态重写
【免费下载链接】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 的 monorepo 中,迁移(migration)是nx migrate升级工作区时自动执行的修复脚本,负责把用户配置与源码从旧形态改写为新形态。而"废弃模式"(deprecated patterns)指两类不该被新代码复制的写法:一类只存在于 git 历史中、只能在阅读旧迁移或第三方插件时遇到;另一类仍存活在仓库中但被明确禁止复制。本文以 .claude/skills/author-migration/deprecated-patterns.md 为核心骨架,结合仓库源码与配套技能文档,讲清每一类废弃模式的特征签名(recognition signature)、为何被废弃,以及对应的现代替代写法,帮助你迁移或移植旧迁移时直接改写为现代形态,而不是照搬。
为什么需要一份"废弃模式"清单
Nx 的迁移体系经历了从 Angular Devkit Schematics 到@nx/devkit的漫长演进,十余年间沉淀了大量历史写法。同一份migrations.json中既有老条目也有新条目,git 历史中更埋藏着 v6–v16 各时代的实现。当开发者需要:
- 移植某个上游框架自带的迁移到 Nx;
- 参照一条旧迁移写新迁移;
- 审查第三方插件中的迁移实现;
最容易犯的错误就是"照着历史抄"。但历史写法往往依赖已被移除的运行路径(例如schematics适配器会丢弃迁移的返回值),或者与当前运行时契约(runtime-contract.md)相冲突。因此 Nx 官方将废弃模式分为两个 registry:
| 类别 | 含义 | 适用场景 |
|---|---|---|
| 仅存于历史(Historical only) | 已从仓库删除,运行时会直接失败 | 阅读旧迁移作参考、移植第三方插件时 |
| 仍存活但禁止复制(Still live, do not copy) | 代码还在运行,旧条目仍在用 | 编写新迁移时绝不复制,需用现代替代 |
无论哪种,规则只有一条:迁移或引用旧迁移时,必须改写为现代形态,绝不原样复现。
历史遗留模式:只存在于 git 历史的写法
以下模式已从仓库删除,遇到时需识别并整体替换。
Angular Devkit Schematic Rules(v6–11 时代)
特征签名:import { Rule, chain } from '@angular-devkit/schematics',配合updateJsonInTree、readJsonInTree、createOrUpdate、带Change对象的insert,以及在结尾追加formatFiles()作为 Rule。
现代替代:默认导出的async function (tree: Tree),统一使用@nx/devkit。示例形态见 templates/spec-skeleton.md 中的 spec 骨架——现代迁移直接导入默认导出并调用,而非通过 Schematic 的 collection 机制。
顶层schematics段(2023 年之前)
特征签名:migrations.json中条目挂在"schematics"键下而非"generators"。
现代替代:一律使用generators段。依据 runtime-contract.md,schematics段在运行时走 Angular Devkit 适配器,且适配器会完全丢弃迁移的返回值——nextSteps、agentContext全部失效,这正是该段被废弃的根本原因。
迁移代码内的包版本升级(v6–10 时代)
特征签名:在 .ts 实现里调用addUpdateTask(...)、链式RunSchematicTask,或通过updateJsonInTree('package.json', ...)直接改版本。
现代替代:声明式的packageJsonUpdates组;只有依赖变更依赖工作区状态(条件性变更)时才写 .ts 实现。这一点在 SKILL.md 第 1 节有明确规定:无条件升级永远不写 .ts 实现,条件性依赖变更才使用addDependenciesToPackageJson/removeDependenciesFromPackageJson。
workspace.json / angular.json 编辑(v8–11 时代)
特征签名:updateWorkspaceInTree、getWorkspace/updateWorkspace、updateBuilderConfig。
现代替代:项目配置用getProjects/updateProjectConfiguration,Nx 级配置用readNxJson/updateNxJson。核心原因在 SKILL.md 第 4 节 Config edits:getProjects直接感知项目图,且updateProjectConfiguration不会像裸updateJson那样静默跳过基于 package.json 的项目。
@nrwl/*导入(约 v15 之前)
特征签名:from '@nrwl/workspace'、from '@nrwl/devkit',以及readWorkspaceConfiguration/updateWorkspaceConfiguration。
现代替代:统一@nx/devkit。
SchematicTestRunner 规格测试(v6–13 时代)
特征签名:SchematicTestRunner、UnitTestTree、通过 collection 执行runMigration('<name>', ...)。
现代替代:createTreeWithEmptyWorkspace()+ 直接导入迁移的默认导出。文档特别提醒了一个被丢失的能力:旧 helper 通过 migrations.json 按名字加载迁移,因此会验证"名字 → 实现"的接线是否正确;而直接导入的 spec 不再做这种校验——这正是为什么 pre-PR 检查清单要求打开 manifest 路径背后的真实文件确认实现指向正确(SKILL.md 第 7 节)。
AI 指令包装工厂(2026 年中之前)
特征签名:工厂函数读取files/<name>.md模板,通过tree.write写出tools/ai-migrations/MIGRATE_<THING>.md,并返回string[]。
现代替代:prompt键直接指向同目录(colocate)的 .md;runner 自身会把托管的工作区副本写到tools/ai-migrations/下。即路径写入职责从迁移代码移交给了 runner(见 runtime-contract.md 的prompt行)。
@nx/devkit/src/*深路径导入(pre-23)
特征签名:from '@nx/devkit/src/generators/...'。
现代替代:@nx/devkit/internal。半公开 helper(如forEachExecutorOptions、target-default helper)从@nx/devkit/internal导入是 SKILL.md 第 4 节 Common canon 的明确要求。
仍存活但禁止复制的模式
以下写法仍存在于仓库旧条目中(运行时也不一定立刻报错),但新条目一律禁止复制。
"cli": "nx"键
特征签名:generators 条目内出现"cli"键(旧条目中很普遍)。
规则:死键(dead key)。依据 runtime-contract.md,cli在"按段选择 runner"的改造后就已不再使用,schema 已标注 "No longer used"。新条目直接省略。
factory键
特征签名:"factory": "./dist/..."。
规则:被容忍的别名。运行时implementation与factory等价,且两者同时存在时implementation优先;但新条目只写implementation。不要对存量条目做批量改名。
packageJsonUpdates 上的x-prompt
特征签名:"x-prompt": "Do you want to update..."。
规则:仅交互式生效,且已被标记为 Nx v24 移除;改用requires做门控。参见 runtime-contract.md 与 templates/migrations-json.md。
无 slug 或带点号的条目键
特征签名:update-22-2-0(只有版本、没有动作 slug)、16.0.0-remove-nrwl-cli(版本段用点号而非连字符)、裸版本目录如21-0-0/。
规则:键名应表达动作(无 slug 的键无法区分同一次发布中的两条迁移),版本段之间用连字符而非点号;目录统一为update-<ver>/。键的具体形态遵循该文件的主流约定(SKILL.md 第 3 节)。
裸updateJson(tree, 'nx.json', ...)
特征签名:直接对 nx.json 调用updateJson。
规则:改用readNxJson/updateNxJson,且仅在内容变化时写回(SKILL.md 第 4 节)。
裸updateJson编辑 project.json
特征签名:updateJson(tree, join(root, 'project.json'), ...)。
规则:改用updateProjectConfiguration。裸编辑会静默跳过基于 package.json 的项目——这是项目图统一抽象下最容易踩的坑。
返回GeneratorCallback
特征签名:返回类型为Promise<GeneratorCallback>,函数返回安装任务。
规则:runner 会静默丢弃 callback(runtime-contract.md Return values)。现代返回值为void | string[] | { nextSteps, agentContext, skipAgentic };安装由 runner 通过 diff package.json 完成,绝不返回安装任务、绝不调用installPackagesTask。
console.*或 nx 的output工具
特征签名:console.warn(...)、import { output } from 'nx/src/utils/output'。
规则:改用 devkit 的logger。原因:agentic 运行会捕获 generator 的 logger 输出并喂给验证 agent(<generator_output>),好的警告应点名文件和剩余工作;console.*输出不在该通道内。
静态import * as ts from 'typescript'
特征签名:模块顶部值导入 TypeScript。
规则:顶部用 type-only 导入(import type * as ts from 'typescript'),首次使用时惰性ensureTypescript()(来自@nx/js/internal)或ensurePackage<typeof import('typescript')>('typescript', '*')。这避免在不需要 TypeScript API 的运行中支付加载成本。
深层nx/src/*导入
特征签名:插件迁移里出现from 'nx/src/utils/...'。
规则:使用 devkit 导出;跨边界导入在旧代码中被容忍,新代码不允许。例外是packages/nx内部自身使用相对导入。
忽略文件的子串检查
特征签名:content.includes(entry)后字符串拼接。
规则:改用addEntryToGitIgnore。其实现位于 packages/nx/src/utils/ignore.ts,基于ignore包解析而非子串匹配,能正确处理已覆盖模式、并自动创建不存在的文件。实际使用范例见 packages/nx/src/migrations/update-23-0-0/add-migrate-runs-to-git-ignore.ts:
export default async function addMigrateRunsToGitIgnore(tree: Tree) { if (!tree.exists('.gitignore')) { return; } // Lerna users that don't use nx.json may not expect .nx directory changes if (tree.exists('lerna.json') && !tree.exists('nx.json')) { return; } addEntryToGitIgnore(tree, '.gitignore', '.nx/migrate-runs'); await formatChangedFiles(tree); }注意它同时示范了两个通用守卫:无.gitignore直接返回;Lerna 用户未使用 nx.json 时不写.nx目录。对应条目见 packages/nx/migrations.json 的23-0-0-add-migrate-runs-to-git-ignore。
非 colocate 的 prompt 文件
特征签名:prompt指向某个 generator 的files/目录。
规则:把 .md 与迁移放在同一update-<ver>/目录中。依据 runtime-contract.md,prompt相对路径会被校验必须留在 migrations 目录内,因此 colocate 是硬性前提。
以文档风格编写的 prompt .md
特征签名:作为prompt接入的文件里出现#### Sample Code Changes这类 h4 标题。
规则:prompt 使用 runbook 风格(模板见 templates/prompt-runbook.md),h4 是documentation文件的体裁。一个典型的 prompt-runbook 包含:Overview(含 out of scope)、Pre-Migration Checklist(作用域任务须以 no-op guard 开头)、分步 Before/After、Post-Migration Validation 的循环直到变绿、Nx-Specific Notes。
迁移中的 devkitglob
特征签名:从@nx/devkit导入的glob(。
规则:原地弃用(deprecated in place),改用globAsync。
从"识别"到"改写":现代迁移的标准形态
识别出废弃模式后,改写时直接落到现代规范。综合 SKILL.md 与 templates/migrations-json.md,现代迁移的标准形态可以概括为三层。
文件布局与 manifest 条目
packages/<plugin>/src/migrations/update-<major>-<minor>-<patch>/<name>.ts packages/<plugin>/src/migrations/update-<major>-<minor>-<patch>/<name>.spec.ts (有实现时才需要) packages/<plugin>/src/migrations/update-<major>-<minor>-<patch>/<name>.md (documentation,与 .ts 同名) packages/<plugin>/src/migrations/update-<major>-<minor>-<patch>/<other-name>.md (prompt,基名必须与任何 .ts 不同)manifest 中挂generators段,路径使用发布形态(dist 前缀):
"update-23-2-0-remove-foo-option": { "version": "23.2.0-beta.3", "description": "Removes the deprecated `foo` option from the @nx/bar:build executor options", "implementation": "./dist/src/migrations/update-23-2-0/remove-foo-option", "documentation": "./dist/src/migrations/update-23-2-0/remove-foo-option.md" }实现层通则
export default async function update(tree: Tree),不接收选项;runner 以(tree, {})调用。- 只用 Tree API,绝不使用
fs;路径用joinPathFragments或node:path的posixhelper 构建。 - 结尾
await formatFiles(tree)(不触碰 JS/TS/JSON 面时可跳过;packages/nx内部用formatChangedFiles)。 - 失败开放(fail open),绝不 throw——一个抛错的迁移会让整个
--run-migrations无法恢复;无法解析的文件跳过并记录进返回的agentContext。 - 幂等构造:重写消耗自身触发条件、写回以
updated !== original门控,或显式 already-migrated 守卫(nx repair会无条件重跑 nx-core 迁移)。 - 版本字符串冻结为文件内本地常量,绝不从插件的
utils/versions导入(那些常量随每次发布浮动,用户运行的是编译进目标版本的迁移)。
返回值契约
现代迁移的返回值为void | string[] | { nextSteps, agentContext, skipAgentic }:
string[]是nextSteps的简写;nextSteps:展示在运行结束摘要与失败回顾中,被 Nx Console 持久化,但永不进入 agent prompt;agentContext:注入 agent prompt 作为<advisory_context>;在纯人工运行时被丢弃,因此面向人的内容必须同步进nextSteps;skipAgentic: true:声明确定性运行已覆盖一切,跳过本会执行的 AI 步骤(hybrid 的 prompt 阶段或 generator-only 迁移后的验证步骤),仅在迁移能证明"无事可做"时返回,绝不与agentContext同返。
新旧形态对照速查
| 废弃模式 | 特征签名 | 现代替代 |
|---|---|---|
| Schematic Rules | @angular-devkit/schematics的Rule/chain | 默认导出async (tree: Tree) |
顶层schematics段 | 条目在schematics下 | generators段 |
| 迁移内升级包 | addUpdateTask、RunSchematicTask | 声明式packageJsonUpdates |
| 编辑 workspace.json | updateWorkspaceInTree等 | getProjects/updateProjectConfiguration、readNxJson/updateNxJson |
@nrwl/*导入 | @nrwl/workspace、@nrwl/devkit | @nx/devkit |
| SchematicTestRunner spec | 按名字runMigration | createTreeWithEmptyWorkspace+ 直接导入默认导出 |
| AI 指令工厂 | 读模板 +tree.write到tools/ai-migrations/ | prompt键 + runner 自管副本 |
@nx/devkit/src/*深路径 | from '@nx/devkit/src/...' | @nx/devkit/internal |
"cli": "nx" | 条目内cli键 | 省略(死键) |
factory | "factory": "./dist/..." | implementation |
x-prompt | packageJsonUpdates 上的交互提示 | requires门控 |
| 无 slug / 点号键 | update-22-2-0、16.0.0-remove-nrwl-cli | 动作 slug + 连字符版本段 |
裸updateJson(nx.json) | 直接改 nx.json | readNxJson/updateNxJson |
裸updateJson(project.json) | join(root, 'project.json') | updateProjectConfiguration |
GeneratorCallback返回 | 返回安装任务 | void/string[]/{ nextSteps, agentContext, skipAgentic } |
console.*/output | console.warn、nx/src/utils/output | devkitlogger |
| 静态 ts 导入 | 顶部import * as ts | type-only +ensureTypescript() |
深nx/src/*导入 | 插件迁移里的nx/src/utils/... | devkit 导出 |
| 忽略文件子串检查 | content.includes(entry) | addEntryToGitIgnore(packages/nx/src/utils/ignore.ts) |
| 非 colocate prompt | prompt指向 generator 的files/ | 迁移update-<ver>/目录内 colocate |
| 文档体裁 prompt | h4#### Sample Code Changes | runbook 体裁(templates/prompt-runbook.md) |
devkitglob | 迁移中的glob( | globAsync |
验证与收尾:让改写经得起检查
改写完成后,用两层机制验证。机械层由仓库 validator 把关:npx nx run-many -t test,lint -p <plugin>跑根级migrations.spec.ts(assertValidMigrationPaths校验每条条目路径能在源码树中解析、无孤儿 .ts/.md)与@nx/nx-plugin-checks(校验 manifest 形态、重复键);pnpm nx-cloud conformance:check中的migration-markdown-assets规则校验发布形态(每个被引用的 .md 确实被打包进构建产物)。
判断层(SKILL.md 第 7 节)是 validator 覆盖不到的"人工残留":确认implementation指向本迁移的文件(validator 只查存在性,不查指向是否正确);确认使用implementation而非factory、无cli键;确认版本是该发布轨(train)的下一个精确预发布版、requires相对落地版本评估且无编码源窗口的上界;确认 spec 覆盖全部必测用例(negative 必测,幂等、畸形输入、多编辑、优先级、列表健全、行为复现按触发条件必测)。核心原则始终如一:从历史与旧条目中识别模式、改写为现代形态,而不是复现它们。
【免费下载链接】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),仅供参考