
react-spring 单仓从 Yarn 3 迁移到 pnpm 的研究记录版本锁定、严格隔离与构建脚本白名单的八项关键决策【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring本文为 react-spring 单仓monorepo包管理器迁移Yarn 3 Berry → pnpm在实施前的 Phase 0 研究成果解读覆盖版本与锁定机制、workspace 清单形态、postinstall 脚本策略、CI 缓存等八项关键决策R-001 至 R-008并结合仓库中已落地的 根 package.json 与 pnpm-workspace.yaml 等配置文件佐证其最终形态帮助读者掌握一次「不换架构、只换包管理器」迁移的完整研究方法论与可复制操作清单。1. 背景一次「只换包管理器不改任何其他东西」的结构性迁移本次迁移的功能定义见 spec.md将yarn3.8.7nodeLinker: node-modules扁平提升布局整体替换为 pnpm 的严格隔离node_modules布局并配套完成 lockfile、workspace 清单、packageManager锁定、CI 工作流、下游消费示例fixture等全部配置面的切换。plan.md 明确了迁移范围16 个 workspacepackages/与targets/下的库、demo与docs、5 个 GitHub Actions 工作流、5 个下游消费者 fixture且要求以单个 PR 原子落地「不存在有中间态价值」的半迁移状态。research.md 是该迁移的 Phase 0 输出回答实施前必须拍板的 8 个研究问题R-001 到 R-008。每一条决策都遵循「Decision决策 Rationale理由 Alternatives considered被否方案」的三段结构。下文逐条展开并给出当前仓库中的落地证据。2. R-001pnpm 大版本选择与版本锁定机制决策采用迁移 PR 打开时点 pnpm9.x 的最新稳定版通过根package.json的packageManager字段packageManager: pnpm9.x.y锁定并由 Corepack 激活。研究文档给出的三条理由pnpm 9 是当时的稳定线——虽然 pnpm 10 已发布但生态采纳仍在磨合期而 9 已经提供了迁移所依赖的三个能力严格的node-linkerisolated、确定性 lockfile 格式 v9、以及onlyBuiltDependencies构建脚本白名单见 R-003packageManager字段 Corepack 与 Yarn 3 现行锁定方式packageManager: yarn3.8.7.yarn/releases/yarn-3.8.7.cjs是同构的约定现代 Node 均支持corepack enable锁定单一版本可以消除 lockfile 漂移这一类 CI 偶发失败flake。被否方案pnpm 10.x「集成方还在追赶」的噪声超过了迁移这种以「不出意外」为目标的工作的边际收益推迟到后续升级只用engines.pnpm而不写packageManager贡献者环境间不可靠且 Corepack 感知型工具恰恰以packageManager为准全局安装 pnpm 不走 Corepack贡献者环境会漂移违背锁定的初衷。仓库佐证当前 根 package.json 即为packageManager: pnpm9.15.9与研究决策完全一致——迁移后贡献者与 CI 通过 Corepack 自动收敛到同一个 pnpm 版本。3. R-002pnpm-workspace.yaml的形态决策用独立的pnpm-workspace.yaml替代根package.json中的workspaces字段# pnpm-workspace.yaml packages: - packages/* - targets/* - demo - docs同时删除既有的packages/parallax/react-spring/parallax-demo条目——该目录在仓库中已不存在packages/parallax/下只有src/、test/等。理由这是一个从旧布局遗留下来的「死重量」。Yarn Berry 对失效的 workspace 路径静默容忍而 pnpm 会对缺失路径告警/报错顺手清理即可其余 4 个 glob 覆盖了构建当前实际使用的所有 workspace。被否方案保留失效条目「以防万一」研究文档直言这是「六个月后咬你一口的静默腐化」故拒绝用逐 workspace 的显式路径替代 globglob 形态与 Yarn 时期一致且新包目录可被自动纳管故拒绝。仓库佐证当前仓库根目录的 pnpm-workspace.yaml 恰好就是这 4 条 glob逐字未变——R-002 的决策已被原样采纳。4. R-003pnpm 9 下的 postinstall 构建脚本白名单决策在根package.json中显式声明pnpm.onlyBuiltDependencies白名单。研究文档给出的起始清单best-guess starter为pnpm: { onlyBuiltDependencies: [ swc/core, cypress, esbuild, remix-run/dev, parcel/watcher, core-js, core-js-pure ] }核心动机pnpm 9 默认阻止依赖的安装/postinstall 脚本没有白名单时需要原生构建的包swc/core、esbuild、cypress等虽然会装上但跳过其 post-build 步骤造成运行期失败。根目录自身的postinstall脚本属于根级生命周期而非依赖脚本不受此机制影响予以保留。研究文档特别强调起始清单只是猜测真正的清单要在安装时收敛——pnpm 9 会精确打印被忽略脚本的告警逐个将确实需要构建的包加入白名单直到告警列表对「需要构建的包」为空。被否方案unsafe-perm之类全局关闭安全检查这恰是应当尊重而非绕过的正确性设置拒绝维护独立的.pnpm-allowlist文件package.json才是正确归属已提交、可评审、作用域限定在仓库内。仓库佐证与演化当前 根 package.json 的最终白名单收敛为pnpm: { onlyBuiltDependencies: [ parcel/watcher, swc/core, core-js, core-js-pure, es5-ext, esbuild ] }对比可见 R-003「以安装告警为准、迭代收敛」方法论的实际效果cypress与remix-run/dev在最终清单中消失es5-ext被补入——从当前仓库脚本看如 根 package.json 的test:unit已基于 vitest、devDependencies 中为playwright而非cypress这与仓库工具链后续演进相吻合。研究决策的「先猜后收敛」流程在真实仓库中得到验证。5. R-004Huskyprepare脚本在 pnpm 下的行为决策原样保留既有的prepare生命周期脚本。prepare是标准 npm 生命周期钩子并非 yarn 专属pnpm 在pnpm install时与 yarn 一样会执行它Husky 的钩子接线命令本身也不依赖包管理器。因此无需任何额外动作唯一验证点是全新pnpm install之后 pre-commit 与 commit-msg 钩子能够正常触发。被否方案把 Husky 换成simple-git-hooks或lefthook——超出范围迁移目标是「只换包管理器其他不变」。仓库佐证当前 根 package.json 中prepare: husky仍然存在且承担同一职责Husky 9 的新式调用形式印证了「生命周期不变、仅验证行为」的结论。相关验收在>- uses: pnpm/action-setupv4 # 版本来自 package.json 的 packageManager —— 无需 version: 输入。 - uses: actions/setup-nodev4 with: node-version: ${{ matrix.node }} cache: pnpm - run: pnpm install --frozen-lockfile缓存 key 自动从pnpm-lock.yaml推导。理由pnpm/action-setup是 GitHub Actions 中安装 pnpm 的推荐方式自动尊重packageManager锁定让 CI 与贡献者锁步actions/setup-node内置的 pnpm 缓存覆盖全局 store既有的cache: yarn只是换一词为cache: pnpm。被否方案用corepack enable手动装 pnpm可行但多一步且放弃了pnpm/action-setup的缓存层手写actions/cachekey脆弱且没必要。contracts/ci-commands.md 进一步补充了操作细节Setup pnpm步骤必须位于Setup node之前因为setup-node的cache: pnpm要求 pnpm 已在 PATH 上Linux runner 上 pnpm 全局 store 位于~/.local/share/pnpm/store/v3由setup-node负责缓存并给出了五个工作流的改写迭代顺序checks.yml→bundle-size.yml→tests.yml→experimental.yml/nightly.yml。10. 研究如何驱动落地从 research.md 到可执行任务research.md 的 8 项决策并非孤立结论而是被下游设计文档逐条消费data-model.md 把决策物化为 8 个「配置面实体」E1 lockfile、E2 workspace 清单、E3 包管理器锁、E4 安装/hoist 配置、E5 CI 缓存、E6 Husky 接线、E7 下游 fixture、E8 文档面每个实体都定义了前置状态、后置状态与验证规则并明确「不存在有效中间态」——迁移要么在严格隔离下干净安装要么还需要更多 workspace 声明contracts/developer-commands.md 与 contracts/ci-commands.md 分别固化开发者与 CI 两侧的命令契约「迁移后仍出现 yarn 形式即视为缺陷」quickstart.md 把 R-007/R-008 的成果转写为贡献者 onboarding 文本corepack enable→pnpm install→ 日常命令表并专门解释了严格隔离的行为差异遇到Cannot find module foo应声明依赖而非加 hoist 规则tasks.md 再将其拆为 T001–T065 的依赖序任务图其中 Phase 2 的「发现性安装 并行幽灵依赖修复 收敛重装」正是 R-006 方法论的直接执行。11. 小结可复制的迁移研究清单以 react-spring 这次 Yarn 3 → pnpm 迁移为样本一次结构型包管理器迁移的研究阶段需要回答并落档的问题可以归纳为大版本与锁定选哪个 pnpm 大版本用什么机制packageManager Corepack保证贡献者与 CI 收敛workspace 清单新清单的 glob 形态顺手清理失效条目构建脚本策略用onlyBuiltDependencies显式白名单应对 pnpm 9 的默认脚本阻断且接受「起始清单 → 安装告警 → 收敛」的迭代过程当前 package.json 的最终清单就是这一过程的产物生命周期钩子确认prepareHusky等标准 npm 生命周期在 pnpm 下等价执行无需迁移下游消费面CI 中的消费者 fixture 独立于主仓选型选最低公分母npm降低 CI 面幽灵依赖暴露面列出每个 workspace 的疑似缺失声明把首次安装当发现步骤以定向修复替代全局 hoist命令映射逐条给出 yarn → pnpm 的等价命令并固化为契约文档CI 缓存pnpm/action-setupcache: pnpm的标准块以及步骤顺序约束pnpm 先于 node setup。这套「决策 理由 被否方案 仓库佐证」的研究记录结构配合 spec.md 的验收场景与 quickstart.md 的验证流水线构成了一个可被其他 monorepo 直接套用的迁移研究模板。【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考