
包管理器开发工具CLI【免费下载链接】pnpmFast, disk space efficient package manager项目地址https://gitcode.com/gh_mirrors/pn/pnpm点击查看免费下载导读本文基于 pnpm 仓库中.changeset/peer-hoist-skips-project-relative-root-deps.md这条 changeset 记录深入剖析一项针对peer dependency 提升peer hoisting的修复在开启resolvePeersFromWorkspaceRoot时工作区根项目以link:、file:或路径形式workspace:如workspace:../pkg声明的依赖将不再被当作裸路径整体提升进其他项目而是被钉为该链接包自身的版本号后再参与 peer 满足判定。读完本文你将理解这条修复解决的具体缺陷、源码中的判定与落地逻辑、以及它对 monorepo 中 peer 依赖解析语义的影响。背景peer 依赖为什么会提升到工作区根pnpm 的依赖解析分为多个阶段其中peer 解析peer resolution由 resolvePeers.ts 承担。当一个 importer项目缺少某个必需 peer 时pnpm 会尝试从**工作区根项目root project**或既有版本池中为它找一个提供者这一机制称为 peer 提升peer hoisting。resolvePeersFromWorkspaceRoot正是控制这一行为的开关开启后工作区根项目可以直接补位满足其他子项目缺失的 peer。从源码看这一开关从配置层一直贯穿到解析层配置定义Config.ts 中声明resolvePeersFromWorkspaceRoot?: boolean解析上下文传递index.ts 将其传入解析器核心判定resolvePeers.ts 中当开关开启且项目数大于 1 时会从projects中找出id .的根项目并把它的直接依赖directNodeIdsByAlias并入每个 importer 的 provider 候选集合使根项目与子项目声明过的依赖一同参与 peer 匹配。问题恰恰出在这个补位环节根项目声明的依赖若是相对路径 specifier提升时如果原样搬运就会在目标项目peer 被提升进去的项目的目录上下文里解析出完全不同的路径——甚至解析不到任何东西产生一个坏链接broken link。缺陷本质相对路径 specifier 离开声明方即失效changeset 明确指出link:、file:以及路径形式的workspace:workspace:../pkg这类注意不是workspace:^或workspace:*这种范围形式都是相对于声明它们的那个项目来解析的。以workspace:../pkg为例它写在工作区根项目的package.json里时指向根目录上一级的pkg目录但如果 pnpm 把这一 specifier 原样提升到另一个位于不同层级的子项目如packages/app相对路径../pkg从packages/app出发指向的目录就完全不同甚至不存在。link:与file:同理——它们的路径基线是声明方目录而不是被提升目标所在的目录。这一问题的另一个隐蔽后果与锁文件复用有关当根依赖已存在于 lockfile 中、被复用而跳过完整解析时它可能根本没有地址address或地址上没有normalizedBareSpecifier。此时若提升路径 specifier同一份 manifest 在首次安装与依赖锁文件重装之间会得到不一致的结果。修复方案把路径依赖钉回它自己的版本这条 changeset 对应的核心修复逻辑位于 resolveDependencies.ts 的getHoistableRootDeps及其辅助函数中整体分三步1. 收集根项目可被用于满足 peer 的依赖getHoistableRootDeps 先汇总根项目的依赖优先取解析结果pkgAddress上的normalizedBareSpecifier取不到则回退到 manifest 中声明的 wanted specifierwantedSpecifierByAlias保证复用 lockfile 的安装与全新安装同一 manifest行为一致。随后把根项目声明过但未出现在解析结果中的依赖也补进候选列表。2. 识别项目相对 specifier判定函数 isProjectRelativeSpecifier 只匹配三种前缀return bareSpecifier.startsWith(link:) || bareSpecifier.startsWith(file:) || bareSpecifier.startsWith(workspace:.)注意第三项是workspace:.即路径形式的workspace:../pkg、workspace:./pkg而workspace:^8.0.0、workspace:*这类范围形式会命中同一工作区包、从任何 importer 解析结果都一致因此不需要进入后续钉版本流程。3. 将路径解析为链接包的版本号对命中的路径 specifierpinProjectRelativeDepToItsVersion 会去掉协议前缀取:之后的路径段以根项目目录为基准拼接出完整路径path.resolve(rootDir, pathWithoutProtocol)从磁盘读取该目录的package.jsonreadManifestOfLocalTarget取得name与version用semver.valid(version)校验版本合法性后将normalizedBareSpecifier替换为该链接包自身的版本号。如果目标没有可读 manifest例如file:指向的是 tarball 而非目录、或 manifest 中没有合法版本号则该依赖不成为peer 满足候选——normalizedBareSpecifier被置为undefined在后续匹配中被跳过。之所以从磁盘重新读取 manifest 而不是直接使用pkgAddress.pkg是因为被锁文件复用的链接依赖没有pkg字段从磁盘读取能保证复用 lockfile 的重复安装与同 manifest 的全新安装提升出相同结果见 getHoistableRootDeps 的注释说明。源码中的匹配优先级为什么版本号就能钉住 peer修复之后根依赖以版本号形态进入 hoistPeers.ts 的workspaceRootDeps列表。当某个 importer 缺失 peer 时hoistPeers通过findWorkspaceRootDep在根依赖中查找提供者hoistPeers.ts其匹配优先级为alias 精确匹配根依赖的alias与 peer 名一致直接命中包名匹配根依赖的pkgName与 peer 名一致取所有候选里 alias 字典序最小的一个保证选择稳定alias 不相等时如npm:alias 会把同一包装到不同 slot必须区分只认带normalizedBareSpecifier的依赖——没有可用 specifier 就无法安装或约束 peer。命中后peer 的满足值就是根依赖的normalizedBareSpecifierhoistPeers.ts。经过本次修复路径 specifier 已在这一步之前被替换为版本号于是根项目对 peer 的权威与用版本范围声明该依赖时完全一致changeset 原话root has the same authority over the peerpeer 从任意 importer 解析都指向同一个包不再依赖各 importer 目录的相对路径上下文不再产生跨 importer 不一致、甚至指向不存在路径的坏链接。在resolveDependencies.ts中hoistPeers实际是以workspaceRootDeps参数被注入的resolveDependencies.ts测试用例也覆盖了workspace:协议范围如workspace:*在提升时不抛错、以及file:../postcss这类路径依赖进入根依赖候选的场景见 hoistPeers.test.ts 与 hoistPeers.test.ts。版本与范围说明该修复以 patch 级别变更的形式发布涉及pnpm/installing.deps-resolver、pnpm以及pacquet三个包见 changeset 文件头仅在resolvePeersFromWorkspaceRoot开启时生效该配置项在 Config.ts 中定义默认未开启适用范围仅限路径形式的项目相对 specifierlink:、file:、workspace:.前缀范围形式的workspace:不受影响若file:目标为 tarball、或链接目录无合法版本号该根依赖不会参与 peer 提升。结语这条 changeset 修复了一个只在 monorepo 根项目 peer 权威场景下才会暴露的边界问题相对路径依赖的语义天然绑定声明方目录不能脱离声明方被搬运。pnpm 通过路径 → 版本的钉化pin转换让resolvePeersFromWorkspaceRoot下的 peer 提升回归一致的包级语义同时兼顾了锁文件复用场景下的可复现性。理解这一修复有助于在大型工作区中正确规划根项目依赖与 peer 策略避免路径依赖被提升后产生静默的坏链接。赞分享包管理器开发工具CLI【免费下载链接】pnpmFast, disk space efficient package manager项目地址https://gitcode.com/gh_mirrors/pn/pnpm点击查看免费下载相关推荐pnpm catalog 不再收录本地路径依赖catalogMode / --save-catalog 对 file、tarball 与 workspace:path 的行为变更解读pnpm catalog 不再收录本地路径依赖 catalogMode / save catalog 对 file、tarball 与 workspace:包管理器开发工具CLIpnpm peers check 修复workspace:^、workspace:~ 与裸 workspace: peer 依赖不再被误报为未满足pnpm peers check 修复 workspace:^ 、 workspace:~ 与裸 workspace: peer 依赖不再被误报为未满足 导读包管理器开发工具CLIpnpm scriptShell 配置深度解析pnpm-workspace.yaml 相对路径解析修复与底层实现pnpm scriptShell 配置深度解析pnpm workspace.yaml 相对路径解析修复与底层实现 导读 scriptShell 是 pnpm包管理器开发工具CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考