Skip to Content
DocumentationModules frontendVue d'ensemble

Vue d’ensemble

Les modules frontend sont les blocs de construction fondamentaux pour créer des applications dans O3. Ce sont des morceaux de fonctionnalité autonomes qui peuvent être chargés dans O3 par l’app shell. Par exemple, vous pourriez avoir un module frontend qui gère les préoccupations de rendu liées aux signes vitaux et aux biométriques. Ce module pourrait inclure ce qui suit:

  • Une page qui affiche un aperçu tabulaire des signes vitaux et des biométriques d’un patient
  • Une page qui affiche des visualisations de graphiques des signes vitaux et des biométriques d’un patient.
  • Un formulaire qui permet à un utilisateur d’enregistrer un nouvel ensemble de lectures de signes vitaux et de biométriques.
  • Un en-tête de signes vitaux qui affiche les lectures les plus récentes des signes vitaux et des biométriques d’un patient.

Ces composants seraient définis dans le dossier src du module frontend et exportés depuis src/index.ts. Le fichier routes.json du module nomme ces exports et déclare où ils doivent apparaître. Pendant l’assemblage de la distro, spa-assemble-config.json produit à la fois une import map et un registre de routes. L’app shell utilise le registre de routes pour découvrir les pages et extensions, puis l’import map pour récupérer le code du module quand une page ou une extension doit être rendue.

Si vous créez un nouveau module plutôt que de comprendre un module existant, commencez par la recette Création d’un module frontend. Cette vue d’ensemble explique les pièces en jeu; la recette décrit le workflow publié npm create @openmrs/o3-app@latest.

Concepts critiques (à ne pas manquer)

Voici quatre concepts clés qu’on oublie facilement:

  1. Câblage de la distro (ce qui se charge réellement) : spa-assemble-config.json définit quels modules frontend et quelles versions sont inclus dans votre distro. L’assemblage génère une import map pour les URLs du code et un registre de routes à partir du routes.json de chaque module. Si un module manque dans l’un des deux, son UI ne se rendra pas correctement.
  2. Flux d’enregistrement (pourquoi pages/extensions s’affichent) : routes.json déclare les pages, extensions, modals, workspaces, feature flags et conditions runtime. L’app shell enregistre d’abord ces métadonnées. Plus tard, quand une route ou un slot correspondant se rend, il charge l’export de lifecycle nommé depuis src/index.ts.
  3. Tags de version (pourquoi vos changements ne sont pas visibles) : latest et next sont différents. Votre distro ne charge que le tag indiqué dans spa-assemble-config.json.
  4. Conditions runtime (pourquoi quelque chose se charge mais ne s’affiche pas): routes.json peut déclarer des dépendances backend, des privilèges, des feature flags et un comportement online/offline. Un module peut être présent dans l’import map et ne pas afficher une extension si l’une de ces conditions n’est pas remplie.

Modèle mental (comment les modules frontend fonctionnent réellement)

Si vous débutez avec O3, voici le modèle mental à garder en tête:

  1. Un module frontend est un package npm. Chaque module est publié indépendamment et possède son propre package.json, src/index.ts et routes.json.
  2. L’app shell utilise deux artefacts de distro. Il lit le registre de routes pour connaître les contributions UI et l’import map pour savoir où se trouve le bundle JavaScript de chaque module.
  3. Les modules relient métadonnées et code à deux endroits:
    • routes.json déclare les pages, extensions, modals, workspaces, feature flags, dépendances backend, privilèges et conditions online/offline.
    • src/index.ts exporte les fonctions de lifecycle nommées par routes.json, ainsi que la configuration de démarrage optionnelle comme les schémas de config, breadcrumbs et traductions.
  4. Pages et extensions ne sont pas la même chose.
    • Pages = routes complètes (ex. /patient/123/vitals).
    • Extensions = fragments UI branchés sur des extension slots appartenant à d’autres modules.
  5. Les slots vivent dans l’UI hôte. Un module hôte expose des slots, d’autres modules y enregistrent des extensions. C’est le cœur de la modularité d’O3.
  6. L’import map est la source de vérité pour les URLs du code. Elle associe les noms de modules aux URLs de bundles et contrôle les versions. Le registre de routes est la source de vérité pour ce que le module contribue à l’UI.
  7. En dev local, on peut surcharger les deux artefacts. openmrs develop et Devtools peuvent pointer un module vers votre serveur local et utiliser son routes.json local sans changer le spa-assemble-config.json de la distro.

