From 8cf38d609b9860d67bdcf9c49daa0a400738adb3 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Sat, 20 Jun 2026 16:46:30 +0100 Subject: [PATCH] feat: add upgrading expo skill (#150) --- .agents/skills/upgrading-expo/SKILL.md | 134 +++++++++++++++ .../skills/upgrading-expo/agents/openai.yaml | 4 + .../references/expo-av-to-audio.md | 132 +++++++++++++++ .../references/expo-av-to-video.md | 160 ++++++++++++++++++ .../upgrading-expo/references/native-tabs.md | 124 ++++++++++++++ .../references/new-architecture.md | 79 +++++++++ .../upgrading-expo/references/react-19.md | 79 +++++++++ .../references/react-compiler.md | 59 +++++++ .../react-navigation-to-expo-router.md | 61 +++++++ skills-lock.json | 11 ++ 10 files changed, 843 insertions(+) create mode 100644 .agents/skills/upgrading-expo/SKILL.md create mode 100644 .agents/skills/upgrading-expo/agents/openai.yaml create mode 100644 .agents/skills/upgrading-expo/references/expo-av-to-audio.md create mode 100644 .agents/skills/upgrading-expo/references/expo-av-to-video.md create mode 100644 .agents/skills/upgrading-expo/references/native-tabs.md create mode 100644 .agents/skills/upgrading-expo/references/new-architecture.md create mode 100644 .agents/skills/upgrading-expo/references/react-19.md create mode 100644 .agents/skills/upgrading-expo/references/react-compiler.md create mode 100644 .agents/skills/upgrading-expo/references/react-navigation-to-expo-router.md create mode 100644 skills-lock.json diff --git a/.agents/skills/upgrading-expo/SKILL.md b/.agents/skills/upgrading-expo/SKILL.md new file mode 100644 index 0000000..5769946 --- /dev/null +++ b/.agents/skills/upgrading-expo/SKILL.md @@ -0,0 +1,134 @@ +--- +name: upgrading-expo +description: Guidelines for upgrading Expo SDK versions and fixing dependency issues +version: 1.0.0 +license: MIT +--- + +## References + +- ./references/react-19.md -- SDK +54: React 19 changes (useContext → use, Context.Provider → Context, forwardRef removal) +- ./references/new-architecture.md -- SDK +53: New Architecture migration guide +- ./references/react-compiler.md -- SDK +54: React Compiler setup and migration guide +- ./references/native-tabs.md -- SDK +55: Native tabs changes (Icon/Label/Badge now accessed via NativeTabs.Trigger.\*) +- ./references/expo-av-to-audio.md -- SDK +55: Migrate audio playback and recording from expo-av to expo-audio +- ./references/expo-av-to-video.md -- SDK +55: Migrate video playback from expo-av to expo-video +- ./references/react-navigation-to-expo-router.md -- SDK +56: Migrate `@react-navigation/*` imports to `expo-router` entry points (codemod + manual mapping) + +## Beta/Preview Releases + +Beta versions use `.preview` suffix (e.g., `55.0.0-preview.2`), published under `@next` tag. + +Check if latest is beta: https://exp.host/--/api/v2/versions (look for `-preview` in `expoVersion`) + +```bash +npx expo install expo@next --fix # install beta +``` + +## Step-by-Step Upgrade Process + +1. Upgrade Expo and dependencies + +```bash +npx expo install expo@latest +npx expo install --fix +``` + +2. Run diagnostics: `npx expo-doctor` + +3. Clear caches and reinstall + +```bash +npx expo export -p ios --clear +rm -rf node_modules .expo +watchman watch-del-all +``` + +## Breaking Changes Checklist + +- Check for removed APIs in release notes +- Update import paths for moved modules +- Review native module changes requiring prebuild +- Test all camera, audio, and video features +- Verify navigation still works correctly + +## Prebuild for Native Changes + +**First check if `ios/` and `android/` directories exist in the project.** If neither directory exists, the project uses Continuous Native Generation (CNG) and native projects are regenerated at build time — skip this section and "Clear caches for bare workflow" entirely. + +If upgrading requires native changes: + +```bash +npx expo prebuild --clean +``` + +This regenerates the `ios` and `android` directories. Ensure the project is not a bare workflow app before running this command. + +## Clear caches for bare workflow + +These steps only apply when `ios/` and/or `android/` directories exist in the project: + +- Clear the cocoapods cache for iOS: `cd ios && pod install --repo-update` +- Clear derived data for Xcode: `npx expo run:ios --no-build-cache` +- Clear the Gradle cache for Android: `cd android && ./gradlew clean` + +## Housekeeping + +- Review release notes for the target SDK version at https://expo.dev/changelog +- If using Expo SDK 54 or later, ensure react-native-worklets is installed — this is required for react-native-reanimated to work. +- Enable React Compiler in SDK 54+ by adding `"experiments": { "reactCompiler": true }` to app.json — it's stable and recommended +- Delete sdkVersion from `app.json` to let Expo manage it automatically +- Remove implicit packages from `package.json`: `@babel/core`, `babel-preset-expo`, `expo-constants`. +- If the babel.config.js only contains 'babel-preset-expo', delete the file +- If the metro.config.js only contains expo defaults, delete the file + +## Deprecated Packages + +| Old Package | Replacement | +| -------------------- | ---------------------------------------------------- | +| `expo-av` | `expo-audio` and `expo-video` | +| `expo-permissions` | Individual package permission APIs | +| `@expo/vector-icons` | `expo-symbols` (for SF Symbols) | +| `AsyncStorage` | `expo-sqlite/localStorage/install` | +| `expo-app-loading` | `expo-splash-screen` | +| expo-linear-gradient | experimental_backgroundImage + CSS gradients in View | + +When migrating deprecated packages, update all code usage before removing the old package. For expo-av, consult the migration references to convert Audio.Sound to useAudioPlayer, Audio.Recording to useAudioRecorder, and Video components to VideoView with useVideoPlayer. + +## expo.install.exclude + +Check if package.json has excluded packages: + +```json +{ + "expo": { "install": { "exclude": ["react-native-reanimated"] } } +} +``` + +Exclusions are often workarounds that may no longer be needed after upgrading. Review each one. +## Removing patches + +Check if there are any outdated patches in the `patches/` directory. Remove them if they are no longer needed. + +## Postcss + +- `autoprefixer` isn't needed in SDK +53. Remove it from dependencies and check `postcss.config.js` or `postcss.config.mjs` to remove it from the plugins list. +- Use `postcss.config.mjs` in SDK +53. + +## Metro + +Remove redundant metro config options: + +- resolver.unstable_enablePackageExports is enabled by default in SDK +53. +- `experimentalImportSupport` is enabled by default in SDK +54. +- `EXPO_USE_FAST_RESOLVER=1` is removed in SDK +54. +- cjs and mjs extensions are supported by default in SDK +50. +- Expo webpack is deprecated, migrate to [Expo Router and Metro web](https://docs.expo.dev/router/migrate/from-expo-webpack/). + +## Hermes engine v1 + +Since SDK 55, users can opt-in to use Hermes engine v1 for improved runtime performance. This requires setting `useHermesV1: true` in the `expo-build-properties` config plugin, and may require a specific version of the `hermes-compiler` npm package. Hermes v1 will become a default in some future SDK release. + +## New Architecture + +The new architecture is enabled by default, the app.json field `"newArchEnabled": true` is no longer needed as it's the default. Expo Go only supports the new architecture as of SDK +53. diff --git a/.agents/skills/upgrading-expo/agents/openai.yaml b/.agents/skills/upgrading-expo/agents/openai.yaml new file mode 100644 index 0000000..09c353d --- /dev/null +++ b/.agents/skills/upgrading-expo/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Upgrading Expo" + short_description: "Upgrade Expo SDKs, fix dependencies, adopt React 19 / React Compiler, and migrate deprecated Expo packages" + default_prompt: "Use $upgrading-expo to upgrade an Expo SDK, run diagnostics, fix dependency conflicts, decide whether prebuild/cache clearing applies, and migrate away from deprecated Expo packages." diff --git a/.agents/skills/upgrading-expo/references/expo-av-to-audio.md b/.agents/skills/upgrading-expo/references/expo-av-to-audio.md new file mode 100644 index 0000000..afacde7 --- /dev/null +++ b/.agents/skills/upgrading-expo/references/expo-av-to-audio.md @@ -0,0 +1,132 @@ +# Migrating from expo-av to expo-audio + +## Imports + +```tsx +// Before +import { Audio } from 'expo-av'; + +// After +import { useAudioPlayer, useAudioRecorder, RecordingPresets, AudioModule, setAudioModeAsync } from 'expo-audio'; +``` + +## Audio Playback + +### Before (expo-av) + +```tsx +const [sound, setSound] = useState(); + +async function playSound() { + const { sound } = await Audio.Sound.createAsync(require('./audio.mp3')); + setSound(sound); + await sound.playAsync(); +} + +useEffect(() => { + return sound ? () => { sound.unloadAsync(); } : undefined; +}, [sound]); +``` + +### After (expo-audio) + +```tsx +const player = useAudioPlayer(require('./audio.mp3')); + +// Play +player.play(); +``` + +## Audio Recording + +### Before (expo-av) + +```tsx +const [recording, setRecording] = useState(); + +async function startRecording() { + await Audio.requestPermissionsAsync(); + await Audio.setAudioModeAsync({ allowsRecordingIOS: true, playsInSilentModeIOS: true }); + const { recording } = await Audio.Recording.createAsync(Audio.RecordingOptionsPresets.HIGH_QUALITY); + setRecording(recording); +} + +async function stopRecording() { + await recording?.stopAndUnloadAsync(); + const uri = recording?.getURI(); +} +``` + +### After (expo-audio) + +```tsx +const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); + +async function startRecording() { + await AudioModule.requestRecordingPermissionsAsync(); + await recorder.prepareToRecordAsync(); + recorder.record(); +} + +async function stopRecording() { + await recorder.stop(); + const uri = recorder.uri; +} +``` + +## Audio Mode + +### Before (expo-av) + +```tsx +await Audio.setAudioModeAsync({ + allowsRecordingIOS: true, + playsInSilentModeIOS: true, + staysActiveInBackground: true, + interruptionModeIOS: InterruptionModeIOS.DoNotMix, +}); +``` + +### After (expo-audio) + +```tsx +await setAudioModeAsync({ + playsInSilentMode: true, + shouldPlayInBackground: true, + interruptionMode: 'doNotMix', +}); +``` + +## API Mapping + +| expo-av | expo-audio | +|---------|------------| +| `Audio.Sound.createAsync()` | `useAudioPlayer(source)` | +| `sound.playAsync()` | `player.play()` | +| `sound.pauseAsync()` | `player.pause()` | +| `sound.setPositionAsync(ms)` | `player.seekTo(seconds)` | +| `sound.setVolumeAsync(vol)` | `player.volume = vol` | +| `sound.setRateAsync(rate)` | `player.playbackRate = rate` | +| `sound.setIsLoopingAsync(loop)` | `player.loop = loop` | +| `sound.unloadAsync()` | Automatic via hook | +| `playbackStatus.positionMillis` | `player.currentTime` (seconds) | +| `playbackStatus.durationMillis` | `player.duration` (seconds) | +| `playbackStatus.isPlaying` | `player.playing` | +| `Audio.Recording.createAsync()` | `useAudioRecorder(preset)` | +| `Audio.RecordingOptionsPresets.*` | `RecordingPresets.*` | +| `recording.stopAndUnloadAsync()` | `recorder.stop()` | +| `recording.getURI()` | `recorder.uri` | +| `Audio.requestPermissionsAsync()` | `AudioModule.requestRecordingPermissionsAsync()` | + +## Key Differences + +- **No auto-reset on finish**: After `play()` completes, the player stays paused at the end. To replay, call `player.seekTo(0)` then `play()` +- **Time in seconds**: expo-audio uses seconds, not milliseconds (matching web standards) +- **Immediate loading**: Audio loads immediately when the hook mounts—no explicit preloading needed +- **Automatic cleanup**: No need to call `unloadAsync()`, hooks handle resource cleanup on unmount +- **Multiple players**: Create multiple `useAudioPlayer` instances and store them—all load immediately +- **Direct property access**: Set volume, rate, loop directly on the player object (`player.volume = 0.5`) + +## API Reference + +https://docs.expo.dev/versions/latest/sdk/audio/ diff --git a/.agents/skills/upgrading-expo/references/expo-av-to-video.md b/.agents/skills/upgrading-expo/references/expo-av-to-video.md new file mode 100644 index 0000000..5c9bec1 --- /dev/null +++ b/.agents/skills/upgrading-expo/references/expo-av-to-video.md @@ -0,0 +1,160 @@ +# Migrating from expo-av to expo-video + +## Imports + +```tsx +// Before +import { Video, ResizeMode } from 'expo-av'; + +// After +import { useVideoPlayer, VideoView, VideoSource } from 'expo-video'; +import { useEvent, useEventListener } from 'expo'; +``` + +## Video Playback + +### Before (expo-av) + +```tsx +const videoRef = useRef