12
minutes
Mis à jour le
7/11/2024


Share this post

Découvrez comment la Génération Native Continue (CNG) d'Expo révolutionne le développement React Native. Guide technique complet sur le prebuild et l'automatisation du code natif.

#
React Native
#
Mobile application
Gabriela Bertoli
Software Engineer

À propos des applications mobiles

Vous vous lancez dans le développement mobile ? Vous vous demandez sûrement comment créer des applications qui fonctionnent simultanément sur différents appareils.

En réalité, parfois, nous choisissons de ne pas le faire. La question se pose : devons-nous opter pour le natif, avec des bases de code distinctes pour iOS et Android, ou pour le cross-platform, en poursuivant le rêve du "Coder une fois, déployer partout" ? React Native s'est imposé comme une solution cross-platform privilégiée par de nombreux développeurs.

"Coder une fois, déployer partout", disaient-ils. "Ca sera amusant". Et ça l'est... jusqu'à ce que vous vous retrouviez à débuguer simultanément sur Xcode et Android Studio, vous demandant si "cross-platform" n'était pas juste une plaisanterie. Car "cross-platform" ne signifie pas "zéro code natif".

Le code natif reste incontournable. Chaque plateforme nécessite ses propres configurations - gestion des dépendances, paramètres de build, permissions, gestion des ressources. Bien que ces tâches puissent sembler gérables individuellement, elles se complexifient avec chaque intégration tierce, créant des configurations natives complexes que les développeurs doivent maintenir et synchroniser minutieusement entre les plateformes.

C'est là qu'Expo entre en jeu, réinventant complètement cette expérience en transformant les tâches natives manuelles en configurations déclaratives. Pour y parvenir, ils ont introduit la Génération Native Continue (CNG), et aujourd'hui, nous allons démystifier son implémentation technique !

Mais commençons par un rappel sur le fonctionnement d'une application React Native.

L'anatomie d'une application React Native

Si vous maîtrisez déjà React Native, vous pouvez passer cette section. Pour les autres, voici un aperçu rapide 😃

Une application cross-platform construite avec React Native combine des couches natives et JavaScript pour fonctionner sur un appareil natif.

La couche JavaScript, basée sur React, définit l'interface utilisateur et la logique applicative, s'exécutant dans un moteur JavaScript (comme Hermes, un moteur JS optimisé pour RN) intégré à l'application native.

Les couches natives, écrites dans des langages spécifiques aux plateformes (Swift/Objective-C pour iOS, Java/Kotlin pour Android), offrent une interopérabilité C++ via des mécanismes intégrés : Objective-C++ (fichiers .mm) pour iOS et JNI (Java Native Interface) pour Android.

