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!
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 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});Let's think about practical applications of dynamic importing in our Jurassic Park management system:
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});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});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});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}Let's see what some of the dynamically imported modules in our system might look like:
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}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}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();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 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));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};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:
In the next exercise we'll look at JavaScript debugging tools, which will be invaluable when something goes wrong in our park management systems!