Cours JavaScript et TypeScript · Module 5: JavaScript avancé

L'import dynamique de modules

15 min de lecture
Dans cette leçon7

Dans les exercices précédents, nous avons découvert les deux principaux systèmes de modules JavaScript : les modules ES et CommonJS. Les deux permettent d'organiser le code en modules indépendants, mais nous avons surtout manipulé des imports statiques. Passons maintenant au niveau supérieur - l'import dynamique de modules, qui nous offre une flexibilité bien plus grande.

Pour notre système de gestion de Jurassic Park, les imports dynamiques peuvent être cruciaux. Imagine avoir besoin de procédures d'urgence différentes selon ce qui se passe - une évasion de T-Rex demande un ensemble d'actions différent d'une panne de courant ou d'un ouragan qui approche !

Introduction à l'import dynamique

L'import dynamique de modules permet de charger les modules pendant l'exécution du programme, plutôt que lors de son initialisation. Cela signifie qu'on peut charger un module exactement quand on en a besoin, ce qui économise de la mémoire et du temps de chargement pour des modules qui ne seront peut-être jamais utilisés.

Syntaxe de l'import dynamique

L'import dynamique utilise la fonction import(), qui renvoie une Promise contenant le module chargé :

1// Import statique (toujours chargé)
2import { DinosaurTracker } from './dinosaurTracker.js';
3
4// Import dynamique (chargé à la demande)
5import('./emergencyProcedures.js')
6  .then(module => {
7    // On peut maintenant utiliser le module chargé
8    module.initializeEmergencyProtocol();
9  })
10  .catch(error => {
11    console.error("Échec du chargement des procédures d'urgence :", error);
12  });

Il est encore plus pratique d'utiliser les imports dynamiques avec async/await :

1async function handleEmergency(type) {
2  try {
3    // Importe dynamiquement le bon module de procédure d'urgence
4    const emergencyModule = await import(`./emergencies/${type}.js`);
5
6    // Utilise le module chargé
7    return emergencyModule.executeProtocol();
8  } catch (error) {
9    console.error(`Échec du chargement de la procédure : ${type}`, error);
10    // Repli sur la procédure d'urgence générale
11    const fallbackModule = await import('./emergencies/general.js');
12    return fallbackModule.executeProtocol();
13  }
14}
15
16// Utilise notre fonction
17handleEmergency('dinosaurEscape').then(result => {
18  console.log("État de la procédure d'urgence :", result);
19});

Applications de l'import dynamique dans Jurassic Park

Réfléchissons aux applications pratiques de l'import dynamique dans notre système de gestion de Jurassic Park :

1. Chargement à la demande

On peut charger des fonctionnalités uniquement quand on en a besoin, ce qui est particulièrement utile pour des modules volumineux mais rarement utilisés :

1// dashboardController.js
2async function loadDashboard(dashboardType) {
3  // Le tableau de bord de base est toujours chargé
4  const statusPanel = document.getElementById('statusPanel');
5  statusPanel.innerHTML = 'Chargement...';
6
7  try {
8    // Importe dynamiquement le type de tableau de bord demandé
9    const dashboardModule = await import(`./dashboards/${dashboardType}Dashboard.js`);
10
11    // Initialise le tableau de bord
12    const dashboard = new dashboardModule.default();
13    await dashboard.initialize(statusPanel);
14
15    console.log(`Tableau de bord ${dashboardType} chargé avec succès`);
16  } catch (error) {
17    console.error(`Erreur de chargement du tableau de bord ${dashboardType} : `, error);
18    statusPanel.innerHTML = `Erreur de chargement du tableau de bord : ${error.message}`;
19  }
20}
21
22// Boutons de l'interface
23document.getElementById('btnDinosaurs').addEventListener('click', () => {
24  loadDashboard('dinosaurs');
25});
26
27document.getElementById('btnSecurity').addEventListener('click', () => {
28  loadDashboard('security');
29});
30
31document.getElementById('btnVisitors').addEventListener('click', () => {
32  loadDashboard('visitors');
33});

2. Différentes implémentations selon l'environnement

On peut charger dynamiquement différentes implémentations selon l'environnement (production, développement, test) :

1async function initializeTracking() {
2  const environment = process.env.NODE_ENV || 'development';
3
4  try {
5    // Charge l'implémentation adaptée à l'environnement
6    const trackingModule = await import(`./tracking/${environment}.js`);
7
8    // Initialise le système de suivi
9    const tracker = trackingModule.createTracker();
10    tracker.initialize();
11
12    return tracker;
13  } catch (error) {
14    console.error(`Échec de l'initialisation du suivi pour l'environnement ${environment} : `, error);
15    // Repli sur la version de développement
16    const fallbackModule = await import('./tracking/development.js');
17    return fallbackModule.createTracker();
18  }
19}
20
21// Utilisation de la fonction
22initializeTracking().then(tracker => {
23  tracker.trackEvent('app_initialized', { timestamp: Date.now() });
24});

