pnpm 支持带 Scheme 的 peerDependencies 说明符:named-registry、npm: 别名与 file:/git/URL 规范的匹配规则详解
2026/9/19 22:37:22 网站建设 项目流程
  • 包管理器
  • 开发工具
  • CLI

【免费下载链接】pnpm

Fast, disk space efficient package manager

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

导读

本文围绕 pnpm 对peerDependencies字段校验规则的变更(对应.changeset/peer-scheme-specifiers.md),系统讲解 pnpm 与 pacquet(pnpm 的 Rust 实现,本仓库 pnpm/crates 即其源码)如何从"一刀切拒绝"演进为"按 scheme 解析匹配":peer 声明现在可以写成work:5.x.xnpm:bar@^5file:../pkg或 git/URL 等带 scheme 的说明符,匹配时按说明符携带的 semver 范围校验,安装时仍由原始说明符选择包。读完你将掌握:哪些 peer 声明格式合法、每种格式如何提取匹配范围、裸name@version为何仍被拒绝,以及这条规则在源码中的完整实现链路。

一、变更背景:peerDependencies 曾经"拒绝一切非常规说明符"

peerDependencies中,声明格式长期被严格限制为:

  • 合法的 semver 范围,如^5.0.0>=1.0.0 <2.0.0
  • workspace:/catalog:引用。

一旦出现其他写法,pnpm 会以错误码ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION中止安装,报错信息形如:

The peerDependencies field named '{dep_name}' of package '{project_id}' has an invalid value: '{specifier}'

该错误在 Rust 实现中的定义位于 resolve_dependency_tree.rs,其 help 文本明确给出了合法值范围:

The values in peerDependencies should be a valid semver range, aworkspace:/catalog:spec, or a dependency specifier such as a named-registry (<registry>:<version>),npm:,file:, or git/URL spec

