Intro
Expo est devenu le point de départ de nombreux développeurs React Native car il élimine la friction des outils de compilation natifs. Pourtant, les équipes de production découvrent rapidement que les applications réelles nécessitent plus que expo start. Elles doivent comprendre ce qui se passe derrière la CLI, comment le pipeline de build fonctionne, où vit la configuration, et comment diagnostiquer un échec de mise à jour OTA ou un build Android qui compile localement mais plante sur un appareil physique.
Cet article explique les concepts avancés d'Expo à travers des exemples pratiques : le graphe de modules interne, l'ordre de résolution de la configuration d'application, le code natif personnalisé via des development builds, la gestion des variables d'environnement, les rouages d'EAS Build et d'EAS Update, et les modes de défaillance qui surviennent dans les équipes réelles. Chaque section inclut des commandes concrètes, le résultat attendu et une procédure de récupération. L'objectif est la confiance opérationnelle : vous devez être capable d'inspecter avant de modifier, de limiter le rayon d'impact, de ne jamais exposer de secrets, et de vérifier le résultat.
Les exemples supposent Expo SDK 51 ou plus récent, Node.js 20 LTS, et la CLI Expo installée globalement. Là où le comportement diffère entre le managed workflow et les development builds, la différence est explicitement indiquée.
Inventaire des versions et de l'environnement
Avant de modifier quoi que ce soit dans un projet Expo, enregistrez les versions exactes et la topologie. La première commande en lecture seule est :
npx expo --version
Résultat attendu :
1.0.0
Ensuite, inspectez le SDK installé et le graphe de dépendances :
npx expo install --check
Si des packages sont obsolètes, la sortie liste les dépendances incohérentes avec les versions attendues, par exemple :
The following packages should be updated for best compatibility with the installed expo version:
[email protected] - expected version: ~51.0.28
[email protected] - expected version: 0.74.5
N'exécutez pas expo install aveuglément. Comparez d'abord l'état actuel avec le fichier de verrouillage :
git status --short
Seulement après avoir confirmé un arbre de travail propre, appliquez une mise à jour minimale de dépendance :
npx expo install expo@~51.0.28
La portée des versions est importante car les packages Expo sont liés au SDK. Mélanger des packages SDK 50 et SDK 51 provoque des erreurs d'exécution comme Invariant Violation: Native module cannot be null. La voie de récupération consiste à épingler tous les packages liés à Expo au même SDK avec npx expo install --fix, puis à exécuter npx expo-doctor pour valider le projet.
La topologie de déploiement d'un projet compte également. Si vous utilisez EAS Update, listez les canaux et les branches :
npx eas channel:list
Résultat attendu pour une configuration de staging typique :
Name Created
production 2024-03-01T10:00:00Z
staging 2024-03-01T10:00:00Z
Enregistrez ces valeurs dans votre runbook : version du SDK, version de la CLI, version de Node, noms des canaux et noms des profils de build. Cet inventaire est la base de chaque section ultérieure.
Chemin de configuration sûr
Expo lit la configuration depuis app.json, app.config.js ou app.config.ts, et l'ordre de résolution compte. Si app.json et app.config.js existent tous deux, le fichier JavaScript l'emporte. Cela permet une configuration spécifique à l'environnement sans duplication de fichiers.
Un motif sûr consiste à conserver les valeurs statiques dans app.json et à exposer uniquement les valeurs dynamiques via app.config.js :
// app.config.js
export default ({ config }) => {
return {
...config,
extra: {
apiUrl: process.env.API_URL || 'https://api.example.com',
enableAnalytics: process.env.ENABLE_ANALYTICS === 'true',
},
};
};
Cette configuration est ensuite accessible en JavaScript via expo-constants :
import Constants from 'expo-constants';
const apiUrl = Constants.expoConfig?.extra?.apiUrl;
Important : les valeurs dans extra sont intégrées au bundle client au moment du build. Ce ne sont pas des secrets. Si vous mettez une clé privée dans extra, elle est livrée à chaque utilisateur. Toute variable d'environnement contenant des secrets doit rester côté serveur ou être fournie via les secrets EAS Build.
Pour ajouter un secret à EAS Build sans le committer :
npx eas secret:create --name API_PRIVATE_KEY --value "sk_live_12345" --scope project
Ensuite, référencez-le dans eas.json :
{
"build": {
"production": {
"env": {
"API_PRIVATE_KEY": "$API_PRIVATE_KEY"
}
}
}
}
La variable n'est disponible que pendant le processus de build, pas dans l'application en cours d'exécution.
Avant de modifier toute configuration, observez la configuration fusionnée actuelle avec :
npx expo config --type public
Cela affiche la configuration publique résolue qu'Expo utilisera. Vérifiez que les plugins, les autorisations et les valeurs extra sont conformes aux attentes. Ensuite, effectuez un changement à la fois et relancez npx expo config --type public pour confirmer le delta.
Une erreur courante consiste à modifier android.package ou ios.bundleIdentifier après que l'application a été publiée sur les stores. Changer le nom du package crée une nouvelle identité d'application et invalide les builds existants et les mises à jour OTA. La voie de récupération pour un changement accidentel consiste à revenir sur l'identifiant dans app.json et à reconstruire le binaire. Ne changez jamais les identifiants de package après la soumission au store sans un plan de migration complet.
Graphe de modules interne et code natif personnalisé
Le managed workflow masque les dossiers natifs, mais les modules Expo exécutent toujours du code natif. Lorsque vous avez besoin d'un module natif personnalisé, vous utilisez soit un config plugin qui modifie le projet natif au moment du prebuild, soit vous créez un development build qui inclut votre propre code natif.
Un config plugin est une fonction JavaScript qui reçoit la configuration du projet natif et la modifie. Par exemple, pour ajouter la permission Android pour Bluetooth :
// plugins/withBluetoothPermission.js
const { withAndroidManifest } = require('@expo/config-plugins');
module.exports = function withBluetoothPermission(config) {
return withAndroidManifest(config, async (config) => {
const manifest = config.modResults;
const usesPermission = {
$: {
'android:name': 'android.permission.BLUETOOTH',
},
};
manifest.manifest['uses-permission'] = manifest.manifest['uses-permission'] || [];
manifest.manifest['uses-permission'].push(usesPermission);
return config;
});
};
Enregistrez le plugin dans app.json :
{
"expo": {
"plugins": ["./plugins/withBluetoothPermission"]
}
}
Maintenant, npx expo prebuild appliquera ce plugin au projet Android généré. Pour vérifier, exécutez :
npx expo prebuild --no-install
Ensuite, inspectez android/app/src/main/AndroidManifest.xml et confirmez que la ligne de permission existe. L'approche par plugin est sûre car elle est reproductible : tout prebuild futur régénère les mêmes fichiers natifs. Si vous modifiez les fichiers natifs directement sans plugin, le prochain prebuild écrasera vos modifications. La récupération de cette erreur consiste à recréer le changement en tant que config plugin et à relancer le prebuild.
Pour les modules React Native personnalisés qui ne font pas partie du SDK Expo, vous ne pouvez pas utiliser un build géré simple. Vous avez besoin d'un development build :
npx expo run:android --variant debug
Cela génère le projet natif, le compile avec votre code personnalisé et installe l'application de débogage sur un appareil connecté. Le build de débogage inclut le lanceur de développement Expo, vous pouvez donc toujours charger le JavaScript depuis Metro.
Avant de créer un development build, vérifiez que toutes les dépendances natives sont compatibles avec le SDK Expo :
npx expo-doctor
Résultat attendu sur un projet sain :
✅ Checked 15/15 checks passed
Si expo-doctor signale un package incompatible, la commande suggère la version correcte. Appliquez la suggestion avec npx expo install <package>@<version> et relancez le docteur.
Variables d'environnement et secrets
De nombreuses équipes rencontrent pour la première fois les variables d'environnement Expo lorsqu'elles essaient d'utiliser process.env.API_URL dans leur code React Native. Cela échoue car le bundler Metro n'insère pas automatiquement les variables d'environnement Node.js dans le bundle client. Les approches correctes sont :
- Utiliser
app.config.jsetexpo-constantscomme montré précédemment pour les valeurs non secrètes. - Utiliser des fichiers
.envavecexpo-envoudotenvuniquement si les valeurs ne sont pas secrètes et que vous acceptez le risque de les inclure dans le bundle. - Utiliser les secrets EAS Build pour les secrets au moment du build.
- Utiliser un mécanisme de récupération de secrets à l'exécution, comme la récupération depuis un point de terminaison sécurisé après authentification.
Voici un exemple concret de chargement d'un fichier .env avec expo-env en développement :
npx expo install expo-env
Dans babel.config.js :
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
['module:expo-env', {
envFiles: ['.env.development', '.env.production']
}]
]
};
};
Puis dans le code :
import { API_URL } from 'expo-env';
La valeur est insérée au moment du bundling. Une erreur courante est de committer un fichier .env contenant de vraies clés. Pour éviter cela, ajoutez chaque fichier .env* à .gitignore et fournissez un .env.example avec des valeurs factices :
# .env.example
API_URL=https://api.example.com
Pour vérifier qu'aucun secret n'est dans le bundle, recherchez dans le JavaScript compilé après le build :
npx expo export --platform web --output-dir dist
Puis utilisez grep :
grep -r "API_PRIVATE_KEY" dist/ || echo "No secret found"
Si un secret est trouvé, la récupération consiste à supprimer le secret de la configuration, à révoquer la clé immédiatement et à reconstruire l'application. Cette vérification devrait faire partie de chaque pipeline CI.
EAS Build et EAS Update : les coulisses
EAS Build exécute votre compilation native dans le cloud. Comprendre ses rouages vous aide à déboguer les builds échoués sans deviner. Un eas.json typique pour un build de production ressemble à ceci :
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"autoIncrement": true,
"android": {
"buildType": "app-bundle"
}
}
},
"submit": {
"production": {}
}
}
Pour lancer un build :
npx eas build --platform android --profile production
La CLI téléverse votre projet (en respectant .easignore), puis exécute expo prebuild, installe les dépendances, exécute le build Gradle et signe la sortie. Les journaux de build sont disponibles dans le terminal et sur le tableau de bord EAS.
Si le build échoue, la première commande de diagnostic est :
npx eas build:view
Sélectionnez le build échoué et faites défiler jusqu'à l'erreur. Signaux d'échec courants :
- Erreur de résolution de dépendance Gradle - corrigez en alignant les versions du SDK ou en utilisant
expo install. - Variable d'environnement manquante - vérifiez
eas secret:listet les références danseas.json. - Keystore perdu - si vous perdez le keystore Android, vous ne pouvez pas mettre à jour l'application sur Google Play. EAS stocke votre keystore si vous le laissez le générer ; sinon, gardez une sauvegarde locale.
EAS Update pousse le JavaScript et les assets par liaison radio sans examen du store. La commande pour publier une mise à jour :
npx eas update --branch production --message "Fix crash on login"
La mise à jour est liée au build natif via le champ runtimeVersion dans app.json. Par exemple :
{
"expo": {
"runtimeVersion": "1.0.0"
}
}
Seuls les builds avec la même runtimeVersion reçoivent la mise à jour. Cela empêche un code natif incompatible de télécharger un nouveau bundle JS. Le principal écueil est de confondre runtimeVersion avec la version de l'application dans package.json. Elles sont indépendantes ; runtimeVersion doit rester stable pour un binaire natif donné.
Pour vérifier qu'une mise à jour est en ligne :
npx eas update:list --branch production
Le résultat attendu montre les mises à jour avec leurs identifiants et horodatages. Pour annuler une mauvaise mise à jour, vous pouvez republier le commit précédent avec une nouvelle mise à jour, ou utiliser le drapeau --republish :
npx eas update --branch production --message "Rollback to last stable" --republish
Testez toujours les mises à jour sur une branche de staging d'abord. Une mauvaise mise à jour OTA atteint les utilisateurs plus rapidement que toute publication sur le store, donc le rayon d'impact est immédiat.
Débogage et diagnostics
Lorsque quelque chose casse dans une application Expo, le chemin de diagnostic fiable le plus rapide commence par expo start --clear pour réinitialiser le cache Metro :
npx expo start --clear
Si le bundler échoue toujours, inspectez les journaux Metro pour les erreurs de résolution de module. Une erreur classique est :
Unable to resolve module './src/components/Button' from '/path/to/project/App.tsx'
Cela signifie que le chemin d'importation est incorrect. Vérifiez la sensibilité à la casse du fichier ; les systèmes de fichiers Android et iOS sont insensibles à la casse, mais Metro est sensible à la casse. Renommez le fichier ou corrigez l'importation et redémarrez le bundler.
Pour les plantages à l'exécution sur un development build, utilisez les journaux de l'appareil :
npx react-native log-android
Filtrez pour ReactNativeJS pour voir les erreurs JavaScript :
ReactNativeJS: Error: Cannot read property 'map' of undefined
Ajoutez un console.trace() temporaire dans le chemin de code suspecté et reconstruisez pour voir la pile. Une fois corrigé, retirez la trace.
Le débogage à distance a changé : l'ancien débogueur Chrome est remplacé par React Native DevTools. Lancez-le avec :
npx expo start --dev-client
Ensuite, appuyez sur j dans le terminal. Cela ouvre une fenêtre de débogage dédiée avec des points d'arrêt, des source maps et l'inspecteur de composants React. Une erreur courante est d'essayer de connecter Chrome directement au port Metro, ce qui nécessite maintenant une configuration spéciale et échoue souvent. Utilisez les DevTools intégrés pour SDK 50 et plus récent.
Pour vérifier que le bundle JavaScript est valide pour la production, exportez-le localement :
npx expo export --platform android --output-dir dist-android
Servez-le ensuite avec un serveur statique et chargez-le dans l'application Expo Go en utilisant une URL personnalisée. Cela simule une mise à jour OTA avant la publication.
Modes de défaillance et récupération
Les défaillances les plus perturbatrices ne sont pas celles qui surviennent pendant le développement, mais celles qui affectent les applications publiées. Voici trois modes de défaillance concrets et des étapes de récupération testées.
Défaillance 1 : la mise à jour OTA s'installe mais l'application plante au lancement
Symptôme : Les utilisateurs signalent que l'application se ferme immédiatement après la mise à jour. Cause : La mise à jour référence un module natif absent du binaire installé, ou le bundle JavaScript est corrompu. Récupération :
- Republiez la mise à jour précédente connue comme bonne :
npx eas update --branch production --message "Emergency rollback" --republish. - Demandez aux utilisateurs affectés de forcer la fermeture de l'application et de la rouvrir ; Expo Go et les development builds vérifient les mises à jour au lancement.
- Enquêtez sur la mauvaise mise à jour en comparant les commits. Ne publiez pas de nouvelle mise à jour tant que la cause n'est pas confirmée.
Défaillance 2 : EAS Build réussit mais Google Play rejette l'AAB
Symptôme : Le build se termine, mais la Play Console signale une erreur concernant le code de version ou la signature. Cause : Le versionCode n'a pas été incrémenté, ou la clé de téléversement diffère de la version précédente. Récupération :
- Vérifiez le
versionCodeactuel dansapp.jsonouapp.config.js. - Incrémentez-le manuellement ou définissez
"autoIncrement": truedanseas.jsonpour le profil de production. - Vérifiez la clé de signature : si EAS a généré le keystore, récupérez-le depuis le tableau de bord EAS ou utilisez
npx eas credentialspour le télécharger. Si vous avez perdu le keystore et n'avez pas utilisé EAS, Google ne peut pas aider ; vous devez publier une nouvelle application.
Défaillance 3 : le config plugin casse le prebuild sur CI
Symptôme : Le prebuild local fonctionne, mais la CI échoue avec une erreur comme Cannot find module './plugins/withCustomPlugin'. Cause : Le fichier du plugin n'est pas inclus dans le téléversement parce que .easignore ou .gitignore exclut le répertoire plugins. Récupération :
- Vérifiez le journal de build pour le fichier manquant exact.
- Ajoutez le fichier manquant au projet et assurez-vous que
.easignorene l'exclut pas (le.easignorepar défaut inclut tout ce qui n'est pas commité, mais si le plugin est gitignoré, il ne sera pas téléversé). - Committez le plugin et relancez le build.
Chaque étape de récupération suppose que vous disposez d'un inventaire de base de la première section. Sans numéros de version et noms de canaux, la récupération devient un jeu de devinettes.
Pièges courants
Au-delà des modes de défaillance ci-dessus, plusieurs pièges apparaissent à plusieurs reprises dans les projets Expo :
1. Traiter app.json comme la seule source de vérité
De nombreux développeurs modifient app.json et s'attendent à ce que le changement s'applique immédiatement, mais si app.config.js existe, il remplace app.json. La configuration fusionnée n'est visible qu'avec npx expo config --type public. Exécutez toujours cette commande après des changements de configuration.
2. Utiliser les mauvaises versions de packages
Exécuter npm install react-native-svg sans npx expo install peut installer une version incompatible avec le SDK actuel. Cela provoque des erreurs natives cryptiques. La correction est npx expo install react-native-svg qui choisit la version correcte.
3. Ignorer le runtimeVersion dans les mises à jour OTA
Si runtimeVersion change, les builds existants cessent de recevoir des mises à jour. C'est intentionnel mais souvent accidentel. Gardez runtimeVersion stable pour un build natif donné, et ne le modifiez que lorsque le code natif change.
4. Committer des secrets dans app.config.js
Les variables d'environnement sont tentantes, mais expo-constants expose extra au client. Utilisez les secrets EAS et la validation côté serveur.
5. Supprimer les dossiers ios et android sans plan de prebuild
Si vous utilisez des development builds, les dossiers natifs sont générés. Les supprimer est sûr si vous avez des config plugins et exécutez npx expo prebuild à nouveau. Mais si vous avez modifié manuellement ces dossiers, les supprimer perd du travail. Récupérez en réappliquant les modifications comme plugins.
6. Ne pas tester les mises à jour OTA avant de publier en production
Publiez toujours sur une branche de staging, installez la mise à jour sur un appareil de test et vérifiez. Une mauvaise mise à jour de production affecte chaque utilisateur immédiatement.
Liste de contrôle des opérations
Utilisez cette liste de contrôle avant chaque version ou changement de configuration majeur :
- [ ] Exécutez
npx expo-doctoret résolvez tous les problèmes. Responsable : Développeur principal. Fréquence : à chaque build. - [ ] Exécutez
npx expo config --type publicet vérifiez la sortie. Responsable : Ingénieur DevOps. Fréquence : avant chaque build. - [ ] Confirmez que
runtimeVersiondansapp.jsoncorrespond au build natif prévu. Responsable : Gestionnaire de version. Fréquence : à chaque version. - [ ] Vérifiez qu'aucun secret n'est présent dans
app.config.js, les fichiers.envou le bundle exporté. Responsable : Réviseur de sécurité. Fréquence : avant les builds de production. - [ ] Publiez une mise à jour OTA sur un canal de staging et testez sur un appareil physique. Responsable : Ingénieur QA. Fréquence : à chaque mise à jour.
- [ ] Vérifiez que la dernière mise à jour est en ligne sur la branche de production avec
npx eas update:list. Responsable : Gestionnaire de version. Fréquence : après chaque publication en production. - [ ] Documentez tout changement natif manuel comme config plugins. Responsable : Développeur mobile. Fréquence : continu.
Chaque élément a un seul responsable pour éviter la dilution de la responsabilité.
Conclusion
Le travail avancé avec Expo ne consiste pas à mémoriser des commandes ; il s'agit de comprendre l'ordre de résolution, le pipeline de build et les signaux de défaillance. Les pratiques de cet article réduisent les surprises : inventaire d'abord, changement d'une chose à la fois, vérification avec des commandes explicites, et conservation d'un chemin de récupération.
Commencez par une vérification à faible risque : exécutez npx expo config --type public et comparez la sortie avec votre attente. Ensuite, ajoutez un config plugin pour un petit changement natif et exécutez npx expo prebuild --no-install. Ces deux exercices construisent la mémoire musculaire pour le reste.
Un flux de travail Expo fiable rend les défaillances visibles, protège les secrets, limite les changements à la ressource visée et définit la récupération avant qu'un incident ne force la décision. Versionnez tout, automatisez les vérifications et ne livrez jamais une configuration que vous n'avez pas vue dans la sortie fusionnée.