3. Chargement paresseux des composants d'interface

Dans une application frontend, on peut différer le chargement des composants d'interface pour accélérer le chargement initial de la page :

1// dynamicComponents.js
2export async function loadComponent(componentName) {
3  try {
4    const module = await import(`./components/${componentName}.js`);
5    return module.default;
6  } catch (error) {
7    console.error(`Échec du chargement du composant ${componentName} : `, error);
8    throw error;
9  }
10}
11
12// Dans le fichier principal de l'application
13import { loadComponent } from './dynamicComponents.js';
14
15document.getElementById('btnShowMap').addEventListener('click', async () => {
16  try {
17    // Charge la carte du parc uniquement quand l'utilisateur en a besoin
18    const ParkMap = await loadComponent('ParkMap');
19    const mapContainer = document.getElementById('mapContainer');
20
21    const parkMap = new ParkMap({
22      container: mapContainer,
23      initialZoom: 2,
24      centerCoordinates: [34.5, 12.8]
25    });
26
27    parkMap.render();
28  } catch (error) {
29    alert(`Échec du chargement de la carte : ${error.message}`);
30  }
31});

4. Chargement dynamique des procédures et protocoles de sécurité

L'une des applications les plus importantes dans notre parc sera le système de procédures de sécurité :

1// securitySystem.js
2export class SecuritySystem {
3  constructor() {
4    this.activeProtocols = new Map();
5    this.alertLevel = 'green';
6  }
7
8  async activateProtocol(protocolName, params = {}) {
9    try {
10      // Vérifie si le protocole est déjà actif
11      if (this.activeProtocols.has(protocolName)) {
12        console.log(`Le protocole ${protocolName} est déjà actif`);
13        return this.activeProtocols.get(protocolName);
14      }
15
16      // Importe dynamiquement le protocole
17      const protocolModule = await import(`./protocols/${protocolName}.js`);
18
19      // Initialise le protocole
20      const protocol = new protocolModule.default(params);
21      await protocol.initialize();
22
23      // Ajoute aux protocoles actifs
24      this.activeProtocols.set(protocolName, protocol);
25
26      console.log(`Protocole ${protocolName} activé avec succès`);
27      return protocol;
28    } catch (error) {
29      console.error(`Erreur lors de l'activation du protocole ${protocolName} : `, error);
30      throw new Error(`Échec de l'activation du protocole ${protocolName} : ${error.message}`);
31    }
32  }
33
34  async deactivateProtocol(protocolName) {
35    const protocol = this.activeProtocols.get(protocolName);
36    if (!protocol) {
37      console.log(`Le protocole ${protocolName} n'est pas actif`);
38      return false;
39    }
40
41    try {
42      await protocol.shutdown();
43      this.activeProtocols.delete(protocolName);
44      console.log(`Protocole ${protocolName} désactivé avec succès`);
45      return true;
46    } catch (error) {
47      console.error(`Erreur lors de la désactivation du protocole ${protocolName} : `, error);
48      return false;
49    }
50  }
51
52  async handleEmergency(type, location) {
53    console.log(`Gestion de l'urgence : ${type} à l'emplacement ${location}`);
54
55    // Met à jour le niveau d'alerte
56    this.alertLevel = 'red';
57
58    try {
59      // Importe la procédure d'urgence appropriée
60      const emergencyModule = await import(`./emergencies/${type}.js`);
61
62      // Exécute la procédure
63      const procedure = new emergencyModule.EmergencyProcedure(location);
64      const emergencyId = await procedure.execute();
65
66      console.log(`Procédure d'urgence pour ${type} lancée, ID : ${emergencyId}`);
67      return emergencyId;
68    } catch (error) {
69      console.error(`Échec de la gestion de l'urgence ${type} : `, error);
70
71      // Repli sur la procédure d'urgence générale
72      try {
73        const generalModule = await import('./emergencies/general.js');
74        const procedure = new generalModule.EmergencyProcedure(location, { originalType: type });
75        return procedure.execute();
76      } catch (fallbackError) {
77        console.error(`Erreur critique ! Même la procédure générale a échoué : `, fallbackError);
78        return null;
79      }
80    }
81  }
82}
83
84// Utilisation du système de sécurité
85const security = new SecuritySystem();
86
87// Exemple d'activation de protocole
88document.getElementById('btnLockdown').addEventListener('click', async () => {
89  try {
90    const protocol = await security.activateProtocol('lockdown', {
91      sectors: ['A1', 'B2'],
92      reason: 'Évasion de dinosaure'
93    });
94
95    document.getElementById('status').textContent = `Protocole de confinement actif dans ${protocol.affectedSectors.length} secteurs`;
96  } catch (error) {
97    alert(`Erreur d'activation du protocole : ${error.message}`);
98  }
99});
100
101// Exemple de signalement d'urgence
102async function reportEmergency(type, location) {
103  try {
104    const emergencyId = await security.handleEmergency(type, location);
105    return emergencyId;
106  } catch (error) {
107    console.error(`Échec du signalement de l'urgence : `, error);
108    return null;
109  }
110}

