☰
pnpm v12 工作区任务编排指南:tasks/dependsOn 依赖图、并发组与缓存化 CI 管线
2026/10/10 5:56:43 网站建设 项目流程
  • AI 技能
  • 人工智能

【免费下载链接】skills

Anthony Fu's curated collection of agent skills.

项目地址:https://gitcode.com/gh_mirrors/skills11/skills
点击查看免费下载

本篇聚焦 pnpm v12 的Workspace Task Orchestration(工作区任务编排):如何用pnpm-workspace.yaml中的tasks/dependsOn声明跨项目任务依赖图、用并发组与优先级控制执行槽位,以及如何使用实验性的pnpm pipeline运行带缓存的 CI 式任务管线。读完本文,你可以在 monorepo 中精确控制"谁先跑、谁并行跑、跑多少",并用--dry-run和tasks status验证调度行为,最终把 CI 变成可增量缓存的 pipeline 执行。

一、任务调度模型:任务图与就绪规则

pnpm -r run <script>并不是简单地对每个项目依次执行脚本,而是调度一张工作区任务图(task graph)。核心概念如下:

  • 任务的表示形式:一个任务即<project>#<script>,例如packages/ui#build;
  • 就绪(ready)规则:一个任务只有在它依赖的所有任务都成功完成后才变为就绪;
  • 并发上限:就绪任务在--workspace-concurrency的上限内并行执行,相互独立的任务执行顺序不可预测(由调度器自行决定);
  • 失败语义:默认--bail行为下,一旦有任务失败,正在运行的任务会被取消,且不再派发新任务。

这套模型是 core-workspaces 中"跨项目任务图(Cross-project task graphs)"章节的完整展开,也出现在 CLI 命令参考 的 "Task orchestration & pipelines" 一节中:

pnpm -r run <script> # 运行任务图 pnpm -r run --dry-run build # 检查任务图 pnpm tasks status # 查看每个并发组的运行/等待任务(v12.6) pnpm pipeline [name] # 带缓存的 CI 式运行(v12.4,实验性)

二、声明任务依赖:tasks与dependsOn

依赖关系配置在pnpm-workspace.yaml的tasks键下(注意:pnpm v12 的配置使用camelCase键,且不再从.npmrc或package.json的pnpm字段读取设置):

tasks: build: dependsOn: - ^build # 先构建每个 workspace 依赖项 test: dependsOn: - build # 先在同一项目内执行 build

dependsOn中的条目支持两种写法:

  • build→ 同一项目内的build任务(项目内依赖);
  • ^build→ 每个被选中的 workspace 依赖项中的build任务(跨项目依赖,^前缀)。

默认规则与"显式空依赖"陷阱

两条默认规则直接决定你的依赖图形状,必须特别注意:

  1. 没有tasks条目的任务:默认依赖其在 workspace 依赖项中的同名任务——即未配置的build行为等价于dependsOn: ['^build'],从而天然保持"依赖先于依赖方(deps-before-dependents)"的拓扑顺序;
  2. 一旦某任务有了tasks条目,但省略了dependsOn:含义是dependsOn: [](无依赖),而不是继承默认行为。

也就是说,如果你为了给build设置concurrency而新建了条目,却又想让拓扑顺序继续生效,必须显式声明:

tasks: build: concurrency: 2 dependsOn: ['^build'] # 必须显式写出,否则变成无依赖

依赖作用域与 pass-through 语义

  • 任务依赖始终限制在--filter/includeWorkspaceRoot所选中的项目范围内——被过滤掉的项目不会引入额外的依赖边;
  • 某个项目如果缺失被引用的脚本名(例如dependsOn指向build但该项目没有build脚本),该任务被视为pass-through:会被报告为 skipped,但不会中断依赖链。

这一 pass-through 语义对混合仓库(部分包没有 lint/test 脚本)非常关键:声明统一的tasks图而无需为每个项目补齐同名脚本。

三、任务级并发:concurrency与--workspace-concurrency的区别

tasks: build: concurrency: 2 # 所有项目中同时运行的 build 实例最多 2 个 dependsOn: ['^build']

两者是相互独立的调度维度:

  • --workspace-concurrency:整个递归调度的"工作区槽位"总数(也可在pnpm-workspace.yaml中以workspaceConcurrency配置,例如workspaceConcurrency: 4);
  • tasks.<name>.concurrency:单个任务名(跨项目)的实例上限。

关键行为:一个任务在等待自己的concurrency槽位时,并不占用工作区槽位。这意味着 CPU 密集型的build被限制到 2 个并发时,等待中的项目不会阻塞 I/O 型任务(如 lint)使用工作区槽位——调度粒度比单纯的--workspace-concurrency更细。

四、并发组(v12.5.0):跨进程、机器级的槽位池

