Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript

Dynamiczne importowanie modułów

14 min czytania
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

  1. Mniejsze początkowe obciążenie - Aplikacja ładuje tylko te moduły, które są potrzebne w danym momencie
  2. Lepsza wydajność - Podział kodu (code splitting) pozwala na szybsze ładowanie strony
  3. Elastyczność - Możliwość ładowania modułów w zależności od warunków lub interakcji użytkownika
  4. Warunkowe importy - Możliwość wyboru różnych implementacji w zależności od środowiska, parametrów, itp.
  5. Łatwiejsza konserwacja - Podział aplikacji na mniejsze, niezależne moduły

Wady

  1. Złożoność - Dodaje warstwę złożoności do aplikacji
  2. Obsługa błędów - Wymaga dokładnej obsługi błędów dla przypadków, gdy moduł nie może zostać załadowany
  3. Opóźnienia - Ładowanie modułu w czasie wykonania może wprowadzić opóźnienia w interfejsie użytkownika
  4. Zwiększony rozmiar pakietów - W niektórych przypadkach może prowadzić do duplikacji kodu
  5. 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:

Przydatne artykuły