← 文章 / 编程开发
nx 6小时前 · 2026-10-09 01:58:56 · 0 阅读

Nx 23.3 发布:基于任务的受影响检测、更快的图构建以及面向智能体的友好输出

加速 CI 运行的最简单方法就是只运行必要的内容。这正是 nx affected 的作用,而 Nx 23.3 通过基于任务的受影响分析让精准度大幅提升。

这仅仅是个开始。我们将在 10 月 22 日举办一场产品活动,届时将宣布关于如何为智能体产生的大量 PR 和测试扩展 CI 能力的重要消息。请务必注册参加!

接下来看看 Nx 23.3 的亮点。

基于任务的受影响分析

nx affected 一直工作在项目级别。你修改一个文件,Nx 会找出哪个项目拥有该文件,并标记所有依赖它的项目为受影响。然后,它在所有这些项目上运行指定的目标。这种方式虽然有效,但它是一种防御性策略,往往会执行超出必要的任务。例如,修改共享库中的一个 spec 文件,会导致每个依赖项目重新运行其 build 任务,尽管没有任何项目会读取该 spec 文件。在 Nx 仓库中,仅仅这样一次修改过去就会选中 45 个测试任务。

从 23.3 版本开始,基于任务的受影响分析会根据你的修改是否触及每个任务读取的内容来选择任务。

现在,修改那一个 spec 文件只会运行它所在项目的 test 任务,其余均不运行。这种效果在输入较窄的目标上最为显著,如 build、test 和 e2e,这些通常占据了大部分 CI 时间。而那些读取项目大部分内容的任务,如 lint,精简幅度很小。

NX_LEGACY_AFFECTED=false nx affected -t test

基于任务的受影响分析在 23.3 版本中需手动启用,我们计划在 Nx 24 中将其设为默认选项。请在你的 CI 中试一试,并告诉我们它在你的工作空间中的表现。

Nx 如何判定任务受影响

其理念与缓存机制相同:任务的输入定义其行为,当输入变更时行为随之改变,该任务即被判定为受影响。这一规则适用于所有任务,无论是否可缓存。

Nx 从你改动涉及的文件出发,这些文件依然从 git 获取。凡是把其中某个文件声明为输入的任务,都算"直接受影响"。受影响的任务会产生不同的输出,因此读取这些输出的任务也受影响,Nx 会沿着整个任务图持续追踪下去。最终未受影响的任务行为不会变化,所以 Nx 会把它们从任务图中剔除,不再执行。

一个任务的输入可能来自三个地方:

  • 自身文件 - 其 inputs 在所在项目内覆盖的内容,比如 {projectRoot}/**/*。
  • 依赖项目的文件 - 通过 ^production、^default 这类 ^ 输入引用的依赖项目源文件。
  • 依赖任务的构建产物 - 通过 dependentTasksOutputFiles 引用的所依赖任务的输出。

dependsOn 并不决定任务是否受影响,它只控制任务的执行顺序。假设 app:test 配置了 dependsOn: ["^build"],但它的 inputs 只覆盖 {projectRoot}/**/*。

a) 在项目级 affected 下,ui 的变更会触发 app:test,因为 app 依赖 ui。

b) 在基于任务的 affected 下则不会,因为 app:test 读取的内容没有任何变化。dependsOn 的作用只是在两者都被选中时,确保 ui:build 先执行。

Affected explain

大多数工作区不会察觉到差别,因为默认的 inputs 已包含 ^production 或类似的命名输入,依赖变更仍能传递到任务。真正受影响的是手写 inputs 的 target,最常见的情况是设置了 "cache": false 并自带 inputs 的 target。如果它需要在依赖变更时重新执行,请在 inputs 中加上 ^default 或 dependentTasksOutputFiles。更多关于命名输入和 dependentTasksOutputFiles 的内容请查阅我们的文档。

当某个任务出现(或没出现)而你搞不清原因时,--explain 会告诉你答案:

NX_LEGACY_AFFECTED=false nx affected -t build --explain
 NX   12 个 build 任务中有 3 个受影响

修改 1 个文件会触发 1 个 build 任务。使用 --verbose 可列出每个任务及其原因。

  libs/ui/src/index.ts:
    - ui:build

触及这些任务会改变 2 个 build 任务读取的输出结果。使用 --verbose 可列出每个任务及其原因。
  - admin:build
  - app:build

第一组列出的是自身输入文件被修改的任务,按变更文件归类。第二组列出的是读取这些输出的任务。使用 --verbose 可以看到每个任务的判断依据,例如文件匹配的模式或它读取了谁的输出;使用 --explain=stdout 可以以 JSON 格式获取相同的数据。--explain 会退出而不实际执行任何操作,nx show projects --affected -t build --explain 的行为也相同。

更直观的哈希计算

按任务读取的内容来筛选任务,前提是任务哈希必须覆盖其读取的所有内容。23.3 版本填补了其中两个漏洞。

