JavaScript and TypeScript course Β· Module 5: Advanced JavaScript

Dynamic Module Importing

14 min read
In this lesson7

In the previous exercises we learned about two main JavaScript module systems: ES Modules and CommonJS. Both systems allow organizing code into independent modules, but we mainly dealt with static imports. Now we'll go to the next level - dynamic module importing, which gives us much greater flexibility.

For our Jurassic Park management system, dynamic imports can be crucial. Imagine needing different emergency procedures depending on what's happening - a T-Rex escape requires a different set of actions than a power outage or an approaching hurricane!

Introduction to Dynamic Importing

Dynamic module importing allows loading modules during program execution, rather than during its initialization. This means we can load a module exactly when it's needed, saving memory and load time for modules that may never be used.

Dynamic Import Syntax

Dynamic import uses the import() function, which returns a Promise containing the loaded module:

1// Static import (always loaded)
2import { DinosaurTracker } from './dinosaurTracker.js';
3
4// Dynamic import (loaded on demand)
5import('./emergencyProcedures.js')
6  .then(module => {
7    // Now we can use the loaded module
8    module.initializeEmergencyProtocol();
9  })
10  .catch(error => {
11    console.error('Failed to load emergency procedures:', error);
12  });

It's even more convenient to use dynamic imports with async/await:

1async function handleEmergency(type) {
2  try {
3    // Dynamically import the appropriate emergency procedure module
4    const emergencyModule = await import(`./emergencies/${type}.js`);
5
6    // Use the loaded module
7    return emergencyModule.executeProtocol();
8  } catch (error) {
9    console.error(`Failed to load procedure: ${type}`, error);
10    // Fall back to the general emergency procedure
11    const fallbackModule = await import('./emergencies/general.js');
12    return fallbackModule.executeProtocol();
13  }
14}
15
16// Use our function
17handleEmergency('dinosaurEscape').then(result => {
18  console.log('Emergency procedure status:', result);
19});

Dynamic Importing Applications in Jurassic Park

Let's think about practical applications of dynamic importing in our Jurassic Park management system:

1. Load on Demand

We can load functionality only when needed, which is especially useful for rarely used but large modules:

1// dashboardController.js
2async function loadDashboard(dashboardType) {
3  // Base dashboard always loaded
4  const statusPanel = document.getElementById('statusPanel');
5  statusPanel.innerHTML = 'Loading...';
6
7  try {
8    // Dynamically import the requested dashboard type
9    const dashboardModule = await import(`./dashboards/${dashboardType}Dashboard.js`);
10
11    // Initialize the dashboard
12    const dashboard = new dashboardModule.default();
13    await dashboard.initialize(statusPanel);
14
15    console.log(`Dashboard ${dashboardType} loaded successfully`);
16  } catch (error) {
17    console.error(`Error loading dashboard ${dashboardType}: `, error);
18    statusPanel.innerHTML = `Dashboard loading error: ${error.message}`;
19  }
20}
21
22// Interface buttons
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. Different Implementations for Different Environments

We can dynamically load different implementations depending on the environment (production, development, testing):

1async function initializeTracking() {
2  const environment = process.env.NODE_ENV || 'development';
3
4  try {
5    // Load the appropriate implementation for the environment
6    const trackingModule = await import(`./tracking/${environment}.js`);
7
8    // Initialize the tracking system
9    const tracker = trackingModule.createTracker();
10    tracker.initialize();
11
12    return tracker;
13  } catch (error) {
14    console.error(`Failed to initialize tracking for environment ${environment}: `, error);
15    // Fall back to the development version
16    const fallbackModule = await import('./tracking/development.js');
17    return fallbackModule.createTracker();
18  }
19}
20
21// Using the function
22initializeTracking().then(tracker => {
23  tracker.trackEvent('app_initialized', { timestamp: Date.now() });
24});

3. Lazy Loading of Interface Components

In a frontend application we can delay loading interface components to speed up the initial page load:

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(`Failed to load component ${componentName}: `, error);
8    throw error;
9  }
10}
11
12// In the main application file
13import { loadComponent } from './dynamicComponents.js';
14
15document.getElementById('btnShowMap').addEventListener('click', async () => {
16  try {
17    // Load the park map only when the user needs it
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(`Failed to load map: ${error.message}`);
30  }
31});