Exemples d'implémentation de modules

Voyons à quoi pourraient ressembler certains des modules importés dynamiquement dans notre système :

Exemple de protocole de sécurité (lockdown.js)

1// protocols/lockdown.js
2export default class LockdownProtocol {
3  constructor(params) {
4    this.sectors = params.sectors || [];
5    this.reason = params.reason || 'Raison inconnue';
6    this.timestamp = new Date();
7    this.active = false;
8    this.affectedSectors = [];
9  }
10
11  async initialize() {
12    console.log(`Initialisation du protocole de confinement pour les secteurs : ${this.sectors.join(', ')}`);
13
14    // Simule l'activation du confinement pour chaque secteur
15    this.affectedSectors = await Promise.all(
16      this.sectors.map(async (sector) => {
17        try {
18          // Dans un vrai système, ce serait un appel API
19          await this.activateSectorLockdown(sector);
20          return {
21            sector,
22            status: 'secured',
23            securedAt: new Date()
24          };
25        } catch (error) {
26          console.error(`Erreur de confinement du secteur ${sector} : `, error);
27          return {
28            sector,
29            status: 'failed',
30            error: error.message
31          };
32        }
33      })
34    );
35
36    this.active = true;
37
38    // Renvoie l'état
39    return {
40      active: this.active,
41      affectedSectors: this.affectedSectors,
42      initializedAt: new Date()
43    };
44  }
45
46  async shutdown() {
47    if (!this.active) {
48      return { success: false, reason: 'Protocole non actif' };
49    }
50
51    console.log(`Désactivation du protocole de confinement pour les secteurs : ${this.sectors.join(', ')}`);
52
53    // Simule la désactivation du confinement pour chaque secteur
54    await Promise.all(
55      this.sectors.map(async (sector) => {
56        try {
57          // Dans un vrai système, ce serait un appel API
58          await this.deactivateSectorLockdown(sector);
59        } catch (error) {
60          console.error(`Erreur de déverrouillage du secteur ${sector} : `, error);
61        }
62      })
63    );
64
65    this.active = false;
66
67    return {
68      success: true,
69      deactivatedAt: new Date()
70    };
71  }
72
73  // Méthode simulant l'activation du confinement d'un secteur
74  async activateSectorLockdown(sector) {
75    return new Promise((resolve, reject) => {
76      // Simule un délai réseau et une réponse API
77      setTimeout(() => {
78        if (Math.random() > 0.1) {
79          resolve({
80            sector,
81            status: 'secured',
82            gates: { closed: 4, total: 4 },
83            fences: { energized: true }
84          });
85        } else {
86          reject(new Error(`Erreur de confinement du secteur ${sector} : panne système`));
87        }
88      }, 500 + Math.random() * 1000);
89    });
90  }
91
92  // Méthode simulant la désactivation du confinement d'un secteur
93  async deactivateSectorLockdown(sector) {
94    return new Promise((resolve, reject) => {
95      // Simule un délai réseau et une réponse API
96      setTimeout(() => {
97        if (Math.random() > 0.05) {
98          resolve({
99            sector,
100            status: 'unlocked',
101            gates: { closed: 0, total: 4 },
102            fences: { energized: true } // Les clôtures restent sous tension
103          });
104        } else {
105          reject(new Error(`Erreur de déverrouillage du secteur ${sector} : panne système`));
106        }
107      }, 300 + Math.random() * 700);
108    });
109  }
110}

Exemple de procédure d'urgence (dinosaurEscape.js)

