Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript
Dynamiczne importowanie modułów
W tej lekcji7
W poprzednich ćwiczeniach poznaliśmy dwa główne systemy modułów w JavaScript: ES Modules oraz CommonJS. Oba systemy umożliwiają organizację kodu w niezależne moduły, ale zapoznaliśmy się głównie ze statycznymi importami. Teraz przejdziemy do następnego poziomu - dynamicznego importowania modułów, które daje nam znacznie większą elastyczność.
Dla naszego systemu zarządzania Parkiem Jurajskim, dynamiczne importy mogą być kluczowe. Wyobraź sobie, że potrzebujemy różnych procedur awaryjnych w zależności od tego, co się dzieje - ucieczka T-Rexa wymaga innego zestawu akcji niż awaria zasilania czy zbliżający się huragan!
Wprowadzenie do dynamicznego importowania
Dynamiczne importowanie modułów pozwala na ładowanie modułów w trakcie działania programu, a nie podczas jego inicjalizacji. Oznacza to, że możemy wczytać moduł dokładnie wtedy, gdy jest potrzebny, oszczędzając pamięć i czas ładowania dla modułów, które mogą nigdy nie zostać użyte.
Syntaktyka dynamicznego importu
Dynamiczny import używa funkcji import(), która zwraca Promise zawierające załadowany moduł:
1// Statyczny import (zawsze ładowany)
2import { DinosaurTracker } from './dinosaurTracker.js';
3
4// Dynamiczny import (ładowany na żądanie)
5import('./emergencyProcedures.js')
6 .then(module => {
7 // Teraz możemy użyć załadowanego modułu
8 module.initializeEmergencyProtocol();
9 })
10 .catch(error => {
11 console.error('Nie udało się załadować procedur awaryjnych:', error);
12 });Jeszcze wygodniej jest używać dynamicznego importu z async/await:
1async function handleEmergency(type) {
2 try {
3 // Dynamicznie importujemy odpowiedni moduł procedury awaryjnej
4 const emergencyModule = await import(`./emergencies/${type}.js`);
5
6 // Używamy załadowanego modułu
7 return emergencyModule.executeProtocol();
8 } catch (error) {
9 console.error(`Nie udało się załadować procedury: ${type}`, error);
10 // Fallback do ogólnej procedury awaryjnej
11 const fallbackModule = await import('./emergencies/general.js');
12 return fallbackModule.executeProtocol();
13 }
14}
15
16// Użycie naszej funkcji
17handleEmergency('dinosaurEscape').then(result => {
18 console.log('Status procedury awaryjnej:', result);
19});Zastosowania dynamicznego importowania w Parku Jurajskim
Zastanówmy się nad praktycznymi zastosowaniami dynamicznego importowania w naszym systemie zarządzania Parkiem Jurajskim:
1. Ładowanie na żądanie
Możemy ładować funkcjonalności tylko wtedy, gdy są potrzebne, co jest szczególnie przydatne dla rzadko używanych, ale dużych modułów:
1// dashboardController.js
2async function loadDashboard(dashboardType) {
3 // Bazowy dashboard zawsze załadowany
4 const statusPanel = document.getElementById('statusPanel');
5 statusPanel.innerHTML = 'Ładowanie...';
6
7 try {
8 // Dynamicznie importujemy żądany typ dashboardu
9 const dashboardModule = await import(`./dashboards/${dashboardType}Dashboard.js`);
10
11 // Inicjalizujemy dashboard
12 const dashboard = new dashboardModule.default();
13 await dashboard.initialize(statusPanel);
14
15 console.log(`Dashboard ${dashboardType} załadowany pomyślnie`);
16 } catch (error) {
17 console.error(`Błąd ładowania dashboardu ${dashboardType}: `, error);
18 statusPanel.innerHTML = `Błąd ładowania dashboardu: ${error.message}`;
19 }
20}
21
22// Przyciski w interfejsie użytkownika
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. Różne implementacje dla różnych środowisk
Możemy dynamicznie ładować różne implementacje w zależności od środowiska (produkcja, rozwój, testowe):
1async function initializeTracking() {
2 const environment = process.env.NODE_ENV || 'development';
3
4 try {
5 // Ładujemy odpowiednią implementację dla danego środowiska
6 const trackingModule = await import(`./tracking/${environment}.js`);
7
8 // Inicjalizujemy system śledzenia
9 const tracker = trackingModule.createTracker();
10 tracker.initialize();
11
12 return tracker;
13 } catch (error) {
14 console.error(`Nie udało się zainicjalizować śledzenia dla środowiska ${environment}: `, error);
15 // Fallback do wersji dla developmentu
16 const fallbackModule = await import('./tracking/development.js');
17 return fallbackModule.createTracker();
18 }
19}
20
21// Użycie funkcji
22initializeTracking().then(tracker => {
23 tracker.trackEvent('app_initialized', { timestamp: Date.now() });
24});3. Opóźnione ładowanie komponentów interfejsu (lazy loading)
W aplikacji frontendowej możemy opóźnić ładowanie komponentów interfejsu, aby przyspieszyć początkowe ładowanie strony:
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(`Nie udało się załadować komponentu ${componentName}: `, error);
8 throw error;
9 }
10}
11
12// W głównym pliku aplikacji
13import { loadComponent } from './dynamicComponents.js';
14
15document.getElementById('btnShowMap').addEventListener('click', async () => {
16 try {
17 // Ładujemy mapę parku tylko, gdy użytkownik jej potrzebuje
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(`Nie udało się załadować mapy: ${error.message}`);
30 }
31});4. Dynamiczne ładowanie procedur i protokołów bezpieczeństwa
Jednym z najważniejszych zastosowań w naszym parku będzie system procedur bezpieczeństwa:
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 // Sprawdzamy, czy protokół jest już aktywny
11 if (this.activeProtocols.has(protocolName)) {
12 console.log(`Protokół ${protocolName} jest już aktywny`);
13 return this.activeProtocols.get(protocolName);
14 }
15
16 // Dynamicznie importujemy protokół
17 const protocolModule = await import(`./protocols/${protocolName}.js`);
18
19 // Inicjalizujemy protokół
20 const protocol = new protocolModule.default(params);
21 await protocol.initialize();
22
23 // Dodajemy do aktywnych protokołów
24 this.activeProtocols.set(protocolName, protocol);
25
26 console.log(`Protokół ${protocolName} aktywowany pomyślnie`);
27 return protocol;
28 } catch (error) {
29 console.error(`Błąd aktywacji protokołu ${protocolName}: `, error);
30 throw new Error(`Nie udało się aktywować protokołu ${protocolName}: ${error.message}`);
31 }
32 }
33
34 async deactivateProtocol(protocolName) {
35 const protocol = this.activeProtocols.get(protocolName);
36 if (!protocol) {
37 console.log(`Protokół ${protocolName} nie jest aktywny`);
38 return false;
39 }
40
41 try {
42 await protocol.shutdown();
43 this.activeProtocols.delete(protocolName);
44 console.log(`Protokół ${protocolName} dezaktywowany pomyślnie`);
45 return true;
46 } catch (error) {
47 console.error(`Błąd dezaktywacji protokołu ${protocolName}: `, error);
48 return false;
49 }
50 }
51
52 async handleEmergency(type, location) {
53 console.log(`Obsługa sytuacji awaryjnej: ${type} w lokalizacji ${location}`);
54
55 // Aktualizujemy poziom alertu
56 this.alertLevel = 'red';
57
58 try {
59 // Importujemy odpowiednią procedurę awaryjną
60 const emergencyModule = await import(`./emergencies/${type}.js`);
61
62 // Uruchamiamy procedurę
63 const procedure = new emergencyModule.EmergencyProcedure(location);
64 const emergencyId = await procedure.execute();
65
66 console.log(`Procedura awaryjna dla ${type} uruchomiona, ID: ${emergencyId}`);
67 return emergencyId;
68 } catch (error) {
69 console.error(`Nie udało się obsłużyć sytuacji awaryjnej ${type}: `, error);
70
71 // Fallback do ogólnej procedury awaryjnej
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(`Krytyczny błąd! Nawet ogólna procedura nie działa: `, fallbackError);
78 return null;
79 }
80 }
81 }
82}
83
84// Użycie systemu bezpieczeństwa
85const security = new SecuritySystem();
86
87// Przykłady aktywacji protokołów
88document.getElementById('btnLockdown').addEventListener('click', async () => {
89 try {
90 const protocol = await security.activateProtocol('lockdown', {
91 sectors: ['A1', 'B2'],
92 reason: 'Ucieczka dinozaura'
93 });
94
95 document.getElementById('status').textContent = `Protokół blokady aktywny w ${protocol.affectedSectors.length} sektorach`;
96 } catch (error) {
97 alert(`Błąd aktywacji protokołu: ${error.message}`);
98 }
99});
100
101// Przykład obsługi sytuacji awaryjnej
102async function reportEmergency(type, location) {
103 try {
104 const emergencyId = await security.handleEmergency(type, location);
105 return emergencyId;
106 } catch (error) {
107 console.error(`Nie udało się zgłosić sytuacji awaryjnej: `, error);
108 return null;
109 }
110}Implementacja przykładowych modułów
Zobaczmy, jak mogą wyglądać niektóre z modułów, które dynamicznie importujemy w naszym systemie:
Przykładowy protokół bezpieczeństwa (lockdown.js)
1// protocols/lockdown.js
2export default class LockdownProtocol {
3 constructor(params) {
4 this.sectors = params.sectors || [];
5 this.reason = params.reason || 'Nieznany powód';
6 this.timestamp = new Date();
7 this.active = false;
8 this.affectedSectors = [];
9 }
10
11 async initialize() {
12 console.log(`Inicjalizacja protokołu blokady dla sektorów: ${this.sectors.join(', ')}`);
13
14 // Symulacja aktywacji blokady dla każdego sektora
15 this.affectedSectors = await Promise.all(
16 this.sectors.map(async (sector) => {
17 try {
18 // W rzeczywistym systemie, tu byłoby 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(`Błąd blokady sektora ${sector}: `, error);
27 return {
28 sector,
29 status: 'failed',
30 error: error.message
31 };
32 }
33 })
34 );
35
36 this.active = true;
37
38 // Zwracamy 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(`Wyłączanie protokołu blokady dla sektorów: ${this.sectors.join(', ')}`);
52
53 // Symulacja deaktywacji blokady dla każdego sektora
54 await Promise.all(
55 this.sectors.map(async (sector) => {
56 try {
57 // W rzeczywistym systemie, tu byłoby API call
58 await this.deactivateSectorLockdown(sector);
59 } catch (error) {
60 console.error(`Błąd odblokowania sektora ${sector}: `, error);
61 }
62 })
63 );
64
65 this.active = false;
66
67 return {
68 success: true,
69 deactivatedAt: new Date()
70 };
71 }
72
73 // Metoda symulująca aktywację blokady sektora
74 async activateSectorLockdown(sector) {
75 return new Promise((resolve, reject) => {
76 // Symulacja opóźnienia sieciowego i odpowiedzi 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(`Błąd blokady sektora ${sector}: Awaria systemu`));
87 }
88 }, 500 + Math.random() * 1000);
89 });
90 }
91
92 // Metoda symulująca deaktywację blokady sektora
93 async deactivateSectorLockdown(sector) {
94 return new Promise((resolve, reject) => {
95 // Symulacja opóźnienia sieciowego i odpowiedzi 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 } // Ogrodzenia pozostają pod napięciem
103 });
104 } else {
105 reject(new Error(`Błąd odblokowania sektora ${sector}: Awaria systemu`));
106 }
107 }, 300 + Math.random() * 700);
108 });
109 }
110}Przykładowa procedura awaryjna (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(`Wykonywanie procedury awaryjnej dla ucieczki dinozaura w lokalizacji ${this.location}`);
13 this.status = 'in_progress';
14
15 try {
16 // 1. Aktywacja alarmu w strefie
17 await this.activateZoneAlarm(this.location);
18
19 // 2. Ewakuacja zwiedzających z zagrożonego obszaru
20 await this.evacuateVisitors(this.location);
21
22 // 3. Wysłanie zespołu bezpieczeństwa
23 const securityTeam = await this.dispatchSecurityTeam(this.location);
24
25 // 4. Aktywacja protokołu blokady w tym i sąsiednich sektorach
26 const adjacentSectors = await this.getAdjacentSectors(this.location);
27 const allSectors = [this.location, ...adjacentSectors];
28 await this.activateLockdown(allSectors);
29
30 // 5. Powiadomienie personelu
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(`Procedura awaryjna ${this.id} zakończona pomyślnie`);
40
41 return this.id;
42 } catch (error) {
43 this.status = 'failed';
44 console.error(`Procedura awaryjna ${this.id} nie powiodła się: `, error);
45 throw error;
46 }
47 }
48
49 // Metody pomocnicze...
50 async activateZoneAlarm(zone) {
51 console.log(`Aktywacja alarmu w strefie ${zone}`);
52 // Implementacja...
53 return { success: true, zone };
54 }
55
56 async evacuateVisitors(zone) {
57 console.log(`Ewakuacja zwiedzających ze strefy ${zone}`);
58 // Implementacja...
59 return { success: true, evacuatedCount: Math.floor(Math.random() * 50) + 10 };
60 }
61
62 async dispatchSecurityTeam(zone) {
63 console.log(`Wysyłanie zespołu bezpieczeństwa do strefy ${zone}`);
64 // Implementacja...
65 return {
66 teamId: 'ST-' + Math.floor(Math.random() * 10) + 1,
67 members: Math.floor(Math.random() * 5) + 3,
68 estimatedArrival: new Date(Date.now() + 5 * 60 * 1000)
69 };
70 }
71
72 async getAdjacentSectors(sector) {
73 // W rzeczywistym systemie, pobieralibyśmy to z bazy danych
74 const sectorMap = {
75 'A1': ['A2', 'B1'],
76 'A2': ['A1', 'A3', 'B2'],
77 'B1': ['A1', 'B2', 'C1'],
78 // ... więcej mapowań
79 };
80
81 return sectorMap[sector] || [];
82 }
83
84 async activateLockdown(sectors) {
85 console.log(`Aktywacja blokady w sektorach: ${sectors.join(', ')}`);
86
87 // Dynamicznie importujemy moduł protokołu blokady
88 const LockdownModule = await import('../protocols/lockdown.js');
89 const lockdown = new LockdownModule.default({
90 sectors,
91 reason: 'Ucieczka dinozaura'
92 });
93
94 return lockdown.initialize();
95 }
96
97 async notifyStaff(data) {
98 console.log(`Powiadamianie personelu o sytuacji awaryjnej: `, data);
99 // Implementacja...
100 return { success: true, notifiedCount: Math.floor(Math.random() * 30) + 5 };
101 }
102}Budowanie dynamicznego systemu z importami zdeterminowanymi w czasie wykonania
Możemy też tworzyć bardziej zaawansowane systemy, w których ścieżki importów są determinowane w czasie wykonania:
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 // Sprawdź cache, jeśli ignorowanie cache nie jest włączone
12 if (!ignoreCache && this.cache.has(fullPath)) {
13 console.log(`Zwracanie modułu ${fullPath} z cache`);
14 return this.cache.get(fullPath);
15 }
16
17 try {
18 console.log(`Dynamiczne ładowanie modułu: ${fullPath}`);
19 const module = await import(fullPath);
20
21 // Zapisz w cache
22 this.cache.set(fullPath, module);
23
24 return module;
25 } catch (error) {
26 console.error(`Błąd ładowania modułu ${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(`Błąd ładowania wielu modułów: `, 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 // Wyczyść cały 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 // Ładujemy podstawowe moduły
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(`System śledzenia dinozaurów zainicjalizowany.`);
74 }
75
76 async trackDinosaur(dinosaurId, species) {
77 if (this.trackers.has(dinosaurId)) {
78 console.log(`Dinozaur ${dinosaurId} jest już śledzony.`);
79 return this.trackers.get(dinosaurId);
80 }
81
82 try {
83 // Dynamicznie ładujemy moduł śledzenia dla danego gatunku
84 const specificTrackingModule = await this.moduleLoader.load(`./species/${species.toLowerCase()}.js`);
85
86 // Tworzymy i inicjalizujemy tracker dla tego dinozaura
87 const tracker = new specificTrackingModule.default(dinosaurId, {
88 utils: this.utils,
89 core: this.trackingCore
90 });
91
92 await tracker.initialize();
93
94 // Zapisujemy tracker i oznaczamy dinozaura jako aktywnego
95 this.trackers.set(dinosaurId, tracker);
96 this.activeDinosaurs.add(dinosaurId);
97
98 console.log(`Rozpoczęto śledzenie dinozaura ${dinosaurId} (${species}).`);
99 return tracker;
100 } catch (error) {
101 console.error(`Błąd śledzenia dinozaura ${dinosaurId}: `, error);
102
103 // Jeśli nie ma konkretnego modułu dla gatunku, używamy ogólnego
104 try {
105 console.log(`Próba użycia ogólnego modułu śledzenia dla ${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(`Rozpoczęto ogólne śledzenie dinozaura ${dinosaurId} (${species}).`);
119 return tracker;
120 } catch (fallbackError) {
121 console.error(`Nie udało się użyć nawet ogólnego modułu śledzenia: `, fallbackError);
122 throw new Error(`Nie można śledzić dinozaura ${dinosaurId}: ${error.message}`);
123 }
124 }
125 }
126
127 async stopTracking(dinosaurId) {
128 if (!this.trackers.has(dinosaurId)) {
129 console.log(`Dinozaur ${dinosaurId} nie jest śledzony.`);
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(`Zatrzymano śledzenie dinozaura ${dinosaurId}.`);
141 return true;
142 } catch (error) {
143 console.error(`Błąd zatrzymania śledzenia dinozaura ${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// Użycie systemu śledzenia
169async function initTrackingSystem() {
170 const trackingSystem = new DinosaurTrackingSystem();
171 await trackingSystem.initialize();
172
173 // Śledzenie różnych gatunków dinozaurów
174 try {
175 const rexTracker = await trackingSystem.trackDinosaur('rex-01', 'tyrannosaurus');
176 console.log('Status T-Rexa:', rexTracker.getStatus());
177
178 const raptorTracker = await trackingSystem.trackDinosaur('raptor-02', 'velociraptor');
179 console.log('Status Velociraptora:', raptorTracker.getStatus());
180
181 // Nawet jeśli nie mamy specyficznego modułu, system powinien użyć fallbacku
182 const stegoTracker = await trackingSystem.trackDinosaur('stego-01', 'stegosaurus');
183 console.log('Status Stegozaura:', stegoTracker.getStatus());
184
185 console.log(`Aktywne śledzenie: ${trackingSystem.getActiveTrackingCount()} dinozaurów`);
186 } catch (error) {
187 console.error(`Błąd inicjalizacji systemu śledzenia: `, error);
188 }
189}
190
191// Inicjalizacja systemu
192initTrackingSystem();Wady i zalety dynamicznego importowania
Zalety
- Mniejsze początkowe obciążenie - Aplikacja ładuje tylko te moduły, które są potrzebne w danym momencie
- Lepsza wydajność - Podział kodu (code splitting) pozwala na szybsze ładowanie strony
- Elastyczność - Możliwość ładowania modułów w zależności od warunków lub interakcji użytkownika
- Warunkowe importy - Możliwość wyboru różnych implementacji w zależności od środowiska, parametrów, itp.
- Łatwiejsza konserwacja - Podział aplikacji na mniejsze, niezależne moduły
Wady
- Złożoność - Dodaje warstwę złożoności do aplikacji
- Obsługa błędów - Wymaga dokładnej obsługi błędów dla przypadków, gdy moduł nie może zostać załadowany
- Opóźnienia - Ładowanie modułu w czasie wykonania może wprowadzić opóźnienia w interfejsie użytkownika
- Zwiększony rozmiar pakietów - W niektórych przypadkach może prowadzić do duplikacji kodu
- Debugowanie - Może być trudniejsze w debugowaniu niż statyczne importy
Implementacja w różnych środowiskach
Przeglądarki
W nowoczesnych przeglądarkach dynamiczny import działa natywnie:
1// Przykład dla przeglądarki
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('Nie udało się załadować modułu blokady:', error);
9 }
10});Node.js
Node.js od wersji 13.2.0 oficjalnie wspiera dynamiczne importy:
1// Przykład dla Node.js
2async function runDinosaurAnalysis(species) {
3 try {
4 // Dynamiczny import modułu analizy dla danego gatunku
5 const analyzer = await import(`./analyzers/${species.toLowerCase()}.js`);
6 return analyzer.analyzeGenome();
7 } catch (error) {
8 console.error(`Nie ma analizatora dla gatunku ${species}: `, error);
9 // Użyj ogólnego analizatora
10 const genericAnalyzer = await import('./analyzers/generic.js');
11 return genericAnalyzer.analyzeGenome(species);
12 }
13}
14
15// Użycie
16runDinosaurAnalysis('velociraptor')
17 .then(results => console.log('Wyniki analizy genomu:', results))
18 .catch(error => console.error('Błąd analizy:', error));Webpack i inne bundlery
Bundlery jak Webpack, Rollup czy Parcel automatycznie wykrywają dynamiczne importy i dzielą kod na mniejsze fragmenty (chunks):
1// Webpack automatycznie zrozumie i obsłuży ten import
2const loadAdminPanel = async () => {
3 // Zostanie wygenerowany osobny chunk dla adminPanel
4 const adminModule = await import(/* webpackChunkName: "admin" */ './adminPanel');
5 return new adminModule.default();
6};
7
8// Można nawet wskazać priorytet ładowania
9const prefetchReportGenerator = () => {
10 // Pobierz moduł z wyprzedzeniem, ale wykonaj dopiero gdy potrzebny
11 import(/* webpackPrefetch: true */ './reportGenerator');
12};Podsumowanie
Dynamiczne importowanie modułów to potężna funkcja, która pozwala na większą elastyczność i wydajność w aplikacjach JavaScript. W kontekście naszego Parku Jurajskiego, daje nam możliwość budowania systemów, które mogą dynamicznie reagować na różne sytuacje, co jest kluczowe dla bezpieczeństwa parku pełnego niebezpiecznych dinozaurów!
Dzięki dynamicznym importom możemy:
- Ładować moduły tylko wtedy, gdy są potrzebne
- Wybierać różne implementacje w zależności od kontekstu
- Zmniejszyć początkowy czas ładowania aplikacji
- Tworzyć elastyczne systemy z modułami określanymi w czasie wykonania
W następnym ćwiczeniu zajmiemy się narzędziami do debugowania JavaScript, które będą nieocenione, gdy nastąpi jakaś awaria w naszych systemach zarządzania parkiem!
Kod do tej lekcji: main.js
1// Dynamiczne importowanie modułów - Park Jurajski
2// Lazy loading i Code Splitting
3
4console.log("Dynamiczne Importowanie Modułów");
5console.log("System ładowania na żądanie\n");
6
7// ===========================================
8// SYMULACJA DYNAMICZNYCH MODUŁÓW
9// ===========================================
10
11// Symulowane moduły jako funkcje
12const modules = {
13 '/modules/trex-handler.js': () => ({
14 name: 'T-Rex Handler Module',
15 handleTRex: (trex) => {
16 console.log(`Obsługa T-Rexa: ${trex.name}`);
17 console.log(` Poziom zagrożenia: ${trex.dangerLevel}/10`);
18 console.log(` Status: Wymagana podwójna ochrona`);
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(`Obsługa roślinożercy: ${dino.name}`);
31 console.log(` Poziom zagrożenia: ${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(`Obsługa Velociraptora: ${raptor.name}`);
45 console.log(` Poziom zagrożenia: ${raptor.dangerLevel}/10`);
46 console.log(` Status: Wysoka czujność - polują stadami!`);
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 ? 'Zwiększ bezpieczeństwo' : 'Poziom bezpieczeństwa OK'
69 };
70 }
71 }),
72
73 '/modules/emergency.js': () => ({
74 name: 'Emergency Protocol Module',
75 activateEmergency: (reason) => {
76 console.log(`\nPROTOKÓŁ AWARYJNY AKTYWOWANY!`);
77 console.log(`Powód: ${reason}`);
78 console.log(`1. Zamknięcie wszystkich wybiegów`);
79 console.log(`2. Ewakuacja gości`);
80 console.log(`3. Mobilizacja ekip bezpieczeństwa`);
81 return {
82 status: 'ACTIVE',
83 timestamp: new Date().toISOString(),
84 actions: ['LOCK_ENCLOSURES', 'EVACUATE', 'ALERT_SECURITY']
85 };
86 }
87 })
88};
89
90// Symulacja import() dla demonstracji
91async function dynamicImport(path) {
92 console.log(`⏳ Ładowanie modułu: ${path}...`);
93
94 // Symulacja opóźnienia sieciowego
95 await new Promise(resolve => setTimeout(resolve, 500));
96
97 if (modules[path]) {
98 const module = modules[path]();
99 console.log(`Załadowano: ${module.name}\n`);
100 return module;
101 }
102
103 throw new Error(`Module not found: ${path}`);
104}
105
106// ===========================================
107// SYSTEM ZARZĄDZANIA DINOZAURAMI
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(`Dodano dinozaura: ${name} (${species})`);
120 return dinosaur;
121 }
122
123 async handleDinosaur(dinosaur) {
124 console.log(`\nPrzetwarzanie: ${dinosaur.name}...`);
125
126 // Dynamiczne ładowanie odpowiedniego modułu
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(`Brak dedykowanego modułu dla ${dinosaur.species}`);
141 return null;
142 }
143
144 // Sprawdź cache
145 if (!this.loadedModules.has(modulePath)) {
146 const module = await dynamicImport(modulePath);
147 this.loadedModules.set(modulePath, module);
148 } else {
149 console.log(`Użyto 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 // Dodawanie dinozaurów
184 console.log("Rejestracja dinozaurów:");
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(`\nCałkowita liczba dinozaurów: ${manager.dinosaurs.length}`);
192
193 // Dynamiczne ładowanie modułów dla każdego dinozaura
194 console.log("\n=== ŁADOWANIE MODUŁÓW ON-DEMAND ===");
195
196 const trexProtocol = await manager.handleDinosaur(trex);
197 console.log('Protokół bezpieczeństwa:', trexProtocol);
198
199 const raptor1Protocol = await manager.handleDinosaur(raptor1);
200 console.log('Protokół bezpieczeństwa:', raptor1Protocol);
201
202 const raptor2Protocol = await manager.handleDinosaur(raptor2);
203 console.log('Protokół bezpieczeństwa:', raptor2Protocol);
204
205 const brachiProtocol = await manager.handleDinosaur(brachi);
206 console.log('Protokół bezpieczeństwa:', brachiProtocol);
207
208 const triceProtocol = await manager.handleDinosaur(trice);
209 console.log('Protokół bezpieczeństwa:', triceProtocol);
210
211 // Raport analityczny
212 console.log("\n=== ANALITYKA ===");
213 const report = await manager.generateAnalytics();
214 console.log('\nRaport:', JSON.stringify(report, null, 2));
215
216 // Sytuacja awaryjna
217 if (parseFloat(report.averageDangerLevel) > 6) {
218 const emergency = await manager.triggerEmergency('Wysoki średni poziom zagrożenia');
219 console.log('\nStatus awaryjny:', emergency);
220 }
221
222 console.log(`\nZaładowane moduły: ${manager.loadedModules.size}`);
223 console.log(`Lista: ${Array.from(manager.loadedModules.keys()).join(', ')}`);
224
225 console.log("\n=== ZALETY DYNAMICZNEGO IMPORTOWANIA ===\n");
226 console.log("Lazy Loading:");
227 console.log(" - Moduły ładowane tylko gdy potrzebne");
228 console.log(" - Szybszy start aplikacji\n");
229
230 console.log("Code Splitting:");
231 console.log(" - Mniejsze bundlowanie początkowe");
232 console.log(" - Lepsze wykorzystanie pamięci\n");
233
234 console.log("Conditional Loading:");
235 console.log(" - Ładowanie na podstawie warunków runtime");
236 console.log(" - Elastyczność w zarządzaniu zasobami\n");
237
238 console.log("Caching:");
239 console.log(" - Raz załadowany moduł jest cached");
240 console.log(" - Szybszy dostęp przy kolejnych wywołaniach\n");
241
242 console.log("=== SKŁADNIA ===\n");
243 console.log("// Dynamiczny import zwraca 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 zakończona!");
257}
258
259// Uruchom demo
260runDemo().catch(console.error);Widzisz błąd w tej lekcji?
Zadania praktyczne w grze
- Klikanie w kolejności
Ułóż elementy eksportu domyślnego w poprawnej kolejności:
- Klikanie w kolejności
Ułóż elementy eksportu modułu JavaScript:
- Układanie w poziomie
Ułóż elementy importu modułu JavaScript: