Backstage React 18 迁移实战:从依赖升级、TypeScript 报错治理到测试库迁移的完整路径

发布时间:2026/9/13 10:03:20
Backstage React 18 迁移实战:从依赖升级、TypeScript 报错治理到测试库迁移的完整路径 Backstage React 18 迁移实战从依赖升级、TypeScript 报错治理到测试库迁移的完整路径【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文基于 Backstage 官方教程 react18-migration.md 展开完整讲解如何把一个 Backstage 实例以及其中的前端插件从 React 17 升级到 React 18包括根package.json的resolutions配置、应用入口切换到react-dom/client新渲染 API、TypeScript 类型破坏的渐进式修复策略以及最棘手的testing-library/react测试库升级与测试代码改写模式。当前仓库本身已处于迁移完成状态文中每一步都会给出仓库内的真实文件作为佐证读者对照即可在自己的项目中复制同一套路径。为什么迁移以及迁移的基本前提Backstage 的核心库与插件同时兼容 React 17 到 React 18 的全部版本因此项目可以按自己的节奏迁移。官方仍鼓励尽早迁移原因有二一是跟上不断演进的生态系统二是 React 18 带来了性能提升其中在测试tests场景下的收益尤其明显。需要注意两个前提约束该教程已更新为包含移除 React 16 支持的步骤因为 React 16 已正式弃用deprecated。当前仓库中各前端插件的peerDependencies恰好印证了这一点例如 auth-react/package.jsonpeerDependencies: { types/react: ^17.0.0 || ^18.0.0, react: ^17.0.0 || ^18.0.0, react-dom: ^17.0.0 || ^18.0.0, ... }下限已经收窄到^17.0.0不再包含^16.x。官方明确警告对大型项目来说这是一次难以拆分成小步渐进的迁移最难的环节在于测试库testing-library/react的切换——新版测试库不支持 React 17旧版又不支持 React 18两个大版本之间没有重叠的兼容区间因此测试部分必须“一次性”完成。升级 Backstage 实例应用侧三步走第 1 步更新根 package.json 的 resolutions修改仓库根目录 package.json 中的resolutions段把types/react和types/react-dom指到 18 版resolutions: { // 迁移前 types/react: ^17, types/react-dom: ^17, // 迁移后 types/react: ^18, types/react-dom: ^18, }当前仓库的 package.json 中已经是迁移后的最终形态resolutions: { types/react: ^18.0.0, types/react-dom: ^18.0.0, ... }这一步的作用是通过 Yarn 的 resolutions 机制强制整个 monorepo 中所有工作区共享同一份 React 18 类型定义避免类型版本分裂。第 2 步更新 packages/app 的运行时依赖把packages/app/package.json中dependencies里的react与react-dom升级到 18dependencies: { ... // 迁移前 react: ^17.0.2, react-dom: ^17.0.2, // 迁移后 react: ^18.0.2, react-dom: ^18.0.2, ... }当前仓库 packages/app/package.json 即为迁移后状态react: ^18.0.2, react-dom: ^18.0.2,第 3 步应用入口切换到 react-dom/client 渲染 APIReact 18 引入的并发渲染基于新的根 API。官方教程给出的packages/app/src/index.tsx改动如下import backstage/cli/asset-types; // 迁移前 // import ReactDOM from react-dom; // import App from ./App; // ReactDOM.render(App /, document.getElementById(root)); // 迁移后 import ReactDOM from react-dom/client; import App from ./App; ReactDOM.createRoot(document.getElementById(root)!).render(App /);当前仓库 packages/app/src/index.tsx 的现行代码正是这一写法document.getElementById(root)!中的!是非空断言因为createRoot要求传入非空节点import backstage/cli/asset-types; import ReactDOM from react-dom/client; import app from ./App; import backstage/ui/css/styles.css; ReactDOM.createRoot(document.getElementById(root)!).render(app);仓库中遗留的旧版应用入口 packages/app-legacy/src/index.tsx 也已同步使用react-dom/client的createRootAPI说明迁移是覆盖全部前端入口的而非只改主应用。完成以上更新后应用与插件应像之前一样正常工作只是底层运行在 React 18 之上。教程特别提醒修改package.json之后务必重新生成 lockfile本仓库使用 Yarn 4见根 package.json 的packageManager字段声明。升级前端插件插件侧两步走对于自研或第三方 Backstage 前端插件迁移集中在其package.json的两个字段devDependencies与peerDependencies需要同时调整react、react-dom、types/react三项devDependencies: { ... // 迁移前 // types/react: ^16.13.1 || ^17.0.0, // react: ^16.13.1 || ^17.0.0, // react-dom: ^16.13.1 || ^17.0.0, // 迁移后 types/react: ^17.0.0 || ^18.0.0, react: ^17.0.0 || ^18.0.0, react-dom: ^17.0.0 || ^18.0.0, ... }peerDependencies做完全相同的替换。当前仓库 plugins/auth-react/package.json 就是一个标准样例devDependencies中使用react: ^18.0.2、types/react: ^18.0.0开发期锁定 18 进行类型检查与测试而peerDependencies保持^17.0.0 || ^18.0.0的宽区间让消费者自行决定运行在哪个大版本上——这正是“核心库双版本兼容”承诺在插件层的落地方式。TypeScript 报错治理可以渐进也可以与版本升级解耦升级类型定义到 18 后项目里大概率会出现大量 TypeScript 类型错误这来自 DefinitelyTyped 对 React 18 类型的一次性破坏性变更例如JSX.Element到ReactElement的收紧、事件处理器泛型签名变化等可查阅 DefinitelyTyped 引入这些变更的 Pull Request 获取完整清单官方同时提供了一个 codemod 辅助迁移。评估影响面使用仓库根 package.json 中定义的命令yarn tsc:full # 等价于 backstage-cli repo clean tsc --skipLibCheck false --incremental false这里有一个非常实用的策略类型修复可以先于版本升级完成。即便项目仍然运行在 React 17这些新类型错误也都可以被修复。如果报错数量庞大可以按批次提交修复——每一批都不带第 1 步的版本号变更先合入主分支。这样可以在不真正切换到 React 18 的前提下渐进地清洗整个项目的类型等所有类型破坏都清零后再做版本号切换与测试迁移这两步把高风险操作压缩到最后。迁移测试没有重叠版本区间必须一步到位当应用跑通且类型错误清零后运行测试时可能发现大量用例失败。原因很直接当时项目使用的testing-library/react旧版本不支持 React 18而支持 React 18 的新版本又不再支持 React 17。两个主版本之间没有兼容重叠这就是官方所说“测试迁移只能一次性完成”的根本原因。依赖升级将testing-library/react至少升到 v13支持 React 18 的起点但官方建议顺带升到 v14 乃至更高因为附加的破坏性变更影响很小。当前仓库各插件已推进到 v16例如 packages/app/package.json、plugins/catalog-react/package.jsontesting-library/dom: ^10.0.0, testing-library/jest-dom: ^6.0.0, testing-library/react: ^16.0.0, testing-library/user-event: ^14.0.0同时删除testing-library/react-hooks依赖renderHook等能力已并入testing-library/react本体。本仓库基本完成这一清理仅剩 packages/core-app-api/package.json 中残留一处testing-library/react-hooks: ^8.0.0的 devDependency 声明可作为迁移收尾时排查残留依赖的参照。对大量package.json做批量更新时官方给出了两条查找替换的正则查找Find替换Replacetesting-library/react: .*testing-library/react: ^14.0.0testing-library/react-hooks: .*,?空即整行删除测试代码改写七个高频模式安装新依赖后测试改写是一个相当机械化的过程。推荐做法是先把全部测试跑一遍找出失败项然后逐个测试文件处理。官方在迁移 Backstage 自身时总结了以下模式大量act(...)调用可以直接删除。act语义已被内建到waitFor、.findBy*、testing-library/user-event等测试工具中。等待元素出现用.findBy*例如await screen.findByRole(button, { name: Login })。等待其他状态变化或多个元素用waitFor(...)。用户交互统一改用testing-library/user-event而不是旧的fireEvent直打事件。renderHookAPI 有多处变化不再返回waitForValueToChange与waitForNextUpdate改用waitFor代替出错时直接 throw而不是把错误放进返回值不再把initialProps透传给wrapper需要按官方 API 文档给出的 workaround 处理。“等待 mock 函数被调用 → 断言渲染状态已更新”这一写法不再可靠需要显式等待状态收敛。组件在用户输入后常常不会立即更新更常见的是需要waitFor或其他等待工具等到期望状态出现再断言。仓库中的真实范例迁移后的 renderHook 测试plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx 完整体现了上述模式从testing-library/react导入renderHook与waitFor注意是react包而非react-hooks包用wrapper注入TestApiProvider提供 API mock所有异步状态断言都包在waitFor中import { renderHook, waitFor } from testing-library/react; const { result } renderHook( () useCookieAuthRefresh({ pluginId: techdocs }), { wrapper: ({ children }) ( TestApiProvider apis{[...]}{children}/TestApiProvider ), }, ); expect(result.current).toStrictEqual({ status: loading }); await waitFor(() expect(result.current).toStrictEqual({ status: error, error, retry: expect.any(Function), }), );这里的waitFor(() expect(...))就是模式 6/7 的标准落地不依赖渲染与断言之间的隐式时序而是显式轮询直到期望状态成立。类似的迁移后测试还可以参考 plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.tsx、plugins/catalog/src/components/CatalogExportButton/file-download/useStreamingExport.test.tsx 等文件。收尾核对清单迁移完成后可以用当前仓库的实际状态作为参照逐项自检检查项仓库内参照期望状态根resolutions的类型版本package.jsontypes/react、types/react-dom均为^18.0.0应用运行时依赖packages/app/package.jsonreact、react-dom为^18.0.2入口渲染 APIpackages/app/src/index.tsx使用react-dom/client的createRoot插件 peer 区间plugins/auth-react/package.json^17.0.0 \|\| ^18.0.0React 16 已移除测试库版本packages/app/package.jsontesting-library/react为 v16≥v13react-hooks残留各包package.json仅 packages/core-app-api/package.json 残留一处声明无源码引用lockfileyarn.lock已随依赖变更重新生成最后如果整个项目无法一次性迁移官方给出的折中方案是给后续要迁移的个别插件单独添加react/react-domv17 的devDependencies让其余部分先行推进。需要说明的是该方案官方并未在实践中验证过属于社区探索路径。整体迁移顺序可以概括为一句话类型先清洗可渐进→ 版本号切换实例三步→ 测试库一步到位依赖升级 按模式批量改写测试三步按此顺序推进可以把 React 18 迁移的风险拆到最小。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考