1// emergencies/dinosaurEscape.js
2export class EmergencyProcedure {
3  constructor(location, options = {}) {
4    this.location = location;
5    this.options = options;
6    this.id = 'EM-' + Date.now().toString(36).toUpperCase();
7    this.status = 'initialized';
8    this.startTime = new Date();
9  }
10
11  async execute() {
12    console.log(`Exécution de la procédure d'urgence pour une évasion de dinosaure à l'emplacement ${this.location}`);
13    this.status = 'in_progress';
14
15    try {
16      // 1. Active l'alarme de zone
17      await this.activateZoneAlarm(this.location);
18
19      // 2. Évacue les visiteurs de la zone menacée
20      await this.evacuateVisitors(this.location);
21
22      // 3. Dépêche l'équipe de sécurité
23      const securityTeam = await this.dispatchSecurityTeam(this.location);
24
25      // 4. Active le protocole de confinement dans ce secteur et les secteurs adjacents
26      const adjacentSectors = await this.getAdjacentSectors(this.location);
27      const allSectors = [this.location, ...adjacentSectors];
28      await this.activateLockdown(allSectors);
29
30      // 5. Notifie le personnel
31      await this.notifyStaff({
32        type: 'dinosaur_escape',
33        location: this.location,
34        securityTeam,
35        sectors: allSectors
36      });
37
38      this.status = 'completed';
39      console.log(`Procédure d'urgence ${this.id} terminée avec succès`);
40
41      return this.id;
42    } catch (error) {
43      this.status = 'failed';
44      console.error(`La procédure d'urgence ${this.id} a échoué : `, error);
45      throw error;
46    }
47  }
48
49  // Méthodes utilitaires...
50  async activateZoneAlarm(zone) {
51    console.log(`Activation de l'alarme dans la zone ${zone}`);
52    return { success: true, zone };
53  }
54
55  async evacuateVisitors(zone) {
56    console.log(`Évacuation des visiteurs de la zone ${zone}`);
57    return { success: true, evacuatedCount: Math.floor(Math.random() * 50) + 10 };
58  }
59
60  async dispatchSecurityTeam(zone) {
61    console.log(`Envoi de l'équipe de sécurité vers la zone ${zone}`);
62    return {
63      teamId: 'ST-' + Math.floor(Math.random() * 10) + 1,
64      members: Math.floor(Math.random() * 5) + 3,
65      estimatedArrival: new Date(Date.now() + 5 * 60 * 1000)
66    };
67  }
68
69  async getAdjacentSectors(sector) {
70    // Dans un vrai système, on récupérerait ceci depuis une base de données
71    const sectorMap = {
72      'A1': ['A2', 'B1'],
73      'A2': ['A1', 'A3', 'B2'],
74      'B1': ['A1', 'B2', 'C1'],
75      // ... d'autres correspondances
76    };
77
78    return sectorMap[sector] || [];
79  }
80
81  async activateLockdown(sectors) {
82    console.log(`Activation du confinement dans les secteurs : ${sectors.join(', ')}`);
83
84    // Importe dynamiquement le module de protocole de confinement
85    const LockdownModule = await import('../protocols/lockdown.js');
86    const lockdown = new LockdownModule.default({
87      sectors,
88      reason: 'Évasion de dinosaure'
89    });
90
91    return lockdown.initialize();
92  }
93
94  async notifyStaff(data) {
95    console.log(`Notification du personnel concernant l'urgence : `, data);
96    return { success: true, notifiedCount: Math.floor(Math.random() * 30) + 5 };
97  }
98}

Construire un système dynamique avec des imports déterminés à l'exécution

On peut aussi créer des systèmes plus avancés où les chemins d'import sont déterminés à l'exécution :

