react-spring 从 Yarn 3 Berry 迁移到 pnpm:一份 65 项任务清单的拆解、执行策略与仓库落地证据
【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring
react-spring 仓库在specs/001-migrate-to-pnpm/目录下完整保留了一次典型的 Monorepo 包管理器迁移工程档案:以 tasks.md 为执行主体,配套 plan.md、spec.md、research.md、quickstart.md 以及两份命令契约文档。这篇技术文章以 tasks.md 的 8 个阶段、65 项任务(T001–T065)为骨架,逐阶段解读其任务编排逻辑、依赖关系与验证策略,并用当前仓库中已经落地的配置文件、CI 工作流与基线审计数据印证迁移的最终状态。
读完本文,你可以掌握:如何用"基线采集 → 发现式安装 → 幽灵依赖修复 → 按用户故事验证 → 抛光收尾"的方法论组织一次包管理器迁移;pnpm 严格隔离模式下pnpm-workspace.yaml、packageManager锁定、pnpm.onlyBuiltDependencies、.npmrc的最小化配置如何落地;以及如何用pnpm pack产物对比来证明发布物与迁移前等价。
一、迁移背景与工程档案结构
react-spring 原使用 Yarn 3 Berry(yarn@3.8.7,nodeLinker: node-modules),即通过"扁平 hoist 树"模拟传统 node_modules 布局。plan.md 给出的迁移目标概括为:
- 用 pnpm 严格隔离的
node_modules(pnpm 默认布局)替换 Yarn 的 hoisted 布局,让每个 workspace 只能看到自己声明的依赖; - 工作区清单从根
package.json的workspaces字段迁移到pnpm-workspace.yaml; - 重写所有与 yarn 耦合的根脚本、5 个 GitHub Actions 工作流和 5 个下游消费侧 fixture;
- 严格隔离暴露出的"幽灵依赖"(phantom dependencies)必须在迁移 PR 内补齐声明;
- 已发布包的打包内容必须与迁移前保持等价(对应 spec 中的 FR-015 / SC-005)。
spec.md 定义了 4 个用户故事(US1 贡献者本地流程、US2 CI 全绿、US3 changesets 发布流程、US4 parallax E2E)和 7 条可量化成功标准(SC-001 至 SC-007),tasks.md 就是围绕这些故事与标准展开的任务分解。tasks.md 开头的组织约定值得注意:
- 任务格式为
[ID] [P?] [Story] 描述,[P]表示"操作不同文件、不依赖未完成任务,可并行执行"; - 每项任务必须指明确切的文件路径或目录;
- 该迁移的"测试"就是既有测试套件本身(当时的 Jest 单测、
tsc --noEmit、Cypress E2E)——不新增测试文件,验证任务直接运行既有套件。
说明:tasks.md 的日期上下文是 2026-05 的仓库状态(spec 中记录 Node 22.15.0、CI 矩阵 Node 18/20、Jest + Cypress 测试栈)。当前仓库已在此之后继续演进(如 specs/002-vitest-browser-migration/ 将测试栈换成 Vitest browser 模式、specs/003-remix-to-react-router-7/ 将文档站从 Remix 迁移到 React Router 7),本文涉及差异处会分别标注"迁移时点"与"当前仓库"。
二、任务总览:8 个阶段与执行顺序
tasks.md 将 65 项任务编排为 8 个阶段,其中阶段 0–2 是跨故事的前置条件,阶段 3–6 一一对应 US1–US4,阶段 7 是收尾:
| 阶段 | 任务区间 | 定位 | 关键产出 |
|---|---|---|---|
| Phase 0 基线采集 | T001 | 迁移前在干净next分支记录 yarn 侧时间/产物基线 | baseline.md+ 各 workspace 的.tgz对照包 |
| Phase 1 Setup | T002–T004 | 引入 pnpm 配置面,与 yarn 配置并存 | pnpm-workspace.yaml、.npmrc、根package.json改造 |
| Phase 2 Foundational | T005–T027 | 发现式安装、幽灵依赖修复、清除 yarn 残留 | pnpm-lock.yaml、删除yarn.lock/.yarnrc.yml/.yarn/ |
| Phase 3 US1 贡献者流程 | T028–T040 | 全新克隆可 install/build/test/lint/dev | 重写后的根脚本与文档 |
| Phase 4 US2 CI | T041–T055 | 5 个工作流 + 5 个 publish-ci fixture 迁 pnpm/npm | 全绿 CI |
| Phase 5 US3 发布 | T056–T059 | changesets 流程 dry-run + pack 等价性 | 每个发布包 PASS/DIFF 记录 |
| Phase 6 US4 E2E | T060 | parallax E2E 套件跑通 | E2E green |
| Phase 7 Polish | T061–T065 | 文档清扫、grep 断尾、warm-cache 验证、基线脚手架拆除 | 审计记录保留,临时产物删除 |
tasks.md 的"Dependencies & Execution Order"一节给出了一张精确的依赖图:Phase 0 必须最先完成且不可并行;Phase 2 中 T005(首次安装 + 发现问题)必须先完成,T006–T021 的幽灵依赖修复可全部并行,T022 再安装、T023 提交 lockfile,T024–T027 清理任务可并行;US1 与 US2 互为独立可并行,US3 依赖 US1 和 Phase 0 的基线包,US4 只依赖 Phase 2;Phase 7 依赖全部完成,其中 T064 还要求 CI 缓存积累至少 3 次运行。
执行策略部分给出了三种走法:MVP first(只做到 US1 就停下来验证,此时本地开发已完整迁移、CI 还在 yarn,diff 依然可评审)、增量交付(Phase 0 → US1 开 draft PR → US2 → US3 → US4 → Polish,squash 合并)、并行团队策略(坦承这种强耦合迁移基本是单作者工作,唯一值得拆分的并行点是 T006–T021 的幽灵依赖修复和 T049–T053 五个同构的 fixture 迁移)。
三、Phase 0:先留下"可证伪的对照组"(T001)
T001 是整个迁移中约束最强的一条任务,文档原文用CRITICAL标注:必须在任何 pnpm 改动触碰仓库之前、在一份干净的next分支检出上(无node_modules、无.pnpm-store)完成,否则 SC-001/002/003/005 这些时间/等价性标准将"不可证伪"(unfalsifiable)。它要求采集:
- 冷/热 yarn install 耗时(
time yarn install --immutable,删node_modules后重测); yarn build-ci、yarn test:ts、yarn test:unit的耗时;- 对每一个对外发布的 workspace(
packages/与targets/下package.json不含private: true的目录)执行yarn pack,把 tarball 存入specs/001-migrate-to-pnpm/baseline/<workspace>.tgz,并把解包文件列表与name/version/exports/main/module/types/files字段写入baseline.md; - 这些基线产物随迁移分支提交,作为迁移后的对照物,最终在 T065 删除。
当前仓库中保留着这一阶段的审计产物:baseline/_baseline.json 记录了迁移时点 12 个发布 workspace 的完整 pack 数据——每个包的 11–13 个文件清单、关键package.json字段、tarball 字节数与 pack 耗时(例如@react-spring/core为 12 个文件、119760 字节、约 238ms),这正对应 T001 要求的"文件列表 + 关键字段"基线。这个 JSON 文件本身就是 T059 pack 等价性对比的数据来源。
四、Phase 1:pnpm 配置面的最小化落地(T002–T004)
Phase 1 的目标是"双轨并存":引入 pnpm 配置但不拆除 yarn 配置(拆除留给 Phase 2),三个任务互不依赖、可并行。
T002:pnpm-workspace.yaml只写"活着的"四个 glob
T002 要求根目录创建pnpm-workspace.yaml,内容严格取 research.md R-002 确认的四个存活 glob:packages/*、targets/*、demo、docs,并明确不要包含packages/parallax/@react-spring/parallax-demo——该目录在磁盘上已不存在,是 Yarn Berry 时代容忍下来的"死条目",而 pnpm 会对缺失的 workspace 路径告警/报错,顺势清掉。当前仓库的 pnpm-workspace.yaml 与任务描述完全一致:
packages: - 'packages/*' - 'targets/*' - 'demo' - 'docs'T003:根package.json的三处改动
T003 规定:(a) 整段移除workspaces字段;(b)packageManager从yarn@3.8.7换成当时最新的 pnpm 9.x;(c) 新增pnpm.onlyBuiltDependencies数组,种子列表为["@swc/core","cypress","esbuild","@remix-run/dev","@parcel/watcher","core-js","core-js-pure"]。同时刻意不动任何脚本体和 devDependencies——脚本重写在 Phase 3 的 T029/T030。
这里的设计依据来自 research.md R-001/R-003:packageManager字段 + Corepack 是 pnpm 的标准锁定路径,与 Yarn 3 当时用packageManager+.yarn/releases/的约定对等,能消除"lockfile 漂移"类 CI 抖动;而 pnpm 9 默认拦截依赖的 postinstall 构建脚本,必须显式放行@swc/core、esbuild等需要本地构建的原生包,否则"装上了但跳过构建步骤",运行时才会爆。
当前 package.json 的状态印证了这一设计:
"packageManager": "pnpm@9.15.9", "pnpm": { "onlyBuiltDependencies": [ "@parcel/watcher", "@swc/core", "core-js", "core-js-pure", "es5-ext", "esbuild" ] }可见放行清单在迁移完成后又随依赖栈演进收敛过(cypress/@remix-run/dev已随测试栈与文档栈的后续迁移退出仓库,新增了es5-ext)——这与 tasks.md Notes 中"最终列表由安装时的 ignored-build-script 告警决定"的说明一致。
T004:.npmrc只放 registry,坚守严格隔离
T004 要求 .npmrc只包含registry 锁定一行,且明确禁止预置node-linker=hoisted或任何public-hoist-pattern条目——spec 的"Resolution mode"一节认为,把 hoist 垫片搬过来"等于把现有技术债锁死,违背迁移初衷"。只有当 T005 发现确实无法修复的个案时才允许补 hoist 规则。当前仓库的.npmrc内容就是一行:
registry=https://registry.npmjs.org/严格隔离策略一直保留到了今天的开发者指引 CLAUDE.md:"node_modules是非 hoist 的,workspace 只能 import 自己package.json里声明的包;遇到Cannot find module 'foo',去补依赖声明,不要加 hoist 规则。"
五、Phase 2:发现式安装与幽灵依赖修复(T005–T027)
这是整个迁移中"工程判断密度最高"的阶段,tasks.md 同样以CRITICAL标注:在 Phase 2 完成前,任何用户故事工作都不允许开始。
2a 发现式安装(T005)
T005 的动作很朴素:启用 Corepack 后跑第一次pnpm install,完整捕获所有输出——每一条 "Module not found"、每一条 "An import-name-required dep is missing" 警告、每一条 "ignored build script" 提示。这一步被明确定性为 discovery pass(发现趟),后续 T006–T021 都是对它的响应。research.md R-006 给出了预期暴露面的预判表:targets/three可能缺@react-three/fiberpeer 声明、targets/konva/zdog缺渲染库 peer、packages/core需确认reactpeer、docs需确认 Remix dev 依赖等。
2b 幽灵依赖修复(T006–T021,全部 [P])
T006–T021 共 16 项任务,逐一对应一个 workspace 的package.json(core、animated、shared、rafz、types、parallax、mock-raf、eslint-config、react-spring 伞包、targets 下的 web/native/three/konva/zdog、demo、docs)。tasks.md 对此有一个很值得借鉴的规范:如果 T005 对某 workspace 报告零问题,任务也要标记完成并在 PR 里写明 "no changes required"——Notes 一节强调"确认过没有缺失声明"本身就是验证结果,而不是静默跳过。这避免了任务清单在评审时被误读为"没做"。
每项任务的判断流程固定为:读该 workspace 当前package.json→ 与 T005 的错误/警告输出交叉比对 → 把缺失项补进dependencies或peerDependencies。docs 那条(T021)额外要求验证@remix-run/dev的 postinstall 在 pnpm 下仍会触发,因为根目录有一个"postinstall": "remix setup node"依赖它。
2c 收口 lockfile 与清理(T022–T027)
- T022重跑
pnpm install并迭代 T006–T021,直到安装零ERR_PNPM_*错误、且没有未解释的 "ignored build script" 警告(只放行真正需要构建的依赖); - T023提交生成的
pnpm-lock.yaml; - T024–T026删除
yarn.lock、.yarnrc.yml、整个.yarn/目录(releases/plugins/cache)——对应 spec 的 FR-009,要求所有 Yarn 3 Berry 专属产物在迁移提交中移除; - T027更新
.gitignore:移除.yarn/*、!.yarn/patches等 Berry 模式,保留标准 pnpm 模式(node_modules)。
Phase 2 的 Checkpoint 验收语写得很具体:严格隔离的pnpm-lock.yaml已存在、无 yarn 残留、且能从干净状态跑通pnpm install --frozen-lockfile。当前仓库中pnpm-lock.yaml存在于根目录,而yarn.lock、.yarnrc.yml、.yarn/均已不存在,说明该 Checkpoint 已达成。
六、Phase 3:US1 贡献者流程迁移(T028–T040)
US1 的独立测试定义就是验收标准本身:在全新克隆上依次执行pnpm install --frozen-lockfile、pnpm build、pnpm test:ts、pnpm test:unit、pnpm lint、pnpm prettier:check,全部成功,且 test commit 时 pre-commit hooks 正常触发。
脚本体重写:T028–T030
三个任务分别处理根package.json中三组脚本体,重写依据是 contracts/developer-commands.md 的命令映射契约。该契约还立了一条不变量:脚本名不许变,只改脚本体——有肌肉记忆的贡献者照常工作。核心映射包括:
| 动作 | 迁移前 | 迁移后 |
|---|---|---|
| 锁定安装 | yarn install --immutable | pnpm install --frozen-lockfile |
| 指定 workspace 跑脚本 | yarn workspace <name> <cmd> | pnpm --filter <name> <cmd> |
| 根目录加依赖 | yarn add <pkg> -W | pnpm add -w <pkg> |
| 查依赖为什么被装 | yarn why <pkg> | pnpm why <pkg> |
需要重写脚本体的条目及其前后形态(引自同一契约文档):
| 脚本 | 迁移前脚本体 | 迁移后脚本体 |
|---|---|---|
docs:dev | yarn workspace @react-spring/docs dev | pnpm --filter @react-spring/docs dev |
demo:dev | yarn workspace @react-spring/demo dev | pnpm --filter @react-spring/demo dev |
test | yarn test:ts && yarn test:unit && yarn test:e2e | pnpm test:ts && pnpm test:unit && pnpm test:e2e |
test:e2e | start-server-and-test 'yarn vite serve packages/parallax/test --host' … 'yarn cypress run' | start-server-and-test 'pnpm vite serve packages/parallax/test --host' … 'pnpm cypress run' |
release | yarn clean && yarn && yarn build && … | pnpm clean && pnpm install && pnpm build && … |
当前 package.json 中可以看到这些脚本体的 pnpm 形态已经落地,例如docs:dev为pnpm --filter @react-spring/docs dev、release为pnpm clean && pnpm install && pnpm build && pnpm changeset publish --no-git-tag(测试栈换成 Vitest 后,test:ts/test:unit步骤后来从 release 链上调整了位置,这是迁移完成后的后续演进)。
验证与计时任务:T031–T037
T031–T037 是一组"把验收场景跑一遍并留痕"的任务:干净克隆安装(US1 场景 1)、Husky hooks 触发验证(T032 要求真实造一个 noop commit 确认 pre-commit 的 Prettier 检查与 commit-msg 的 commitlint 都执行,然后git reset --soft HEAD~1回退,对应 FR-008)、time pnpm build/time pnpm test:ts/time pnpm test:unit三次计时并写入baseline.md对应小节(T033–T035,服务于 SC-001)、lint 与 prettier 干净确认(T036)、docs:dev与demo:dev双 dev server 启动并确认可服务内容(T037,验证完杀掉进程)。
Husky 的兼容性依据在 research.md R-004:prepare是标准 npm 生命周期钩子,pnpm 与 yarn 一视同仁地执行,husky install本身不依赖包管理器,唯一要做的是验证。当前根package.json中"prepare": "husky"(Husky 9 的简化调用形式)保留着这条链路。
文档同步:T038–T040
T038 要求把 README 的安装/使用说明替换为 quickstart.md 的内容;T039 要求更新 CLAUDE.md 中 "Stack" 一节的包管理器描述和 "Common commands" 表;T040([P])清扫docs/目录 markdown 中所有 yarn 引用。current 仓库的 CLAUDE.md 第一条 Stack 即为"Package manager: pnpm 9.15.9 with strict isolated node_modules (default). Pinned via packageManager … activated through Corepack",说明 T039 已完成。
七、Phase 4:US2 CI 全量迁移(T041–T055)
US2 的规模是"5 个工作流 + 5 个消费侧 fixture",重写依据是 contracts/ci-commands.md。
标准安装块:顺序即约束
契约文档给出了替换所有cache: 'yarn'+yarn install --immutable组合的标准块:
- name: Checkout repo uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v4 # 无需 version 输入 —— pnpm/action-setup 从 package.json 的 packageManager 读取 - name: Setup node ${{ matrix.node || '20' }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node || '20' }} cache: 'pnpm' - name: Install run: pnpm install --frozen-lockfile这里有一个容易踩的顺序陷阱被显式强调:Setup pnpm 必须排在 Setup node 之前,因为actions/setup-node的cache: 'pnpm'需要 pnpm 已在 PATH 上。缓存 key 自动从pnpm-lock.yaml的 SHA 派生,无需手写key:;pnpm 全局 store(Linux runner 上位于~/.local/share/pnpm/store/v3)由 setup-node 负责缓存。
逐工作流、逐步骤的重写映射
T041–T048 覆盖checks.yml(lint/prettier 步骤)、bundle-size.yml(yarn build --filter=!@react-spring/docs→pnpm build --filter=!@react-spring/docs)、tests.yml的 build / test-unit / test-types 三个 job(包括 test-types 中yarn add typescript@${{ matrix.ts }}→pnpm add -w typescript@${{ matrix.ts }}这种"往工作区装特定版本工具链"的写法)、experimental.yml与nightly.yml(均为 install 块 +build-ci的简单形态)。契约还规定了重读的推荐顺序:checks(面最小,先验证安装块)→ bundle-size → tests(面最大,含 paths-filter)→ experimental/nightly(模式照搬)。
两个特殊任务:
- T046(paths-filter):
tests.yml的 changes job 中把yarn.lock换成pnpm-lock.yaml、把.github/publish-ci/**/yarn.lock换成.github/publish-ci/**/package-lock.json,其余过滤条目(packages/**、targets/**、.github/workflows/*.yml等)不变。这是"换包管理器但别漏了触发条件"这类细节的典型代表。 - T049–T053(publish-ci fixture,全部 [P]):五个下游消费侧 fixture(cra5、next、vite、node-standard、node-esm)每个都是模拟终端消费者的独立小项目,research.md R-005 的决策是把它们迁到 npm 而不是 pnpm——fixture 里装的是构建 job 产出的
package.tgz,用哪个包管理器与 react-spring 正确性无关,而 npm 随 Node 自带、且每个 Node 版本都有,能减少 CI 中包管理器表面积。T054 则把test-published-artifactjob 的每一步改成 npm:npm uninstall @react-spring/web、npm install ./web/package.tgz …、npm info && npm ls、npm run build、npm test,它显式声明依赖 T049–T053(fixture 必须已经是 npm)。
当前仓库中.github/publish-ci/下每个 fixture 目录都已含package-lock.json(如 .github/publish-ci/next/package-lock.json),且.github/workflows/下bundle-size.yml、checks.yml、experimental.yml、nightly.yml、tests.yml等文件内容均含 pnpm 调用,印证 T041–T054 已落地。(仓库后续又新增了docs-deploy.yml、release.yml,cra5fixture 在后续清理中移除,均属迁移完成后的演进。)
T055 是 US2 的收尾验证:推送迁移分支、在 PR 上确认五个工作流全过,并把每个 job 的冷安装耗时记入baseline.md的 "CI cold install (pnpm)",对照 yarn 基线(SC-002)。
八、Phase 5:US3 发布流程与 pack 等价性证明(T056–T059)
US3 的独立测试是:pnpm changeset→pnpm vers→ 完整pnpm release(到实际 publish 前一步为止),版本正确 bump、构建与测试通过、每个发布 workspace 的 tarball 与迁移前基线一致。
- T056真实跑一次
pnpm changeset并加一个chorechangeset 记录迁移本身,验证交互流程能生成.changeset/<name>.md; - T057跑
pnpm vers验证所有版本锁定(version-locked)包 bump 到同一目标版本,然后git restore复原; - T058跑
pnpm release到changeset publish之前,验证clean → install → build → test:ts → test:unit链路连续成功; - T059是迁移的"硬证明"任务:对每个发布 workspace(
packages/、targets/下package.json不含private: true的目录)执行pnpm pack,解包后与 T001 产生的baseline/<workspace>.tgz逐一比对 (a) 文件列表 (b)name/version/exports/main/module/types/files字段。判定规则写得非常精确:生成元数据内部的空白与顺序差异可容忍,其他任何差异都是必须在合并前解决的缺陷,并逐 workspace 把 PASS/细节记录到baseline.md的 "Pack equivalence" 小节。
当前仓库的 baseline/_pack-diff.json 正是 T059 的机器化结果:12 个 workspace 中 11 个verdict: PASS(文件数完全一致、字段无 diff),唯一的DIFF出现在packages/parallax——pnpm 打包少了 1 个文件package/test/README.md(yarn 侧 13 个文件 vs pnpm 侧 12 个)。这类"一个测试目录的 README 是否该进包"的差异正是 T059 设计出来要人工裁决的粒度,也说明该机制真实地拦截到了细微的发布物差异。
九、Phase 6 与 Phase 7:E2E 收尾与抛光(T060–T065)
T060(US4)单任务收尾:从一次全新pnpm install出发跑pnpm test:e2e,确认 Vite dev server 在 3000 端口对packages/parallax/test起服务、Cypress 无头启动、parallax.cy.ts通过。tasks.md 特意解释了为什么 E2E 优先级只给 P3:当时 E2E 只在本地跑(CI job 被注释掉),失败不阻塞合并——但它覆盖的正是"更严格的模块解析可能悄悄引入的那类回归",所以必须保绿。(当前仓库中该 E2E 已由后续迁移改为 Vitest browser 模式,入口为tests/e2e/parallax.spec.tsx,对应根脚本pnpm test:e2e。)
Phase 7 的五项任务构成"长尾清扫 + 拆除脚手架":
- T061([P])清扫
packages/*/README.md与targets/*/README.md中的 yarn 引用; - T062全仓库
grep -rn "yarn " --include="*.md" --include="*.yml" --include="*.yaml" --include="*.json" --include="*.sh",逐一解决非历史性(changelog/releases)命中,另点名检查scripts/、.changeset/、根.eslintrc*/tsconfig*。这条服务 SC-007:"100% 的仓库级文档从yarn …更新为 pnpm 等价命令"; - T063最终端到端计时:删
node_modules后跑 quickstart 全流水线(install --frozen-lockfile → build-ci → test:ts → test:unit → package → test:e2e),每步计时并追加到baseline.md的 "End-to-end pipeline (pnpm)",断言冷安装+流水线总耗时 ≤ 迁移前同款的 110%(SC-001 的可执行版本); - T064等 PR 上至少 3 次 CI 运行后,检查
actions/setup-node@v4(cache: 'pnpm')的缓存命中率与 warm 安装耗时,对照 yarn warm 基线(SC-003);若不达标,要么调pnpm-lock.yaml缓存 key,要么在 PR 描述中如实记录并接受回归——不硬压数据; - T065合并前拆除基线脚手架:删除
specs/001-migrate-to-pnpm/baseline/下的对照 tarball(临时物),保留测量记录本身作为 SC-001/002/003/005 的审计痕迹。tasks.md Notes 一节把这条生命周期又强调了一遍:"tarballs are deleted; the measurements are the audit trail"。
十、Notes:任务清单背后的工程纪律
tasks.md 末尾的 Notes 是整份文档方法论的浓缩,值得单独提炼:
- [P] 的严格定义:不同文件、不依赖未完成工作,才允许并行;
- 幽灵依赖任务允许是 no-op:T006–T021 中若某 workspace 本来就声明齐全,任务照样存在并"确认无缺失"后完成——"确认过"不等于"跳过了";
- 按逻辑组提交:Phase 0 → 1 → 2 → 每个用户故事各一个提交点,使 git history 可以按任务粒度评审;
- 全程禁用
--no-verify:迁移本身也不能绕开 Husky hooks,否则 FR-008 的验证就是自我欺骗; - hoist 规则的最小化授权:如果 T022 暴露出一个真正无法修复的幽灵依赖(比如某第三方包 import 了未声明的 peer),允许在
.npmrc加定向的public-hoist-pattern[]=<exact-pkg>并内联写明原因,但明令禁止放宽成public-hoist-pattern[]=*——一条通配 hoist 等于宣告迁移失败。
十一、当前仓库的最终状态核对
把 tasks.md 的验收点与当前仓库实际文件对照,可以确认迁移已完整落地并持续演进:
| tasks.md 验收点 | 当前仓库证据 |
|---|---|
| T002 四个 glob 的 workspace 清单 | pnpm-workspace.yaml 逐行一致 |
T003packageManager锁定 +onlyBuiltDependencies | package.json:pnpm@9.15.9+ 6 项放行清单 |
T004.npmrc仅 registry、无 hoist 规则 | .npmrc 单行registry=https://registry.npmjs.org/ |
| T023–T027 lockfile 与 yarn 产物 | 根目录存在pnpm-lock.yaml;yarn.lock/.yarnrc.yml/.yarn/不存在 |
| T028–T030 脚本体 pnpm 化 | package.json 中docs:dev/demo:dev/test/release均为pnpm调用 |
| T039 CLAUDE.md 文档更新 | CLAUDE.md Stack 首条 + 完整 pnpm 命令表 + 严格隔离说明 |
| T041–T053 CI 与 fixture | .github/workflows/各 yml 含 pnpm 安装块;.github/publish-ci/各 fixture 持有package-lock.json |
| T001/T059 基线与 pack 等价审计 | baseline/_baseline.json、baseline/_pack-diff.json(11 PASS / 1 DIFF) |
需要说明的两点适用前提:其一,tasks.md 中引用的部分文件路径(如packages/eslint-config、targets/konva、targets/zdog、targets/native、packages/react-spring伞包、cra5fixture)是迁移时点的仓库结构,当前仓库已经历后续重构,这些目录已调整;其二,任务文档中的绝对路径(/Users/josh.ellis/code/react-spring/...)是作者本机检出位置,读者应理解为其指向本仓库对应相对路径。
十二、方法论小结
这份 tasks.md 给出的可复用范式可以概括为五步:
- 先造对照组(Phase 0):任何"等价性"声明,先有基线数据才有意义;基线产物随迁移分支提交、验证完成后拆除临时物但保留测量记录;
- 双轨过渡再收口(Phase 1→2):新旧配置短暂并存,用一次"发现式安装"把问题全部逼出水面,再按文件粒度并行修复、统一收口 lockfile、一次性清除旧残留;
- 以用户故事为单位验证(Phase 3–6):每个故事都有独立测试定义和 Checkpoint,MVP 只做 US1 也能形成可评审、可回退的中间态;
- 命令契约化:把"每个 yarn 命令对应什么 pnpm 命令"写成契约文档(contracts/developer-commands.md、contracts/ci-commands.md),脚本体、CI step、文档清扫全部对照执行,保证迁移不留"半 yarn 半 pnpm"的死角;
- 用产物而不是口述证明等价(T059/T063):
pnpm pack的文件列表 + 关键字段 diff、端到端流水线计时断言(≤110% 基线)、CI 缓存命中率观察,把"迁移没弄坏东西"变成可复跑的检查项。
对于任何维护 Turborepo/pnpm workspaces 的 TypeScript Monorepo 的仓库,这套"基线 → 发现 → 修复 → 故事化验证 → 审计留痕"的编排方式,以及"严格隔离优先、hoist 规则最小授权"的取舍原则,都是可以直接搬走的工程实践。
【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考