关于 expo-migrate-module
面向 Expo 模块维护者的迁移技能,指导将 Swift 实现转向 2.0 API,同时保持 JavaScript 接口不变。重点包括核实运行时支持、逐步迁移及在示例应用中验证行为。
主要功能
- 01保留现有 JS API
- 02核对宏与运行时支持
- 03以混合模式逐步迁移
- 04审查线程行为与兼容性
- 05构建并测试示例应用
适用场景
- 01升级现有 Swift Expo 模块
- 02安全处理不支持的 2.0 能力
- 03保持 JS 可见行为不变
- 04迁移后验证原生模块注册
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ expo/ skills --skill expo-migrate-module安装后在 Claude Code 中输入/expo-migrate-module确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/compatibility.mdexample.mdmigration-map.md详细介绍
迁移 Expo 模块
在不改变其可观察 JS API 的前提下,迁移现有 Expo 模块的 Swift 部分。将当前的 JS/TypeScript 接口和测试视为兼容性契约。除非用户明确扩大任务范围,否则 Kotlin 保持使用 1.0 DSL。
前置条件
使用 expo 57.0.21 或更高版本。更早的 57.x 版本虽然可以编译宏,但缺少许多 2.0 功能和性能优化,因此不要以这些版本为目标。编辑前,检查目标中安装的版本(package.json/lockfile 中的 expo,或运行 npm ls expo)。如果版本较旧,请停止并告知用户先升级。
也要检查示例应用自身的 package.json,不能只检查模块根目录。示例应用是你在第 4 步中构建和启动的集成界面,因此它也需要满足相同的最低版本要求。
这是最低版本要求,并不意味着一定兼容:宏和核心接口在 57.x 中仍会有所不同,因此第 2 步仍必须核实当前检出的源码。
SDK 57 中的宏属于实验性且未公开文档的功能,官方 Beta 版本将在 SDK 58 发布。API 仍可能发生变化。在进行大规模迁移前,务必告知用户这一点,并优先采用增量式混合模式,而非一次性转换整个模块。
参考资料
- 修改源码前,先阅读
references/migration-map.md。其中包含 1.0 到 2.0 的映射、语义陷阱和混合模式规则。 - 阅读
references/example.md,了解一个模块从混合模式到完整迁移的前后完整示例。需要了解各成员规则如何组合时,请参考此文。 - 如果无法确定当前检出的
expo-modules-core版本或分支是否支持所有请求的宏,请阅读references/compatibility.md。此文介绍如何验证实际的编译时和运行时接口,而不是根据版本号猜测,并列出了宏插件截至0.10.0新增的功能。
工作流程
1. 确立契约
编辑前,检查仓库说明和工作区。找出 Swift 模块类、记录、共享对象、原生视图、JS/TS 绑定、测试、示例应用、podspec、expo-module.config.json,以及已安装或检出的 expo-modules-core。
重写前,盘点所有导出项:
- 模块和共享对象的 JS 名称
- 函数名称、参数数量、标签、默认值、可空性、同步/异步行为、错误和队列/线程语义(注明哪些
AsyncFunction函数体会执行阻塞或长时间运行的工作,以及是否使用任何.runOnQueue(...)) - 属性名称、可变性和常量缓存行为
- 事件的线缆名称和负载结构
- 记录字段名称、默认值、必填性和可空性
- 共享对象构造函数,以及实例/静态位置(
Function与StaticFunction/StaticAsyncFunction) - 生命周期钩子和视图
使用 TypeScript 声明和 JS 调用位置消除歧义。语法迁移期间,不要擅自“改进”必填性、重命名事件或更改同步行为。
2. 核实可用的 2.0 接口
检查目标实际使用的依赖中的宏声明和对应的核心钩子。不要因为某个宏可以编译,就假设 2.0 设计中的所有项目都已提供。
将每个 1.0 项目归类为:
- **迁移:**其宏和所需的核心运行时支持均已存在。
- **保留在 DSL 中:**混合模式可以安全保留该项,或 2.0 没有对应功能。
- **受阻:**迁移会改变 JS 契约,或需要当前不可用的运行时支持。
优先采用增量式混合模式,而非推测性生成代码。只要仍有 DSL 元素,就保留 definition();仅当它为空且 @ExpoModule 保留了解析后的模块名称时,才将其删除。
3. 执行迁移
一次迁移一个语义组:模块命名、函数、属性/常量、事件、共享对象,然后是记录。尽量缩小差异范围。
遵循以下不变量:
- 当 Swift 命名规则或宏默认值与现有名称不同时,明确保留每个现有的 JS 可见名称。
- 保留原有的可选/默认行为。不能仅仅因为 2.0 能表达必填字段,就把 1.0 记录中的可选字段改成必填。
- 除非当前检出的宏会对同一 JS 名称的重载进行分组和分派,否则不要迁移这些重载。
- 保留异步线程行为。1.0 的
AsyncFunction函数体在 JS 线程之外运行;2.0 的async@JS成员则从 JS 线程开始运行,直到遇到第一个await才会离开。因此,不包含await的函数体会阻塞 JS 线程,JS 签名则不会改变。审查第一个await之前运行的代码,绝不要让阻塞式 I/O 留在 JS actor 上。使用@JS(.concurrent)修复,或改用 Swift Concurrency 或 continuation。固定在队列上的函数也要同样处理。请参阅references/migration-map.md中的线程处理部分。 - 迁移视图、联合类型、同步事件、共享对象静态成员或自由形式的
Any参数前,先验证 core 是否支持。对于这些功能,宏插件的发布版本一直领先于 core,因此应检查当前检出的 core,而不是插件版本,并将不受支持的成员保留在 1.0 DSL 中。references/compatibility.md列出了各项功能的检查方法。 - 保留
expo-module.config.json中apple.modules下的所有模块类。条目是裸 Swift 类名,因此迁移时重命名类会导致模块无声地脱离:代码可以构建,但模块在运行时不会出现。迁移后的类也必须保持public或open。SDK 58 的自动发现机制才会使这些条目不再需要。 - 除非用户要求更改 API,否则不要更改 Kotlin、JS 封装或公开的
.d.ts文件。 - 绝不要将宏生成的符号写入模块源代码。宏会生成这些符号;手动编写或覆盖它们不属于迁移的一部分。
每完成一个组,都要搜索应已迁移的旧 DSL 条目和调用点。避免大范围格式调整或无关清理。
缺少 2.0 等效项或迁移组失败时
如果步骤 2 将某个项目归类为 Blocked,或者迁移后的组无法构建或违反约定,不要强行迁移。停止处理该组,并:
-
询问用户如何处理该项目,并提供两个选项:
- 共存: 将该项目保留在 1.0
definition()DSL 中,与迁移后的@ExpoModule并存(混合模式),然后继续迁移其他组。 - 还原: 撤销该组的修改,将其原样保留在 1.0 中,然后继续处理。
如果已确认混合模式安全,默认选择共存,因为它能保留最多进展。如果部分完成的修改导致模块无法构建,且无法逐步修复,则选择还原。
- 共存: 将该项目保留在 1.0
-
在
expo/expo上创建跟踪 issue,说明 2.0 尚未覆盖的功能,以便记录这一差距,而不是悄悄绕过。使用gh issue create --repo expo/expo,并在发布前征得用户确认(遵循仓库约定,未经批准不要发布对外评论)。请包含:- 1.0 成员及其 JS 约定
- 缺失的具体宏或 core hook(引用
references/compatibility.md中缺失的证据) - 已检出的
expo-modules-core版本/分支
在交接说明中引用该 issue,以便将保留的 DSL 条目关联到已知限制。
继续迁移能够顺利完成的组;一个受阻成员不会阻止其余成员。
4. 验证行为
先运行范围最小的检查,再验证真正的集成面:
- 针对目标
expo-modules-core构建或进行 Apple 模块的类型检查。 - 运行原生单元测试和 JS/TS 测试。
- 如果仓库提供示例应用,则构建并启动该应用。
- 确认
expo-module.config.json仍在apple.modules下列出所有模块类,并与当前 Swift 类名一致。过期条目可以顺利通过构建,但只会在运行时失败,因此示例应用必须实际解析该模块,而不能只通过编译。 - 将最终导出的接口与步骤 1 中的清单进行比较。
- 搜索过期的
Name、已迁移的Function/AsyncFunction/StaticFunction/StaticAsyncFunction/Property/Constant/Events条目、旧的sendEvent调用、@Field和重复注册。搜索你编写的源代码,而不是宏展开输出。
仅运行展开测试还不够:生成的宏代码看起来可能正确,但与不匹配的核心库链接或运行时仍可能失败。如果依赖项发生变化或缺少宏插件标志,请酌情重新安装 JS 依赖,运行仓库的 CocoaPods 安装工作流,并在排查插件通信故障前重启 Xcode。
交接
切勿向用户输出宏生成的或核心库内部的符号名。references/compatibility.md 中提及这些名称,方便你通过 grep 搜索,但它们属于实现细节,会随插件版本而变化,在报告中也只会造成干扰。应使用宏(@JS static、@Event(sync:))和可观察到的 JS 行为来说明功能。应表述为“已安装的核心库尚不支持解码自由格式的字典参数”,而不是写出缺失方法的名称。例外情况是针对 expo/expo 的跟踪问题,此时具体缺失的钩子正是重点。
请报告:
- 哪些成员迁移到了 2.0
- 哪些成员有意保留在 1.0 DSL 中,以及原因
- 任何对兼容性敏感的选择,尤其是事件名称、记录必填性、常量和队列
- 运行过的命令,以及尚未完成的验证
提交反馈
如果你在此技能中遇到错误、误导性信息或过时信息,请报告,以便 Expo 改进:
npx --yes @expo/agent-cli@latest feedback --category skills --subject "expo-migrate-module" "<actionable feedback>"
仅在有具体且可执行的反馈时提交。请尽可能提供相关背景。如果 Agent 反复无法完成 Expo 任务,或用户不得不介入,请使用 --category evals 和相关 Expo 包或命令作为 --subject,简要报告任务、预期结果及实际问题。