1// dynamicModuleLoader.js
2export class ModuleLoader {
3  constructor(basePath = '') {
4    this.basePath = basePath;
5    this.cache = new Map();
6  }
7
8  async load(modulePath, ignoreCache = false) {
9    const fullPath = this.basePath ? `${this.basePath}/${modulePath}` : modulePath;
10
11    // Vérifie le cache sauf s'il est ignoré
12    if (!ignoreCache && this.cache.has(fullPath)) {
13      console.log(`Renvoi du module ${fullPath} depuis le cache`);
14      return this.cache.get(fullPath);
15    }
16
17    try {
18      console.log(`Chargement dynamique du module : ${fullPath}`);
19      const module = await import(fullPath);
20
21      // Sauvegarde dans le cache
22      this.cache.set(fullPath, module);
23
24      return module;
25    } catch (error) {
26      console.error(`Erreur de chargement du module ${fullPath} : `, error);
27      throw error;
28    }
29  }
30
31  async loadMultiple(modulePaths) {
32    try {
33      const modulePromises = modulePaths.map(path => this.load(path));
34      return await Promise.all(modulePromises);
35    } catch (error) {
36      console.error(`Erreur de chargement de plusieurs modules : `, error);
37      throw error;
38    }
39  }
40
41  clearCache(modulePath) {
42    if (modulePath) {
43      const fullPath = this.basePath ? `${this.basePath}/${modulePath}` : modulePath;
44      return this.cache.delete(fullPath);
45    } else {
46      // Vide tout le cache
47      this.cache.clear();
48      return true;
49    }
50  }
51}
52
53// DinosaurTrackingSystem.js
54import { ModuleLoader } from './dynamicModuleLoader.js';
55
56export class DinosaurTrackingSystem {
57  constructor() {
58    this.moduleLoader = new ModuleLoader('./dinosaurs');
59    this.trackers = new Map();
60    this.activeDinosaurs = new Set();
61  }
62
63  async initialize() {
64    // Charge les modules de base
65    const [baseUtils, trackingCore] = await this.moduleLoader.loadMultiple([
66      './utils.js',
67      './trackingCore.js'
68    ]);
69
70    this.utils = baseUtils;
71    this.trackingCore = trackingCore;
72
73    console.log(`Système de suivi des dinosaures initialisé.`);
74  }
75
76  async trackDinosaur(dinosaurId, species) {
77    if (this.trackers.has(dinosaurId)) {
78      console.log(`Le dinosaure ${dinosaurId} est déjà suivi.`);
79      return this.trackers.get(dinosaurId);
80    }
81
82    try {
83      // Charge dynamiquement le module de suivi pour l'espèce donnée
84      const specificTrackingModule = await this.moduleLoader.load(`./species/${species.toLowerCase()}.js`);
85
86      // Crée et initialise un tracker pour ce dinosaure
87      const tracker = new specificTrackingModule.default(dinosaurId, {
88        utils: this.utils,
89        core: this.trackingCore
90      });
91
92      await tracker.initialize();
93
94      // Sauvegarde le tracker et marque le dinosaure comme actif
95      this.trackers.set(dinosaurId, tracker);
96      this.activeDinosaurs.add(dinosaurId);
97
98      console.log(`Suivi du dinosaure ${dinosaurId} (${species}) démarré.`);
99      return tracker;
100    } catch (error) {
101      console.error(`Erreur de suivi du dinosaure ${dinosaurId} : `, error);
102
103      // S'il n'y a pas de module spécifique pour l'espèce, utilise le module générique
104      try {
105        console.log(`Tentative d'utilisation du module de suivi générique pour ${species}`);
106        const genericTrackingModule = await this.moduleLoader.load('./genericTracker.js');
107
108        const tracker = new genericTrackingModule.default(dinosaurId, species, {
109          utils: this.utils,
110          core: this.trackingCore
111        });
112
113        await tracker.initialize();
114
115        this.trackers.set(dinosaurId, tracker);
116        this.activeDinosaurs.add(dinosaurId);
117
118        console.log(`Suivi générique du dinosaure ${dinosaurId} (${species}) démarré.`);
119        return tracker;
120      } catch (fallbackError) {
121        console.error(`Échec de l'utilisation même du module de suivi générique : `, fallbackError);
122        throw new Error(`Impossible de suivre le dinosaure ${dinosaurId} : ${error.message}`);
123      }
124    }
125  }
126
127  async stopTracking(dinosaurId) {
128    if (!this.trackers.has(dinosaurId)) {
129      console.log(`Le dinosaure ${dinosaurId} n'est pas suivi.`);
130      return false;
131    }
132
133    try {
134      const tracker = this.trackers.get(dinosaurId);
135      await tracker.shutdown();
136
137      this.trackers.delete(dinosaurId);
138      this.activeDinosaurs.delete(dinosaurId);
139
140      console.log(`Suivi du dinosaure ${dinosaurId} arrêté.`);
141      return true;
142    } catch (error) {
143      console.error(`Erreur lors de l'arrêt du suivi du dinosaure ${dinosaurId} : `, error);
144      return false;
145    }
146  }
147
148  getActiveTrackingCount() {
149    return this.activeDinosaurs.size;
150  }
151
152  getTrackerInfo(dinosaurId) {
153    const tracker = this.trackers.get(dinosaurId);
154    if (!tracker) {
155      return null;
156    }
157
158    return {
159      dinosaurId,
160      species: tracker.species,
161      lastKnownLocation: tracker.getLastLocation(),
162      status: tracker.getStatus(),
163      isActive: tracker.isActive()
164    };
165  }
166}
167
168// Utilisation du système de suivi
169async function initTrackingSystem() {
170  const trackingSystem = new DinosaurTrackingSystem();
171  await trackingSystem.initialize();
172
173  // Suit différentes espèces de dinosaures
174  try {
175    const rexTracker = await trackingSystem.trackDinosaur('rex-01', 'tyrannosaurus');
176    console.log('État du T-Rex :', rexTracker.getStatus());
177
178    const raptorTracker = await trackingSystem.trackDinosaur('raptor-02', 'velociraptor');
179    console.log('État du Velociraptor :', raptorTracker.getStatus());
180
181    // Même sans module spécifique, le système devrait utiliser le repli
182    const stegoTracker = await trackingSystem.trackDinosaur('stego-01', 'stegosaurus');
183    console.log('État du Stegosaurus :', stegoTracker.getStatus());
184
185    console.log(`Suivis actifs : ${trackingSystem.getActiveTrackingCount()} dinosaures`);
186  } catch (error) {
187    console.error(`Erreur d'initialisation du système de suivi : `, error);
188  }
189}
190
191// Initialise le système
192initTrackingSystem();

