mcpField
E
expo-migrate-module

expo-migrate-module

github.com/expo开发3.5k20.1k 次安装今天更新
代码仓库

关于 expo-migrate-module

面向 Expo 模块维护者的迁移技能,指导将 Swift 实现转向 2.0 API,同时保持 JavaScript 接口不变。重点包括核实运行时支持、逐步迁移及在示例应用中验证行为。

主要功能

  1. 01保留现有 JS API
  2. 02核对宏与运行时支持
  3. 03以混合模式逐步迁移
  4. 04审查线程行为与兼容性
  5. 05构建并测试示例应用

适用场景

  1. 01升级现有 Swift Expo 模块
  2. 02安全处理不支持的 2.0 能力
  3. 03保持 JS 可见行为不变
  4. 04迁移后验证原生模块注册

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/expo/skills --skill expo-migrate-module

安装后在 Claude Code 中输入/expo-migrate-module确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
references/
compatibility.md
example.md
migration-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. 询问用户如何处理该项目,并提供两个选项:

    • 共存: 将该项目保留在 1.0 definition() DSL 中,与迁移后的 @ExpoModule 并存(混合模式),然后继续迁移其他组。
    • 还原: 撤销该组的修改,将其原样保留在 1.0 中,然后继续处理。

    如果已确认混合模式安全,默认选择共存,因为它能保留最多进展。如果部分完成的修改导致模块无法构建,且无法逐步修复,则选择还原。

  2. 在 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. 验证行为

先运行范围最小的检查,再验证真正的集成面:

  1. 针对目标 expo-modules-core 构建或进行 Apple 模块的类型检查。
  2. 运行原生单元测试和 JS/TS 测试。
  3. 如果仓库提供示例应用,则构建并启动该应用。
  4. 确认 expo-module.config.json 仍在 apple.modules 下列出所有模块类,并与当前 Swift 类名一致。过期条目可以顺利通过构建,但只会在运行时失败,因此示例应用必须实际解析该模块,而不能只通过编译。
  5. 将最终导出的接口与步骤 1 中的清单进行比较。
  6. 搜索过期的 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,简要报告任务、预期结果及实际问题。

评论

支持 Markdown