移动/游戏
在保留 JS/TS 契约的前提下,迁移已有…
适用于已有 Expo Apple/Swift 原生模块需要从 1.0 definition DSL 迁移到 2.0 宏 API,且不能改变可观察 JavaScript/TypeScript 契约的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
在保留 JS/TS 契约的前提下,迁移已特定
解决思路
- 只迁移 Swift 侧,不改变可观察 JS API;把当前 JS/TypeScript 表面和测试作为兼容契约。
- 除非用户明确扩大任务,否则 Kotlin 留在 1.0 DSL。
- 使用 `expo` `57.0.21` 或更新版本作为迁移目标;编辑前检查目标安装的版本(`expo` 在 `package.json`/lockfile,或 `npm ls expo`);如果更旧,停止并告诉用户先升级。
- 同时检查示例应用自己的 `package.json`,而不仅是模块根目录;示例应用是第 4 步构建和启动的集成面,需要相同的版本下限。
- 把版本下限视为最低要求而非保证;`57.x` 内确切的宏和核心表面仍会变化,因此第 2 步仍必须验证 checked-out source。
- 在大型迁移前告知用户:SDK 57 中宏是实验性且未文档化的,官方 beta 在 SDK 58,API 仍可能变化;优先增量 mixed mode,而不是整模块转换。
- 改源码前阅读 `references/migration-map.md`,获取 1.0 到 2.0 映射、语义陷阱和 mixed-mode 规则。
- 阅读 `references/example.md`,查看一个模块从 mixed mode 到完整迁移的完整 before/after walkthrough;当需要看 per-member 规则如何组合时查阅它。
- 当 checked-out `expo-modules-core` 版本或分支未知是否支持每个请求的宏时,阅读 `references/compatibility.md`;它解释如何验证实际 compile-time 和 runtime surface,而不是猜测版本号,并列出 macros plugin 到 `0.10.0` 获得的内容。
- 编辑前检查 repository 指令和 worktree;定位 Swift module classes、records、shared objects、native views、JS/TS bindings、tests、example app、podspec、`expo-module.config.json`、installed/checked-out `expo-modules-core`。
- 重写前 inventory 每个导出项:module 和 shared-object JS names。
- inventory function names、arity、labels、defaults、nullability、sync/async behavior、errors、queue/thread semantics;注意哪些 `AsyncFunction` bodies 做阻塞或长任务,以及任何 `.runOnQueue(...)`。
- inventory property names、mutability 和 constant caching behavior。
- inventory event wire names 和 payload shapes。
- inventory record field names、defaults、requiredness 和 nullability。
- inventory shared-object constructors 和 instance/static placement(`Function` vs `StaticFunction`/`StaticAsyncFunction`)。
- inventory lifecycle hooks 和 views。
- 用 TypeScript declarations 和 JS call sites 解决歧义;不要悄悄改进 requiredness、重命名 event,或在语法迁移中改 sync behavior。
- 检查目标实际使用的依赖中的 macro declarations 和 matching core hooks;不要因为一个宏能编译就假设 2.0 设计中的所有项都存在。
- 将每个 1.0 项分类为 Migrate(迁移):宏和所需 core runtime support 都存在;Keep in DSL(保留在 DSL):mixed mode 可安全保留,或 2.0 无等价物;Blocked(阻塞):迁移会改变 JS contract 或需要不可用 runtime support。
- 优先增量 mixed-mode 结果,而不是推测性生成的代码;为任何剩余 DSL 元素保留 `definition()`;仅当它为空且 resolved module name 已由 `@ExpoModule` 保留时删除。
- 每次迁移一个语义组:module naming、functions、properties/constants、events、shared objects、records;保持 diff 窄。
- 当 Swift naming rules 或 macro defaults 不同时,显式保留每个现有 JS-visible name。
- 保持原 optional/default behavior;1.0 可选 record field 不得仅因 2.0 能表达 required fields 就变成 required。
- 不要迁移 same-JS-name overloads,除非 checked-out macro 会分组和分发它们。
- 保留 async threading behavior:1.0 `AsyncFunction` body 在 JS thread 外运行;2.0 `async` `@JS` member 从 JS thread 开始,只有第一个 `await` 才离开;从不 `await` 的 body 会阻塞 JS thread,而 JS signature 不变;审计第一个 `await` 前运行的内容,绝不要在 JS actor 上留阻塞 I/O;用 `@JS(.concurrent)` 修复,或重构到 Swift Concurrency/continuation;Queue-pinned functions 同样处理;见 `references/migration-map.md` 的 threading 部分。
- 迁移 views、unions、synchronous events、shared-object static members 或 free-form `Any` arguments 前验证 core support;macros plugin 在这些方面都可能领先 core,所以检查 checked-out core 而不是 plugin version,并把未支持成员留在 1.0 DSL;`references/compatibility.md` 有 per-capability checks。
- 保持 `expo-module.config.json` 在 `apple.modules` 下列出每个 module class;条目是裸 Swift class names,所以迁移中重命名 class 会静默分离模块:它能 build,运行时缺失;迁移的 class 也必须保持 `public` 或 `open`;SDK 58 的 auto-discovery 才会淘汰这些条目。
- 除非用户要求 API 变更,不要改 Kotlin、JS wrappers 或 public `.d.ts` 文件。
- 绝不要把 macro-generated symbols 写入模块源码;宏会生成它们,手写或覆盖它们不是迁移的一部分。
- 每组之后,搜索应该移动的旧 DSL entries 和 call sites;避免大范围格式化或无关清理。
- 当 2.0 等价物缺失或组失败:当第 2 步将项分类为 Blocked,或迁移组 build 失败/破坏 contract,不要强推;停止该组。
- 询问用户如何处理该项,给两个选项:Co-exist:在 migrated `@ExpoModule` 旁边把 item 保留在 1.0 `definition()` DSL(mixed mode),并继续其他组;Revert:回退该组 edits,留在 1.0,并继续。
- 默认 co-existence,当 mixed mode 已验证安全,因为它保留最多进度;当半应用变更导致模块 non-building 且无法增量挽救时 revert。
- 在 `expo/expo` 开 tracking issue,记录 2.0 尚未覆盖的功能;使用 `gh issue create --repo expo/expo`,并在发布前与用户确认(按 repo conventions,未经批准不要发对外评论)。
- tracking issue 包含:1.0 member 及其 JS contract;缺失的特定 macro 或 core hook(引用 `references/compatibility.md` 的证据缺口);checked-out 的 `expo-modules-core` 版本/分支。
- 在 handoff 中引用该 issue,使剩余 DSL entry 可追溯到已知限制。
- 继续能干净迁移的组;一个 blocked member 不阻塞其余。
- 先运行最窄可用检查,然后真实集成面:针对 target `expo-modules-core` build 或 type-check Apple module。
- 运行 native unit tests 和 JS/TS tests。
- 当 repo 提供 example app 时,构建并启动它。
- 确认 `expo-module.config.json` 仍按 `apple.modules` 列出每个 module class,匹配当前 Swift class names;stale entry 会 build 干净但只在 runtime 失败,所以 example app 必须实际 resolve 模块,不只是 compile。
- 比较最终 exported surface 与第 1 步 inventory。
- 搜索 stale `Name`、迁移后的 `Function`/`AsyncFunction`/`StaticFunction`/`StaticAsyncFunction`/`Property`/`Constant`/`Events` entries、旧 `sendEvent` calls、`@Field`、duplicate registrations;搜索你写的源码,而不是 macro expansion output。
- Expansion tests 单独不够:生成的 macro code 可能看起来正确,但 link 或 run 失败 against mismatched core;如果依赖变了或 macro plugin flags 缺失,适当重装 JS dependencies,运行 repo 的 CocoaPods 安装流程,并重启 Xcode,再诊断 plugin communication failures。
- Handoff:绝不向用户打印 macro-generated 或 core-internal symbol names;`references/compatibility.md` 引用它们以便 grep,但它们是实现细节,会在 plugin revisions 间变化,报告中是噪音。
- 用宏(`@JS static`、`@Event(sync:)`)和可观察 JS 行为命名能力;说“安装的 core 尚不支持解码自由形式字典参数”,而不是缺失方法的名称;例外是 `expo/expo` 上的 tracking issue,其中具体缺失 hook 才是重点。
- 报告:哪些成员移到 2.0。
- 报告:哪些成员有意留在 1.0 DSL 及原因。
- 报告:任何兼容性敏感选择,特别是 event names、record requiredness、constants、queues。
- 报告:运行的命令和未完成的验证。
- 如果遇到错误、误导或过时信息,运行 `npx --yes submit-expo-feedback@latest --category skills --subject "expo-migrate-module" "<actionable feedback>"` 报告给 Expo。
- 只有有具体可操作内容才提交;包含尽可能多相关上下文。
- 如果 AI agent 反复失败或用户不得不接管 Expo 任务,加载 expo-skill-feedback skill 并遵循其 eval-candidate flow,而不是重复上面的命令。
适用边界 · 注意事项
- 不要用于创建新模块。
- 不要用于一般 Expo SDK 升级。
- 不要用于 Android/Kotlin 迁移;`expo` 或 example app 低于 `57.0.21` 时先停止并升级。