À partir de ces éléments, le cœur C++ de React Native (l'Interface JavaScript ou JSI) sert d'interface bas niveau entre JavaScript et le code natif, créant et maintenant des liaisons - des objets hôtes C++ qui peuvent être détenus et appelés directement depuis JavaScript.

Dans React Native, trois éléments principaux sont construits sur cette stack :

  • Fabric : L'aspect visuel — le moteur de rendu qui transforme les composants UI
  • Turbo Modules : Les fonctionnalités — fournissent les fonctionnalités natives
  • CodeGen : Les connexions — génère l'interface type-safe pour que Fabric et Turbo Modules puissent interagir avec JSI

Lorsque vous examinez une application React Native, son point d'entrée est une enveloppe native (située dans les couches natives) qui initialise l'environnement JS et charge le code JS bundlé. Les dépendances natives sont gérées via des systèmes de build spécifiques aux plateformes (CocoaPods pour iOS, Gradle pour Android).

Le processus de build compile le code natif et C++ (via les compilateurs de plateforme), bundle le code JS, et les combine en une application prête pour la production avec un binaire final (.ipa pour iOS, .apk/.aab pour Android). À l'exécution, ces composants compilés collaborent pour créer les liaisons JSI qui font le pont entre JS et le code natif.

Une fois votre application compilée pour la production, vous pouvez mettre à jour le bundle JS sans mettre à jour l'application entière grâce aux mises à jour OTA, car le bundle JS est traité comme un asset pour l'application native. Mais pour les parties natives, une nouvelle compilation est nécessaire.

Ce que cela signifie pour les développeurs web

Si vous venez du développement web et React, considérez React Native comme React pour mobile, avec quelques différences clés :

  • Au lieu d'éléments DOM, vous utilisez des composants React Native qui correspondent à des éléments UI natifs
  • Vous accédez aux APIs spécifiques aux plateformes (ex : caméra, capteurs, système de fichiers) via les turbo modules
  • Le processus de build est plus complexe, impliquant une compilation native aux côtés du bundling JS

Bien que l'architecture principale diffère significativement du fonctionnement d'une application web, React Native et Expo supportent en fait l'intégration web grâce à différentes solutions et bibliothèques permettant un code source unique entre toutes les plateformes principales (web, iOS et Android).

Comprendre le fonctionnement d'Expo prebuild

L'écosystème Expo ayant considérablement évolué depuis sa création, comprendre ses composants et les éléments exploitables dans une "application Expo" peut être complexe. Il y a beaucoup de nouveau vocabulaire, même pour les développeurs RN. Dans cette section, nous allons explorer et résumer les concepts clés qui alimentent l'architecture et le workflow d'Expo.

Les fondamentaux d'Expo

Expo introduit plusieurs nouveaux concepts. Pour apprécier son fonctionnement — particulièrement si vous comptez créer des modules ou plugins personnalisés qui s'intègrent à Expo — il est crucial de comprendre les mécanismes sous-jacents. Analysons les composants essentiels et examinons leurs rôles dans l'écosystème. Nous couvrirons :

  • La signification de CNG
  • Le SDK Expo
  • Les modules Expo
  • Les plugins de configuration Expo
  • Le fonctionnement de la commande prebuild
Qu'est-ce que la CNG dans Expo, exactement ?

Dans une application React Native traditionnelle, votre couche native est stockée dans des dossiers natifs (ios/, android/) au sein de votre projet. Historiquement, ces dossiers étaient traités comme du code source - des fichiers projet permanents que les développeurs devaient maintenir, modifier et gérer manuellement. Toute modification des dépendances ou configurations natives nécessitait une manipulation minutieuse de ces dossiers pour éviter les changements cassants ou les conflits.

Expo réinvente cette expérience avec la Génération Native Continue (CNG). Au lieu de générer le code natif une fois puis le gérer manuellement, CNG traite le code natif comme un artéfact généré à partir de vos fichiers JavaScript et de configuration. Concrètement :

  • Les dossiers natifs sont générés à la demande via la commande expo prebuild
  • Plus de maintenance manuelle du code natif
  • Régénération propre à partir de la source de vérité (app.json, plugins)

Cette approche transforme le workflow de développement, passant de "maintenir le code natif" à "déclarer ce dont vous avez besoin" - rendant tout le cycle de vie du développement mobile plus maintenable, de la configuration initiale aux mises à jour des dépendances et au déploiement.

L'idée centrale de CNG : le code natif devient un output de build plutôt qu'un code source à maintenir.

Le code natif devenant un output prévisible, réversible et personnalisable compris par le framework, CNG permet de continuer à utiliser les outils et services Expo tout en vous permettant d'ajouter des modules natifs personnalisés et d'effectuer des modifications au niveau natif.

Par exemple, la commande prebuild peut s'exécuter localement sur votre machine, et c'est la même commande qui est utilisée lors de la compilation de votre application avec les services Expo.

De quoi ai-je besoin pour l'utiliser ?
  • Le SDK Expo
  • Un fichier app.json ou app.config.json valide à la racine de votre projet : c'est un fichier de configuration d'application pour Expo
💡 Bien que cela semble assez simple, pour les applications plus anciennes qui migrent vers Expo, certaines solutions alternatives sont nécessaires. L'équipe Expo a fourni un exemple de commit de migration que vous pouvez consulter si vous essayez de l'appliquer à votre projet. Revenez-y peut-être une fois que vous serez plus à l'aise avec les concepts.
SDK Expo (La boîte à outils)

Le SDK Expo comprend :

  1. le package expo central
  2. Tous les modules expo-* qui sont inclus dans le SDK ou peuvent être installés une fois que vous avez le package central
  3. Un ensemble de code natif pré-configuré et d'outils de build
Les deux (@)expo

@expo est un package scopé qui contient des outils comme "@expo/cli" (qui contient et définit la commande prebuild) et @expo/prebuild-config (principalement utilisé par @expo/cli pour la configuration interne de la commande prebuild).

Le package principal expo inclut en fait beaucoup de ces packages @expo scopés comme dépendances dans sa configuration package.json, et c'est ce que nous appelons généralement le SDK expo.

Le SDK et les modules Expo

Quand nous parlons de packages/modules Expo, cela peut faire référence à plusieurs choses :

Le package expo central,

Les modules SDK Expo et bibliothèques (qui fournissent des fonctionnalités spécifiques),

Les outils de développement et de build (utilisés pour le développement, la compilation ou la gestion des projets Expo),

ou les packages de configuration (pour configurer le processus de build et de bundling des projets Expo).

Lorsque vous voyez une dépendance expo-*[quelquechose], cela signifie généralement qu'il s'agit d'un package ou utilitaire destiné à fonctionner dans l'écosystème Expo. Vous avez généralement besoin du package central expo, entre autres, pour les intégrer dans un projet.

L'écosystème Expo est la force du framework mais il peut être déroutant quand on débute !

💡 Par exemple, un package utile fourni par Expo est l'utilitaire install-expo-modules qui facilite l'intégration des modules SDK dans une application RN existante. Lorsque vous exécutez :
npx install-expo-modules@latest
.
Cette commande effectue (ou tente d'effectuer) plusieurs choses
automatiquement :

  • Ajoute les dépendances nécessaires (comme expo et expo-modules-core)
  • Modifie votre code natif pour supporter les modules Expo
  • Met à jour vos configurations de build

Imaginons que vous souhaitiez interagir avec la caméra native d'un appareil. Vous essayez d'utiliser le module expo-camera. L'API de la caméra provient du SDK de votre appareil et se trouve en dehors de votre application. Comment tout cela fonctionne-t-il ensemble ? Voici une illustration de ce qui se passe en coulisses :

Lors de l'installation initiale du module expo-camera, le code natif est téléchargé dans le répertoire node_modules de votre projet. Cependant, à ce stade :

  • Ce code ne fait pas encore partie des projets natifs de votre application
  • Le code natif ne peut pas encore être appelé ou utilisé par votre application, car votre code natif n'a pas encore été modifié

Pendant le processus de prebuild :

  • Expo modifie les fichiers projet iOS et Android pour inclure toutes les dépendances natives nécessaires requises par expo-camera
  • Les permissions et configurations nécessaires sont ajoutées aux fichiers projet natifs grâce aux plugins de configuration
La pièce manquante : Les plugins de configuration Expo (et les mods)

Les modules Expo sont assez similaires aux packages React Native standard, mais leur intégration avec Expo leur permet d'utiliser et de fournir des plugins de configuration.

La plupart des modules Expo sont livrés avec des configurations natives intégrées grâce à des plugins de configuration prédéfinis, ce qui permet au développeur d'éviter la configuration native tant qu'il fournit le plugin correspondant à la configuration de son propre projet.

À leur cœur, les plugins sont des fonctions qui modifient un objet ExpoConfig. Les mods (abréviation de modificateurs) sont des actions spécifiques que les plugins utilisent pour modifier des fichiers natifs individuels. On pourrait dire que les plugins décident quoi changer, et les mods sont les outils qu'ils utilisent pour effectuer ces changements spécifiques. Expo utilise ces mods pour effectuer des modifications plus complexes du code natif.

Les plugins de configuration sont principalement appliqués sur l'objet ExpoConfig selon le flux suivant :

Les mods sont exécutés séquentiellement pendant la phase de configuration du prebuild. Chaque mod peut lire l'état actuel de la config et des fichiers projet, faire des modifications, et écrire ces modifications dans le système de fichiers. Ils ne mettent pas seulement à jour l’objet ExpoConfig mais modifient directement les fichiers natifs (Info.plist, build.gradle) pendant la génération (evalModsAsync).

Voici quelques exemples de plugins de configuration fournis par Expo :

  • expo-build-properties, pour modifier les paramètres de build natifs
  • expo-splash-screen : l'écran de démarrage d'une application mobile nécessite une configuration native, qu'Expo expose dans le module expo-splash-screen. C'est à la fois un module expo (pour gérer l'écran de démarrage à l'exécution) et un plugin de configuration* (pour la configuration native) !

*Le processus de prebuild d'Expo inclut une liste de plugins intégrés par défaut qu'Expo applique automatiquement. Lorsque vous installez expo-splash-screen dans votre projet, vous n'avez pas besoin d'ajouter manuellement un plugin de configuration à votre app.json ou app.config.js.

Chaque fois que vous souhaitez automatiser le processus de génération de configuration native au moment du prebuild, vous pouvez également écrire votre propre plugin de configuration personnalisé.

Un cas d'usage courant serait si vous devez intégrer un NativeModule personnalisé non disponible dans l'écosystème Expo à votre processus de prebuild, et que ce module nécessite une configuration native. Dans ce cas, vous devriez exploiter les plugins de configuration :

  1. Créer ou installer le module natif
  2. Ajouter un plugin de configuration personnalisé pour gérer programmatiquement toute configuration native. La convention de nommage est withXXX (où XXX est le nom de votre module personnalisé).
Analysons un exemple concret

Stripe est une solution de traitement des paiements qui prend en charge diverses méthodes de paiement, notamment Apple Pay et Google Pay. Des fonctionnalités comme Apple Pay et Google Pay sont natives à leurs plateformes respectives : elles font partie du système d'exploitation central et disposent d'APIs ou de services directement gérés par iOS et Android. Pour que la bibliothèque stripe-react-native fonctionne avec la CNG, elle doit définir manuellement certaines configurations natives dans un plugin de configuration :

  • Apple Pay nécessite que les applications enregistrent un identifiant de marchand et l'incluent dans un fichier d'autorisation, utilisé par le système iOS pour valider les permissions de l'application à traiter les paiements.
  • Google Pay nécessite des métadonnées dans l'AndroidManifest.xml pour confirmer l'éligibilité de l'application à utiliser les APIs de Google Pay.

En examinant l'implémentation, le plugin prend l'objet de configuration Expo, applique des plugins intermédiaires pour chaque plateforme et le retourne.


Dans le plugin de configuration iOS, il :

  • Récupère le merchantIdentifier des props du plugin
  • Utilise withEntitlementsPlist (un plugin mod fourni par Expo pour accéder et modifier le plist d'autorisations).

withEntitlementsPlist s'attend à ce que vous modifiiez le config.modResults, qui représente le contenu du fichier Entitlements.plist sous forme d'objet JavaScript. Cet objet est en fait typé.

  • Appelle setApplePayEntitlement (une méthode personnalisée dans le fichier plugin) pour mettre à jour le plist.

withEntitlementsPlist modifie le mod entitlements (qui est typé comme un dictionnaire) de votre configuration.


Ensuite, setApplePayEntitlement retourne un Record javascript qui correspond au format du fichier xml .entitlement final.

Maintenant, toute application utilisant la CNG peut utiliser stripe-react-native :

1. La configuration du plugin vous permet de référencer le plugin dans votre propre fichier de configuration app.json expo :

2. Vous pouvez maintenant exécuter la commande prebuild pour appliquer les configurations personnalisées :
npx expo prebuild

Assemblons tous les éléments : que se passe-t-il lorsque vous exécutez expo prebuild ?

Les templates de dossiers natifs sont initialisés. L’objet ExpoConfig est mis à jour tout au long du processus de prebuild et les fichiers natifs sont écrasés par les mods pendant l'étape de synchronisation. À la fin, les dépendances sont réinstallées (pod install pour iOS) et vous obtenez des dossiers natifs configurés 🎉

Vos dossiers natifs sont maintenant des artéfacts de build générés de manière prévisible qui peuvent toujours être régénérés et principalement configurés et versionnés depuis votre couche JS.

Cela signifie que chaque défi de configuration native a désormais une solution déclarative.

  • les propriétés principales de l'application sont gérées via la configuration de l'app
  • les dépendances natives tierces sont gérées via l'autolinking et les plugins
  • les configurations natives personnalisées peuvent être définies programmatiquement via des plugins de configuration
  • même le code natif vraiment personnalisé trouve sa place dans les Modules Expo locaux.

Quelques conseils supplémentaires

Un grand merci à Kadi Kraman de l'équipe Expo pour m’avoir contacté et proposé d’enrichir l’article avec quelques conseils ! 🙌

  • Lors de l'utilisation de prebuild en local, vos dossiers natifs doivent être ajoutés à votre fichier .gitignore, car ils sont maintenant considérés comme des artéfacts générés plutôt que du code source.
  • Un exemple pratique de prebuild en action : lorsque vous exécutez npx expo run:ios ou run:android pour la première fois, Expo exécute automatiquement prebuild pour vous.
  • La commande eas build intègre prebuild dans son processus, mais avec une distinction importante : si vos dossiers natifs sont correctement ignorés dans git, ils sont traités comme des artéfacts générés et eas build utilise la CNG. Cependant, s'ils ne sont pas ignorés, eas build utilisera directement le contenu de vos dossiers natifs existants. Ce comportement est particulièrement important à garder en tête lors du déploiement de vos applications, et c'est en fait ce qui définit la différence entre les workflows "managed" et "bare" d’Expo.
  • Pour l'expérience prebuild la plus fiable, utilisez le flag --clean. Comme les applications mod ne garantissent pas d'être idempotentes (ce qui peut les rendre dangereuses), ce flag assure un contenu correct en réinitialisant le template existant et en ré-appliquant les configurations de zéro.

Laissez le framework gérer la complexité

En automatisant la génération et la gestion du code natif, prebuild et CNG sont des ajouts majeurs apportés à l’écosystème React Native. Bien que ce soit déjà beaucoup à assimiler, nous n'avons fait qu'effleurer la surface, car la suite d'outils disponible aujourd'hui continue de s'étendre et évolue vers des versions stables.

La plupart des développeurs n'auront pas besoin de s'inquiéter d'implémenter des plugins de configuration à partir de zéro—la communauté fait de son mieux pour fournir les plugins manquants. Vous pouvez simplement profiter des solutions fournies par le framework sans plonger dans tous les détails techniques. Mais si vous prenez le temps de les maîtriser, vous pourrez libérer tout le potentiel du framework pour vos applications.

La meilleure façon de donner du sens à tout cela est de le mettre en pratique. 💪 Cela vous sera utile lorsque vous voudrez aller plus loin et implémenter des configurations natives personnalisées ! :) 📱💻