expo-module

Par expo · skills

Guide pour la rédaction de modules et vues natifs Expo à l'aide de l'Expo Modules API (Swift, Kotlin, TypeScript). Couvre le DSL de définition de modules, les vues natives, les objets partagés, les config plugins, les hooks de cycle de vie, l'autolinking et le système de types. À utiliser lors de la création ou de la modification de modules natifs pour Expo.

npx skills add https://github.com/expo/skills --skill expo-module

Écrire des modules Expo

Référence complète pour créer des modules natifs et des vues en utilisant l'API Expo Modules. Couvre Swift (iOS), Kotlin (Android) et TypeScript.

Quand l'utiliser

  • Créer un nouveau module natif Expo ou une nouvelle vue native
  • Ajouter une fonctionnalité native (caméra, capteurs, API système) à une application Expo
  • Encapsuler des SDK de plateforme pour la consommation React Native
  • Créer des config plugins qui modifient les fichiers de projet natifs

Références

Consultez ces ressources selon les besoins :

references/
  native-module.md           DSL de définition de module : Name, Function, AsyncFunction, Property, Constant, Events, système de types, objets partagés
  native-view.md             Composants de vue native : View, Prop, EventDispatcher, cycle de vie de la vue, fonctions basées sur ref
  lifecycle.md               Hooks de cycle de vie : module, app/AppDelegate iOS, listeners d'activité/application Android
  config-plugin.md           Config plugins : modification d'Info.plist, AndroidManifest.xml, lecture des valeurs dans le code natif
  module-config.md           Champs de expo-module.config.json et configuration d'autolinking

Démarrage rapide

Créer un module local (dans une application existante)

Toujours générer le scaffold avec create-expo-module d'abord, puis modifier le code généré. Cela garantit un podspec, build.gradle et config de module corrects — en évitant les erreurs de compilation courantes.

CI=1 npx create-expo-module@latest --local \
  --name MyModule \
  --description "My Expo module" \
  --package expo.modules.mymodule

CI=1 ignore les invites interactives et utilise les flags fournis.

Important : En mode CI=1 (non-interactif), le scaffold crée toujours le répertoire sous modules/my-module/ car le slug est dérivé de customTargetPath qui est undefined pour les modules --local — le flag --name définit uniquement le nom de la classe native, pas le répertoire. Après le scaffold, renommez-le en kebab-case correspondant à votre module (par ex. KeyValueStoremodules/key-value-store/), puis exécutez cd ios && pod install pour que CocoaPods utilise le bon chemin. Ignorer le renommage fonctionne, mais ignorer pod install après tout renommage provoque des erreurs de compilation iOS (« Build input file cannot be found »).

Flags disponibles :

Flag Description Exemple
--name Nom du module natif (PascalCase) --name KeyValueStore
--description Description du module --description "Native key-value storage"
--package Nom du package Android --package expo.modules.keyvaluestore
--author-name Nom de l'auteur --author-name "dev"
--author-email Email de l'auteur --author-email "dev@example.com"
--author-url URL du profil auteur --author-url "https://github.com/dev"
--repo URL du repository --repo "https://github.com/dev/repo"

Le scaffold génère à la fois un module natif (fonctions, events, constantes) et un composant de vue native (exemple WebView avec props et events). Après le scaffold :

  1. Décidez ce dont vous avez besoin : Si vous n'avez besoin que d'un module natif (pas d'UI), supprimez les fichiers de vue. Si vous n'avez besoin que d'une vue native, supprimez le boilerplate de fonction du module. Si vous avez besoin des deux, conservez les deux et remplacez les implémentations.
  2. Supprimez le boilerplate inutile : Le scaffold inclut du code d'exemple (fonction hello(), constante PI, event onChange, vue basée sur WebView avec prop url). Supprimez tout cela et remplacez-le par votre implémentation réelle.
  3. Supprimez les fichiers web si non nécessaires : Le scaffold génère des fichiers *.web.ts/*.web.tsx pour le support de la plateforme web. Supprimez-les si le module est natif uniquement. Supprimez aussi "web" du tableau platforms dans expo-module.config.json.

Éléments à supprimer pour un module uniquement (pas de vue native) :

  • Supprimez ios/MyModuleView.swift, android/.../MyModuleView.kt
  • Supprimez src/MyModuleView.tsx, src/MyModuleView.web.tsx
  • Supprimez le bloc View(...) de la définition du module en Swift et Kotlin
  • Supprimez les types liés à la vue de MyModule.types.ts et l'export de vue de index.ts

Éléments à supprimer pour une vue uniquement (pas de fonctions de module) :

  • Supprimez les blocs Function, AsyncFunction, Constant, Events de la définition du module (conservez Name et View)
  • Simplifiez le fichier du module TypeScript pour exporter uniquement la vue

Structure générée (après renommage de my-module en kebab-case de votre module) :

modules/
  my-module/                     # Renommez en kebab-case, par ex. key-value-store/
    android/
      build.gradle
      src/main/java/expo/modules/mymodule/
        MyModule.kt              # Définition du module (fonctions, events, enregistrement de vue)
        MyModuleView.kt          # Vue native (sous-classe ExpoView)
    ios/
      MyModule.podspec
      MyModule.swift             # Définition du module
      MyModuleView.swift         # Vue native (sous-classe ExpoView)
    src/
      MyModule.ts                # Liaison du module natif
      MyModule.web.ts            # Implémentation web
      MyModule.types.ts          # Types partagés
      MyModuleView.tsx           # Composant de vue native
      MyModuleView.web.tsx       # Composant de vue web
    expo-module.config.json
    index.ts                     # Ré-exporte le module + la vue

Créer un module autonome (pour publication)

npx create-expo-module@latest my-module

Référence de la structure du module

Le DSL Swift et Kotlin partagent la même structure. Les deux plateformes sont affichées ici comme référence — dans d'autres fichiers de référence, Swift est montré comme langue principale sauf si le pattern Kotlin diffère significativement.

Swift (iOS) :

import ExpoModulesCore

public class MyModule: Module {
  public func definition() -> ModuleDefinition {
    Name("MyModule")

    Function("hello") { (name: String) -> String in
      return "Hello \(name)!"
    }
  }
}

Kotlin (Android) :

package expo.modules.mymodule

import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition

class MyModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("MyModule")

    Function("hello") { name: String ->
      "Hello $name!"
    }
  }
}

TypeScript :

import { requireNativeModule } from "expo";

const MyModule = requireNativeModule("MyModule");

export function hello(name: string): string {
  return MyModule.hello(name);
}

expo-module.config.json

{
  "platforms": ["android", "apple"],
  "apple": {
    "modules": ["MyModule"]
  },
  "android": {
    "modules": ["expo.modules.mymodule.MyModule"]
  }
}

Note : iOS utilise juste le nom de la classe ; Android utilise le nom de classe complet (package + classe). Voir references/module-config.md pour tous les champs.

Skills similaires