concurrencyGroup解决的问题超出了单次pnpm -r run的范围:多个 pnpm 进程共享同一份机器级限制。只要进程使用相同的stateDir,限制就互相可见(包括pnpm pipeline进程):

tasks: test:rust: concurrencyGroup: cargo dependsOn: [] concurrencyGroups: cargo: 2 # 全机器同时最多 2 个 cargo 任务,跨进程

典型场景:Rust/Cargo 子项目的测试极耗 CPU,你希望即使有多个 pnpm 进程(例如多个 Agent 会话、多个 CI 容器挂载同一stateDir)在跑,同时进行的 cargo 任务总数也不超过 2。语义要点:

  • 嵌套在同一组内的pnpm run会复用父进程已占用的槽位,不会重复计数;
  • 槽位在进程退出或崩溃时释放,不会因任务"卡住"而泄漏给僵尸计数;
  • 缺失或为 0 的组上限表示不限制——组只是一个命名标签,必须配合concurrencyGroups中的数字才生效;
  • 改变stateDir即得到一套独立的槽位池,因此不同stateDir的进程互不影响。

五、任务优先级(v12.6.0)与组状态检查

优先级priority

priority为整数,默认值0。它只影响"等待中的任务"在可用槽位释放时的抢占顺序——数值高的先运行;数值相同时按到达先后排序:

tasks: build:critical: { concurrencyGroup: build, priority: 10 } build:cleanup: { concurrencyGroup: build, priority: -1 }

上例中,build:critical和build:cleanup共享build并发组,但槽位紧张时关键构建永远排队在前,清理任务排在最后。

检查组内状态(v12.6.0)

pnpm tasks status [groups...] # 显示每个组的运行中 + 等待中任务 pnpm pm tasks status # 若项目里有名为 "tasks" 的 npm script 遮蔽了内置命令,用 pm 前缀强制调用内置命令

pnpm pm是绕过package.jsonscripts 遮蔽的强制入口——这在仓库根目录定义了"tasks": "some-script"时特别有用。

六、检查任务图:--dry-run

在真正执行前验证依赖声明是否正确,两个命令:

pnpm -r run --dry-run build # 输出稳定的拓扑顺序,不执行任何脚本 pnpm -r run --dry-run --json test # 以 JSON 输出节点与边

--dry-run给出稳定的拓扑顺序,适合用来核对:^build展开后是否指向了正确的项目集合、--filter收窄后的图是否符合预期。JSON 形态(节点 + 边)则方便脚本化校验,例如在 CI 中 diff 任务图防止依赖声明被意外改动。

七、递归运行的调度选项

选项行为
--resume-from <pkg>从某个包的任务处断点续跑,跳过上一次同一调用(same invocation)已记录为通过的任务
--reverse反转所有边——依赖方先运行(例如先拆下游再动上游)
--no-bail某个任务失败后,继续运行其余相互独立的就绪任务(默认--bail会取消正在运行的任务并停止派发)

--resume-from对长时间构建非常实用:一次pnpm -r run build在某个包失败后,修复该包再跑同一调用加--resume-from,已通过的任务直接跳过。

输出行为:同一时刻只有一个脚本能运行时,输出直接继承终端(无缓冲前缀);多个脚本并行时输出会被管道收集。需要实时输出时用--stream(带项目名前缀、逐行即时打印),需要按项目聚合时用--aggregate-output。这与 core-workspaces 中给出的日常运行方式一致:

pnpm -r run build # 全部项目 pnpm -r --workspace-concurrency=1 run build # 严格拓扑串行 pnpm -r --parallel run test # 并行 pnpm -r --stream run dev # 流式输出

八、循环依赖:ERR_PNPM_TASK_CYCLE与ignoreWorkspaceCycles

任务图中出现环会在任何脚本执行之前失败,错误码为ERR_PNPM_TASK_CYCLE。只有在你刻意制造了环(例如两个包必须互相先构建)时,才设置:

ignoreWorkspaceCycles: true

该配置会让 pnpm 发出警告并丢弃环内成员之间的排序保证——即环内任务的执行顺序不再由依赖关系决定。core-workspaces 中也把它与requiredScripts(每个项目必须存在的脚本,否则pnpm -r run <name>直接失败)并列为工作区设置项,默认值为false。

九、忽略tasks声明的命令

以下情况tasks/dependsOn声明不生效,调度回退到原有行为:

  • --no-sort:显式禁用拓扑排序,忽略tasks声明;
  • --parallel:隐式蕴含--no-sort,同样忽略tasks声明(纯并行广播);
  • 递归exec:pnpm -r exec <cmd>没有脚本名,因此无法加入dependsOn依赖图;但它仍然使用依赖感知调度,并支持--resume-from。

换言之,凡是需要"跨项目依赖排序"的场景都应走run;exec只享受调度层面的依赖感知(工作区依赖完成后再开始)。