Les modules frontend dans O3 sont généralement construits avec React. Comme nous utilisons single-spa  sous le capot, d’autres frameworks sont possibles, mais ils sont moins courants. Par exemple, le module frontend Form entry est écrit en Angular et encapsule l’Angular form engine . En ce sens, l’app shell est agnostique du framework. En pratique, les fonctionnements de bas niveau de l’app shell ont été abstraits pour que les ingénieurs frontend puissent se concentrer sur le comportement des modules plutôt que sur le framework.

Les modules frontend sont généralement organisés en dépôts spécifiques à un domaine. Par exemple, les modules frontend concernant la gestion des patients existent dans le monorepo Patient Management . Certains modules vivent dans des monorepos, d’autres dans des dépôts autonomes, selon la portée. Quelle que soit l’organisation, chaque module est construit et publié comme un paquet npm indépendant afin d’être référencé dans l’import map de votre distribution.

Dans cette structure, vous aurez typiquement un répertoire packages à la racine du monorepo. Ce répertoire contient tous vos modules frontend. Par exemple, le monorepo de gestion des patients a la structure suivante:

Anatomie rapide (en un coup d’œil):

  • package.json - métadonnées et dépendances du module
  • src/index.ts - point d’entrée du module
  • routes.json - métadonnées statiques des routes (pages/extensions et leurs routes)
  • config-schema.ts - où vivent les propriétés de configuration du module
        • config-schema.ts
        • declarations.d.ts
        • index.ts
        • routes.json
  • package.json
  • yarn.lock

Chaque module frontend est un paquet npm indépendant avec:

  • Son propre fichier package.json (séparé du fichier package.json à la racine). Ce fichier définit les dépendances et les métadonnées du module.
  • Un répertoire src qui contient le code source du module. Ce répertoire contient les fichiers suivants:
    • Un fichier config-schema.ts qui définit le schéma de configuration du module
    • Un fichier declarations.d.ts qui définit les déclarations TypeScript du module
    • Un fichier index.ts qui définit le point d’entrée du module
    • Un fichier routes.json qui définit les métadonnées statiques du module (routes, pages, dépendances backend)

Les modules frontend sont aussi appelés microfrontends dans O3. Ils ont les caractéristiques suivantes:

  • Ils suivent la convention de nommage esm-, même si le chargement se fait au runtime via des bundles Module Federation.
  • Ils ont une partie descriptive qui indique la fonctionnalité du module. Par exemple, esm-patient-search-app gère la recherche de patients.
  • Ils peuvent avoir un suffixe -app dans leur nom, mais ce n’est pas obligatoire.

Versions des modules frontend expliquées

Chaque module frontend est un package npm, généralement avec un nom se terminant par -app. Les ESM d’OpenMRS sont publiés avec trois étiquettes différentes : “next”, “latest” et un numéro de version. Voici ce que chaque étiquette signifie :

  • 🔴⚠️🚧 “next” = Pré-version, en développement. La plus récente, à la pointe, encore en construction. “next” se réfère toujours à la version la plus récente mais pas encore publiée d’un ESM (par exemple, 3.2.1-pre.1067). Les versions étiquetées “next” ne sont pas recommandées pour une utilisation en production car elles sont considérées comme des travaux en cours instables et n’ont souvent pas subi de tests d’intégration.
  • 🟡 “latest” = Dernière version publiée. “latest” se réfère toujours à la version la plus récente publiée d’un ESM (par exemple, 3.2.0). Bien que vous puissiez utiliser la version “latest” de n’importe quel ESM, vous avez plus de contrôle en spécifiant le numéro de version exact de chaque ESM que vous utilisez.
  • 🟢✅ vX.X.X = Une version spécifique. Un numéro de version se réfère toujours à une version spécifique de chaque ESM. Par exemple, 3.2.0 ou 3.2.1-pre.1067 sont des versions spécifiques de l’ESM @openmrs/esm-api, bien que cette dernière soit une pré-version.

