关于 web-to-native
该技能为已有 Web React 应用迁移到 Expo 原生应用制定分阶段流程,适合希望逐屏迁移并持续保持可发布状态的团队。它先用 DOM 组件承载完整 Web 界面,再按价值重构原生屏幕,并提供运行时验证方法。
主要功能
- 01评估并分类页面
- 02搭建 Expo 路由外壳
- 03先以 DOM 组件运行 Web 界面
- 04按优先级逐屏原生化
- 05接入数据、认证与存储
- 06运行应用验证内容和行为
适用场景
- 01将现有 Web React 应用渐进迁移至原生
- 02先发布可运行的移动端版本,再逐屏重构
- 03规划原生化优先级并验证屏幕行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ expo/ skills --skill web-to-native安装后在 Claude Code 中输入/web-to-native确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/false-friends.mdnative-patterns.mdrun-as-goal.mdverify-on-device.md详细介绍
Web 转原生
Web React 应用不会转换成原生应用——没有转译器。它会逐个屏幕地迁移,就像绞杀榕围绕树木生长并逐渐取而代之:先搭建原生外壳,第一天就让整个 Web UI 在其中运行,然后按优先级将各个屏幕逐一绞杀为原生屏幕。本技能是安排工作顺序的主干;每一步都会交接给现有的 Expo 技能,而不是重新解释相关内容。它将 Expo 的《使用 React 从 Web 迁移到原生》付诸实践——请阅读该文了解原因。
flowchart TD
A1[1 · Assess: write the worklist] --> A2[2 · Scaffold Expo shell]
A2 --> A3[3 · DOM-component shell<br/>· use-dom · SHIP DAY ONE]
A3 --> A4[4 · Strangle screens to native<br/>highest-value first · building-native-ui]
A4 -->|more screens| A4
A4 --> A5[5 · Wire data / auth / storage<br/>· native-data-fetching]
A5 --> A6[6 · Ship · expo-deployment]
原则
- 迁移,不要重写。 绝不要一次性全部替换;每一步都要确保应用始终可交付。
- 第一天就交付。 在任何内容原生化之前,Web UI 就会先在 DOM 组件外壳中运行(步骤 3)——这就是里程碑;之后的一切都是完善。
- 按价值逐步绞杀。 将高频屏幕原生化,其余部分留在 WebView 中。每个 DOM 屏幕都会带来约 2 MB 的 Web 运行时——光这一点就足以说明不应将所有内容都作为 DOM 交付。
- 原生化意味着重新设计,而不是换皮。 被绞杀的屏幕应该看起来像 Apple/Google 发布的产品,而不是换了原生外观的网页。优先使用
@expo/ui——它渲染真正的 SwiftUI/Compose,因此感觉与操作系统完全一致;只有自定义布局才使用带样式的 RN 基元作为后备方案。此外,还要使用平台导航(expo-router:NativeTabs、大标题)、通过@expo/ui实现的 liquid glass 和原生组件,以及移动端 UX(弹出层、滑动、触感反馈)。Web→原生模式对照表见./references/native-patterns.md。如果看起来仍像网站,那你做的是移植,而不是重新设计。 - 通过运行来验证,而不是编译。 构建成功什么也证明不了(空白 WebView 也能顺利编译)。运行每个屏幕——但要对照 Web 原版评估内容和行为,而不是像素(原生化后的屏幕应该更有原生感,而非完全相同)。
- 协调整合,不要重复造轮子。 每一步都会导向一项现有技能。这里的价值在于顺序和注意事项——逐条惯用写法的映射见
./references/false-friends.md。
循环执行(推荐)
迁移是一个漫长的“重复直到完成”循环,因此第一步是编写目标说明并启动它——而不是手动埋头逐个处理屏幕。请在 ./references/run-as-goal.md 中为此应用填写目标说明并提交;它会在每次迭代时重新读取本技能,因此每一轮 /goal 都会重新加载操作手册和工作清单,并推动下一个屏幕的迁移(甚至会自动启动评估步骤)。然后使用该目标说明运行 /goal——或者,如果执行环境无法循环运行,就将其写入 migration-goal.md 并让用户启动它。下面的步骤就是每次迭代要做的事;只有在不使用循环时才手动执行。
迁移
没有要迁移的仓库——只是一名 Web 开发者要从头构建原生应用?你不需要执行这些步骤:请使用
building-native-ui,并保持打开./references/false-friends.md,以便查阅 Web→原生惯用写法对照表。以下所有内容均假设已有一个 Web 应用。
1. 评估 → 编写工作清单
阅读代码仓库并生成 migration-progress.md,作为后续迁移检查项的持久工作清单。分两步梳理:
- 屏幕与后端。 页面路由(
page.tsx)是需要迁移的屏幕;服务端路由(route.ts)、ORM 和身份验证处理程序仍留在服务端。一次性确定后端方案:保持已部署状态(原生应用成为 HTTP 客户端),或迁移到 EAS Hosting(expo-api-routes)。 - 为每个屏幕分类,确定其迁移方式:按原样移植(表现层 → 在 DOM WebView 中运行)、立即原生化(使用频繁或需要原生体验,例如手势、列表、键盘)、稍后原生化或混合(原生外壳包裹网页子树,例如用原生外壳包裹 Markdown 渲染器的聊天列表)。
阅读时记下框架特征——RSC 还是客户端、Tailwind/shadcn、数据获取位置——因为这些决定了每个屏幕的移植方式(false-friends 中列出了对应关系;尤其是异步服务端组件,在迁移前必须拆分为客户端获取数据和表现层组件)。也要标记第三方服务/SDK——浏览器 SDK 无法直接沿用(false-friends → 服务与 SDK);尤其是支付,应该分叉而非替换(应用内数字商品必须通过 RevenueCat 使用商店 IAP,费用约为 30%——不能用 Stripe)。这是现在就要做的业务模式决策,而不是等到 App Store 审核时再决定。只有在每条路由都已分类、每个屏幕都已归类后,这份工作清单才值得信赖。
2. 搭建外壳
使用 create-expo-app,然后在 Expo Router 中映射网页路由——Next 的路由树几乎可以一对一映射(注意 [id]/page.tsx → [id].tsx,路由也可能位于 src/app/ 中)。每条路由创建一个空屏幕。
3. 在 DOM 组件中封装 —— 第一天的里程碑
将每个屏幕都作为 DOM 组件(按照 use-dom 技能使用 'use dom')迁移过来,并通过其原生路由渲染,这样任何屏幕原生化之前,整个应用就能在手机上运行。预计每个屏幕都需要修改——拆解服务端组件、替换框架导入(next/link)、沿用现有样式——这些内容都已在 false-friends 中涵盖。然后按下文所述运行验证;这样即可提交到 TestFlight。
4. 按价值将屏幕原生化
从上到下依次处理 migration-progress.md。对每个屏幕,都要用原生方式重新设计,而不是移植网页布局。优先使用 @expo/ui(真正的 SwiftUI/Compose——按钮、列表、工作表、选择器、滑块;./references/native-patterns.md 介绍了网页模式与原生组件的对应关系),然后使用平台导航(expo-router——NativeTabs、大标题)和移动端体验(滑动、触觉反馈、动量滚动/反向滚动);只有自定义布局才使用 RN 基础组件。每种惯用写法都请查阅 ./references/false-friends.md。@expo/ui 和 DOM 组件都能在 Expo Go(SDK 56+)中运行——只有使用自定义原生模块时才需要开发构建(expo-dev-client 技能)。对照正在运行的网页原版验证内容和行为(外观应更具原生感),然后标记完成。每轮处理一个屏幕,应用始终可发布。这是一个围绕持久工作清单循环执行的流程,因此可以无人值守运行——将其交给目标循环(./references/run-as-goal.md)。
5. 接入数据、身份验证和存储
网页数据层无法在迁移后继续使用——相对路径请求、Cookie 会话、localStorage 和环境变量都需要更改(替换方案见 false-friends)。使用 native-data-fetching 处理请求和缓存;如果后端已迁移到 EAS Hosting,则添加 expo-api-routes。
6. 发布
使用 expo-deployment 构建商店版本(App Store / Play / TestFlight),之后通过 EAS Update 推送 OTA 更新。
通过运行验证,而非编译
expo export 成功只能证明屏幕可以打包,不能证明它可以渲染——屏幕可能构建成功,却仍然显示空白或渲染错误。因此,在搭建好外壳后,以及每个屏幕原生化后,都要针对同一路由比较两个正在运行的应用:
- Web 原版 — 使用
agent-browser(vercel-labs CLI)捕获:open路由、snapshot --json获取无障碍树、screenshot截图。 - 原生应用 — 使用
argent驱动模拟器:通过describe/debugger-component-tree检查结构,并在每轮使用flow重放检查。
通过标准是内容和行为一致——而非像素一致:原生化后的屏幕应比 Web 屏幕更有原生感,绝不能完全相同(DOM 外壳阶段除外——此时它就是 Web UI,因此应该一致)。原生体验也包括感受,无法通过截图捕捉——对于带有转场或手势的屏幕,请录制一段短视频,而不只是截图(参见 native-patterns.md → Feel)。此流程对所用工具有明确要求:如果尚未安装 agent-browser 或 argent,请先询问用户并安装,再继续——不要改用手动截图。完整流程和设置说明见 ./references/verify-on-device.md。
参考资料
./references/false-friends.md— Web 习惯用法 → 原生对应方案,以及各自需要注意的问题。步骤 3–5 的查询指南,也适用于需要改掉 Web 开发习惯用法时。./references/native-patterns.md— Web UX 模式 → 原生重新设计(优先使用@expo/ui)。步骤 4 的重新设计指南,帮助屏幕拥有原生操作系统体验,而非仅换一层皮。./references/verify-on-device.md— 双 Agent 的一致性验证流程:驱动 Web 应用(浏览器 Agent)和原生应用(argent),打开同一路由并进行比较。./references/run-as-goal.md— 用于无人值守执行步骤 4 的现成迁移专用目标(每轮都会重新读取此技能)。- Expo — 使用 React 从 Web 迈向原生 — 本技能落地实践的权威指南。