mcpField
W
web-to-native

web-to-native

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

关于 web-to-native

该技能为已有 Web React 应用迁移到 Expo 原生应用制定分阶段流程,适合希望逐屏迁移并持续保持可发布状态的团队。它先用 DOM 组件承载完整 Web 界面,再按价值重构原生屏幕,并提供运行时验证方法。

主要功能

  1. 01评估并分类页面
  2. 02搭建 Expo 路由外壳
  3. 03先以 DOM 组件运行 Web 界面
  4. 04按优先级逐屏原生化
  5. 05接入数据、认证与存储
  6. 06运行应用验证内容和行为

适用场景

  1. 01将现有 Web React 应用渐进迁移至原生
  2. 02先发布可运行的移动端版本,再逐屏重构
  3. 03规划原生化优先级并验证屏幕行为

安装

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

$npx skills add https://github.com/expo/skills --skill web-to-native

安装后在 Claude Code 中输入/web-to-native确认已加载。

包含的文件

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

references/
false-friends.md
native-patterns.md
run-as-goal.md
verify-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。

参考资料

评论

支持 Markdown