react-spring 从 Yarn 3 Berry 迁移到 pnpm:一份 65 项任务清单的拆解、执行策略与仓库落地证据
2026/9/19 9:46:57 网站建设 项目流程

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.yamlpackageManager锁定、pnpm.onlyBuiltDependencies.npmrc的最小化配置如何落地;以及如何用pnpm pack产物对比来证明发布物与迁移前等价。

一、迁移背景与工程档案结构

react-spring 原使用 Yarn 3 Berry(yarn@3.8.7nodeLinker: node-modules),即通过"扁平 hoist 树"模拟传统 node_modules 布局。plan.md 给出的迁移目标概括为:

  • 用 pnpm 严格隔离的node_modules(pnpm 默认布局)替换 Yarn 的 hoisted 布局,让每个 workspace 只能看到自己声明的依赖;
  • 工作区清单从根package.jsonworkspaces字段迁移到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 SetupT002–T004引入 pnpm 配置面,与 yarn 配置并存pnpm-workspace.yaml.npmrc、根package.json改造
Phase 2 FoundationalT005–T027发现式安装、幽灵依赖修复、清除 yarn 残留pnpm-lock.yaml、删除yarn.lock/.yarnrc.yml/.yarn/
Phase 3 US1 贡献者流程T028–T040全新克隆可 install/build/test/lint/dev重写后的根脚本与文档
Phase 4 US2 CIT041–T0555 个工作流 + 5 个 publish-ci fixture 迁 pnpm/npm全绿 CI
Phase 5 US3 发布T056–T059changesets 流程 dry-run + pack 等价性每个发布包 PASS/DIFF 记录
Phase 6 US4 E2ET060parallax E2E 套件跑通E2E green
Phase 7 PolishT061–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-ciyarn test:tsyarn 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/*demodocs,并明确不要包含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)packageManageryarn@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/coreesbuild等需要本地构建的原生包,否则"装上了但跳过构建步骤",运行时才会爆。

当前 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 的错误/警告输出交叉比对 → 把缺失项补进dependenciespeerDependencies。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-lockfilepnpm buildpnpm test:tspnpm test:unitpnpm lintpnpm prettier:check,全部成功,且 test commit 时 pre-commit hooks 正常触发。

脚本体重写:T028–T030

三个任务分别处理根package.json中三组脚本体,重写依据是 contracts/developer-commands.md 的命令映射契约。该契约还立了一条不变量:脚本名不许变,只改脚本体——有肌肉记忆的贡献者照常工作。核心映射包括:

动作迁移前迁移后
锁定安装yarn install --immutablepnpm install --frozen-lockfile
指定 workspace 跑脚本yarn workspace <name> <cmd>pnpm --filter <name> <cmd>
根目录加依赖yarn add <pkg> -Wpnpm add -w <pkg>
查依赖为什么被装yarn why <pkg>pnpm why <pkg>

需要重写脚本体的条目及其前后形态(引自同一契约文档):

脚本迁移前脚本体迁移后脚本体
docs:devyarn workspace @react-spring/docs devpnpm --filter @react-spring/docs dev
demo:devyarn workspace @react-spring/demo devpnpm --filter @react-spring/demo dev
testyarn test:ts && yarn test:unit && yarn test:e2epnpm test:ts && pnpm test:unit && pnpm test:e2e
test:e2estart-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'
releaseyarn clean && yarn && yarn build && …pnpm clean && pnpm install && pnpm build && …

当前 package.json 中可以看到这些脚本体的 pnpm 形态已经落地,例如docs:devpnpm --filter @react-spring/docs devreleasepnpm 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:devdemo: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-nodecache: '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.ymlyarn build --filter=!@react-spring/docspnpm 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.ymlnightly.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/webnpm install ./web/package.tgz …npm info && npm lsnpm run buildnpm test,它显式声明依赖 T049–T053(fixture 必须已经是 npm)。

当前仓库中.github/publish-ci/下每个 fixture 目录都已含package-lock.json(如 .github/publish-ci/next/package-lock.json),且.github/workflows/bundle-size.ymlchecks.ymlexperimental.ymlnightly.ymltests.yml等文件内容均含 pnpm 调用,印证 T041–T054 已落地。(仓库后续又新增了docs-deploy.ymlrelease.ymlcra5fixture 在后续清理中移除,均属迁移完成后的演进。)

T055 是 US2 的收尾验证:推送迁移分支、在 PR 上确认五个工作流全过,并把每个 job 的冷安装耗时记入baseline.md的 "CI cold install (pnpm)",对照 yarn 基线(SC-002)。

八、Phase 5:US3 发布流程与 pack 等价性证明(T056–T059)

US3 的独立测试是:pnpm changesetpnpm vers→ 完整pnpm release(到实际 publish 前一步为止),版本正确 bump、构建与测试通过、每个发布 workspace 的 tarball 与迁移前基线一致。

  • T056真实跑一次pnpm changeset并加一个chorechangeset 记录迁移本身,验证交互流程能生成.changeset/<name>.md
  • T057pnpm vers验证所有版本锁定(version-locked)包 bump 到同一目标版本,然后git restore复原;
  • T058pnpm releasechangeset 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.mdtargets/*/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@v4cache: '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 是整份文档方法论的浓缩,值得单独提炼:

  1. [P] 的严格定义:不同文件、不依赖未完成工作,才允许并行;
  2. 幽灵依赖任务允许是 no-op:T006–T021 中若某 workspace 本来就声明齐全,任务照样存在并"确认无缺失"后完成——"确认过"不等于"跳过了";
  3. 按逻辑组提交:Phase 0 → 1 → 2 → 每个用户故事各一个提交点,使 git history 可以按任务粒度评审;
  4. 全程禁用--no-verify:迁移本身也不能绕开 Husky hooks,否则 FR-008 的验证就是自我欺骗;
  5. 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锁定 +onlyBuiltDependenciespackage.json:pnpm@9.15.9+ 6 项放行清单
T004.npmrc仅 registry、无 hoist 规则.npmrc 单行registry=https://registry.npmjs.org/
T023–T027 lockfile 与 yarn 产物根目录存在pnpm-lock.yamlyarn.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-configtargets/konvatargets/zdogtargets/nativepackages/react-spring伞包、cra5fixture)是迁移时点的仓库结构,当前仓库已经历后续重构,这些目录已调整;其二,任务文档中的绝对路径(/Users/josh.ellis/code/react-spring/...)是作者本机检出位置,读者应理解为其指向本仓库对应相对路径。

十二、方法论小结

这份 tasks.md 给出的可复用范式可以概括为五步:

  1. 先造对照组(Phase 0):任何"等价性"声明,先有基线数据才有意义;基线产物随迁移分支提交、验证完成后拆除临时物但保留测量记录;
  2. 双轨过渡再收口(Phase 1→2):新旧配置短暂并存,用一次"发现式安装"把问题全部逼出水面,再按文件粒度并行修复、统一收口 lockfile、一次性清除旧残留;
  3. 以用户故事为单位验证(Phase 3–6):每个故事都有独立测试定义和 Checkpoint,MVP 只做 US1 也能形成可评审、可回退的中间态;
  4. 命令契约化:把"每个 yarn 命令对应什么 pnpm 命令"写成契约文档(contracts/developer-commands.md、contracts/ci-commands.md),脚本体、CI step、文档清扫全部对照执行,保证迁移不留"半 yarn 半 pnpm"的死角;
  5. 用产物而不是口述证明等价(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),仅供参考

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

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

立即咨询