4. Dynamic Loading of Security Procedures and Protocols

One of the most important applications in our park will be the security procedures system:

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      // Check if the protocol is already active
11      if (this.activeProtocols.has(protocolName)) {
12        console.log(`Protocol ${protocolName} is already active`);
13        return this.activeProtocols.get(protocolName);
14      }
15
16      // Dynamically import the protocol
17      const protocolModule = await import(`./protocols/${protocolName}.js`);
18
19      // Initialize the protocol
20      const protocol = new protocolModule.default(params);
21      await protocol.initialize();
22
23      // Add to active protocols
24      this.activeProtocols.set(protocolName, protocol);
25
26      console.log(`Protocol ${protocolName} activated successfully`);
27      return protocol;
28    } catch (error) {
29      console.error(`Error activating protocol ${protocolName}: `, error);
30      throw new Error(`Failed to activate protocol ${protocolName}: ${error.message}`);
31    }
32  }
33
34  async deactivateProtocol(protocolName) {
35    const protocol = this.activeProtocols.get(protocolName);
36    if (!protocol) {
37      console.log(`Protocol ${protocolName} is not active`);
38      return false;
39    }
40
41    try {
42      await protocol.shutdown();
43      this.activeProtocols.delete(protocolName);
44      console.log(`Protocol ${protocolName} deactivated successfully`);
45      return true;
46    } catch (error) {
47      console.error(`Error deactivating protocol ${protocolName}: `, error);
48      return false;
49    }
50  }
51
52  async handleEmergency(type, location) {
53    console.log(`Handling emergency: ${type} at location ${location}`);
54
55    // Update alert level
56    this.alertLevel = 'red';
57
58    try {
59      // Import the appropriate emergency procedure
60      const emergencyModule = await import(`./emergencies/${type}.js`);
61
62      // Run the procedure
63      const procedure = new emergencyModule.EmergencyProcedure(location);
64      const emergencyId = await procedure.execute();
65
66      console.log(`Emergency procedure for ${type} initiated, ID: ${emergencyId}`);
67      return emergencyId;
68    } catch (error) {
69      console.error(`Failed to handle emergency ${type}: `, error);
70
71      // Fall back to general emergency procedure
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(`Critical error! Even the general procedure failed: `, fallbackError);
78        return null;
79      }
80    }
81  }
82}
83
84// Using the security system
85const security = new SecuritySystem();
86
87// Example protocol activation
88document.getElementById('btnLockdown').addEventListener('click', async () => {
89  try {
90    const protocol = await security.activateProtocol('lockdown', {
91      sectors: ['A1', 'B2'],
92      reason: 'Dinosaur escape'
93    });
94
95    document.getElementById('status').textContent = `Lockdown protocol active in ${protocol.affectedSectors.length} sectors`;
96  } catch (error) {
97    alert(`Protocol activation error: ${error.message}`);
98  }
99});
100
101// Example emergency reporting
102async function reportEmergency(type, location) {
103  try {
104    const emergencyId = await security.handleEmergency(type, location);
105    return emergencyId;
106  } catch (error) {
107    console.error(`Failed to report emergency: `, error);
108    return null;
109  }
110}

Example Module Implementations

Let's see what some of the dynamically imported modules in our system might look like:

Example Security Protocol (lockdown.js)

