GeoLeaf-JS — Architecture Guide
Package : @geoleaf/core
Version : 2.0.0
Target : Browser (ESM), TypeScript strict
Table des matières
- Vue d'ensemble
- Séquence de boot B1→B11
- Formats de bundle
- Adaptateur cartographique IMapAdapter
- Modules built-in vs optional
- Système de lazy loading
- Façades publiques (GeoLeaf.*)
- Couche shim
- Pattern Plugin Registry
- Module de sécurité
- État partagé
- Règle no-premium-in-core
Vue d'ensemble
GeoLeaf-JS est une bibliothèque TypeScript de cartographie interactive construite sur MapLibre GL JS ^5.0.0. Elle est structurée en monorepo (npm workspaces + Turborepo) avec trois packages :
| Package | Licence | Registre |
|---|---|---|
@geoleaf/core | MIT | npmjs.org (public) |
@geoleaf-plugins/storage | Commercial | npm.pkg.github.com |
@geoleaf-plugins/addpoi | Commercial | npm.pkg.github.com |
Le package core expose :
- Un bundle ESM CDN (
geoleaf.esm.js, assignewindow.GeoLeaf.*) pour usage CDN/navigateur - Un bundle ESM avec 31 exports nommés pour les bundlers (Vite, webpack, etc.)
- Un bundle Lite (ESM sans Table, Labels, Route, Search) pour les déploiements allégés
- 11 chunks ESM lazy-loadés pour le code splitting
Séquence de boot B1→B11
La séquence de boot est orchestrée par src/modules/globals.ts, qui importe les sous-modules de domaine dans un ordre strict. Cet ordre est critique — ne jamais le modifier sans comprendre toutes les dépendances aval.
bundle-esm-entry.ts
│
└── globals.ts (orchestrateur — imports dans l'ordre)
│
├── B1+B2 globals.core.ts
│ ├── B1: Log, Errors, CONSTANTS, Security, CSRFToken
│ └── B2: Utils (AnimationHelper, DOMSecurity, ErrorLogger,
│ EventHelpers, EventListenerManager, EventBus,
│ FetchHelper, FileValidator, MapHelpers,
│ PerformanceProfiler, LazyLoader, TimerManager,
│ ObjectUtils, ScaleUtils)
│
├── B3+B4 globals.config.ts
│ ├── B3: Helpers, Validators
│ └── B4: Renderers, Data, Loaders, Map, Config
│
├── B5 globals.geojson.ts
│ └── B5: GeoJSON (INTERNE), Route
│
├── B6+B7+B9 globals.ui.ts
│ ├── B6: Labels
│ ├── B7: Legend, LayerManager
│ └── B9: Themes, UI, Controls, Filters
│
├── B8 globals.storage.ts
│ └── B8: Namespace Storage (peuplé par le plugin à runtime)
│
├── B10 globals.poi.ts
│ └── B10: POI, AddForm, Renderers
│
└── B11 globals.api.ts ← DOIT ÊTRE EN DERNIER
└── B11: Toutes les façades publiques (Core, GeoLeafAPI, POI, Route, Table, UI,
Filters, Baselayers, Legend, LayerManager, Helpers,
Validators, Themes, Labels, Geocoding, Search, Permalink,
Events, Notifications, PWA) + PluginRegistry + BootInfo
app/helpers.ts (helpers au boot)
app/init.ts (orchestrateur d'initialisation)
app/boot.ts (boot principal — s'exécute après globals)ModuleRegistry (Sprint 3+)
En complément des globals, app/boot.ts instancie un ModuleRegistry qui gère le cycle de vie des modules via un graphe de dépendances :
boot.ts
│
└── new ModuleRegistry()
├── register(new SecurityModule())
├── register(new CoreMapModule())
├── register(new ConfigModule())
├── register(new SharedModule())
├── register(new GeoJSONModule())
├── register(new UIModule())
├── register(new POIModule())
├── register(new APIModule())
│
│ [après loadConfig() — modules optionnels selon profil]
├── register(new RouteModule()) si route.enabled !== false
├── register(new LabelsModule()) si labels.enabled !== false
├── register(new LegendModule()) si ui.showLegend !== false
├── register(new TableModule()) si ui.showTable !== false
└── register(new SearchModule()) si ui.showSearch !== falseLe tri topologique (algorithme de Kahn BFS) garantit l'ordre d'initialisation en respectant les dépendances déclarées dans chaque module via ICoreModule.dependencies.
Règles critiques
globals.api.ts(B11) doit être en dernier — il lit les façades enregistrées par B1–B10.globals.core.ts(B1+B2) doit être en premier — tous les modules dépendent deLogetErrors.globals.storage.ts(B8) configure le namespaceGeoLeaf.Storage— le plugin commercial le peuple à runtime.- Ne jamais modifier l'ordre de chargement sans lire tous les fichiers consommateurs.
Formats de bundle
ESM CDN (bundle-esm-entry.ts → geoleaf.esm.js)
Produit dist/geoleaf.esm.js (bundle flat CDN). Assigne window.GeoLeaf.* au chargement via les side-effects de globals.ts.
Usage (CDN/navigateur) :
<script type="module" src="https://unpkg.com/@geoleaf/core@2.0.0/dist/geoleaf.esm.js"></script>
<!-- window.GeoLeaf est maintenant disponible -->ESM (bundle-esm-entry.ts)
Produit dist/esm/bundle-esm-entry.js + dist/chunks/ (11 lazy chunks). Exporte 31 symboles nommés importables par les consommateurs TypeScript/ESM. Déclenche le même boot side-effect.
Usage (bundler/npm) :
import { Core, POI, UI, Filters } from "@geoleaf/core";Lite (bundle-core-lite-entry.ts)
Variante ESM Lite sans les modules Table, Labels et Route. Vise une réduction de ~30% de la taille du bundle pour les déploiements qui n'ont pas besoin de ces fonctionnalités. Utilise boot-lite.ts et globals.api-lite.ts à la place de leurs équivalents complets.
Adaptateur cartographique IMapAdapter
GeoLeaf V2 abstrait totalement le moteur cartographique derrière l'interface IMapAdapter (définie dans src/contracts/map-adapter.contract.ts). Aucun module métier ne doit importer directement depuis maplibre-gl.
Types géographiques
| Type | Définition | Usage |
|---|---|---|
GeoLeafLatLng | { lat, lng } (WGS 84) | Coordonnée ponctuelle |
GeoLeafBounds | { north, south, east, west } | Emprise géographique |
GeoLeafPoint | { x, y } (pixels, origine haut-gauche) | Projection écran |
Convention d'ordre : GeoLeaf utilise { lat, lng } ; MapLibre GL utilise [lng, lat] (ordre GeoJSON). La conversion est exclusivement dans l'adaptateur.
Surface de l'interface IMapAdapter
interface IMapAdapter {
// Initialisation
init(options: MapInitOptions): void;
isReady(): boolean;
destroy(): void;
// Vue / Navigation
setView(center: GeoLeafLatLng, zoom: number): void;
getCenter(): GeoLeafLatLng;
getZoom(): number;
setZoom(zoom: number): void;
panTo(center: GeoLeafLatLng): void;
flyTo(center: GeoLeafLatLng, zoom?: number): void;
fitBounds(bounds: GeoLeafBounds, options?: { padding?: GeoLeafPoint; animate?: boolean }): void;
getBounds(): GeoLeafBounds;
// Événements (set normalisé)
on(event: MapEvent, handler: (e: unknown) => void): void;
off(event: MapEvent, handler: (e: unknown) => void): void;
once(event: MapEvent, handler: (e: unknown) => void): void;
// Couches GeoJSON
addGeoJSONLayer(id: string, data: unknown, options?: GeoLeafLayerOptions): void;
removeLayer(id: string): void;
hasLayer(id: string): boolean;
showLayer(id: string): void;
hideLayer(id: string): void;
updateLayerData(id: string, data: unknown): void;
setLayerStyle(id: string, style: GeoLeafStyleOptions): void;
setLayerFilter(id: string, filter: unknown): void;
// Marqueurs
createMarker(id: string, position: GeoLeafLatLng, options?: GeoLeafMarkerOptions): void;
removeMarker(id: string): void;
updateMarkerPosition(id: string, position: GeoLeafLatLng): void;
createClusterGroup(id: string, options?: Record<string, unknown>): void;
// Popups (handles opaques)
createPopup(content: string | HTMLElement, options?: GeoLeafPopupOptions): unknown;
openPopup(popup: unknown, position?: GeoLeafLatLng): void;
closePopup(popup?: unknown): void;
// Contrôles
addControl(control: unknown, position: GeoLeafControlPosition): GeoLeafControl;
removeControl(control: GeoLeafControl): void;
// Utilitaires
latLngToPoint(latlng: GeoLeafLatLng): GeoLeafPoint;
pointToLatLng(point: GeoLeafPoint): GeoLeafLatLng;
getContainer(): HTMLElement;
}L'implémentation concrète est MaplibreAdapter dans src/adapters/maplibre/.
Modules built-in vs optional
Modules built-in (toujours présents)
Chargés dans le bundle ESM (geoleaf.esm.js). Aucun import réseau supplémentaire nécessaire.
Module (window.GeoLeaf.*) | Source (src/modules/) | Description |
|---|---|---|
Log | utils/log/ | Système de log interne |
Errors | utils/errors/ | 9 classes d'erreur typées |
CONSTANTS | utils/constants/ | Constantes globales |
Security | security/ | Sanitisation XSS, CSRF, DOM |
Utils | utils/general/ | ~15 utilitaires (fetch, animation, lazy, perf…) |
Config | built-in/config/ | Chargement profil, taxonomie, normalisation |
Core | geoleaf.core.ts | Création carte MapLibre, couches de base |
Baselayers | geoleaf.baselayers.ts | Fonds de plan raster et vecteur |
Filters | geoleaf.filters.ts | Système de filtres |
UI | geoleaf.ui.ts + ui/ | Interface (notifications, filtres, contrôles) |
Helpers | geoleaf.helpers.ts | Helpers publics |
Validators | geoleaf.validators.ts | Validation de données |
plugins | built-in/api/plugin-registry.ts | GeoLeaf.plugins.* — PluginRegistry |
bootInfo | built-in/api/boot-info.ts | Toast de démarrage |
Modules optional (lazy chunks)
Chargés à la demande via GeoLeaf._loadModule(). Produisent des chunks séparés dans dist/chunks/.
Clé _loadModule() | Fichier source | Description |
|---|---|---|
poi | poi-core → poi-renderers + poi-extras | POI complet (ordre garanti) |
poiCore | lazy/poi-core.ts | Affichage marqueurs POI |
poiRenderers | lazy/poi-renderers.ts | Renderers popup/sidepanel |
poiExtras | lazy/poi-extras.ts | Sidepanel extras |
basemapSelector | lazy/basemap-selector.ts | Sélecteur fond de plan |
route | lazy/route.ts | Couches itinéraires |
layerManager | lazy/layer-manager.ts | Panneau gestionnaire couches |
legend | lazy/legend.ts | Panneau légende |
labels | lazy/labels.ts | Système de labels dynamiques |
themes | lazy/themes.ts | Système de thèmes |
table | lazy/table.ts | Tableau de données |
search | lazy/search.ts | Recherche plein texte |
Système de lazy loading
GeoLeaf._loadModule(moduleName)
Charge un module secondaire à la demande :
await GeoLeaf._loadModule("poi"); // Charge poi-core + poi-renderers + poi-extras
await GeoLeaf._loadModule("route"); // Charge le chunk route
await GeoLeaf._loadModule("legend"); // Charge le chunk légende
await GeoLeaf._loadModule("table"); // Charge le chunk tableau
await GeoLeaf._loadModule("themes"); // Charge le chunk thèmes
await GeoLeaf._loadModule("labels"); // Charge le chunk labels
await GeoLeaf._loadModule("layerManager"); // Charge le gestionnaire de couches
await GeoLeaf._loadModule("search"); // Charge la recherche
await GeoLeaf._loadModule("basemapSelector");Chargement granulaire POI :
await GeoLeaf._loadModule("poiCore"); // Core POI uniquement
await GeoLeaf._loadModule("poiRenderers"); // Renderers POI uniquement
await GeoLeaf._loadModule("poiExtras"); // Extras POI uniquementGeoLeaf._loadAllSecondaryModules()
Charge tous les modules secondaires en parallèle (sauf basemapSelector). Le core POI se charge en premier — poi-renderers et poi-extras en dépendent.
await GeoLeaf._loadAllSecondaryModules();
// Tous les modules secondaires sont maintenant disponibles sur window.GeoLeafFaçades publiques (GeoLeaf.*)
L'API publique est exposée à travers 16+ fichiers façade dans src/modules/. Chaque façade ré-exporte depuis l'implémentation du module de domaine — aucune logique métier dans les façades.
Namespace global (window.GeoLeaf.*)
Après le boot, window.GeoLeaf contient :
| Propriété | Type | Description |
|---|---|---|
GeoLeaf.Core | Object | Init carte, thèmes, cycle de vie |
GeoLeaf.POI | Object | Marqueurs POI, clustering, popup |
GeoLeaf.UI | Object | Contrôles, panneaux, filtres UI |
GeoLeaf.Filters | Object | Panneaux filtres, recherche texte/catégorie/géo |
GeoLeaf.Route | Object | Route/itinéraires |
GeoLeaf.Table | Object | Tableau de données |
GeoLeaf.Legend | Object | Panneau légende |
GeoLeaf.LayerManager | Object | Gestion couches GeoJSON |
GeoLeaf.Baselayers | Object | Fonds de plan (raster + vecteur MapLibre) |
GeoLeaf.Helpers | Object | Helpers utilitaires |
GeoLeaf.Validators | Object | Validateurs d'entrée |
GeoLeaf.Themes | Object | Gestion des thèmes |
GeoLeaf.Labels | Object | Système de labels |
GeoLeaf.Notifications | Object | Système de notifications toast |
GeoLeaf.Permalink | Object | Permalink URL |
GeoLeaf.Events | Object | Bus d'événements |
GeoLeaf.Search | Object | Recherche plein texte |
GeoLeaf.Geocoding | Object | Recherche d'adresse / géocodage |
GeoLeaf.PWA | Object | Progressive Web App (install prompt) |
GeoLeaf.Config | Object | Accès à la configuration (get/set) |
GeoLeaf.Utils | Object | ~15 sous-modules utilitaires |
GeoLeaf.CONSTANTS | Object | Constantes applicatives |
GeoLeaf.Log | Object | Système de log |
GeoLeaf.Errors | Object | 9 classes d'erreur typées |
GeoLeaf.Security | Object | Helpers XSS/CSRF |
GeoLeaf.Storage | Object | Namespace Storage (peuplé par le plugin à runtime) |
GeoLeaf.plugins | Object | Requête/enregistrement plugins (PluginRegistry) |
GeoLeaf.registry | Object | ModuleRegistry public (auto-enregistrement tiers) |
GeoLeaf._loadModule | Function | Lazy load d'un module secondaire |
GeoLeaf._loadAllSecondaryModules | Function | Charge tous les modules secondaires |
GeoLeaf._version | string | Version courante (ex. "2.0.0") |
Note :
GeoLeaf.GeoJSONn'est pas une façade publique. Les couches GeoJSON sont gérées en interne et accessibles viaGeoLeaf.LayerManageret les profils JSON.
Exports nommés ESM (31)
Depuis bundle-esm-entry.ts :
// Façades haut niveau
export { Core } from "./modules/geoleaf.core.js";
export { GeoLeafAPI } from "./modules/geoleaf.api.js";
export { UI } from "./modules/geoleaf.ui.js";
export { POI } from "./modules/geoleaf.poi.js";
export { Route } from "./modules/geoleaf.route.js";
export { Table } from "./modules/geoleaf.table.js";
export { Legend } from "./modules/geoleaf.legend.js";
export { LayerManager } from "./modules/geoleaf.layer-manager.js";
export { Filters } from "./modules/geoleaf.filters.js";
export { Baselayers } from "./modules/geoleaf.baselayers.js";
export { Helpers } from "./modules/geoleaf.helpers.js";
export { Validators } from "./modules/geoleaf.validators.js";
export { Themes } from "./modules/geoleaf.themes.js";
export { Permalink } from "./modules/geoleaf.permalink.js";
export { Events } from "./modules/geoleaf.events.js";
export { Search } from "./modules/geoleaf.search.js";
export { Notifications } from "./modules/geoleaf.notifications.js";
export { PWA } from "./modules/geoleaf.pwa.js";
export { Geocoding } from "./modules/geoleaf.geocoding.js";
// Sous-modules API
export {
APIController,
APIFactoryManager,
APIInitializationManager,
APIModuleManager,
PluginRegistry,
BootInfo,
showBootInfo,
} from "./modules/built-in/api/index.js";
// Utilitaires core
export { Log } from "./modules/utils/log/index.js";
export { Errors } from "./modules/utils/errors/index.js";
export { CONSTANTS } from "./modules/utils/constants/index.js";
export { Utils } from "./modules/utils/general/general-utils.js";
export { Config } from "./modules/built-in/config/geoleaf-config/config-core.js";Couche shim
Les répertoires de premier niveau suivants dans src/ sont des shims de compatibilité ascendante — de simples ré-exporteurs qui redirigent vers src/modules/. Ils ne contiennent aucune logique et ne doivent pas être modifiés directement.
src/baselayers/ → src/modules/baselayers/
src/filters/ → src/modules/filters/
src/geojson/ → src/modules/geojson/
src/helpers/ → src/modules/helpers/
src/layers/ → src/modules/layer-manager/ (ou geojson/)
src/markers/ → src/modules/poi/
src/poi/ → src/modules/poi/
src/storage/ → src/modules/storage/
src/themes/ → src/modules/themes/
src/ui/ → src/modules/ui/
src/validators/ → src/modules/validators/Toute l'implémentation réelle se trouve exclusivement dans src/modules/.
Pattern Plugin Registry
GeoLeaf utilise un pattern d'enregistrement explicite pour maintenir une séparation stricte entre le core MIT et les plugins commerciaux.
Enregistrement (côté plugin)
// Dans le point d'entrée du plugin (ex. plugin-storage/src/entry.ts)
import { PluginRegistry } from "@geoleaf/core";
PluginRegistry.register("storage", {
version: "2.0.0",
requires: ["core"],
label: "GeoLeaf Storage",
});Requête (côté consommateur)
GeoLeaf.plugins.isLoaded("storage"); // → true/false
GeoLeaf.plugins.getLoadedPlugins(); // → ["core", "storage"]
GeoLeaf.plugins.canActivate("addpoi"); // → true si dépendances OK
GeoLeaf.plugins.getAvailableModules(); // → tous les modules (chargés + lazy)
GeoLeaf.plugins.getInfo("storage"); // → { name, version, loaded, loadedAt, … }
await GeoLeaf.plugins.load("layerManager"); // → lazy load depuis le registreRègle no-premium-in-core
packages/core/src/ ne doit jamais importer @geoleaf-plugins/*. Vérification via scripts/verify-no-premium-in-core.cjs (exécuté en CI).
Module de sécurité
Toute injection DOM doit passer par src/modules/security/. Ne jamais utiliser innerHTML directement dans le code applicatif.
import { Security } from "@geoleaf/core";
// via CDN (window.GeoLeaf):
GeoLeaf.Security.sanitize(htmlString); // Sanitisation XSS
GeoLeaf.Security.CSRFToken.get(); // Helper token CSRFUtilitaires clés :
Security.sanitize()— Sanitisation XSS (supprime le HTML dangereux)DOMSecurity— Helpers DOM sécurisésCSRFToken— Gestion du token CSRFsrc/modules/utils/dom-security.ts— Opérations DOM sécurisées
État partagé
src/modules/shared/ contient les objets d'état cross-modules. Avant de modifier un fichier ici, identifier tous les consommateurs — les changements d'état partagé peuvent silencieusement casser plusieurs modules.
Fichiers d'état partagé clés :
poi-state.ts— Sélection POI, marqueurs actifsgeojson-state.ts— Registre des couches GeoJSON chargées
Règle no-premium-in-core
packages/core/src/ doit avoir zéro référence à @geoleaf-plugins/*.
Vérification :
node scripts/verify-no-premium-in-core.cjsCe script s'exécute en CI et fait échouer le build si un import premium est détecté. Les fonctionnalités commerciales sont injectées dans le namespace GeoLeaf par les plugins à runtime, via le pattern Plugin Registry.
Dernière mise à jour : mars 2026
Version GeoLeaf : 2.0.0 — Platform V2