Avantages et inconvénients de l'import dynamique

Avantages

  1. Chargement initial réduit - l'application ne charge que les modules nécessaires à un instant donné
  2. Meilleures performances - le découpage du code (code splitting) permet un chargement plus rapide de la page
  3. Flexibilité - possibilité de charger des modules selon des conditions ou des interactions utilisateur
  4. Imports conditionnels - possibilité de choisir différentes implémentations selon l'environnement, les paramètres, etc.
  5. Maintenance facilitée - découpage de l'application en modules plus petits et indépendants

Inconvénients

  1. Complexité - ajoute une couche de complexité à l'application
  2. Gestion des erreurs - nécessite une gestion soignée des erreurs pour les cas où un module ne peut pas être chargé
  3. Délais - charger un module à l'exécution peut introduire des délais dans l'interface utilisateur
  4. Augmentation de la taille du bundle - dans certains cas, cela peut entraîner une duplication de code
  5. Débogage - peut être plus difficile à déboguer que les imports statiques

Implémentation dans différents environnements

Dans les navigateurs modernes, l'import dynamique fonctionne nativement :

1// Exemple navigateur
2document.getElementById('btnActivateLockdown').addEventListener('click', async () => {
3  try {
4    const lockdownModule = await import('./security/lockdown.js');
5    const lockdown = new lockdownModule.default();
6    lockdown.activateForSectors(['A1', 'B2']);
7  } catch (error) {
8    console.error('Échec du chargement du module de confinement :', error);
9  }
10});

Node.js

Node.js prend officiellement en charge les imports dynamiques depuis la version 13.2.0 :

1// Exemple Node.js
2async function runDinosaurAnalysis(species) {
3  try {
4    // Import dynamique du module d'analyse pour l'espèce donnée
5    const analyzer = await import(`./analyzers/${species.toLowerCase()}.js`);
6    return analyzer.analyzeGenome();
7  } catch (error) {
8    console.error(`Pas d'analyseur pour l'espèce ${species} : `, error);
9    // Utilise l'analyseur générique
10    const genericAnalyzer = await import('./analyzers/generic.js');
11    return genericAnalyzer.analyzeGenome(species);
12  }
13}
14
15// Utilisation
16runDinosaurAnalysis('velociraptor')
17  .then(results => console.log("Résultats de l'analyse du génome :", results))
18  .catch(error => console.error("Erreur d'analyse :", error));

Webpack et autres bundlers

Les bundlers comme Webpack, Rollup ou Parcel détectent automatiquement les imports dynamiques et découpent le code en plus petits chunks :

1// Webpack comprendra et gérera automatiquement cet import
2const loadAdminPanel = async () => {
3  // Un chunk séparé sera généré pour adminPanel
4  const adminModule = await import(/* webpackChunkName: "admin" */ './adminPanel');
5  return new adminModule.default();
6};
7
8// Tu peux même indiquer la priorité de chargement
9const prefetchReportGenerator = () => {
10  // Récupère le module à l'avance, mais ne l'exécute que quand c'est nécessaire
11  import(/* webpackPrefetch: true */ './reportGenerator');
12};

Résumé

L'import dynamique de modules est une fonctionnalité puissante qui apporte plus de flexibilité et de performances aux applications JavaScript. Dans le contexte de notre Jurassic Park, il nous donne la possibilité de construire des systèmes capables de réagir dynamiquement à diverses situations, ce qui est crucial pour la sécurité d'un parc rempli de dinosaures dangereux !

Grâce aux imports dynamiques, on peut :

  • Charger les modules uniquement quand on en a besoin
  • Choisir différentes implémentations selon le contexte
  • Réduire le temps de chargement initial de l'application
  • Créer des systèmes flexibles avec des modules déterminés à l'exécution

Dans l'exercice suivant, nous examinerons les outils de débogage JavaScript, qui seront précieux quand quelque chose tournera mal dans nos systèmes de gestion du parc !