1// protocols/lockdown.js
2export default class LockdownProtocol {
3  constructor(params) {
4    this.sectors = params.sectors || [];
5    this.reason = params.reason || 'Unknown reason';
6    this.timestamp = new Date();
7    this.active = false;
8    this.affectedSectors = [];
9  }
10
11  async initialize() {
12    console.log(`Initializing lockdown protocol for sectors: ${this.sectors.join(', ')}`);
13
14    // Simulate lockdown activation for each sector
15    this.affectedSectors = await Promise.all(
16      this.sectors.map(async (sector) => {
17        try {
18          // In a real system, this would be an API call
19          await this.activateSectorLockdown(sector);
20          return {
21            sector,
22            status: 'secured',
23            securedAt: new Date()
24          };
25        } catch (error) {
26          console.error(`Sector ${sector} lockdown error: `, error);
27          return {
28            sector,
29            status: 'failed',
30            error: error.message
31          };
32        }
33      })
34    );
35
36    this.active = true;
37
38    // Return status
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: 'Protocol not active' };
49    }
50
51    console.log(`Deactivating lockdown protocol for sectors: ${this.sectors.join(', ')}`);
52
53    // Simulate lockdown deactivation for each sector
54    await Promise.all(
55      this.sectors.map(async (sector) => {
56        try {
57          // In a real system, this would be an API call
58          await this.deactivateSectorLockdown(sector);
59        } catch (error) {
60          console.error(`Error unlocking sector ${sector}: `, error);
61        }
62      })
63    );
64
65    this.active = false;
66
67    return {
68      success: true,
69      deactivatedAt: new Date()
70    };
71  }
72
73  // Method simulating sector lockdown activation
74  async activateSectorLockdown(sector) {
75    return new Promise((resolve, reject) => {
76      // Simulate network delay and API response
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(`Sector ${sector} lockdown error: System failure`));
87        }
88      }, 500 + Math.random() * 1000);
89    });
90  }
91
92  // Method simulating sector lockdown deactivation
93  async deactivateSectorLockdown(sector) {
94    return new Promise((resolve, reject) => {
95      // Simulate network delay and API response
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 } // Fences remain energized
103          });
104        } else {
105          reject(new Error(`Error unlocking sector ${sector}: System failure`));
106        }
107      }, 300 + Math.random() * 700);
108    });
109  }
110}

