Skip to main content

expo

Use when shipping a React Native app with Expo — EAS Build/Submit/Update, eas.json profiles and channels, config plugins, prebuild/CNG, runtime-version policy, OTA updates that never land, SDK upgrades, the New Architecture. NOT RN UI, navigation or native-module authoring (that is `react-native`), NOT a Dart app (that is `flutter`).

Aller à l'installation

Informations de source

Dépôt
ericrisco/rsc-harness
Dernière activité de la source
1 septembre 2026 à 22:53
Langue détectée de SKILL.md
anglais
Étoiles
110
Forks
9

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
6 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
expo
description
Use when shipping a React Native app with Expo — EAS Build/Submit/Update, eas.json profiles and channels, config plugins, prebuild/CNG, runtime-version policy, OTA updates that never land, SDK upgrades, the New Architecture. NOT RN UI, navigation or native-module authoring (that is `react-native`), NOT a Dart app (that is `flutter`).
tags
["expo","eas","react-native","mobile","ota-updates","app-store"]
recommends
["react-native","github-actions","ship","deployment","secure-coding"]
origin
risco
# Expo & EAS: shipping React Native **Hand-off — the app vs its motion.** This skill owns the Expo platform: router, config plugins, EAS, native modules, store builds. **Animation and gestures** — Reanimated worklets, the UI runtime, sheets, screen transitions, press feedback, haptics, motion that stutters on a real device — are `../motion-craft/SKILL.md`'s. ## What this skill owns Expo is the toolchain and **EAS cloud platform** layered on React Native: cloud builds, store submission, over-the-air JS updates, and **native configuration declared in JavaScript** instead of hand-edited Xcode/Gradle projects. This skill owns the *shipping pipeline* and *native-config-via-JS*. The verb rule of thumb: if the verb is **build / submit / update / prebuild / plugin / EAS / channel / runtime-version**, you are in `expo`. If it is **render / navigate / animate / bridge / write a native module**, route to `react-native`. Other exits: web React/hooks/state → `react`, a Dart app → `flutter`, native-only Swift/Kotlin → `swift-ios`/`kotlin-android`, a desktop wrapper → `tauri`/`electron`, CI unrelated to EAS → `github-actions`. Current stable as of 2026-06-02: **Expo SDK 55** — React Native 0.83.1, React 19.2.0, shipped 2026-02-25. **SDK 56 is in beta** (beta opened 2026-05-06, ~2-week window; RN 0.85.2, React 19.2.3) — upcoming, not yet shipped stable. Both run exclusively on the New Architecture; the Legacy Architecture was removed in SDK 55. ## Decision rules | Situation | Do this | Why / not that | |---|---|---| | New app, want config-as-JS | **managed + prebuild (CNG)** — no committed `ios/`/`android/` | native dirs are regenerable artifacts; hand-edits get blown away | | You truly need to hand-edit native code long-term | **bare** (commit `ios/`/`android/`) | last resort; you lose `prebuild --clean` upgrades | | Quick demo, only Expo-SDK modules | **Expo Go** | zero build, but custom native deps will crash | | Any custom native dependency or plugin | **dev build** (`developmentClient: true`) | Expo Go cannot load arbitrary native code | | Runtime version, picking a policy | **`fingerprint`** (auto-bumps on native change) | safest default; prevents serving JS to an incompatible binary | | Runtime tied to your release version | `appVersion` | simpler, but you must remember to bump it on native changes | ## The shipping pipeline (core) Four EAS verbs, in this order: **prebuild → build → submit → update**. ```bash npx expo prebuild --clean # regenerate native dirs from app config + plugins (CNG) eas build -p ios --profile production # cloud-build the binary (.ipa/.aab) eas submit -p ios --profile production # upload to App Store / Play Store eas update --branch production --message "fix typo" # ship JS-only over the air ``` `prebuild` is only needed for managed/CNG apps and runs automatically inside `eas build`; run it locally to inspect or to verify a plugin. **Build/submit ship a new binary; update ships JS only** — anything touching native code needs a new build. Minimal `eas.json` with the three default profiles plus channels. Each build is stamped with a `channel`; a channel maps to a same-named EAS Update branch by default (SDK 55 stable builders: RN 0.83.1, Xcode 26 on iOS, Android 16 target): ```jsonc { "cli": { "version": ">= 16.0.0" }, "build": { "development": { "developmentClient": true, "distribution": "internal", "channel": "development" }, "preview": { "distribution": "internal", "channel": "preview" }, "production": { "channel": "production", "autoIncrement": true } }, "submit": { "production": {} } } ``` Run `scripts/verify.sh` inside an Expo project to gate `eas.json`, the runtime policy, committed secrets, and New-Arch readiness. ## EAS Update mental model Runtime version is the **compatibility gate**. An update applies to a build only when **BOTH the platform AND the `runtimeVersion` match exactly** — there is no "close enough". The chain is: build stamped with a **channel** → channel points at a **branch** → you publish updates to a branch. Updates flow only down a matching runtime within the linked branch. The #1 footgun: a hardcoded `runtimeVersion` that drifts from the binary, so the update silently never installs and you stare at unchanged devices. ```jsonc // BAD — hardcoded string in app.json; bump a native dep and every old build // silently stops matching, so your "shipped" OTA never reaches users. { "expo": { "runtimeVersion": "1.0.0" } } ``` ```jsonc // GOOD — fingerprint policy: EAS hashes the native runtime and auto-bumps the // runtime version whenever native code/config changes, so updates only ever // reach binaries that can actually run them. { "expo": { "runtimeVersion": { "policy": "fingerprint" } } } ``` Inspect, branch ops, rollouts/rollbacks, republish, and the full "update not applying" decision flow → `references/eas-update.md`. ## Config plugins / CNG **Never hand-edit `ios/` or `android/`** — they are ephemeral, regenerated by `npx expo prebuild --clean` from `app.config` + plugins + autolinking. To change native config, write a config plugin (a function in the `plugins` array) or use a mod like `withInfoPlist` / `withAndroidManifest`. ```ts // app.plugin.ts — add an iOS Info.plist key during prebuild, the CNG way. import { ConfigPlugin, withInfoPlist } from "expo/config-plugins"; const withCameraUsage: ConfigPlugin<{ reason: string }> = (config, { reason }) => withInfoPlist(config, (cfg) => { cfg.modResults.NSCameraUsageDescription = reason; return cfg; }); export default withCameraUsage; ``` ```ts // app.config.ts — dynamic config; reference the plugin with its options. export default { expo: { name: "MyApp", runtimeVersion: { policy: "fingerprint" }, plugins: [["./app.plugin.ts", { reason: "Scan receipts" }]], }, }; ``` Plugin anatomy, dangerous mods, mod ordering, and prebuild troubleshooting → `references/config-plugins.md`. ## New Architecture & SDK upgrade **The Legacy Architecture is gone.** SDK 54 was the last release to ship it; SDK 55 (2026-02-25, current stable) removed it entirely, and the SDK 56 beta builds on that. On any supported SDK you are **already** on the New Architecture — it is always enabled and cannot be turned off. The `newArchEnabled` flag was deleted from `app.json` in SDK 55; if you still carry one, it is dead config — remove it. There is no legacy fallback to lean on, so a dependency that only works on the old architecture is now a hard blocker, not a "flip the flag back" escape hatch. Upgrade checklist: 1. `npx expo install expo@latest --fix` — bump SDK and align every dependency. 2. `npx expo-doctor@latest` — catch deps that never made the New-Arch jump before you build. 3. `npx expo prebuild --clean` — regenerate native dirs (managed/CNG apps). 4. Build a `development`/`preview` binary on a device before promoting to production. Two upgrade tripwires: every Android app is **edge-to-edge** (on since SDK 54, non-negotiable) — audit manual inset/status-bar code. And SDK 55+ ships **Hermes bytecode diffing** for EAS Update (~75% smaller OTA downloads) automatically; you get it for free once both the build and the update are on SDK 55+. ## Credentials & secrets - Let **EAS manage credentials** (signing keys, provisioning profiles) by default — it stores and rotates them server-side so they never touch the repo. - Supply per-profile config through **EAS environment variables** / `.env` files scoped by profile, not committed plaintext. - **Never commit** a keystore (`*.jks`/`*.keystore`), `*.p12`, or `*.mobileprovision`, and never put API keys in `app.config`/`app.json` — anything in app config ships inside the public bundle. See `../secure-coding/SKILL.md`. ## EAS Workflows EAS Workflows are Expo's own CI: YAML in `.eas/workflows/`. Jobs use pre-packaged `type`s (`build`, `submit`, `update`) and chain via `needs:` + outputs. Route to the `github-actions` skill **only** when the user explicitly wants GH Actions or non-Expo CI. ```yaml # .eas/workflows/release-android.yml — build then submit, chained by needs. name: Release Android on: push: branches: [main] jobs: build: type: build params: platform: android profile: production submit: needs: [build] type: submit params: platform: android build_id: ${{ needs.build.outputs.build_id }} ``` ## Plan limits (set expectations) EAS **Free**: 15 Android + 15 iOS builds/month on the **low-priority queue only** (peak waits can exceed an hour). High-priority queue needs a paid plan; the Production plan includes 2 build concurrencies, with extra concurrency at $50/concurrency/month, up to 5 extra. If a user complains about build queue waits, the fix is usually the plan, not the config. (Pricing per expo.dev/pricing, verified 2026-06-02; re-check before quoting — Expo adjusts tiers and dollar figures.) ## Anti-patterns | Anti-pattern | Do instead | |---|---| | Editing `ios/Info.plist` directly | `prebuild --clean` overwrites it; write a config plugin / `withInfoPlist`. | | Hardcoding `runtimeVersion: '1.0.0'` because it is simpler | it drifts from the binary; updates silently stop matching. Use the `fingerprint` policy. | | Expecting an OTA update to deliver a bumped native dep | EAS Update is JS-only; native changes need a new `eas build`. | | Telling users to "just refresh" when a published update does not land | check the channel→branch and exact runtime match first — wrong channel = no delivery. | | Testing a custom native module in Expo Go | Expo Go can't load arbitrary native code; build a dev client. | | Upgrading the SDK and building straight to production | run `expo-doctor` + a preview build first; there is no Legacy-Arch fallback to catch a New-Arch-incompatible dep. | | Setting `newArchEnabled: false` to dodge a broken native dep | the flag was removed in SDK 55 and the Legacy Architecture is gone; fix or replace the dep. | | Committing the keystore so CI can sign | never; let EAS manage credentials or use EAS secrets. | | Putting the API key in `app.config` extra | app config ships in the public bundle; use EAS env vars / a backend. | | Reaching for GitHub Actions to call `eas build` | EAS Workflows is the native CI; only reach for github-actions if explicitly required. | ## Project grounding (02-DOCS + CLAUDE.md) In a project with a `02-DOCS/` layer (the [`harness`](../harness/SKILL.md) wiki), read `02-DOCS/wiki/stack/expo.md` first and record this app's shipping decisions there — managed-vs-bare, runtime-version policy, channel/branch map, SDK/New-Arch status — linked from the root `CLAUDE.md` `## Knowledge map`, bumping its `Updated` date when a convention changes. No `02-DOCS/`? Skip silently. Conventions are *recorded, not gated* — never block the task on this.
Voir sur GitHub