Code de cette leçon : main.js
1// Dynamic module importing - Jurassic Park
2// Lazy loading i Code Splitting
3
4console.log("Dynamic Module Importing");
5console.log("On-demand loading system\n");
6
7// ===========================================
8// DYNAMIC MODULE SIMULATION
9// ===========================================
10
11// Simulated modules as functions
12const modules = {
13  '/modules/trex-handler.js': () => ({
14    name: 'T-Rex Handler Module',
15    handleTRex: (trex) => {
16      console.log(`T-Rex handling: ${trex.name}`);
17      console.log(`  Danger level: ${trex.dangerLevel}/10`);
18      console.log(`  Status: Double protection required`);
19      return {
20        securityLevel: 'MAXIMUM',
21        requiredGuards: 4,
22        specialEquipment: ['Tranquilizer', 'Electric Fence', 'Tracker']
23      };
24    }
25  }),
26
27  '/modules/herbivore-handler.js': () => ({
28    name: 'Herbivore Handler Module',
29    handleHerbivore: (dino) => {
30      console.log(`Herbivore handling: ${dino.name}`);
31      console.log(`  Danger level: ${dino.dangerLevel}/10`);
32      console.log(`  Status: Standardowa ochrona`);
33      return {
34        securityLevel: 'LOW',
35        requiredGuards: 1,
36        specialEquipment: ['Food Dispenser', 'Health Monitor']
37      };
38    }
39  }),
40
41  '/modules/raptor-handler.js': () => ({
42    name: 'Raptor Handler Module',
43    handleRaptor: (raptor) => {
44      console.log(`Velociraptor handling: ${raptor.name}`);
45      console.log(`  Danger level: ${raptor.dangerLevel}/10`);
46      console.log(`  Status: High alert - they hunt in packs!`);
47      return {
48        securityLevel: 'HIGH',
49        requiredGuards: 3,
50        specialEquipment: ['Pack Tracker', 'Sound System', 'Emergency Protocol']
51      };
52    }
53  }),
54
55  '/modules/analytics.js': () => ({
56    name: 'Analytics Module',
57    generateReport: (dinosaurs) => {
58      const total = dinosaurs.length;
59      const carnivores = dinosaurs.filter(d => d.diet === 'carnivore').length;
60      const herbivores = dinosaurs.filter(d => d.diet === 'herbivore').length;
61      const avgDanger = dinosaurs.reduce((sum, d) => sum + d.dangerLevel, 0) / total;
62
63      return {
64        totalDinosaurs: total,
65        carnivores,
66        herbivores,
67        averageDangerLevel: avgDanger.toFixed(2),
68        recommendation: avgDanger > 6 ? 'Increase security' : 'Security level OK'
69      };
70    }
71  }),
72
73  '/modules/emergency.js': () => ({
74    name: 'Emergency Protocol Module',
75    activateEmergency: (reason) => {
76      console.log(`\nEMERGENCY PROTOCOL ACTIVATED!`);
77      console.log(`Reason: ${reason}`);
78      console.log(`1. Closing all paddocks`);
79      console.log(`2. Guest evacuation`);
80      console.log(`3. Security team mobilization`);
81      return {
82        status: 'ACTIVE',
83        timestamp: new Date().toISOString(),
84        actions: ['LOCK_ENCLOSURES', 'EVACUATE', 'ALERT_SECURITY']
85      };
86    }
87  })
88};
89
90// Simulation import() pour la démonstration
91async function dynamicImport(path) {
92  console.log(`⏳ Loading module: ${path}...`);
93
94  // Simulation network delay
95  await new Promise(resolve => setTimeout(resolve, 500));
96
97  if (modules[path]) {
98    const module = modules[path]();
99    console.log(`Loaded: ${module.name}\n`);
100    return module;
101  }
102
103  throw new Error(`Module not found: ${path}`);
104}
105
106// ===========================================
107// DINOSAUR MANAGEMENT SYSTEM
108// ===========================================
109
110class DinosaurManager {
111  constructor() {
112    this.dinosaurs = [];
113    this.loadedModules = new Map();
114  }
115
116  addDinosaur(name, species, diet, dangerLevel) {
117    const dinosaur = { name, species, diet, dangerLevel };
118    this.dinosaurs.push(dinosaur);
119    console.log(`Added dinosaura: ${name} (${species})`);
120    return dinosaur;
121  }
122
123  async handleDinosaur(dinosaur) {
124    console.log(`\nTraitement : ${dinosaur.name}...`);
125
126    // Dynamic loading of appropriate module
127    let modulePath;
128    let handlerMethod;
129
130    if (dinosaur.species === 'Tyrannosaurus Rex') {
131      modulePath = '/modules/trex-handler.js';
132      handlerMethod = 'handleTRex';
133    } else if (dinosaur.species === 'Velociraptor') {
134      modulePath = '/modules/raptor-handler.js';
135      handlerMethod = 'handleRaptor';
136    } else if (dinosaur.diet === 'herbivore') {
137      modulePath = '/modules/herbivore-handler.js';
138      handlerMethod = 'handleHerbivore';
139    } else {
140      console.log(`No dedicated module for ${dinosaur.species}`);
141      return null;
142    }
143
144    // Check cache
145    if (!this.loadedModules.has(modulePath)) {
146      const module = await dynamicImport(modulePath);
147      this.loadedModules.set(modulePath, module);
148    } else {
149      console.log(`Used cached module: ${modulePath}\n`);
150    }
151
152    const handler = this.loadedModules.get(modulePath);
153    return handler[handlerMethod](dinosaur);
154  }
155
156  async generateAnalytics() {
157    console.log(`\nGenerowanie raportu analitycznego...`);
158
159    if (!this.loadedModules.has('/modules/analytics.js')) {
160      const module = await dynamicImport('/modules/analytics.js');
161      this.loadedModules.set('/modules/analytics.js', module);
162    }
163
164    const analytics = this.loadedModules.get('/modules/analytics.js');
165    return analytics.generateReport(this.dinosaurs);
166  }
167
168  async triggerEmergency(reason) {
169    const module = await dynamicImport('/modules/emergency.js');
170    return module.activateEmergency(reason);
171  }
172}
173
174// ===========================================
175// DEMONSTRACJA
176// ===========================================
177
178async function runDemo() {
179  console.log("=== START DEMONSTRACJI ===\n");
180
181  const manager = new DinosaurManager();
182
183  // Ajout des dinosaures
184  console.log("Rejestracja dinosaurs:");
185  const trex = manager.addDinosaur('Rexy', 'Tyrannosaurus Rex', 'carnivore', 9);
186  const raptor1 = manager.addDinosaur('Blue', 'Velociraptor', 'carnivore', 7);
187  const raptor2 = manager.addDinosaur('Charlie', 'Velociraptor', 'carnivore', 7);
188  const brachi = manager.addDinosaur('Gentle Giant', 'Brachiosaurus', 'herbivore', 2);
189  const trice = manager.addDinosaur('Spike', 'Triceratops', 'herbivore', 3);
190
191  console.log(`\\nNombre total de dinosaures : ${manager.dinosaurs.length}`);
192
193  // Dynamic module loading for each dinosaur
194  console.log("\n=== ON-DEMAND MODULE LOADING ===");
195
196  const trexProtocol = await manager.handleDinosaur(trex);
197  console.log('Security protocol:', trexProtocol);
198
199  const raptor1Protocol = await manager.handleDinosaur(raptor1);
200  console.log('Security protocol:', raptor1Protocol);
201
202  const raptor2Protocol = await manager.handleDinosaur(raptor2);
203  console.log('Security protocol:', raptor2Protocol);
204
205  const brachiProtocol = await manager.handleDinosaur(brachi);
206  console.log('Security protocol:', brachiProtocol);
207
208  const triceProtocol = await manager.handleDinosaur(trice);
209  console.log('Security protocol:', triceProtocol);
210
211  // Report analityczny
212  console.log("\n=== ANALITYKA ===");
213  const report = await manager.generateAnalytics();
214  console.log('\nReport:', JSON.stringify(report, null, 2));
215
216  // Situation d'urgence
217  if (parseFloat(report.averageDangerLevel) > 6) {
218    const emergency = await manager.triggerEmergency('High average danger level');
219    console.log('\nStatus awaryjny:', emergency);
220  }
221
222  console.log(`\nLoaded modules: ${manager.loadedModules.size}`);
223  console.log(`Liste : ${Array.from(manager.loadedModules.keys()).join(', ')}`);
224
225  console.log("\n=== ZALETY DYNAMICZNEGO IMPORTOWANIA ===\n");
226  console.log("Lazy Loading:");
227  console.log("   - Modules loaded only when needed");
228  console.log("   - Szybszy start aplikacji\n");
229
230  console.log("Code Splitting:");
231  console.log("   - Smaller initial bundling");
232  console.log("   - Better memory usage\n");
233
234  console.log("Conditional Loading:");
235  console.log("   - Loading based on runtime conditions");
236  console.log("   - Flexibility in resource management\n");
237
238  console.log("Caching:");
239  console.log("   - Once loaded, module is cached");
240  console.log("   - Faster access on subsequent calls\n");
241
242  console.log("=== SYNTAX ===\n");
243  console.log("// L'import dynamique renvoie une Promise");
244  console.log("import('./module.js')");
245  console.log("  .then(module => {");
246  console.log("    module.doSomething();");
247  console.log("  })");
248  console.log("  .catch(err => console.error(err));\n");
249
250  console.log("// Lub z async/await");
251  console.log("async function loadModule() {");
252  console.log("  const module = await import('./module.js');");
253  console.log("  module.doSomething();");
254  console.log("}\n");
255
256  console.log("Demonstracja completed!");
257}
258
259// Lance la démo
260runDemo().catch(console.error);

Tu as repéré une erreur dans cette leçon ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Ordonne correctement les éléments d'un export par défaut :

  • Clique dans l'ordre

    Ordonne les éléments d'export d'un module JavaScript :

  • Tri horizontal

    Ordonne les éléments d'import d'un module JavaScript :

Articles utiles