Ces tags sont choisis dans votre spa-assemble-config.json, qui alimente l’import map de votre distribution. La configuration de l’application de référence communautaire vit dans frontend/spa-assemble-config.json.

En d’autres termes: le tag de version choisi dans spa-assemble-config.json devient la version chargée par l’app shell.

Ces tags affectent l’import map, qui indique à l’app shell quelle URL utiliser pour charger le bundle de chaque module. Le registre de routes généré à côté de l’import map indique quelles pages, extensions et autres contributions UI existent. Pour plus de détails, consultez les guides App shell et Chargement des modules.

Anatomie d’un module frontend

Chaque module frontend chargé par l’app shell devrait avoir:

  • Un fichier manifeste package.json qui définit les dépendances et les métadonnées du module
  • Un fichier src/index.ts qui définit le point d’entrée du module
  • Un fichier src/routes.json qui définit les métadonnées statiques du module
  • Des exports de lifecycle nommés dans src/index.ts pour chaque composant référencé depuis routes.json
  • Une fonction startupApp quand le module a besoin d’une configuration au chargement, par exemple des schémas de config, des breadcrumbs ou d’autres enregistrements framework

Fichier manifeste (package.json)

Chaque module frontend a un fichier package.json de niveau racine qui définit ses dépendances et ses métadonnées. Voici un extrait raccourci dans la forme générée par npm create @openmrs/o3-app@latest:

{ "name": "@openmrs/esm-active-prescriptions-app", "version": "1.0.0", "license": "MPL-2.0", "description": "An OpenMRS frontend module for active prescriptions", "browser": "dist/openmrs-esm-active-prescriptions.js", "main": "src/index.ts", "source": true, "scripts": { "start": "openmrs develop", "build": "rspack --mode production", "build:development": "rspack --mode development", "analyze": "rspack --mode=production --env.analyze=true", "serve": "rspack serve --mode=development", "lint": "eslint src --ext ts,tsx", "typescript": "tsc", "test": "vitest run --passWithNoTests", "test:watch": "vitest watch", "coverage": "vitest run --coverage --passWithNoTests", "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*.modal.tsx' 'src/index.ts' --config ./tools/i18next-parser.config.js", "prettier": "prettier --config prettier.config.js --write \"src/**/*.{ts,tsx,css,scss}\"", "postinstall": "husky install" }, "dependencies": { "@carbon/react": "^1.83.0" }, "peerDependencies": { "@carbon/react": "1.x", "@openmrs/esm-framework": "*", "dayjs": "1.x", "i18next": "25.x", "react": "18.x", "react-dom": "18.x", "react-i18next": "16.x", "react-router-dom": "6.x", "swr": "2.x" }, "devDependencies": { "openmrs": "next", "@openmrs/esm-framework": "next", "@openmrs/rspack-config": "next", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.5.2", "@rspack/cli": "^1.7.10", "@rspack/core": "^1.7.10", "jsdom": "^28.0.0", "typescript": "^5.9.3", "vitest": "^4.1.2" }, "packageManager": "yarn@4.10.3" }

Voici quelques éléments clés à retenir de l’examen de ce fichier:

  • La propriété name qui définit le nom du module. Cette propriété est utilisée comme identifiant unique du module dans la import map.
  • La propriété browser qui pointe vers le point d’entrée du bundle construit (ce que l’app shell charge au runtime).
  • La propriété main qui définit le point d’entrée du code source du module frontend, typiquement src/index.ts.
  • Les paquets runtime OpenMRS sont généralement des peer dependencies pour les consommateurs et des dev dependencies en next pour le développement local.
  • La configuration Rspack et les paquets CLI sont des dev dependencies, car ce sont des outils de build et non des modules runtime chargés par l’app shell.
  • Les modules frontend React actuels utilisent Rspack et Vitest par défaut. Des modules plus anciens peuvent encore avoir des scripts Webpack ou Jest pendant leur migration.

Le point d’entrée de l’application (index.ts)

Les modules frontend définissent leur point d’entrée dans src/index.ts.

import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle } from "@openmrs/esm-framework"; import { configSchema } from "./config-schema"; import ActivePrescriptionsSummary from "./active-prescriptions-summary.component"; import { moduleName } from "./constants"; export const importTranslation = require.context("../translations", false, /.json$/, "lazy"); const options = { featureName: "active-prescriptions", moduleName, }; export function startupApp() { defineConfigSchema(moduleName, configSchema); } export const root = getAsyncLifecycle(() => import("./root.component"), options); export const activePrescriptionsSummary = getSyncLifecycle(ActivePrescriptionsSummary, options);

Ce fichier est le point d’entrée du module frontend. C’est le premier fichier qui est exécuté lorsque le module frontend est chargé. Il est responsable de la mise en place du module frontend et de l’exportation de la configuration du module. Plus précisément, dans cet exemple:

  • Il exporte une fonction importTranslation qui est utilisée pour charger les traductions du module.
  • Il exporte également deux fonctions de lifecycle nommées, root et activePrescriptionsSummary. Ces noms doivent correspondre aux composants déclarés dans routes.json. L’app shell les appelle quand la page ou l’extension correspondante doit être rendue.
  • Il exporte également une fonction startupApp qui est utilisée pour configurer le module frontend. Dans ce cas, le schéma de configuration du module frontend est défini ici. Les modules qui ont besoin de breadcrumbs ou d’autres enregistrements framework peuvent aussi les effectuer dans startupApp.

La fonction startupApp (démarrage)

Beaucoup de modules frontend définissent une fonction nommée startupApp. Cette fonction effectue une configuration une seule fois, juste avant que le premier lifecycle de page, extension, modal ou workspace du module soit chargé. La fonction startupApp sert généralement à:

  • définir le schéma de configuration
  • enregistrer les breadcrumbs
  • exécuter toute autre configuration nécessaire avant l’utilisation des lifecycles exportés par le module

L’enregistrement des pages et des extensions vient de routes.json, pas de startupApp. Si un module n’a pas besoin de configuration au chargement, startupApp peut être omise.

La fonction importTranslation

Elle est nécessaire quand le module fournit des traductions. Elle indique à l’application frontale comment charger les chaînes de traduction. Notez que le premier argument de require.context est un répertoire, ../translations. Ce répertoire doit exister à cet endroit par rapport au fichier index.ts.

Métadonnées statiques dans routes.json

Le fichier routes.json est utilisé pour configurer les métadonnées statiques du module frontend. Celles-ci incluent:

  • Les extensions que le module frontend fournit
  • Les pages que le module frontend fournit
  • Les modals, workspaces, workspaceGroups et featureFlags que le module frontend fournit
  • Les backendDependencies que le module frontend requiert. Cet objet indique à l’application frontend quels modules serveur OpenMRS le module frontend exige, et dans quelles versions. Si ces dépendances ne sont pas respectées, les administrateurs seront alertés

La structure de ce fichier statique est dictée par le standard OpenMRS Routes JSON schema .

Avant de livrer une nouvelle page ou extension, vérifiez si routes.json doit aussi déclarer:

  • backendDependencies ou optionalBackendDependencies pour les modules backend requis.
  • privileges pour l’accès par rôle à une extension.
  • featureFlags et des valeurs featureFlag par extension pour les fonctionnalités à masquer tant qu’elles ne sont pas activées.
  • Les flags online et offline pour les composants qui doivent se comporter différemment quand le support hors ligne est activé.

Pour plus de détails sur les métadonnées d’extension, consultez le guide Système d’extension. Pour le comportement des dépendances backend et de la configuration côté distribution, consultez la vue d’ensemble de la configuration.

Extensions et extension slots (comment les modules s’assemblent)

L’UI d’O3 se compose en branchant des extensions dans des slots. Voici le flux:

  1. Un module hôte déclare un extension slot dans son UI (par exemple, un slot pour les widgets du dossier patient).
  2. D’autres modules enregistrent des extensions pour ce slot (par exemple, un widget “Signes vitaux”).
  3. L’app shell résout et rend les extensions dans les slots au runtime.

Cela permet d’ajouter, retirer ou remplacer des fragments UI sans modifier le module hôte. C’est l’un des piliers de la modularité d’O3.

Les extensions peuvent aussi être activées, désactivées et configurées via le guide du système de configuration.

Du code à l’écran (cycle complet)

Voici le cycle complet, étape par étape:

  1. Vous construisez et publiez un module sur npm.
  2. Vous l’ajoutez à spa-assemble-config.json (ou mettez à jour son tag).
  3. La distribution génère une import map et un registre de routes à partir des packages dans spa-assemble-config.json.
  4. L’app shell charge les deux artefacts au runtime.
  5. L’app shell enregistre les métadonnées de routes depuis le registre de routes.
  6. Les routes se résolvent et les slots se remplissent à partir de ces métadonnées enregistrées.
  7. L’app shell charge le bundle de votre module depuis l’import map la première fois qu’il a besoin d’un de vos exports de lifecycle.
  8. Votre startupApp s’exécute une fois, puis le lifecycle de page, extension, modal ou workspace demandé s’exécute.

Si votre module n’apparaît pas, l’échec se trouve presque toujours dans l’une de ces étapes.

Dépannage quand un module n’apparaît pas

Utilisez cette checklist pour localiser rapidement le problème:

  1. Le module est-il dans l’import map ? Ouvrez /openmrs/spa/importmap.json et confirmez que le nom du module existe et pointe vers la bonne URL.
  2. Le module est-il dans le registre de routes ? Ouvrez /openmrs/spa/routes.registry.json et confirmez que le module contient les métadonnées de page, extension, modal, workspace ou feature flag attendues.
  3. L’URL est-elle accessible ? Ouvrez l’URL de l’import map dans le navigateur. Vous devez voir un bundle JS, pas une page HTML/404.
  4. Le module exporte-t-il ce que l’app shell attend ? Vérifiez que src/index.ts exporte les pages/extensions nommées déclarées dans routes.json.
  5. Les noms de route correspondent-ils aux exports ? Si routes.json déclare un composant "root", index.ts doit exporter root.
  6. Visez-vous le bon extension slot ? Le nom du slot dans votre extension doit correspondre exactement au slot exposé par l’UI hôte.
  7. Y a-t-il un override d’import map ou de métadonnées de routes en dev ? Vérifiez Devtools et localStorage. Un override obsolète d’import map ou de routes peut masquer vos changements.

Concepts clés (à connaître tôt)

Si vous construisez ou dépannez des modules, ces notions vous feront gagner du temps:

  • Schéma de configurationconfig-schema.ts définit les options configurables du module. L’app shell charge et fusionne la config au runtime, ce qui contrôle le comportement côté implémentation. Voir le guide du système de configuration.
  • Traductions – Les modules fournissent les traductions via importTranslation dans src/index.ts, généralement à partir d’un dossier translations/. Si c’est absent ou mal configuré, la localisation ne fonctionne pas.
  • Lifecycles sync vs asyncgetSyncLifecycle embarque le code dans le bundle principal, tandis que getAsyncLifecycle charge à la demande. Utilisez‑les selon les besoins de performance. Voir Tests unitaires et d’intégration et les notes de performance dans Conventions de codage.
  • Dépendances backendroutes.json peut déclarer des dépendances et versions de modules backend. Si elles ne sont pas satisfaites, O3 alerte les administrateurs.
  • Overrides Devtools – Le dev local repose souvent sur les overrides d’import map et de routes. Voir Développement.
  • Stratégie de versionlatest est la dernière version stable, next est une pré‑version. Épinglez des versions en production, utilisez next pour tester.
Dernière mise à jour le