也就是说,pnpm 此前虽然已经认识到这些带 scheme 的说明符"应该"合法,但校验逻辑并不接受它们,导致work:5.x.xnpm:bar@^5这类写法直接报错(对应 issue #13095)。本次变更(minor 级)正是补齐了这一缺口:允许 scheme-carrying 说明符进入peerDependencies

二、变更内容:现在接受哪些 peer 说明符

依据 changeset,peerDependencies现在接受携带 scheme 的依赖说明符,包括以下四类:

类别写法示例说明
named-registry 说明符work:5.x.xgh:@scope/pkg@^1.0.0<registry>:<version>形式,指向配置的命名 registry 别名
npm:别名npm:bar@^5通过 npm registry 安装别名为其他名字的包
file:说明符file:../vendor/real-peer指向本地目录或文件
git / URL 说明符github:user/repohttps://...指向 git 仓库或 tarball URL

其中"named-registry"别名机制在本仓库有完整支撑:用户可通过pnpm-workspace.yaml声明 registry 别名(见 settings.rs),内置别名在 defaults.rs 定义;<alias>:@scope/pkg这类带 scope 的说明符由 named-registry resolver 专门拆分处理(见 resolver_setup.rs)。

三、匹配规则:从说明符中提取 semver 范围

允许写法只是第一步,关键是"如何匹配"——peer 依赖最终要与实际安装的版本做兼容性校验。核心规则(对应 TypeScript 包@pnpm/deps.peer-range,Rust 镜像实现于 peer_range.rs):

声明写法提取出的匹配范围说明
work:5.x.x5.x.xnamed-registry 说明符取其版本体
npm:bar@^5^5@之后的版本部分
带 scheme 但无版本*file:、git/URL 说明符没有版本信息
workspace:1.2.31.2.3剥掉前缀后剩余部分是范围则直接用
workspace:^/workspace:~/workspace:*无版本可提取,视为任意版本
纯 semver 范围原样返回^5.0.0仍是^5.0.0
catalog:引用原样返回由 catalog 解析处理
work:^1 \|\| work:^2^1 \|\| ^2多个消费者范围合并后的\|\|联合,逐一提取版本体

该规则由 get_peer_version_range 实现,分三步:

  1. 若含||,先按联合拆分、对每个子项递归处理再重新 join;
  2. 若是合法 peer 范围(semver /workspace:/catalog:),交给desugar_workspace_range处理workspace:前缀;
  3. 否则寻找:(要求位置不在首位),取其后的版本体;若版本体本身是合法范围直接使用,若形如bar@^5则取最后一个@之后的部分;仍解析不出则返回*

设计意图很清晰:匹配范围只决定"这个版本是否满足 peer 要求",而安装时选哪个包由原始说明符决定。例如file:../vendor/real-peer匹配范围是*(任何版本都满足),但实际安装的永远是../vendor/real-peer这个本地目录。

四、仍被拒绝:裸name@version

规则收紧的另一面是:裸的name@version值(如foo@1.0.0)几乎总是笔误,仍然被拒绝。这类写法没有 scheme 前缀,又不是合法 semver 范围,校验时会被判为非法。

拒绝逻辑见 is_acceptable_peer_spec:一个 peer 值只有在"是合法范围(semver/workspace:/catalog:)"或"包含:"时才被接受。foo@1.0.0两者都不满足,于是报ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION

校验的入口在 importer.rs 的validate_peer_dependencies:它遍历 manifest 中所有DependencyGroup::Peer条目,对每个(dep_name, specifier)调用is_acceptable_peer_spec,一旦发现非法值立即返回InvalidPeerDependencySpecification错误,其中project_id取 manifest 的name字段、缺失时回退到目录路径。注释点明了这样做的原因:拒绝裸name@version是为了避免这类笔误被当作"项目相对路径依赖"(name/version会被解析成目录)而悄悄解析成功

五、peer 自动安装:scheme 如何参与选择与去重

peer 说明符的 scheme 不仅在校验时起作用,还贯穿"缺失 peer 自动安装"流程:

  • 在 missing_peers.rs,当多个消费者对同一 peer 声明了不同范围时,classify_missing_peer合并这些范围;注释明确说明:hoisting 一个缺失的必需 peer 时需要保留原始说明符的 scheme(如work:5.x.x),因为要按该说明符去拉取包,而范围合并只负责产生一个可比较的版本区间。

  • 在 hoist_peers.rs,preferred_version_specifier选择去重目标版本时调用get_peer_version_range把 scheme 说明符还原成可比范围:如果某 importer 已解析出一个满足该范围的版本,就直接复用("dedupe onto a preferred version");若存在候选版本但都不满足范围,则回退为用范围本身从 registry 解析,而不是硬装一个 peer 明确拒绝的版本。对于没有版本体的说明符(如catalog:、dist-tag),提取结果不是 semver,则保持"去重到最高版本"的旧行为。

六、测试与验证

本仓库围绕该特性提供了三层测试,可作为行为规范:

  • Rust 单元测试:peer_range/tests.rs 中is_acceptable_peer_spec_accepts_scheme_carrying_specifiers验证 scheme 说明符被接受、get_peer_version_range_reduces_a_union_of_scheme_specifiers验证||联合提取;importer_wanted_specs.rs 的accepts_scheme_carrying_peer_specifiers验证整条 importer 解析链路放行这些说明符。

  • CLI 集成测试:cli/tests/suite/peers.rs 覆盖ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION错误码的端到端表现。

  • TypeScript 侧镜像实现@pnpm/deps.peer-range对应的校验逻辑在 validatePeerDependencies.ts,配套测试见 validatePeerDependencies.ts。Rust 侧注释明确要求与 TS 侧"保持同步"(keep the two in sync)。

七、实战配置示例

package.json中声明带 scheme 的 peer 依赖:

{ "name": "my-plugin", "peerDependencies": { "workbox": "work:5.x.x", "bar": "npm:bar@^5", "real-peer": "file:../vendor/real-peer" } }

解析行为对应如下:

  1. 安装时validate_peer_dependencies先放行全部三项(都含:);
  2. 解析/校验时分别按5.x.x^5*匹配消费者实际安装的版本;
  3. 若某 peer 缺失需要自动安装,则用work:5.x.xnpm:bar@^5file:../vendor/real-peer原样作为拉取说明符;
  4. 若有人误写成"workbox": "workbox@5.0.0",安装立即以ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION失败并提示正确写法。

八、影响范围与注意事项

  • 本次变更在 changeset 中以 minor 级发布,同时作用于pnpm(TypeScript 实现)与pacquet(Rust 实现),并同步 bump 了@pnpm/deps.peer-range(minor)、@pnpm/installing.deps-resolver@pnpm/deps.inspection.peers-checker(patch)。
  • 兼容性:原合法的 semver、workspace:catalog:行为完全不变;npm:别名、named-registry、file:/git/URL 由"报错"变为"可解析",属向后兼容的放宽;裸name@version的拒绝行为保持不变,不会静默放行。
  • 使用前提:named-registry 说明符依赖项目已配置对应的 registry 别名(内置别名见 defaults.rs,用户别名经pnpm-workspace.yaml声明,参见 settings.rs);file:/git 说明符需要对应目录或仓库可达。
  • 匹配语义提醒file:/git/URL 说明符的匹配范围是*,意味着 peer 兼容性检查不会因版本号产生警告,但这不代表"任意版本都合理"——peer 的实际内容兼容性仍由声明方自行保证,pnpm 只负责安装时按原始说明符取包。

总结

peer-scheme-specifiers这项变更让 pnpm 的peerDependencies从"严格 semver 白名单"演进为"scheme 感知的说明符解析":named-registry、npm:别名、file:/git/URL 说明符各按其携带的版本体参与匹配(无版本则匹配*),原始说明符始终负责选择安装目标,裸name@version笔误则继续被ERR_PNPM_INVALID_PEER_DEPENDENCY_SPECIFICATION拦截。核心判定与范围提取集中在 peer_range.rs,校验入口在 importer.rs,自动安装的 scheme 保留与去重逻辑分别在 missing_peers.rs 与 hoist_peers.rs,三层测试用例可作为行为规范的最终依据。

  • 包管理器
  • 开发工具
  • CLI

【免费下载链接】pnpm

Fast, disk space efficient package manager

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

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

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

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

立即咨询