Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Dynamiczne importowanie modułów

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!

Przejdź do CodeWorlds