React Spectrum s1-to-s2 升级助手:基于 jscodeshift 将 v3 组件批量迁移到 Spectrum 2
【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum
React Spectrum 仓库内置了一个名为s1-to-s2 升级助手的 CLI 工具,位于 codemods 包。它通过 jscodeshift AST 转换,把使用 React Spectrum v3(S1)组件的存量代码批量升级到基于 Style Macro 的 Spectrum 2(S2),并自动处理导入改写、样式属性转换、图标映射等最繁琐的部分。读完本文,你将掌握该工具的完整命令行用法与参数含义,理解其从包安装、宏配置到 AST 转换的完整执行链路,并能按照仓库既有模式为自己的组件新增一个 codemod。
一、工具定位与快速开始
s1-to-s2 README 将其定义为 “A CLI tool for upgrading React Spectrum components to Spectrum 2”。在需要升级的项目目录下运行:
npx @react-spectrum/codemods s1-to-s2支持的全部命令行选项如下(与 README 的 Options 一节一一对应):
| 选项 | 说明 |
|---|---|
-c, --components <components> | 逗号分隔的待升级组件列表(例如Button,TableView)。不指定时,所有可用组件都会被升级 |
--path <path> | 执行 codemod 的目录路径,默认当前目录(.) |
-d, --dry | 干跑模式:不向磁盘写入任何改动,用于在正式应用前预览迁移结果 |
--agent | 非交互模式:跳过交互提示、包安装和宏配置步骤,适用于 CI 或 Agent 工具调用。注意该模式下@react-spectrum/s2必须已安装且可解析 |
从 CLI 入口 可以确认这些选项的解析方式:入口通过node:util的parseArgs解析参数,并按名称分发到四个内置 codemod(s1-to-s2、use-monopackages、use-subpaths、test-utils-rc-update)。若未指定 codemod 名称或名称未知,会打印可用列表并退出(入口文件)。入口同时为所有 codemod 注入了统一的 jscodeshift 默认值:parser: 'tsx'、ignorePattern: '**/node_modules/**'、extensions: 'js,jsx,mjs,cjs,ts,tsx',即默认覆盖全部 JS/TS 源文件并忽略node_modules。
二、执行流程:交互模式与 Agent 模式
s1-to-s2的实现入口是 s1_to_s2 函数。两种模式的差异在源码中非常清晰。
交互模式(默认)
默认流程按顺序执行四步:
- 欢迎与确认:用 boxen 打印欢迎框,说明工具将“安装
@react-spectrum/s2并配置打包器的 Style Macro 支持 → 升级当前目录组件 → 给出后续步骤”,并等待按回车继续; - 安装 S2 包:调用
installPackage('@react-spectrum/s2'); - 配置宏支持:调用
addMacroSupport()检测/安装 Style Macro 支持; - 执行转换并输出后续步骤:
await transform(options)后打印 Next Steps 框,内容包括:- 在应用入口组件中加入
import '@react-spectrum/s2/page.css';(与 v3 不同,S2 不再需要Provider); - 若宏支持未自动配置,提示在 Webpack/Next.js/Vite/Rollup/ESBuild 中配置
unplugin-parcel-macros(Parcel v2.12.0+ 原生支持宏); - 全局搜索
TODO(S2-upgrade)注释,手动处理 codemod 无法自动完成的剩余升级,并建议运行 Prettier/ESLint 清理格式。
- 在应用入口组件中加入
Agent 模式(--agent)
从 agent 模式分支 可以看到,--agent下直接调用transform(options),完全不执行包安装和宏配置,随后输出五步后续指引:确认@react-spectrum/s2已安装、按需配置打包器的宏支持、按需引入page.css、搜索TODO(S2-upgrade)、参考迁移指南。这解释了为什么 README 特别强调该模式下@react-spectrum/s2必须可解析——因为组件清单的推导依赖它(见下一节)。
转换的调用链
transform()本身很薄:它把 CLI 选项剥离后直接调用 jscodeshift 的 Runner,以 codemod.js 为转换入口,对--path指定的目录执行:
// packages/dev/codemods/src/s1-to-s2/src/transform.ts const transformPath = path.join(__dirname, 'codemods', 'codemod.js'); return await jscodeshift(transformPath, [filePath], jscodeshiftOptions);这也是为什么-d, --dry可以直接透传给 jscodeshift Runner——干跑能力由 jscodeshift 原生提供,而非工具自己实现。
三、升级哪些组件:从 S2 的 exports 推导可用组件清单
工具不会硬编码组件列表。getComponents() 的机制是:
- 先
require.resolve('@react-spectrum/s2'),定位其exports/index.ts(测试环境下直接使用包内index.ts); - 若解析不到已安装的包,则回退到 monorepo 工作区内的
@react-spectrum/s2/exports/index.ts,两者都不存在时抛出Could not resolve @react-spectrum/s2 source for codemods; - 用 Babel 解析该 index 文件,遍历所有
exportKind === 'value'的具名导出,收集组件名。
在这个基线集合之上,主转换文件 做了若干修正,这些细节直接影响升级行为:
- v3 中被
div替代的组件被显式加入:View、Flex、Grid、Well; - 被集合型专属 Item 替代的泛型组件:
Item、Section; - v3 的
Provider被移出清单(availableComponents.delete('Provider')),即不会去改写 v3 Provider 的导入; - 改名组件:
ContextualHelpTrigger→UnavailableMenuItemTrigger,通过renamedComponents映射处理; - 少数组件(
Accordion、Card、CardView、ActionBar)在skipped集合中——它们虽从 S2 导出,但尚未编写对应 codemod,导入暂不替换。
-c选项的“关联组件扩展”机制
单独指定组件时不能只看字面量。例如只升级Menu,其内部的Item/Section以及MenuTrigger等也必须一起处理。relatedComponentGroups 定义了这种关联关系,例如:
Menu关联ContextualHelpTrigger、MenuTrigger、SubmenuTrigger,且Item/Section仅在Menu作用域内转换;TableView关联Cell、Column、Row、TableBody、TableHeader;TooltipTrigger关联Tooltip;Tabs关联TabList、TabPanels。
getComponentSelection()会把显式组件展开为完整转换集合;对于未被显式指定的关联组件(如单独传-c Menu时自动带入的Item),会记录其“允许的父组件”,shouldTransformElement()随后通过path.findParent检查 JSX 父链,只转换位于允许父组件内部的元素——这避免了把无关的Item(比如TagGroup里的)误改。
四、转换都做了什么:导入改写、Style Macro 与图标映射
核心转换逻辑集中在 codemod.ts。它以 recast + Babel parser 解析每个文件(保证未改动部分格式不变),主要处理以下几类:
1. 组件导入统一指向@react-spectrum/s2
- 命中
@adobe/react-spectrum或任何@react-spectrum/*子包(@react-spectrum/s2除外)的ImportDeclaration会被记录;其中属于转换清单的命名导入、以及import * as RSP from ...的命名空间导入(RSP.Button这类 JSX 成员表达式)都会被收集; - 转换完成后,所有待导入组件被合并进一条
import {Button, ...} from '@react-spectrum/s2'(若文件中已有该导入则去重追加),原 v3 导入在被移除后如为空会整条删除; Item、Section等泛型导入若仍被引用(例如在ActionMenu等未转换的父组件内)会保留;- 动态
import('@adobe/react-spectrum')暂不自动处理,源码中直接标注为TODO(S2-upgrade): check this dynamic import(见 动态导入分支)。
2. v3 Style Props 迁移到 Style Macro
v3 的布局类样式属性(marginStart="size-100"、borderWidth="thin"、响应式对象值等)没有 S2 同名 API,需要改写成styles={style({...})}。这一步由 shared/styleProps 完成,转换成功后工具会在文件中插入:
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};若代码中使用了lightDark辅助函数,会一并导入。该导入使用with {type: 'macro'}属性(源码生成assert {type: "macro"}后统一替换为with语法)。若某个 style prop 无法自动转换,元素上会被标注TODO(S2-upgrade): Could not transform style prop automatically: <error>而不是让整个文件失败。
v3 到 S2 的值映射规则(如size-100→8、borderRadius="small"→'sm'、断点base/S/M/L→default/sm/md/lg)完整列在同目录的 UPGRADE.md 的 “Style props” 章节,可作为手动迁移时的对照表。
3. 图标与插画导入重映射
@spectrum-icons/workflow/*和@spectrum-icons/illustrations/*的默认导入会被重写到 S2 的等价路径:
- 图标:
@react-spectrum/s2/icons/<newName>,映射表为 iconMap; - 插画:
@react-spectrum/s2/illustrations/linear/<newName>,映射表为 illustrationMap。
若本地导入名恰好等于旧模块名且新名字在作用域内无冲突,引用点会同步改名。找不到 S2 等价物时,元素上标注TODO(S2-upgrade): A Spectrum 2 equivalent to '<name>' was not found. Please update this icon manually.。
4. 组件级专属转换(动态加载)
通用处理之后,元素转换循环 会按组件名动态require('./components/<Name>/transform')并调用其默认导出。转换目录不存在时静默跳过——这正是“每个组件一个 transform 文件、可增量扩展”的设计。
五、为组件新增一个 Codemod
README 的 “Adding a new codemod” 一节以Button为例给出了三步流程(路径相对于 s1-to-s2 codemod 根目录)。结合仓库现状,完整操作如下:
- 创建转换函数:新建
src/codemods/components/Button/transform.ts,导出default函数,签名为(path: NodePath<t.JSXElement>) => void(path指向单个 JSX 元素节点); - 实现转换逻辑:优先复用 shared/transforms 中的工具函数(
removeProp、updatePropName、updatePropNameAndValue、updatePropValueAndAddNewPropName、updateComponentIfPropPresent等); - 添加测试:在
__tests__/button.test.ts中为转换编写用例(仓库现有 50+ 个组件测试与对应 snapshot 文件均位于该目录,例如 button 测试)。
Button 的真实实现 展示了典型写法:
// variant="cta" → variant="accent" updatePropNameAndValue(path, { oldPropName: 'variant', oldPropValue: 'cta', newPropName: 'variant', newPropValue: 'accent' }); // variant="overBackground" → variant="primary" staticColor="white" updatePropValueAndAddNewPropName(path, { oldPropName: 'variant', oldPropValue: 'overBackground', newPropName: 'variant', newPropValue: 'primary', additionalPropName: 'staticColor', additionalPropValue: 'white' }); // style → fillStyle updatePropName(path, {oldPropName: 'style', newPropName: 'fillStyle'}); // 移除 isQuiet、elementType removeProp(path, {propName: 'isQuiet'}); removeProp(path, {propName: 'elementType'}); // 含 href 时 Button → LinkButton updateComponentIfPropPresent(path, {propName: 'href', newComponentName: 'LinkButton'});这些映射与 UPGRADE.md 中 “Button” 小节列出的手工迁移规则一致,说明 codemod 实现是迁移指南的代码化落地。
六、自动化辅助:包安装与 Style Macro 配置
交互模式下的两个自动化步骤各有明确的实现边界:
包安装(installPackage):
- 先检查当前目录是否有
package.json,没有则提示手动安装; - 依次探测
yarn.lock/package-lock.json/pnpm-lock.yaml来识别包管理器,分别执行yarn add、npm install、pnpm add; - 通过
execa运行安装命令,失败时给出手动安装的降级提示;dev: true选项会附加-D(开发依赖)。
宏支持配置(addMacroSupport):
- 若
package.json的依赖中出现parcel,提示 “Macros are supported by default in v2.12.0 and newer” 并结束; - 否则自动以 dev 依赖安装
unplugin-parcel-macros; - 注意:当前版本不会自动修改打包器配置(源码中留有
TODO: Try to automatically update bundle config),isMacroSupportEnabled恒为false,因此 Next Steps 中始终会附带各打包器的宏配置提示。使用--agent时此步骤被整体跳过。
七、测试与验证方式
该工具的测试覆盖两个层次:
- 组件级快照测试:tests目录下每个组件一个
.test.ts与一个.snap快照(button、dialog、table、tabs、styleProps、imports等 50+ 个),固化每个转换的精确输出; - CLI 端到端测试:cli.e2e.test.ts 配合testfixtures/cli 下的
full-project(含App.tsx、Form.tsx的 input/output 对照)与subset-project(仅Form.tsx,验证-c子集选择)两组 fixture,验证从输入源码到输出源码的完整 CLI 行为。
升级自己的项目时,推荐的工作流是:先npx @react-spectrum/codemods s1-to-s2 -d(dry run)预览 diff,确认无误后正式执行,再全局搜索TODO(S2-upgrade)处理残留项,最后跑一遍 lint/format。
八、与手动迁移指南的配合关系
codemod 覆盖不了的部分由同目录的 UPGRADE.md 承接:它按组件逐条列出属性变更(如Dialog的 render props 位置调整、TooltipTrigger的 placement 值映射、Item在不同父组件下应改成的具体组件名),并用[PENDING]标注最终发布前仍会变动、当前方案属于临时性质的条目(如暂时注释掉尚未实现的isPending、loadingState)。CLI 的 Next Steps 也明确把TODO(S2-upgrade)标记与迁移指南作为收尾手段,因此“codemod 自动转换 + TODO 注释兜底 + UPGRADE 指南手动对照”构成完整的升级闭环。
九、适用前提与限制
- 运行位置:必须在你想要升级的项目目录下运行(或显式
--path),因为工具会在当前目录寻找package.json和 lockfile; - 组件清单依赖
@react-spectrum/s2:非交互模式下它必须已安装且可解析;monorepo 开发场景下工具会回退到工作区内的 S2 源码; - 默认转换范围:
js,jsx,mjs,cjs,ts,tsx扩展名,node_modules被忽略; - 尚未自动化的部分:v3
Provider不处理、Accordion/Card/CardView/ActionBar暂无 codemod、动态import仅打 TODO、打包器宏配置需手动完成; - 收尾必做:升级后运行项目的 linter/formatter 清理 codemod 产生的格式差异,并按 Next Steps 引入
@react-spectrum/s2/page.css(S2 不需要 v3 的Provider)。
【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考