十、pnpm pipeline(v12.4.0,实验性):带缓存的 CI 式任务管线

pnpm pipeline按"CI 作业"的方式运行一组命名任务:先执行frozen install,再运行"受影响项目"(affected-since-base)的任务图,并恢复缓存命中的结果。它是目前文档中唯一消费outputs/inputs/env/cache/cargoTargetDir这些字段的命令。

完整配置示例

tasks: build: { dependsOn: ['^build'], outputs: ['dist/**'], inputs: ['src/**'], env: ['NODE_ENV'] } test: { dependsOn: ['build'], outputs: [] } lint: { outputs: [] } pipelines: default: [build, test, lint] release: [build]
pnpm pipeline # 运行名为 "default" 的管线 pnpm pipeline release # 运行自定义管线(仅 build) pnpm pipeline --dry-run --json pnpm pipeline --full # 运行所有项目,而不只是 affected-since-base pnpm pipeline --base <ref> # 指定受影响的 diff 基准(默认 origin/main,或配置项 pipelineBase) pnpm pipeline --no-cache

缓存规则(逐字段)

字段作用
outputs任务只有声明了outputs才可缓存。outputs: []是合法且重要的声明——表示"不产生文件",使 linter/test 这类无产物任务也可缓存
inputs收窄缓存 key 的输入范围(+glob语法表示在默认输入之外追加)
env指定需要参与哈希的环境变量名(如NODE_ENV),变量变化则缓存失效
cache: false显式退出缓存

缓存 key 还自动覆盖:脚本文本本身、依赖任务的 key、lockfile、运行时(runtime)——即改了一行脚本、升了一个依赖或换了 Node 版本都会自然 miss。命中时 pnpm恢复文件并回放日志(replay logs),CI 上看起来与真实执行无差。

两条必须牢记的边界

  1. 普通的递归pnpm run永远不会从 pipeline 缓存恢复——outputs/inputs/env/cache/cargoTargetDir只被pnpm pipeline读取。本地开发跑pnpm -r run build不会因为 pipeline 缓存命中而秒过;
  2. 管线默认只跑相对 base(默认origin/main,可用pipelineBase配置)有变更的受影响项目,--full才跑全部项目——这与 CI 增量构建语义完全对齐。

Cargo 构建状态:cargoTargetDir

对 monorepo 中混合的 Rust 包,cargoTargetDir: target让任务在多次运行 / 多个 git worktree 之间通过不可变快照保留 Cargo 的 target 目录,避免每次都冷编译。这与 features-multi-ecosystem 中提到的 Cargo 依赖(实验性多生态支持)配套使用。

十一、其他依赖感知命令

除了pnpm -r run,以下工作区命令同样遵循"项目的工作在其 workspace 依赖完成后立即开始"的依赖感知调度——沿包依赖图(而非tasks声明)推进,不再等待无关的拓扑分组:

  • workspace install(安装)
  • rebuild(重建)
  • pack / publish(打包 / 发布)
  • stage(发布暂存)
  • lifecycle work(生命周期任务)

也就是说,这些命令的排序依据是包图,与第二节的手动tasks声明是两套独立机制。

十二、最小实践清单

把本文的机制落到pnpm-workspace.yaml,一个典型 monorepo 的推荐写法:

tasks: build: concurrency: 2 dependsOn: ['^build'] # 显式声明,避免"有条目即无依赖"的默认值 test: dependsOn: ['build'] # 项目内依赖 pipelines: default: [build, test]

配套操作习惯:

  1. 改动tasks声明后,先用pnpm -r run --dry-run --json build核对节点与边;
  2. 对 CPU 密集任务(如 cargo)配concurrencyGroup+concurrencyGroups数值上限,并用pnpm tasks status观察等待队列;
  3. CI 中改用pnpm pipeline替换pnpm ci && pnpm -r run build,让outputs/inputs/env声明参与缓存命中;
  4. 需要绕过 npm script 遮蔽内置命令时,用pnpm pm <cmd>。

以上内容与仓库中 pnpm 技能的元信息一致:该技能基于 pnpm 12.x 文档生成,覆盖 v11 与 v12 行为,其中工作区任务编排(workspace task orchestration)正是 v12 的重点能力之一,详见 pnpm 技能主文件。由于pipeline仍标注为实验性(experimental),在生产 CI 中建议先用--dry-run --json验证任务图与缓存策略,再逐步启用完整缓存。

  • AI 技能
  • 人工智能

【免费下载链接】skills

Anthony Fu's curated collection of agent skills.

项目地址:https://gitcode.com/gh_mirrors/skills11/skills
点击查看免费下载

相关推荐

上一篇:如何用SRWE工具突破游戏窗口分辨率限制
下一篇:Excalidraw 开源手绘白板:三步画出第一张图

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询