expo-web-to-native

Par expo · skills

Framework (OSS). Migrer une application web React existante vers une application native iOS/Android avec Expo. À utiliser quand l'utilisateur souhaite transformer un site web en application mobile, porter une base de code React Next.js/Vite/CRA vers React Native, réutiliser du code web sur natif de manière incrémentale, ou demande comment les idiomes web (le DOM, CSS, React Router, localStorage, window) se mappent sur le natif. Il s'agit du guide de migration de bout en bout ; utilisez le skill `expo-dom` pour le mécanisme de composant DOM lui-même.

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

Web to Native

Une app React web ne se convertit pas en natif — il n'existe pas de transpileur. Elle migre, écran par écran, comme un figuier étrangleur qui pousse autour d'un arbre et le remplace lentement : mettre en place une enveloppe native, exécuter toute l'interface web à l'intérieur dès le jour 1, puis étouffer chaque écran en natif par ordre de priorité. Cette skill est la colonne vertébrale qui ordonne le travail ; chaque étape bascule vers une skill Expo existante plutôt que de la réexpliquer. Elle opérationnalise le guide From Web to Native with React d'Expo — lis-le pour comprendre le pourquoi.

flowchart TD
    A1[1 · Évaluer : rédiger la liste des tâches] --> A2[2 · Échafauder l'enveloppe Expo]
    A2 --> A3[3 · Enveloppe composants DOM<br/>· expo-dom · LIVRER LE JOUR 1]
    A3 --> A4[4 · Étouffer les écrans en natif<br/>valeur maximale d'abord · expo-router]
    A4 -->|plus d'écrans| A4
    A4 --> A5[5 · Connecter données / auth / stockage<br/>· expo-data-fetching]
    A5 --> A6[6 · Livrer · eas-app-stores]

Principes

  • Migrer, ne pas réécrire. Ne jamais le faire d'un seul coup ; chaque étape garde l'app livrable.
  • Livrer le jour 1. L'interface web s'exécute dans une enveloppe composants DOM (étape 3) avant toute nativisation — c'est le jalon ; tout le reste est un raffinement.
  • Étouffer par valeur. Nativiser les écrans chauds ; laisser le reste dans la webview. Chaque écran DOM porte un runtime web de ~2 MB — raison suffisante de ne pas tout livrer en DOM.
  • Nativiser signifie redesigner, pas relooker. Un écran étranglé devrait ressembler à quelque chose qu'Apple/Google aurait livré, pas à la page web relookée. Atteins d'abord @expo/ui — il rend du vrai SwiftUI/Compose, donc ça ressemble exactement à l'OS ; les primitives RN stylisées sont le recours pour les layouts personnalisés uniquement. Plus la navigation de plateforme (expo-router : NativeTabs, grands titres), verre liquide et composants natifs via @expo/ui, et UX mobile (sheets, swipes, haptics). La carte de modèles web→natif se trouve dans ./references/native-patterns.md. Si ça ressemble toujours à un site web, tu as porté au lieu de redesigner.
  • Vérifier en exécutant, pas en compilant. Une build propre ne prouve rien (une webview vide compile bien). Exécute chaque écran — mais juge contenu et comportement par rapport à l'original web, pas pixels (un écran nativé devrait ressembler plus à du natif, pas identique).
  • Orchestrer, ne pas réinventer. Chaque étape bascule vers une skill existante. La valeur ici est l'ordre et les pièges — les mappings idiome-par-idiome se trouvent dans ./references/false-friends.md.

L'exécuter en boucle (recommandé)

La migration est une longue boucle répéter-jusqu'à-fini, donc le premier mouvement est de rédiger l'objectif cible et le lancer — non pas de traiter les écrans à la main. Remplis l'objectif dans ./references/run-as-goal.md pour cette app et présente-le ; il relit cette skill à chaque itération, donc chaque tour /goal recharge le playbook + la liste des tâches et pilote l'écran suivant (il amorce même l'étape d'évaluation). Puis exécute /goal avec — ou, si le harness ne peut pas boucler, écris-le dans migration-goal.md et demande à l'utilisateur de le lancer. Les étapes ci-dessous sont ce que chaque itération fait ; exécute-les à la main uniquement si tu ne boucles pas.

La migration

Pas de repo à migrer - tu construis du natif fresh en tant que web dev ? Tu n'as pas besoin de ces étapes : utilise expo-router, et garde ./references/false-friends.md ouvert pour la carte idiomatique web→natif. Tout ce qui suit suppose une app web existante.

1. Évaluer → rédiger la liste des tâches

Lis le repo et produis migration-progress.md, la liste durable des tâches que le reste de la migration coche. Fais deux tranches :

  • Écrans vs backend. Les routes de page (page.tsx) sont des écrans que tu migres ; les routes serveur (route.ts), l'ORM et les gestionnaires d'auth restent côté serveur. Décide du backend une fois : le garde déployé (l'app native devient un client HTTP) ou le déplace vers EAS Hosting (eas-hosting).
  • Classe chaque écran par comment il devrait atterrir : port-tel-quel (présentatif → livré dans une webview DOM), nativiser-maintenant (chaud, ou a besoin d'une ressenti natif — gestes, listes, clavier), nativiser-plus-tard, ou hybride (une enveloppe native autour d'une sous-arborescence web, p. ex. une liste de chat enrobant un rendu markdown).

Note les signaux du framework en lisant — RSC vs client, Tailwind/shadcn, où les données sont récupérées — puisqu'ils décident comment chaque écran porte (false-friends a les mappings ; les Server Components asynchrones en particulier doivent être divisés en fetch client + composant présentatif avant de pouvoir bouger). Signale aussi les services/SDKs tiers — les SDKs navigateur ne passent pas (false-friendsServices & SDKs) ; les paiements surtout c'est un fork, pas un swap (les biens numériques in-app doivent utiliser IAP via store via RevenueCat, ~30% — pas Stripe), un choix de modèle métier à prendre maintenant, pas à la revue App Store. La liste des tâches est seulement fiable une fois que chaque route est triée et chaque écran classé.

2. Échafauder l'enveloppe

create-expo-app, puis refléter les routes web dans Expo Router — l'arborescence Next mappe presque 1:1 (note [id]/page.tsx[id].tsx, et les routes peuvent vivre dans src/app/). Écrans vides, un par route.

3. L'envelopper en composants DOM — le jalon du jour 1

Apporte chaque écran en tant que composant DOM ('use dom', par la skill expo-dom) rendu par sa route native, afin que l'app entière s'exécute sur un téléphone avant toute nativisation. Attends-toi à des édits par écran - déballage des Server Components, swap des imports de framework (next/link), portage du styling — tout couvert dans false-friends. Puis vérifie en exécutant (ci-dessous) ; c'est livrable à TestFlight tel quel.

4. Étouffer les écrans en natif — par valeur

Parcours migration-progress.md de haut en bas. Pour chaque écran, redesigne-le en natif - ne porte pas le layout web. Atteins @expo/ui d'abord (vrai SwiftUI/Compose - boutons, listes, sheets, pickers, sliders ; ./references/native-patterns.md mappe quel modèle web devient quel composant natif), puis navigation de plateforme (expo-router - NativeTabs, grands titres) et UX mobile (swipes, haptics, momentum/inverted scroll) ; primitives RN seulement pour les layouts personnalisés. Consulte ./references/false-friends.md pour chaque idiome. @expo/ui et les composants DOM s'exécutent tous deux dans Expo Go (SDK 56+) - une dev build (la skill expo-dev-client) est seulement nécessaire pour les modules natifs personnalisés. Vérifie contenu et comportement par rapport à l'original web en exécution (le look devrait devenir plus natif), puis coche-le. Un écran par passage, app livrable partout. C'est une boucle sur une liste durable, donc elle peut tourner sans surveillance - bascule-la vers une boucle goal (./references/run-as-goal.md).

5. Connecter données, auth et stockage

La couche données web ne survit pas au déplacement - les requêtes relatives, les sessions par cookies, localStorage et les env vars changent tous (swaps dans false-friends). Utilise expo-data-fetching pour les requêtes et la mise en cache ; ajoute eas-hosting si le backend a déménagé vers EAS Hosting.

6. Livrer

eas-app-stores pour les builds store (App Store / Play / TestFlight), EAS Update pour les OTA pushes après.

Vérifier en exécutant, pas en compilant

Un expo export vert prouve qu'un écran assemble, pas qu'il rend — un écran peut compiler et toujours rendre vide ou mal rendre. Donc après l'enveloppe et après chaque écran nativé, compare les deux apps en exécution pour la même route :

  • Original web — capture-le avec agent-browser (CLI vercel-labs) : open la route, snapshot --json l'arborescence d'accessibilité, screenshot.
  • Natif — pilote le simulateur avec argent : describe / debugger-component-tree pour la structure, flow pour rejouer la vérification à chaque passage.

Valide sur la parité de contenu et comportement — pas pixels : un écran nativé devrait ressembler plus natif que le web, jamais identique (l'étape enveloppe DOM est l'exception — là c'est l'interface web, donc ça devrait correspondre). Le ressenti fait partie du natif et ne peut pas être capturé en screenshot — pour les écrans avec transitions ou gestes, capture une courte vidéo, pas juste un still (voir native-patterns.md → Feel). Cette boucle est partisane de son outillage : si agent-browser ou argent n'est pas installé, demande à l'utilisateur et installe avant de procéder — ne te replie pas sur des screenshots manuels. Recette complète et setup dans ./references/verify-on-device.md.

Références

  • ./references/false-friends.md — idiome web → équivalent natif + le piège pour chaque. Le lookup pour les étapes 3–5, et pour tout web dev réapprenant des idiomes.
  • ./references/native-patterns.md — modèle pattern UX web → redesign natif (@expo/ui-d'abord). Le playbook redesign étape 4 afin que les écrans ressemblent à du natif OS, pas du relooké.
  • ./references/verify-on-device.md — la recette parité deux-agents : pilote l'app web (agent navigateur) et l'app native (argent), ouvre la même route, compare.
  • ./references/run-as-goal.md — un objectif goal migration-spécifique tout formé pour piloter l'étape 4 sans surveillance (relit cette skill à chaque itération).
  • Expo — From Web to Native with React — le guide canonique que cette skill opérationnalise.

Skills similaires