ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

pnpm + Lerna 构建 Monorepo:依赖管理与发布流程实战指南

pnpm + Lerna 构建 Monorepo:依赖管理与发布流程实战指南 1. 项目概述为什么是 Monorepo为什么是 pnpm lerna干了多年前端工程化我真正开始系统性折腾 Monorepo 是在一个多包维护的痛点引爆之后。团队同时维护十几个 npm 包每个包独立仓库、独立版本、独立 CI改一个公共类型要发版、提 PR、等合并、再更新依赖循环往复效率低到让人想直接复制粘贴代码。后来切到 Monorepo一个仓库搞定所有包公共代码改完立刻可见依赖统一管理命令一条跑全量这个爽感是立竿见影的。但 Monorepo 不是灵丹妙药它把“包与包之间的依赖问题”从“跨仓库拉扯”变成了“一个仓库里的依赖地狱”。npm 和 yarn 的 workspace 在依赖隔离和磁盘占用上都有各自的问题直到 pnpm 出现才真正把 Monorepo 的依赖管理带到了另一个维度。pnpm 用硬链接和全局存储解决了重复安装问题用非扁平 node_modules 杜绝了幽灵依赖这两点恰好戳中了我当时最疼的两处。而 lerna 则在版本管理和发布流程上做了很好的补充——它管的是“哪些包该升版本、以什么方式升、发布顺序怎么走”和 pnpm 的职责完全不冲突反而形成了一套分工明确的组合拳。这篇内容适合谁看正在从多仓库迁移到单一仓库的团队可以参考整体设计与依赖策略已经用了 npm/yarn workspace 但觉得扁平 node_modules 和重复依赖很烦的人可以看看 pnpm 怎么解决还有那些想用 lerna 做版本发布但被各种配置劝退的开发者。我会从为什么选这套组合、环境怎么装、项目怎么搭、坑怎么填这几个维度完整展开尽量做到看完就能直接上手。2. 安装 pnpm环境准备与各类安装踩坑实录在我接触过的团队里安装 pnpm 这一步挂掉的比例远超预期。热搜词里那一串“无法将 pnpm 项识别为 cmdlet”“不是内部或外部命令”“bing安装 pnpm”全是同一类问题环境装好了壳子没找到可执行文件。所以环境准备这一节我会把安装方式、离线部署、环境变量、版本切换全部铺开这部分踩过坑的人一定深有感触。2.1 常见安装方式与选择逻辑先列出主流的几种安装 pnpm 的方式我实际用过的如下安装方式命令适用场景npm 全局安装npm install -g pnpm已有 Node 环境、历史习惯Corepackcorepack enable / corepack prepare pnpmlatest --activateNode 16.9 自带、官方推荐PowerShell 脚本Windowsiwr https://get.pnpm.io/install.ps1Windows 快速安装curl 脚本macOS/Linux/WSLcurl -fsSL https://get.pnpm.io/install.sh类 Unix 环境快速安装包管理器原生安装brew install pnpmmacOS、scoop install pnpmWindows习惯系统级包管理器离线包安装npm pack pnpm 后安装本地 tgz内网环境、无外网场景我的建议是版本大于等于 Node 16.9 的直接用 corepack 就好它天然跟 Node 绑定不需要额外装运行时而且可以随时切换 pnpm 版本。团队里统一 Node 版本的情况下corepack 是最省心的。如果是从旧项目里带过来的习惯npm 全局装也没问题但要注意全局路径是否在 PATH 里这个细节是后面一半坑的源头。2.2 离线安装与内网部署方案热搜词里“pnpm离线安装”出现频率很高这多半是内网开发环境。没有外网curl 脚本和 npm registry 都白搭。离线安装的核心思路是在外网机器上把 pnpm 的包打包弄下来拷贝进内网再装。最简单的方式是在外网执行下面命令生成一个 tgz 文件# 在外网机器上拉取 pnpm 的打包文件 npm pack pnpmlatest # 也可以指定版本保证内外网一致 npm pack pnpm9.12.0把生成的 pnpm-9.12.0.tgz 拷贝到内网机器然后执行npm install -g ./pnpm-9.12.0.tgz这种方式安装出来的 pnpm 本质上是一个全局 npm 包工作正常但 pnpm 自身的一些独立二进制功能比如 pnpm self-update可能无法使用因为自更新需要访问网络。内网环境下我更推荐直接下载 pnpm 官方提供的 standalone 可执行文件它是一个自包含的二进制不依赖 Node 运行时文件名类似 pnpm-linux-x64、pnpm-win-x64拷贝进去后加上执行权限就能跑。# Linux 示例下载 standalone binary wget https://github.com/pnpm/pnpm/releases/download/v9.12.0/pnpm-linux-x64 chmod x pnpm-linux-x64 mv pnpm-linux-x64 /usr/local/bin/pnpm pnpm -v这个方案的好处是彻底绕开了 node_modules 和 npm 的耦合内网只要装一个二进制就行。坏处是版本升级需要手动替换文件。我个人的经验是内网环境尽量把 pnpm 版本锁死团队统一用同一个版本否则不同版本解析依赖的规则不完全一致容易出现“我这能装他那报错”的奇葩问题。2.3 环境变量与 PATH报错集中营“pnpm : 无法将‘pnpm’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”和“pnpm 不是内部或外部命令也不是可运行的程序或批处理文件”这两类报错我见过无数回。说白了就是操作系统在执行命令时沿着 PATH 路径没找到 pnpm 这个可执行文件。为什么装了 npm 全局包却找不到因为 npm 全局安装目录通常不在 PATH 里。Windows 下最常见的情况PowerShell 窗口是在安装前就打开的PATH 是会话启动时读取的环境变量装完新包后已经打开的终端不会自动刷新。解决方案很简单关掉重新开一个终端。如果重开还不行就是 npm 全局目录本身没加入 PATH。# 查看 npm 全局目录 npm prefix -g # 例如输出 C:\Users\你的用户名\AppData\Roaming\npm然后把这个目录加入系统环境变量 PATH。具体操作右键“此电脑” - 属性 - 高级系统设置 - 环境变量 - 在“系统变量”或“用户变量”的 Path 里添加上述路径保存后再重开终端。另外一个容易忽略的点是 Windows PowerShell 执行策略。即使 PATH 没问题执行 pnpm 也可能被脚本执行策略拦截提示说“无法加载...因为在此系统上禁止运行脚本”。这是 PowerShell 的 ExecutionPolicy 在限制 .ps1 脚本。临时解决用Set-ExecutionPolicy -Scope CurrentUser RemoteSigned这个操作的意思是把当前用户的执行策略调整为允许本地脚本运行远端脚本必须签名。这是官方文档也推荐的做法安全风险可控我建议就按这个设硬要绕开执行策略去运行脚本反而更危险。Linux/macOS 下常见的“找不到命令”则多半是 npm 全局目录没进 PATH。Linux 上用 nvm 装 Node 的话npm 全局目录一般在 ~/.nvm/versions/node/vX.Y.Z/bin这个 nvm 会自动处理但如果是用 apt 装的 Nodenpm 全局目录可能在 /usr/lib/node_modules 或者 /usr/local/lib/node_modules对应 bin 目录不一定在 PATH 里。# 查看 npm 全局 bin 目录 npm bin -g # 或 npm root -g把 bin 目录追加到 shell 配置文件里比如 ~/.zshrc 或 ~/.bashrcexport PATH/usr/local/bin:$PATH改完记得 source ~/.zshrc 或重启终端。我踩过一次很蠢的坑在 Linux 上装了 pnpm直接开新终端找不到后来才发现是用的 zsh但配置写进了 .bashrczsh 根本不加载它。所以先确认自己用的什么 shell再决定改哪个配置文件。2.4 版本管理与删除 pnpm热词里有“删除pnpm”和“安装 pnpm”说明很多人装了之后因为版本问题想卸掉重装。删除 pnpm 的方式取决于当初怎么装的# 如果是 npm 全局安装 npm rm -g pnpm # 如果是 corepack 安装的 corepack uninstall pnpm # 如果是独立二进制安装直接删除对应文件即可 rm /usr/local/bin/pnpm卸载干净后重新安装我建议装完顺手验证两个命令pnpm -v which pnpm # Windows PowerShell 使用 Get-Command pnpmwhich能告诉你 pnpm 到底在哪个目录如果输出了路径说明 PATH 没问题。如果版本号和预期不一致十有八九是环境里残留了多个 pnpm比如 corepack 装了一个、npm 又全局装了一个。排查方式同样是检查 which pnpm 的结果一般优先处理 PATH 顺序靠前的那个版本。我这里强烈建议团队内部使用统一版本管理packageManager字段加上版本约束{ packageManager: pnpm9.12.0 }配合 corepack这个字段会被自动识别并激活对应版本从源头减少了“我这能跑你那不能跑”的版本不一致问题。3. 工作区设计与 lerna 集成依赖管理、版本发布与构建编排环境装好了接下来真正进入 Monorepo 的骨架搭建。这一节是整个项目规划的核心我会重点讲清楚 pnpm 工作区的文件结构、lerna 在其中担任的角色、依赖治理策略以及如何用命令编排构建和发布流程。这块理解到位了项目的扩展长期健康才有保障。3.1 pnpm workspace把多包统一到一个仓库里的机制pnpm 的 workspace 能力是通过 pnpm-workspace.yaml 文件激发的。这个文件本身就是仓库里多包结构的核心声明与 npm/yarn 的 workspaces 字段不同pnpm 用独立文件配置可读性更好。配置文件示例packages: - apps/* - packages/* - tooling/*这样 pnpm 就会把 apps、packages、tooling 目录下的所有子目录都视为独立的包。被识别进 workspace 的包可以互相直接引用而不需要发版到 npm registry。pnpm 会通过 workspace 协议自动关联如果 workspace 里有包 A另一个包 B 声明依赖 Apnpm 在本地直接链接不发版。这个特性我以前在 npm 里折腾半天的“本地开发依赖问题”现在一个配置文件全搞定。很多团队刚把代码搬进 Monorepo 后第一反应是“这跟之前分仓库开发有什么区别”区别在于你能在同一个 PR 里同时修改公共包和业务包CI 一并检查验证提交到主分支时所有包一起被测试。这个协作方式的改变比单纯省几个 node_modules 重要得多。3.2 lerna 在 pnpm workspace 里的定位与配置很多人对 lerna 的理解还停留在“管理 monorepo 的工具”其实 lerna 的职责非常聚焦版本管理和发布流程。它不负责依赖安装那是 pnpm 的事不负责任务调度那是 pnpm -r / turbo 的事它就干两件事审计哪些包需要发版按指定模式更新版本号然后帮你按依赖顺序发布。lerna 的配置文件 lerna.json我的典型配置长这样{ version: independent, npmClient: pnpm, packages: [apps/*, packages/*], command: { publish: { conventionalCommits: true, message: chore(release): publish } } }关键字段拆解version有两种模式。fixed 模式下所有包共用一个版本号每次发布全部升版本适合强耦合的工具链包independent 模式下每个包独立版本适合业务组件库、互相依赖关系弱的包集合。我的实际经验是业务型 Monorepo 用 independent 更灵活公共库采用 semver 语义化版本也就是 major.minor.patch 的规范来管理推荐而不必被全局版本号绑架。npmClient指定 pnpmlerna 会据以调用 pnpm 的命令去安装依赖和触发 lifecycle scripts。这里有个容易被忽略的点lerna 要求 npmClient 是 pnpm 时项目必须已经启用了 pnpm workspace否则 lerna 无法识别包与包的内部链接。command.publish.conventionalCommits开启后会依据 conventional commit 规范比如 fix: 打补丁版本feat: 打新特性版本自动推断下一次版本号。如果你团队还没规范 commit message建议先切换起来省去手动判断版本的痛苦。值得强调的是lerna 6 以后官方架构发生了变化早期的 lerna bootstrap 被废弃不再负责安装依赖。所以新项目里不要跑到网上翻到老教程去执行 lerna bootstrap那一套是上一个时代的玩法。现在的方式是依赖安装交给 pnpm installlerna 只做 version 和 publish。3.3 依赖隔离、幽灵依赖与 hoist 策略pnpm 之所以比 npm/yarn workspace 更优雅根源在于 node_modules 的组织方式。npm/yarn 默认把所有依赖都平铺在一个层级这带来了著名的“幽灵依赖”问题你明明没有在 package.json 里声明某个依赖代码里却能 require 到它因为顶层 node_modules 里有它的副本。开发时侥幸能用上线部署环境一换依赖缺失报错查半天怀疑人生。pnpm 默认使用符号链接与硬链接结构全局存储池管理所有包的实体内容每个项目通过硬链接引用磁盘占用大幅下降同时 node_modules 下并不是扁平结构而是每个包只暴露自己声明过的依赖其他包的依赖被隔离到 .pnpm 目录里无法直接访问。等于把一个隐形的全局窗口关上了该用哪个依赖就得显式声明哪个依赖。但纯隔离也有副作用某些工具链包比如 eslint 插件、typescript 的全局类型包需要在根目录级别访问。pnpm 提供了对应的配置参数# pnpm-workspace.yaml 或 .npmrc 中的配置 public-hoist-pattern: - *types* - *eslint* - *prettier* shamefully-hoist: false我建议默认不开 shamefully-hoist这个参数会把 node_modules 打平成 npm 风格所有依赖全部暴露又退回去幽灵依赖的老路了。宁可花时间把必要的包配置到 public-hoist-pattern也不要图省事全暴露。对于 typescript 这种需要全局访问的需要配合 hoist 配置。这个取舍是 Monorepo 里最常见的设计决策一定要想清楚再动手。3.4 用 pnpm 与 lerna 编排构建和任务执行Monorepo 里最常见的任务是“我要在某个包里跑某个命令”或者“我要在所有包里按依赖顺序执行某个命令”。pnpm 提供了很顺手的过滤语法# 在某个包下执行命令 pnpm --filter my/ui build # 在所有包下按拓扑顺序执行 pnpm -r build # 在入口包执行并依赖其他 workspace 包的最新代码 pnpm --filter my/app build当你执行 pnpm -r build 时pnpm 会先分析包之间的依赖关系按依赖顺序依次构建被依赖的包先跑业务包后跑。这个按依赖排序的能力非常关键不用手工维护构建顺序否则每次加一个包就可能破坏顺序。lerna 的 run 命令也能做类似的事# 使用 lerna 在所有包中执行 script lerna run build # 指定包范围 lerna run build --scopemy/ui但在我看来如果 pnpm 的 -r/--filter 已经满足日常需求其实没有必要在任务执行层面再引入 lernalerna 真正发挥价值是在版本发布环节。所以我的分工习惯是日常构建、测试、lint 用 pnpm发版时用 lerna。两者各管一段协作模式清晰。4. 实操记录从零搭建一个 pnpm lerna 的 Monorepo 示例讲了这么多抽象的设计和策略这里走一遍真实搭建流程。我以一个常见的工程化场景为例一个业务应用apps/web依赖两个公共库packages/ui、packages/utils公共库之间也有依赖关系。步骤会比较细你可以直接跟着敲命令里的包名按自己的实际情况替换。4.1 初始化仓库与基础配置mkdir pnpm-lerna-demo cd pnpm-lerna-demo git init # 创建根 package.json pnpm init根 package.json 里我会手动补上几个关键字段{ name: pnpm-lerna-demo, private: true, packageManager: pnpm9.12.0, scripts: { build: pnpm -r build, test: pnpm -r test, dev:app: pnpm --filter demo/app dev, release: lerna version lerna publish } }然后创建 pnpm-workspace.yamlpackages: - apps/* - packages/*创建 lerna.json{ version: independent, npmClient: pnpm, packages: [apps/*, packages/*], command: { publish: { conventionalCommits: true, message: chore(release): publish } } }4.2 创建子包并配置依赖关系mkdir -p packages/utils packages/ui apps/web在 packages/utils/package.json 中{ name: demo/utils, version: 1.0.0, main: src/index.ts, types: src/index.ts, scripts: { build: tsc, test: vitest run } }packages/ui 依赖 demo/utils所以它的 package.json 里声明{ name: demo/ui, version: 1.0.0, dependencies: { demo/utils: workspace:* } }apps/web 同时依赖 ui 和 utils{ name: demo/app, private: true, dependencies: { demo/ui: workspace:*, demo/utils: workspace:* } }这里有一个关键细节workspace:* 这个协议。它告诉 pnpm 这个依赖应该从 workspace 里解析而不是从 registry 拉取。发布到 npm 之前lerna 或 pnpm publish 会把它自动替换成实际版本号。如果你写死具体版本号比如 “1.0.0”本地开发时 pnpm 还是会尝试看 registry 有没有这个版本导致又发了一个多余的网络请求久而久之可能出现本地 workspace 包和远程包同名导致的混乱。我强烈建议新建包时统一使用 workspace:*。然后执行安装pnpm install执行后你会发现根目录出现了 pnpm-lock.yaml这个锁文件是整个仓库依赖的“数据库”务必提交到版本库。团队里任何人安装依赖都必须基于同一个锁文件才能保证依赖一致性。4.3 使用 pnpm 过滤命令执行构建现在写几个脚本感受一下过滤命令的便利。比如只构建属于 packages 的包pnpm --filter ./packages/** build再比如只构建 ui 包及其依赖pnpm --filter demo/ui... build这里的 ... 语法意思是“这个包以及依赖它的那些包”反向的 ^ 语法表示“它依赖的包”。这两个符号记牢了Monorepo 日常操作大部分任务都能随手完成。4.4 lerna 版本管理与发布流程发布会走两阶段。先执行lerna versionlerna 会分析自上次 tag 以来所有包的 commit message通过 conventional commits 推断每个包应该升 major、minor 还是 patch 版本然后自动更新 package.json 并打 git tag。比如我在 ui 包目录下提交了一个 feat: xxxlerna 就可能把 demo/ui 从 1.0.0 升到 1.1.0。这里我的经验是发布之前先看 lerna changed 可以预览变更影响范围避免误发版本lerna changed如果显示的包列表和你预期不符说明 commit 规范或包的依赖关系可能有问题排查清楚再继续。确认无误后执行发布lerna publishlerna 会按照依赖顺序先发被依赖的包demo/utils再发依赖它的包demo/ui最后发业务应用。如果某个包已经打过 tag 且版本没变lerna 会自动跳过。这个过程非常省心尤其是几十个包的仓库手工发极易漏包或顺序错乱。关于发布时机我个人的习惯是lerna version 和 lerna publish 一般连续执行但如果 npm registry 有登录、认证问题先把包推到内部 registry 再执行 publish 能更早暴露权限问题。另外建议在 CI 里配置 npm 凭据这样版本发布可以作为 CI 流水线的一环避免本地手动发版时权限泄漏。4.5 在代码库里落地 TypeScript 与 ESLint公共类型和共享配置是 Monorepo 的重要价值所以我通常在根目录放一个 tsconfig.base.json各包继承它{ compilerOptions: { target: ES2020, module: ESNext, moduleResolution: Bundler, strict: true, declaration: true } }packages/utils/tsconfig.json{ extends: ../../tsconfig.base.json, include: [src] }ESLint 的共享配置同理可以放在 tooling/eslint-config 包里由 root 和各个业务包引用。这套共享配置方案让团队从“每包独立复制一份配置”变成“维护一份配置所有包自动生效”长期来看维护成本显著下降。5. 常见问题与排查实录关于依赖、构建和发布的那些坑我打算把这几年在 pnpm lerna 实践中积累的典型问题集中整理成速查表每一个都是真实踩过、真实排查过的。篇幅关系不可能全部展开但每个问题我都会把现象、原因、解决路径讲明白。5.1 安装与环境问题速查现象可能原因解决方案Windows 报“无法将 pnpm 项识别为 cmdlet”npm 全局目录不在 PATH、终端未重启重开终端将 npm prefix -g 输出目录加入 PATHLinux/macOS 报“pnpm 不是内部或外部命令”npm 全局 bin 没进 PATH、shell 配置文件不对确认 shell 类型将 npm bin 目录写入对应配置文件PowerShell 禁止运行 pnpm 脚本ExecutionPolicy 限制Set-ExecutionPolicy -Scope CurrentUser RemoteSignedpnpm -v 版本与预期不符环境里多个 pnpm 共存which pnpm 定位实际执行文件清理多余安装内网无法安装无外网访问 npm registry用 npm pack 打包离线安装或下载 standalone 二进制我只强调一件事不管什么平台装完先跑 which pnpmWindows 用 Get-Command pnpm确认执行路径这是排查安装问题的第一步。5.2 幽灵依赖、版本漂移与依赖缺失pnpm 的非扁平结构有一个典型的“反杀时刻”某个依赖在 npm 下能正常使用切到 pnpm 后运行时报 “Cannot find module”。原因就是代码里依赖了一个 package.json 中未声明的传递依赖。比如项目依赖 AA 依赖 B你的代码里直接用了 Bnpm 扁平结构下 B 在顶层 node_modules 里所以能 require 到pnpm 的隔离结构下 B 被锁在 A 的私有目录里你自然找不到。处理这个问题的正确姿势是找到缺失的依赖显式声明后安装。如果这个包确实是工具链必须的优先调整 public-hoist-pattern如果只是业务代码手滑改了 package.json 就行。我见过不少团队遇到这类报错之后直接上 shamefully-hoist 把 pnpm 打回扁平结构等于把好不容易建立的依赖隔离体系废掉了。这种做法短期快速解决长期埋雷我的建议是除非有极特殊的兼容性诉求否则不要引入。版本漂移是另一个隐性问题。Monorepo 里 A 包依赖 B 包时如果写死了版本号比如 demo/utils: 1.0.0那你本地开发的时间越长B 包更新的提交并不会同步到 A 的依赖解析中开发到某一刻就出现“逻辑上是新代码依赖解析里是旧版本”的割裂。这就是我前面强调 workspace:* 协议的核心原因——它保证本地开发始终引用 workspace 内最新的包代码版本漂移只发生在发版时由 lerna 统一管控。5.3 构建顺序、缓存与性能优化按依赖顺序构建是 pnpm -r 内置能力但复杂项目里经常会碰到“应用构建完但类型检查失败原因是公共包还没有先编译”这类问题。这时候要检查的是公共包有没有正确暴露 build script以及根命令是否确实使用 pnpm -r build 而不是并行跑所有包。pnpm -r 默认串行执行但如果你用了 pnpm -r --parallel build所有包同时构建顺序就不保证了。对于更大的仓库我建议在 pnpm 之上引入任务缓存层。pnpm 自身也支持基于 content hash 的构建缓存但更成熟的方案是配合 TurboRepo 或 Nx 做增量构建。这类工具的职责是记住每个包上次构建的产物哈希如果输入没有变化跳过构建直接复用缓存。对于几十个包的 Monorepo构建时间能从十几分钟压到几十秒体验差距是质的飞跃。引入策略也很简单把根目录 package.json 的 scripts 全部切换到 turbo 或 nx 的执行器底层依然调用 pnpm。5.4 lerna 发布过程中的常见拦路虎我用 lerna 发布时遇到过几个典型报错。一个是 publish 时提示“You must sign out in npm and sign in again”这通常是因为 npm 的 token 权限不足权限只有 read 而没有 publish。处理方式是去 npm 账号设置里生成新的 automation token赋予 publish 权限。另一个常见问题是 git tag 冲突lerna 在发布前会基于上个 tag 做版本计算如果不小心手动删过 tag或者 CI 里有其他进程在动 taglerna 的版本计算就会错乱。我处理这一类问题的经验是定期检查 git tag 列表保持 tag 命名统一默认是包名版本号格式不要手动修改。还有一类发版报错是因为包尚未被 publish 但其依赖已经指向了它。举例来说demo/ui 的 package.json 声明依赖 demo/utils: workspace:*lerna publish 时如果 utils 没有被识别为本次要发布的包而 ui 却要先发布npm registry 上找不到 utils发布就会失败。排查方法先运行 lerna changed 看看这次影响的包列表如果发现依赖关系里的某个包没被列进去检查它最近的 commit 是否漏了或版本号没变化必要时手动 bump 那个包。这个连环依赖问题是发版阶段最让人头大的我建议发版前一定要定好发布顺序不能贪图省事跳过检查。5.5 关于 pnpm 下载失败与网络问题的处理建议“pnpm下载失败”这个热词很多内容是指 pnpm install 时去 npm registry 拉取依赖失败。核心解决方案是换 registry 或者配置企业镜像。国内团队比较常用的做法是在根目录放 .npmrcregistryhttps://registry.npmmirror.com如果只是临时拉某个包失败可以用 pnpm add 时加 --registry 参数临时指定pnpm add lodash --registryhttps://registry.npmmirror.com这里要提醒的是不同的 registry 之间的 lockfile 解析可能有细微差异团队内部务必统一镜像源否则容易出现“我这边 lockfile 是 A registry 生成的你那边装包用了 B registry引发依赖不一致”的扯皮事件。另外CI 环境里如果经常出现下载失败建议把依赖缓存挂上去pnpm 的 store 目录可以用环境变量 PNPM_HOME 指定CI 里配置一次缓存命中率极高整个 install 时间可以从分钟级降到秒级。6. 结束前的一些个人体会折腾 Monorepo 这几年我最深的体会是工具只是手段真正难的是让团队理解和接受这些约束。pnpm 的严格依赖隔离逼迫每个开发者规范地声明依赖刚开始大家都会觉得烦但一旦适应那种“本地能跑线上就崩”的隐性 bug 会大幅减少lerna 的版本策略则让发布从“经验主义”变成“流程化”每次版本变动都有迹可循。如果让我重新搭一次 Monorepo我还是会选择 pnpm lerna 这套组合但一定会从一开始就把 workspace:* 协议、public-hoist-pattern、核心命令模板这些约定放进项目脚手架而不是等项目跑起来之后再去补课。最后再分享一个小技巧把 pnpm --filter 带上包名的用法写进 README给团队培训的时候先只教五个命令其余等他们自己遇到问题再来查收益会高很多。
返回列表