- AI 技能
- 人工智能
【免费下载链接】skills
Anthony Fu's curated collection of agent 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 # 先在同一项目内执行 builddependsOn中的条目支持两种写法:
build→ 同一项目内的build任务(项目内依赖);^build→ 每个被选中的 workspace 依赖项中的build任务(跨项目依赖,^前缀)。
默认规则与"显式空依赖"陷阱
两条默认规则直接决定你的依赖图形状,必须特别注意:
- 没有
tasks条目的任务:默认依赖其在 workspace 依赖项中的同名任务——即未配置的build行为等价于dependsOn: ['^build'],从而天然保持"依赖先于依赖方(deps-before-dependents)"的拓扑顺序; - 一旦某任务有了
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 上看起来与真实执行无差。
两条必须牢记的边界
- 普通的递归
pnpm run永远不会从 pipeline 缓存恢复——outputs/inputs/env/cache/cargoTargetDir只被pnpm pipeline读取。本地开发跑pnpm -r run build不会因为 pipeline 缓存命中而秒过; - 管线默认只跑相对 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]配套操作习惯:
- 改动
tasks声明后,先用pnpm -r run --dry-run --json build核对节点与边; - 对 CPU 密集任务(如 cargo)配
concurrencyGroup+concurrencyGroups数值上限,并用pnpm tasks status观察等待队列; - CI 中改用
pnpm pipeline替换pnpm ci && pnpm -r run build,让outputs/inputs/env声明参与缓存命中; - 需要绕过 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.
相关推荐
JetBrains IDE评估重置工具:告别试用期中断的开发伴侣
JetBrains IDE评估重置工具:告别试用期中断的开发伴侣 你是否曾在深夜调试代码时,突然被IDE弹出的试用期到期提示打断思路?或者团队项目临近交付,开发
构建工具开发工具CLIconvex-backend TypeScript Monorepo 开发指南:pnpm 工作区、Turborepo 任务编排与代码组织
convex backend TypeScript Monorepo 开发指南:pnpm 工作区、Turborepo 任务编排与代码组织 本篇指南围绕开源仓库
数据库后端Hyperf DAG 任务编排指南:用有向无环图优雅调度依赖与并发任务
Hyperf DAG 任务编排指南:用有向无环图优雅调度依赖与并发任务 导读 hyperf/dag 是 Hyperf 生态中的轻量级 有向无环图(Directe
后端Web框架微服务RPC框架异步编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考