Example Emergency Procedure (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(`Executing emergency procedure for dinosaur escape at location ${this.location}`);
13    this.status = 'in_progress';
14
15    try {
16      // 1. Activate zone alarm
17      await this.activateZoneAlarm(this.location);
18
19      // 2. Evacuate visitors from the threatened area
20      await this.evacuateVisitors(this.location);
21
22      // 3. Dispatch security team
23      const securityTeam = await this.dispatchSecurityTeam(this.location);
24
25      // 4. Activate lockdown protocol in this and adjacent sectors
26      const adjacentSectors = await this.getAdjacentSectors(this.location);
27      const allSectors = [this.location, ...adjacentSectors];
28      await this.activateLockdown(allSectors);
29
30      // 5. Notify staff
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(`Emergency procedure ${this.id} completed successfully`);
40
41      return this.id;
42    } catch (error) {
43      this.status = 'failed';
44      console.error(`Emergency procedure ${this.id} failed: `, error);
45      throw error;
46    }
47  }
48
49  // Helper methods...
50  async activateZoneAlarm(zone) {
51    console.log(`Activating alarm in zone ${zone}`);
52    return { success: true, zone };
53  }
54
55  async evacuateVisitors(zone) {
56    console.log(`Evacuating visitors from zone ${zone}`);
57    return { success: true, evacuatedCount: Math.floor(Math.random() * 50) + 10 };
58  }
59
60  async dispatchSecurityTeam(zone) {
61    console.log(`Dispatching security team to 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    // In a real system, we'd fetch this from a database
71    const sectorMap = {
72      'A1': ['A2', 'B1'],
73      'A2': ['A1', 'A3', 'B2'],
74      'B1': ['A1', 'B2', 'C1'],
75      // ... more mappings
76    };
77
78    return sectorMap[sector] || [];
79  }
80
81  async activateLockdown(sectors) {
82    console.log(`Activating lockdown in sectors: ${sectors.join(', ')}`);
83
84    // Dynamically import the lockdown protocol module
85    const LockdownModule = await import('../protocols/lockdown.js');
86    const lockdown = new LockdownModule.default({
87      sectors,
88      reason: 'Dinosaur escape'
89    });
90
91    return lockdown.initialize();
92  }
93
94  async notifyStaff(data) {
95    console.log(`Notifying staff about emergency: `, data);
96    return { success: true, notifiedCount: Math.floor(Math.random() * 30) + 5 };
97  }
98}

Building a Dynamic System with Runtime-Determined Imports

We can also create more advanced systems where import paths are determined at runtime:

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    // Check cache unless cache is ignored
12    if (!ignoreCache && this.cache.has(fullPath)) {
13      console.log(`Returning module ${fullPath} from cache`);
14      return this.cache.get(fullPath);
15    }
16
17    try {
18      console.log(`Dynamically loading module: ${fullPath}`);
19      const module = await import(fullPath);
20
21      // Save to cache
22      this.cache.set(fullPath, module);
23
24      return module;
25    } catch (error) {
26      console.error(`Error loading 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(`Error loading multiple 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      // Clear entire 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    // Load base modules
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(`Dinosaur tracking system initialized.`);
74  }
75
76  async trackDinosaur(dinosaurId, species) {
77    if (this.trackers.has(dinosaurId)) {
78      console.log(`Dinosaur ${dinosaurId} is already being tracked.`);
79      return this.trackers.get(dinosaurId);
80    }
81
82    try {
83      // Dynamically load the tracking module for the given species
84      const specificTrackingModule = await this.moduleLoader.load(`./species/${species.toLowerCase()}.js`);
85
86      // Create and initialize a tracker for this dinosaur
87      const tracker = new specificTrackingModule.default(dinosaurId, {
88        utils: this.utils,
89        core: this.trackingCore
90      });
91
92      await tracker.initialize();
93
94      // Save the tracker and mark the dinosaur as active
95      this.trackers.set(dinosaurId, tracker);
96      this.activeDinosaurs.add(dinosaurId);
97
98      console.log(`Started tracking dinosaur ${dinosaurId} (${species}).`);
99      return tracker;
100    } catch (error) {
101      console.error(`Error tracking dinosaur ${dinosaurId}: `, error);
102
103      // If there's no specific module for the species, use the generic one
104      try {
105        console.log(`Trying generic tracking module for ${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(`Started generic tracking for dinosaur ${dinosaurId} (${species}).`);
119        return tracker;
120      } catch (fallbackError) {
121        console.error(`Failed to use even the generic tracking module: `, fallbackError);
122        throw new Error(`Cannot track dinosaur ${dinosaurId}: ${error.message}`);
123      }
124    }
125  }
126
127  async stopTracking(dinosaurId) {
128    if (!this.trackers.has(dinosaurId)) {
129      console.log(`Dinosaur ${dinosaurId} is not being tracked.`);
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(`Stopped tracking dinosaur ${dinosaurId}.`);
141      return true;
142    } catch (error) {
143      console.error(`Error stopping tracking for dinosaur ${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// Using the tracking system
169async function initTrackingSystem() {
170  const trackingSystem = new DinosaurTrackingSystem();
171  await trackingSystem.initialize();
172
173  // Track different dinosaur species
174  try {
175    const rexTracker = await trackingSystem.trackDinosaur('rex-01', 'tyrannosaurus');
176    console.log('T-Rex status:', rexTracker.getStatus());
177
178    const raptorTracker = await trackingSystem.trackDinosaur('raptor-02', 'velociraptor');
179    console.log('Velociraptor status:', raptorTracker.getStatus());
180
181    // Even without a specific module, the system should use the fallback
182    const stegoTracker = await trackingSystem.trackDinosaur('stego-01', 'stegosaurus');
183    console.log('Stegosaurus status:', stegoTracker.getStatus());
184
185    console.log(`Active tracking: ${trackingSystem.getActiveTrackingCount()} dinosaurs`);
186  } catch (error) {
187    console.error(`Error initializing tracking system: `, error);
188  }
189}
190
191// Initialize the system
192initTrackingSystem();

Pros and Cons of Dynamic Importing

Pros

  1. Lower initial load - The application loads only those modules needed at any given moment
  2. Better performance - Code splitting allows for faster page loading
  3. Flexibility - Ability to load modules depending on conditions or user interactions
  4. Conditional imports - Ability to choose different implementations depending on environment, parameters, etc.
  5. Easier maintenance - Splitting the application into smaller, independent modules

Cons

  1. Complexity - Adds a layer of complexity to the application
  2. Error handling - Requires careful error handling for cases when a module cannot be loaded
  3. Delays - Loading a module at runtime may introduce delays in the user interface
  4. Increased bundle size - In some cases, it can lead to code duplication
  5. Debugging - Can be harder to debug than static imports

Implementation in Different Environments

Browsers

In modern browsers, dynamic import works natively:

1// Browser example
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('Failed to load lockdown module:', error);
9  }
10});

Node.js

Node.js officially supports dynamic imports since version 13.2.0:

1// Node.js example
2async function runDinosaurAnalysis(species) {
3  try {
4    // Dynamic import of the analysis module for the given species
5    const analyzer = await import(`./analyzers/${species.toLowerCase()}.js`);
6    return analyzer.analyzeGenome();
7  } catch (error) {
8    console.error(`No analyzer for species ${species}: `, error);
9    // Use the generic analyzer
10    const genericAnalyzer = await import('./analyzers/generic.js');
11    return genericAnalyzer.analyzeGenome(species);
12  }
13}
14
15// Usage
16runDinosaurAnalysis('velociraptor')
17  .then(results => console.log('Genome analysis results:', results))
18  .catch(error => console.error('Analysis error:', error));

Webpack and Other Bundlers

Bundlers like Webpack, Rollup, or Parcel automatically detect dynamic imports and split code into smaller chunks:

1// Webpack will automatically understand and handle this import
2const loadAdminPanel = async () => {
3  // A separate chunk will be generated for adminPanel
4  const adminModule = await import(/* webpackChunkName: "admin" */ './adminPanel');
5  return new adminModule.default();
6};
7
8// You can even specify loading priority
9const prefetchReportGenerator = () => {
10  // Fetch the module in advance, but execute only when needed
11  import(/* webpackPrefetch: true */ './reportGenerator');
12};

Summary

Dynamic module importing is a powerful feature that allows for greater flexibility and performance in JavaScript applications. In the context of our Jurassic Park, it gives us the ability to build systems that can dynamically respond to various situations, which is crucial for the safety of a park full of dangerous dinosaurs!

Thanks to dynamic imports we can:

  • Load modules only when they are needed
  • Choose different implementations depending on context
  • Reduce the initial application load time
  • Create flexible systems with modules determined at runtime

In the next exercise we'll look at JavaScript debugging tools, which will be invaluable when something goes wrong in our park management systems!

Code for this lesson: main.js
1// Dynamic module importing - Jurassic Park
2// Lazy loading and 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: Standard protection`);
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// Simulated import() for the demo
91async function dynamicImport(path) {
92  console.log(`⏳ Loading module: ${path}...`);
93
94  // Simulated 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 dinosaur: ${name} (${species})`);
120    return dinosaur;
121  }
122
123  async handleDinosaur(dinosaur) {
124    console.log(`\nProcessing: ${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(`\nGenerating the analytics report...`);
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// DEMO
176// ===========================================
177
178async function runDemo() {
179  console.log("=== DEMO START ===\n");
180
181  const manager = new DinosaurManager();
182
183  // Adding dinosaurs
184  console.log("Registering 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(`\\nTotal number of dinosaurs: ${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  // Analytics report
212  console.log("\n=== ANALYTICS ===");
213  const report = await manager.generateAnalytics();
214  console.log('\nReport:', JSON.stringify(report, null, 2));
215
216  // Emergency
217  if (parseFloat(report.averageDangerLevel) > 6) {
218    const emergency = await manager.triggerEmergency('High average danger level');
219    console.log('\nEmergency status:', emergency);
220  }
221
222  console.log(`\nLoaded modules: ${manager.loadedModules.size}`);
223  console.log(`List: ${Array.from(manager.loadedModules.keys()).join(', ')}`);
224
225  console.log("\n=== BENEFITS OF DYNAMIC IMPORTS ===\n");
226  console.log("Lazy Loading:");
227  console.log("   - Modules loaded only when needed");
228  console.log("   - Faster application startup\n");
229
230  console.log("Code Splitting:");
231  console.log("   - Smaller initial bundle");
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("// A dynamic import returns a 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("// Or with 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("Demo completed!");
257}
258
259// Run the demo
260runDemo().catch(console.error);

Spotted a mistake in this lesson?

Hands-on tasks in the game

  • Click in order

    Arrange the default export elements in the correct order:

  • Click in order

    Arrange the JavaScript module export elements:

  • Horizontal ordering

    Arrange the JavaScript module import elements:

Useful articles