e2e 任务现在会追踪其所测试的服务端。 一个 e2e 任务测试的是运行中的应用,而开发服务器负责运行该应用,其表现与构建过程类似。以前,Nx 插件是通过让 e2e 任务获取应用的构建输入来弥补这一点的。在 23.3 中,Nx 本身会追踪这条依赖链。依赖于持续任务(例如依赖 serve 的 e2e)的任务,会将该持续任务的输入包含在其哈希中。九个 Nx 插件的 serve、dev、preview 和 start 目标现在都会推断出与其 build 目标相同的输入(#37017、#37024)。

这一点对于基于任务的 affected 尤为重要。在基于项目的 affected 中,e2e 项目通常对应用项目存在隐式依赖。基于任务的 affected 会忽略隐式依赖,仅关注输入,因此正是这一机制将两者连接起来。现在,触及 serve 任务的输入,会影响依赖于它的 e2e 任务。

被 Git 忽略的文件现在也可以作为任务输入。 过去,fileset 输入仅识别 Git 跟踪的文件(遵循 .gitignore 和 .nxignore)。因此,生成的 .env 文件、下载的 schema 或构建产物永远无法参与任务哈希计算。现在只需添加 includeIgnored: true,Nx 就会对磁盘上的实际文件进行哈希处理(#37025):

{
  "inputs": [
    { "fileset": "{projectRoot}/generated/**/*.json", "includeIgnored": true },
    { "fileset": "!{projectRoot}/generated/**/*.map", "includeIgnored": true },
    { "fileset": "{workspaceRoot}/.env.generated", "includeIgnored": true }
  ]
}

路径还可以指定尚不存在的文件,一旦该文件出现,哈希值就会改变。可以使用 nx show target <project>:<target> inputs 查看匹配了哪些文件。

请参阅 inputs 参考文档中关于被 git 忽略的文件的部分,其中还介绍了此机制与 dependentTasksOutputFiles 的关系。

批量执行 Oxlint

在 23.2 版本中,我们添加了 @nx/oxlint 插件。该插件为每个项目推断出 oxlint . 命令,导致 nx run-many -t lint 为每个项目启动独立的 Oxlint 进程;在开启模块边界规则时,每个进程还会重新加载项目图。Oxlint 本身速度极快,因此启动开销占主导地位,在 CI 环境中这种开销累积得很快。

在 23.3 版本中,推断出的 lint 目标使用了新的 @nx/oxlint:lint 执行器,默认采用批量模式。当命令涉及多个项目时(如 nx run-many -t lint 或 nx affected -t lint),Nx 将为所有项目启动单个 Oxlint 进程(#36827)。每个项目仍保留独立的结果、终端输出和缓存条目,因此 nx affected 可以单独识别项目,且单个项目的缓存命中不依赖于其他项目。

以下是在一台本地机器上进行的测试统计,工作区包含 50 个库和 350 个 TypeScript 文件,并开启了模块边界规则:

运行方式耗时
Nx 23.2,每个项目一个 Oxlint 进程16.5s
Nx 23.3,批量执行(默认) 3.0s
Nx 23.3,完全缓存0.86s
在根目录直接运行 oxlint .(不用 Nx)1.5s

你可能会问,和直接在根目录运行 oxlint . 相比,剩下的时间差从哪来。两次运行中 Oxlint 本身都耗时约 1.2 秒,Nx 侧多出的 1.5 秒是固定开销:启动 CLI(0.25s)、创建项目图(0.57s)、计算任务哈希(0.14s)、启动执行批处理的 worker(0.3s),以及为 50 个项目各记录一次结果(0.3s)。这些开销不会随着 lint 代码量增长,作为回报,你获得了按项目缓存和 nx affected 能力。完全缓存时只需 0.86s,已经比在根目录 lint 整个仓库更快;而当只有少数项目受影响时,Nx 只 lint 那些项目。

在 target 上配置或通过命令行传入的选项都会转发给 Oxlint,比如 nx run-many -t lint --type-aware 会带上 --type-aware 运行 Oxlint。由于一个进程会 lint 整个批次,它使用的是第一个项目的选项;如果批次中其他项目设置了不同的选项,Nx 会发出警告。详情见 Oxlint 插件文档。

面向 Agent 的 summary 输出风格

在 23.2 中,我们通过把成功任务折叠成单行来精简日志输出。23.3 为 agent 走得更远,推出了 --output-style=summary:只打印一份包含任务统计的摘要,并为每个失败任务给出完整日志的路径:

nx run-many -t test --output-style=summary
 NX   34 tasks: 31 succeeded, 29 cached, 1 failed, 2 skipped

✖  nx run js:test  (exit 3)
   full log: /repo/.nx/cache/terminalOutputs/9f2c…

Re-run with --output-style=static to inline logs.

Nx 完全不内联任何任务输出,因此输出大小取决于失败任务的数量,而不是它们打了多少日志。Agent 需要细节时再去读取失败任务的日志文件。当 Nx 检测到是由 AI agent 运行、且你没有自行设置输出风格时,会默认采用这种风格(#36703)。

为实现这一功能,现在每个任务完成时都会将终端输出写入磁盘,包括设置了 cache: false 的任务以及批处理模式下的任务。此前这些任务是不会生成日志文件的。

更快的项目图生成

在大型工作区中,每次执行 nx 时

原始来源: nx

评论 (0)