Nx:让 Monorepo 的任务图、增量构建与缓存保持可信
一次看似普通的共享类型调整,只改了 packages/catalog-core 中的一个字段。开发者本机执行受影响测试,Nx 很快返回绿色;合并后,catalog-api 在集成环境启动失败,因为它真正依赖 catalog-core,项目图里却没有这条边。更糟的是,错误的构建结果已经进入共享缓存,后续流水线不断重放同一份“成功证据”。团队得到的不是增量构建,而是增量漏测。
Nx 能把几百个项目的 lint、test、build 和 e2e 组织成可计算的图,也能把相同输入的任务结果在开发机和 CI 之间复用。它不会替团队猜对所有边界:项目依赖是否完整、Git 比较基线是否正确、任务读取了哪些文件和环境变量、产物能否进入共享缓存,仍然必须由工程契约约束。真正值得建设的能力不是“跑得快”,而是只省略已经被哈希和依赖图证明无需重复的工作。
先判断慢在“重复工作”还是“工作本身”
Nx 适合项目边界能够被识别、任务可以用输入和输出描述、多个项目共享同一提交历史的代码库。典型信号是:一次小改动会触发全仓测试;不同脚本重复构建同一共享库;CI 用手写目录规则分片;本机和流水线缺少统一任务入口;团队已经无法回答“这个库变化会影响哪些应用”。
若仓库只有两三个彼此独立的包,全量流水线仍在可接受范围,先统一包管理器、lockfile 和根脚本通常更划算。若主要瓶颈是单个编译任务自身耗时、测试依赖外部慢服务、runner 长时间排队,接入任务图也不会自动消除这些成本。Nx 优化的是图上重复计算与调度,不是编译器、网络和测试设计的替身。
项目图和任务图也不能混为一谈:
项目图描述 catalog-api 依赖 catalog-core 这样的代码或配置关系,affected 依赖它向反向依赖方传播影响。任务图描述执行 catalog-api:build 前必须完成 catalog-core:build,以及 lint、test、build 能否并行。计算哈希描述同一个任务的源文件、依赖、配置、运行时、环境变量和命令参数是否与历史执行相同。
缓存只在哈希相同后重放终端输出和声明的产物;它不会验证被遗漏的输入,也不会修复错误的项目边。
Nx 的任务运行说明把任务来源分为 package.json 脚本、project.json 显式配置和插件推断;缓存机制说明则明确哈希会结合源代码、外部依赖、运行时值和命令参数。把这四层分开,排障时才不会用 nx reset 掩盖项目建模错误。
建立一个可丢弃的 workspace
开发机需要受所选 Nx 发布线支持的 Node.js、一个被团队统一的包管理器、可写工作目录和能够访问批准包源的网络。全局安装 Nx 不是必需条件;仓库中的本地版本和 lockfile 才是团队基线。当前稳定基线可从 Nx 23.1.0 起步,最终仍以仓库 lockfile 与 npx nx report 为准。nx 与官方 @nx/* 包采用 MIT 许可证;Nx Cloud、Nx Agents、Conformance、单租户和自托管企业形态是另一套托管或商业能力,不能因为本地 CLI 是开源软件,就假定云端并发、数据驻留、支持和治理能力也没有套餐边界。先记录运行环境,再创建空 workspace:
node --version
npm --version
npx create-nx-workspace@latest nx-catalog-lab --template=empty
cd nx-catalog-lab
npx nx reportcreate-nx-workspace 会询问 workspace 名称、包管理器和是否连接 Nx Cloud。第一次练习可暂不连接远端缓存,先证明本地模型正确。新建项目说明提供空模板与框架模板入口;命令中的 latest 只用于第一次选择发布线,生成后应提交 manifest 与 lockfile,并通过仓库内的 npx nx、包管理器脚本或等价本地入口运行,避免全局 CLI 漂移。
生成两个 TypeScript 库,第二个库将调用第一个库:
npx nx generate @nx/js:library packages/catalog-core --bundler=tsc
npx nx generate @nx/js:library packages/catalog-api --bundler=tsc
npx nx show projects
npx nx show project catalog-core --web生成器的可选项会随插件发布线变化,先用下面的命令查看当前仓库实际支持的参数,不要从旧文章复制一长串选项:
npx nx generate @nx/js:library --help如果是已有 npm、pnpm、Yarn 或 Bun 仓库,先在干净分支执行增量接入:
npx nx@latest init
npx nx show projects
npx nx graphnx init 会分析现有脚本和工具配置并写入 Nx 依赖与配置。接入前保存全量构建耗时、失败率、项目数量、根脚本和 lockfile 基线;接入后第一阶段只让 Nx 代理已有命令,不同时搬目录、换包管理器和升级框架。这样回退 Nx 时,原始 npm run build 或等价命令仍然可用。
企业网络要把 npm registry、代理和 CA 配置交给包管理器的标准配置层。不要通过关闭 TLS 校验解决安装失败;Nx 及插件会在开发机和 CI 中执行代码生成、图分析与任务调度,依赖来源必须进入与其他构建工具相同的准入、许可证和供应链扫描流程。
项目发现决定了图上有哪些节点
Nx 从 package manager workspace、project.json、项目 package.json 和插件识别项目。先用机器可读输出建立资产清单:
npx nx show projects --json
npx nx graph --file=tmp/nx-project-graph.html
npx nx show project catalog-api --json图中每个节点都应有稳定名称、明确 root、owner 和可运行目标。项目 root 重叠、同名项目、生成目录被误识别,都会让 affected 和缓存边界失真。大仓不要把整个 packages/ 当成一个项目,也不要把每个源文件拆成项目;一个项目通常对应可独立构建、测试、发布或拥有的工程单元。
依赖边可能来自 TypeScript import、package.json 依赖、插件分析和 implicitDependencies。静态分析识别不了代码生成器读取模板、部署项目消费镜像描述、测试项目按字符串加载 fixture 等关系时,要显式建模。下面的配置表示 catalog-e2e 的正确性依赖 catalog-api:
{
"name": "catalog-e2e",
"root": "tests/catalog-e2e",
"implicitDependencies": ["catalog-api"]
}显式边不是“保险起见全部相连”。过多虚假边会让任何改动扩散到全仓,最终团队为了提速又开始跳过任务。新增或删除依赖后,至少执行 nx graph 并用一次真实变更验证影响方向。自定义项目图插件通过 createNodesV2 发现项目和目标,通过 createDependencies 补充依赖边;插件输出必须按稳定顺序返回,不能混入绝对路径、时间戳、随机值或仅存在于某台机器的环境数据,否则同一提交会得到不同项目图哈希并持续 cache miss。跨语言插件还要缓存昂贵分析、为增量文件集合设计失效条件,并在调试期间同时设置 NX_DAEMON=false 与 NX_CACHE_PROJECT_GRAPH=false。Nx 的项目图插件说明给出了当前 API 与调试入口。
推断任务与显式任务要有唯一事实源
Nx 插件会从 vite.config.ts、Jest、ESLint、TypeScript 等工具配置推断命令、可缓存性、输入、输出和任务依赖。显式目标则适合插件无法推断的业务脚本或需要项目级覆盖的场景。配置优先级依次是插件推断、nx.json 的 targetDefaults、项目级 package.json 或 project.json;后者会覆盖前者。
先查看合并后的有效配置,而不是只读某一个 JSON 文件:
npx nx show project catalog-api --web
npx nx show project catalog-api --json
npx nx run catalog-api:build --graph全仓同名目标可以在 nx.json 中声明共同管线:
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"cache": true,
"inputs": ["production", "^production"],
"outputs": ["{workspaceRoot}/dist/{projectRoot}"]
},
"test": {
"cache": true,
"inputs": ["default", "^production"]
}
}
}这里两个 ^ 的作用不同:dependsOn 中的 ^build 要求先构建项目依赖,inputs 中的 ^production 把依赖项目的生产输入纳入当前任务哈希。漏掉前者可能让消费方在依赖产物尚未生成时启动;漏掉后者可能让依赖源代码已变化而消费方仍命中旧缓存。
targetDefaults 按目标名匹配时会影响所有同名任务,而且显式字段会覆盖插件推断值。给 build 写一份不适合所有构建器的 outputs,可能让其中一类项目恢复不到产物。nx.json 参考支持按 plugin、project 或 executor 过滤默认值;应用全局覆盖前,抽查不同技术栈项目的有效配置。Nx 23 的 spread token 可在数组中用 "..." 保留低优先级配置,例如 "inputs": ["...", "{workspaceRoot}/babel.config.json"] 会在插件推断输入之后追加根配置;没有 spread token 时,同一属性仍是替换而不是自动合并。对象的 spread 只在 Nx 识别的合并层级生效,深藏在不透明 executor 选项内部的 "..." 不会递归展开。
用一条真实依赖边验证 affected
先初始化 Git 并提交生成后的干净状态:
git init
git add .
git commit -m "建立 Nx 实验基线"
git branch -M main
git tag nx-lab-base让 catalog-api 从自己的入口导入 catalog-core,确认项目图显示 catalog-api -> catalog-core。然后修改 catalog-core 的导出值,不改 catalog-api:
npx nx graph
npx nx show projects --affected --base=nx-lab-base --head=HEAD
npx nx affected -t build --base=nx-lab-base --head=HEAD --graph
npx nx affected -t build --base=nx-lab-base --head=HEAD由于未提交工作区变化时 HEAD 不包含这些文件,实验要么先提交变更再比较 tag 与 HEAD,要么省略 --head 让 Nx 使用当前文件系统。更稳定的提交式实验如下:
git add packages/catalog-core
git commit -m "调整 catalog core"
npx nx show projects --affected --base=nx-lab-base --head=HEAD
npx nx affected -t build --base=nx-lab-base --head=HEAD正确结果应至少包含 catalog-core 以及依赖它的 catalog-api,任务图应先运行 core 的 build,再运行 api 的 build。若只出现 core,先查项目图中的 import 或 package dependency 是否被识别;若所有项目都出现,查 root 级共享输入、lockfile 变化和隐式依赖是否过宽。
再制造一个能够稳定暴露漏边的反例:暂时移除 catalog-api -> catalog-core 的可识别 import 或显式依赖,只保留构建脚本在运行时读取 core 产物,然后重复 affected 计算。catalog-api 会从集合中消失,这正是生产漏测的机制。修复方式是恢复真实依赖或显式建模,而不是把 catalog-api 硬编码进 CI 目录匹配表。
base 与 head 错一处,受影响集合就失去证明力
Nx 用 Git 找到变化文件,再用项目图传播影响。Affected 官方说明建议 CI 的 base 指向主干最近一次成功运行的提交,head 指向当前待验证提交。只比较 HEAD~1..HEAD 会漏掉一个 PR 中更早的提交;总是比较 origin/main..HEAD 虽然通常安全,却会在主干流水线连续失败时遗漏“上次成功之后尚未被证明”的变化。
export NX_BASE="$LAST_SUCCESSFUL_MAIN_SHA"
export NX_HEAD="$CURRENT_COMMIT_SHA"
npx nx affected -t lint test buildWindows runner 使用环境变量时保持相同语义:
$env:NX_BASE = $env:LAST_SUCCESSFUL_MAIN_SHA
$env:NX_HEAD = $env:CURRENT_COMMIT_SHA
npx nx affected -t lint test buildCI 在执行前必须确认两个对象都存在:
git cat-file -e "${NX_BASE}^{commit}"
git cat-file -e "${NX_HEAD}^{commit}"
git merge-base "$NX_BASE" "$NX_HEAD"
npx nx show projects --affected --base="$NX_BASE" --head="$NX_HEAD"浅克隆中缺少 base 时,常见证据是 fatal: bad object、无法求 merge-base,或 CI 脚本悄悄回退到错误分支。修复应先 fetch 必要历史,再重新计算;不能捕获错误后把空集合当作“无项目受影响”。主干首次接入、历史成功 SHA 已被清理、Git provider API 暂时不可用时,安全降级是执行全量关键任务并发出基线告警。
lockfile 是另一种全局影响源。Nx 默认在 lockfile 变化时把所有项目标为 affected,这是偏保守的保护;@nx/js 可配置 projectsAffectedByDependencyUpdates: "auto",让 Nx 比较解析后的依赖变化并映射到项目:
{
"pluginsConfig": {
"@nx/js": {
"projectsAffectedByDependencyUpdates": "auto"
}
}
}启用 auto 前要用升级直接依赖、传递依赖、包管理器自身版本和 lockfile 格式的样本验证。包管理器、workspace 声明和 lockfile 必须唯一;npm 生成的 lockfile 却用 pnpm 安装,affected 与真正解析出的依赖图就不再描述同一事实。
本地缓存要用“删除产物后恢复”证明
先在一个可缓存 build 上连续执行两次:
npx nx build catalog-api
npx nx build catalog-api第二次应显示任务命中缓存。仅看到执行变快还不够,删除已声明输出后再运行:
rm -rf dist/packages/catalog-api
npx nx build catalog-api
test -e dist/packages/catalog-apiPowerShell 等价操作为:
Remove-Item -Recurse -Force dist/packages/catalog-api
npx nx build catalog-api
Test-Path dist/packages/catalog-api若命中日志出现但产物没有恢复,通常是 outputs 声明错了;若每次都重新执行,查看实际输入、命令参数、运行时版本和生成文件是否不断变化。Nx 默认把本地任务缓存放在 .nx/cache,项目图等 workspace 数据放在 .nx/workspace-data;这两类目录不应提交 Git。
缓存对照必须包含一次完全绕过:
npx nx build catalog-api --skip-nx-cache
npx nx build catalog-api --no-cloud--skip-nx-cache 绕过本地与远端缓存,适合证明真实任务仍能从当前输入成功执行;--no-cloud 只跳过远端,仍可使用本地缓存。跳过缓存说明给出了两者差异。部署制品、签名制品和发布前最终验证通常应从受控输入重新构建,并按供应链要求生成 provenance,而不是直接把开发者可写缓存当作发布来源。
输入、输出和环境变量共同决定缓存是否可信
缓存正确性的核心不在开关,而在任务函数是否近似满足:相同输入产生相同输出,且没有未声明副作用。下面把生产源文件、依赖项目生产输入、Node 版本和构建模式放进哈希:
{
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts",
"!{projectRoot}/**/*.test.ts"
],
"sharedGlobals": [
"{workspaceRoot}/tsconfig.base.json",
{ "runtime": "node --version" }
]
},
"targetDefaults": {
"build": {
"cache": true,
"inputs": ["production", "^production", { "env": "BUILD_MODE" }],
"outputs": ["{workspaceRoot}/dist/{projectRoot}"]
}
}
}inputs 控制哈希失效,outputs 控制命中后恢复哪些文件。任务读取但未声明的 .env、模板、代码生成 schema、工具版本、操作系统能力或 feature flag,会产生假命中;任务写到声明目录之外,命中后会缺文件。Nx 的输入参考支持 fileset、依赖项目输入、环境变量、runtime 与工作目录。覆盖插件推断的 inputs 时,默认会替换原数组;需要增量追加时使用 "...",并通过 npx nx show target catalog-api:build --verbose 查看合并后的 inputs、outputs 与来源,不能只从某个 JSON 文件猜最终配置。
用环境变量制造一次正反实验。构建脚本把 BUILD_MODE 写进产物,先故意不把它声明为输入:
BUILD_MODE=community npx nx build catalog-api
BUILD_MODE=enterprise npx nx build catalog-api
cat dist/packages/catalog-api/build-mode.txt若第二次命中旧缓存,文件仍写着 community,证明环境变量遗漏导致缓存错误。加入 { "env": "BUILD_MODE" } 后重试,哈希应变化并生成 enterprise。随后再执行一次 --skip-nx-cache,确认真实构建与缓存结果一致。
不要把 secret 值随意加入环境输入。虽然远端缓存保存的是输入哈希而非源输入本身,任务终端输出和声明产物仍会上传;构建工具也可能把 token、私有 URL 或授权 header 写入 source map、日志和元数据。正确做法是让缓存任务不读取发布凭证,把签名、上传和部署拆成不可缓存的副作用任务;确实影响非敏感构建变体时,使用低敏感度模式标识而非长期密钥。
还要检查以下非确定性来源:时间戳、随机 ID、绝对路径、主机名、并发顺序和从网络读取的可变内容。无法封闭输入的任务不要缓存。端到端测试若访问共享后端,其结果受外部状态影响,也不满足安全复用条件。缓存任务说明明确要求可缓存操作无副作用且相同输入产生相同输出。
远端缓存是一条制品供应链
本地缓存只影响单机,远端缓存会把一个执行主体产生的终端输出和任务产物交给其他开发机与流水线。Nx Cloud 是与 MIT 许可 CLI 分离的托管产品;Replay、Agents、分析、企业治理、部署区域和自托管能力要按当前官方方案核对,不能把某个试用或套餐能力写成永久平台承诺。连接入口是:
npx nx connect这里继续使用 lockfile 锁定的本地 Nx CLI;@latest 只适合创建新 workspace 或在评审过升级目标后显式迁移,不能让同一提交在不同时间连接到不同发布线。连接后先在受信分支运行一个无敏感信息的 build,再在干净 checkout 中运行同一任务,确认远端命中与产物恢复。不要一开始就把所有 test、代码生成、部署和发布任务设为可缓存。
Nx Replay 说明列出的远端数据包括输入哈希、终端输出和 outputs 中的任务产物;实际源输入不会作为缓存内容上传。数据审查仍要覆盖 source map、测试报告、覆盖率、生成代码和日志,因为这些输出可能含内部路径、测试数据、API 响应与凭证片段。
缓存写权限必须按信任域分开:Nx Cloud CI token 说明区分 read-only 与 read-write。受保护主干的可信 CI 才持有共享缓存写权限;普通 PR、fork 和外部贡献代码使用只读 token,执行产生的结果只进入当前 CI 执行隔离缓存,不能污染全局共享缓存。不要把 read-write token 写进 nx.json、仓库 secret 示例、日志或命令行 URL。
本地 .nx/cache 受本机信任,不能放到网络共享盘让多人读写。直接共享目录会绕过远端服务的不可变性与访问控制,形成缓存投毒入口;缓存安全说明也明确反对手工共享本地缓存。若使用自托管对象存储或自定义 cache server,要把对象不可覆盖、租户隔离、认证、TLS、审计、保留期、灾难恢复和客户端兼容纳入平台责任,不能把“有一个 bucket”当成安全远端缓存。
有数据驻留或制品保密要求时,可评估 Nx Cloud 端到端加密或企业自托管形态。加密密钥应通过 NX_CLOUD_ENCRYPTION_KEY 等受保护注入,不提交配置;端到端加密说明同时指出 Web 控制台可查看的另一份终端输出不属于用于重放的加密制品,因此日志脱敏仍是必要控制。
远端服务不可用时,流水线要区分“缓存未命中后真实执行成功”和“任务本身失败”。缓存是加速层,不应成为无法降级的单点;同时也不能为了可用性吞掉鉴权、TLS 或制品完整性错误。记录 cache hit rate、下载与上传耗时、节省计算时间、远端失败率和重新执行结果,才能判断远端缓存是在降本还是在增加不透明依赖。
CI 先正确裁剪,再缓存,最后分布式执行
CI 的优化顺序应保持因果关系:先证明 affected 集合无漏项,再启用远端缓存,最后对仍需执行的任务做跨机器分配。过早加机器会把错误任务图跑得更快,也会让失败证据散落在多个 agent。
一个平台无关的主流程可以保持为:
npm ci
npx nx report
git cat-file -e "${NX_BASE}^{commit}"
git cat-file -e "${NX_HEAD}^{commit}"
npx nx show projects --affected --base="$NX_BASE" --head="$NX_HEAD"
npx nx affected -t lint test build --base="$NX_BASE" --head="$NX_HEAD"安装必须使用仓库锁定的包管理器和 frozen/immutable 模式,Node 与 Nx 版本由工具版本文件和 lockfile 固定。CI 不应重新执行 npx nx@latest 选择新 CLI;这会让同一提交因时间不同得到不同任务模型。
当单机并行已经受 CPU、内存或关键路径限制时,可使用 Nx Cloud 的 Nx Agents 把任务图中的就绪任务分配到多台机器。分布式执行说明强调 agent 根据任务依赖和历史耗时持续领取任务,远端缓存负责跨机器传输依赖产物。它不是本地 MIT CLI 自带的通用远端执行器,启用前要核对 credits、并发连接、受支持 runner、数据区域和自托管边界。协调 job 仍应拥有 Git base/head 计算、provider 身份认证、最终制品汇总、部署凭证和发布审批;agent 只获得完成当前任务所需的最小权限。
分布式 CI 的容量不能只看 agent 数量。需要观察关键路径长度、任务耗时分布、agent 空闲时间、缓存传输量、队列等待、失败重试和总计算分钟。若一个巨型 e2e 任务占据绝大部分时间,增加 agent 不会缩短关键路径;先拆分该任务或减少外部等待更有效。若任务产物巨大,跨机下载时间可能超过重新构建,应该优化输出边界而不是盲目扩大缓存。
Daemon 故障要区分图缓存、任务缓存和真实代码
Nx Daemon 在开发机监视 workspace 并缓存项目图相关信息;Nx 在识别到 CI 或短生命周期容器时默认关闭 daemon,因为这些环境无法复用长期 watcher 状态。不要为了追求单条命令更快就在临时 runner 强制 NX_DAEMON=true;长生命周期 dev container 确实需要开启时,还要验证挂载目录的 inode/mtime、socket 权限和容器重启后的清理。开发机的典型异常包括:新增项目没有出现、修改插件后图不变化、daemon socket 权限错误、进程占用旧工作目录、IDE 与 shell 看到不同图。先收集证据:
npx nx report
npx nx show projects
npx nx daemon
NX_VERBOSE_LOGGING=true npx nx show project catalog-api --json开发项目图插件时可暂时关闭 daemon 和项目图缓存:
NX_DAEMON=false NX_CACHE_PROJECT_GRAPH=false npx nx graphPowerShell 使用:
$env:NX_DAEMON = "false"
$env:NX_CACHE_PROJECT_GRAPH = "false"
npx nx graph若关闭后图恢复,问题集中在 watcher、daemon 或插件增量缓存;若仍错误,应回到项目发现和依赖分析。不要先删除所有依赖和 lockfile。
nx reset 会清理缓存的 Nx 制品和 workspace 元数据并关闭 daemon,还提供更窄的选项:
npx nx reset --only-daemon
npx nx reset --only-workspace-data
npx nx reset --only-cache
npx nx reset先选最小动作。--only-daemon 适合重启后台进程,--only-workspace-data 适合重新计算项目图元数据,--only-cache 会删除本地任务缓存但不清远端缓存。重置后必须重新运行原始失败命令,并与 --skip-nx-cache 对照;“reset 后好了”只是定位线索,不是根因结论。
缓存命中掩盖失败时,保留下面四组证据:有效 target 配置、affected 项目集合、带 verbose 的执行日志、绕过缓存后的退出码与产物摘要。禁止把完整环境变量、Cloud token 或含业务数据的任务输出上传到公开 issue。
升级 Nx 要把配置迁移和结果等价分开
Nx 与所有 @nx/* 包应保持同一版本。官方的自动迁移说明把升级分成生成迁移与执行迁移两步;Nx 23 还把 required 与 optional 更新拆开,并可为 prompt-only 或 hybrid migration 调用受支持的 AI agent。先把迁移范围收窄到 Nx 自身和随附插件,并显式关闭 agentic flow,得到最容易评审的基线:
npx nx migrate --include=required --no-agentic
npm install
git diff -- package.json package-lock.json migrations.json
npx nx migrate --run-migrations
npx nx report跨多个主版本时选择 gradual 并逐个推荐台阶推进,先只更新 required 依赖,能够减小失败面;稳定后再单独执行 npx nx migrate --include=optional 评估框架和工具链升级。若启用 agentic flow,生成器迁移会先执行并由 agent 验证,prompt-only 与 hybrid migration 也可能改写代码,而且当前流程会为迁移创建独立提交;启用前必须确认工作区、提交策略、可用 agent、凭据和代码审查责任。没有可用 agent 时,未执行的 prompt 文件只会列在后续提示里,不能把“迁移命令退出”当成全部转换已经完成。迁移文件会改工具配置和源代码,必须像普通代码一样评审;社区插件要单独运行 nx migrate <plugin> 并确认其兼容迁移。旧分支尚未合并时,可以暂时保留 migrations.json,让合并后的代码应用同一组转换,避免团队成员手工重演。
升级验证至少包含:项目数量与名称差异、项目边与任务图快照、affected 代表样本、无缓存全量关键任务、缓存命中与产物恢复、CI base/head、远端 cache 权限、不同操作系统 runner,以及生成器 dry-run。回退要同时恢复 package.json、lockfile、nx.json、插件配置和迁移改动;只降级 nx 包而保留新格式配置,会制造更隐蔽的混合状态。
迁移到 inferred tasks 时同样分批处理。插件顺序会影响同名推断目标,targetDefaults 与项目配置又会覆盖推断结果。每迁一个工具,比较 nx show project --json 的 command、inputs、outputs、cache 和 dependsOn,再运行正反缓存实验。配置行数减少不是验收条件,任务语义等价才是。
从普通多包仓库迁入时保留退出路径
成熟仓库适合按四步演进:
先引入本地 Nx,只代理已有 lint、test、build 命令,保留原脚本作为对照。建立项目图和 owner,修复真实依赖边,用选定变更样本证明 affected 无漏项。为确定性任务配置本地缓存,以删除产物后恢复和 --skip-nx-cache 对照证明正确。
远端缓存只向可信 CI 开写权限,稳定后再引入跨机器分布。
每一步都应能独立回退。退出 Nx 时,项目自身的编译器、测试框架和包管理器命令仍应可运行;不要让核心业务构建只能通过一个未维护的私有 executor 触发。自定义插件要有 owner、兼容矩阵、测试样本和下线计划,否则 Nx 升级成本最终会集中在无人理解的图构建代码中。
清理实验 workspace 时只删除明确创建的目录:
cd ..
rm -rf nx-catalog-labPowerShell 先确认路径再执行:
$target = (Resolve-Path .\nx-catalog-lab).Path
if (Split-Path $target -Leaf -ne "nx-catalog-lab") { throw "refuse unsafe cleanup" }
Remove-Item -LiteralPath $target -Recurse -Force团队仓库日常清理优先使用 npx nx reset --only-cache 等窄动作。不要把 rm -rf .nx node_modules dist 包装成通用修复脚本,更不要删除共享 workspace 中其他开发者或 CI job 的目录。
采用阈值来自收益、正确性与维护成本
Nx 的收益可用基线验证,而不是凭项目数量决定。采集全量流水线关键路径、每个任务耗时、重复执行比例、affected 比例、缓存命中率、缓存传输开销、图计算时间和维护工时。只有当节省的开发等待与 CI 计算持续高于图建模、插件升级、缓存平台和治理成本,方案才成立。
选型时关注责任模型:
已有 npm/pnpm/Yarn workspace,希望渐进接入项目图、插件推断、affected 和丰富生成器,Nx 通常匹配度高。主要是 JavaScript/TypeScript,团队更偏好基于 package scripts 的轻量 pipeline,可同时评估 Turborepo。需要严格 hermetic build、跨语言统一规则、远端执行与更强沙箱,并愿意承担 BUILD 规则成本,可评估 Bazel。
需要集中版本策略、变更文件和大型 npm 发布管理,可评估 Rush;也可以让发布治理与任务编排分别选型。
不要在同一批迁移中同时引入两个任务图系统。双重调度会让缓存、并行度、日志和退出码的所有权不清晰。PoC 应选一组代表性项目:一个共享库、一个应用、一个代码生成任务、一个测试任务、一个跨平台 runner 和一个含 lockfile 变化的 PR;用同一组变更比较正确性、关键路径和维护复杂度。
把 Nx 经营成可审计的构建控制面
业务团队拥有项目边界、依赖关系和任务正确性;平台团队拥有 runner、远端缓存、CI 模板、容量与可用性;安全团队定义 fork、token、缓存写入、日志和制品数据边界;工具 owner 负责 Nx 与插件升级、迁移和故障演练。没有 owner 的缓存只会越来越快地传播未知结果。
进入主干的变更应形成一条连续证据:
仓库只使用一种批准的包管理器和唯一 lockfile,本机与 CI 使用同一 Nx 本地版本。nx show projects 的节点、root 和 owner 可追踪,关键静态与隐式依赖边有代表样本验证。CI 的 base 指向主干最近成功提交,head 指向待验证提交;对象缺失或基线服务失败时安全降级为全量。
affected 的正例包含上游项目及其依赖方,故意移除依赖边的反例能够暴露漏测。可缓存任务声明完整 inputs、outputs、环境和 runtime,删除产物后能够恢复,绕过缓存后结果等价。发布、签名、上传、部署和访问可变外部系统的任务不复用普通构建缓存。
普通 PR 与外部代码没有共享 cache 写权限,read-write token 只存在于受保护 CI,日志和产物经过敏感信息审查。daemon、workspace data、local cache 和 remote cache 故障能够分层诊断,重置动作有最小作用域。升级同时验证项目图、任务图、affected、无缓存构建、缓存恢复和跨平台 runner,并保留完整回退提交。
持续观察关键路径、命中率、传输量、失败率、计算成本和维护工时;收益下降时能缩小或退出方案。
做到这些,Nx 才不是盖在脚本上的一层快捷命令。它会成为一套能回答“为什么这个任务要运行、为什么可以不运行、结果从哪里来、谁有权让别人复用”的工程证据系统。
