Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

ES Modules (import/export)

Witaj z powrotem w Parku Jurajskim! Po zdobyciu solidnych podstaw JavaScript i poznaniu technik asynchronicznych, czas na poznanie sposobów organizacji kodu w większe, skalowalne projekty. W świecie tak złożonym jak Park Jurajski, potrzebujemy dobrze zorganizowanej struktury, aby zapewnić bezpieczeństwo i efektywność systemu.

Modularyzacja kodu w JavaScript

Wyobraź sobie, że zarządzasz Parkiem Jurajskim bez podziału na zespoły i działy - wszystko byłoby chaotyczne i niebezpieczne! Podobnie jest z kodem - gdy wszystko znajduje się w jednym pliku, szybko staje się to nieczytelne i trudne w utrzymaniu.

ES Modules (ECMAScript Modules) to standardowy system modułów w JavaScript, który pozwala na:

  • Dzielenie kodu na mniejsze, łatwiejsze w zarządzaniu części
  • Kontrolowanie, które elementy są dostępne na zewnątrz modułu
  • Definiowanie zależności między różnymi częściami programu

Eksportowanie elementów z modułów

Zacznijmy od stworzenia kilku podstawowych modułów dla naszego systemu zarządzania Parkiem Jurajskim:

1. Eksport domyślny (default export)

Gdy mamy jeden główny element do wyeksportowania, używamy eksportu domyślnego:

1// dinosaur.js - Model dinozaura
2class Dinosaur {
3  constructor(name, species, diet, dangerLevel) {
4    this.name = name;
5    this.species = species;
6    this.diet = diet;
7    this.dangerLevel = dangerLevel;
8    this.health = 100;
9    this.isContained = true;
10  }
11
12  feed() {
13    this.health = Math.min(100, this.health + 20);
14    return `${this.name} został nakarmiony. Poziom zdrowia: ${this.health}%`;
15  }
16
17  checkStatus() {
18    return {
19      name: this.name,
20      health: this.health,
21      isContained: this.isContained,
22      warning: this.dangerLevel > 7 ? "WYSOKI POZIOM ZAGROŻENIA" : ""
23    };
24  }
25}
26
27// Eksport domyślny - tylko jedna klasa/funkcja/obiekt per plik
28export default Dinosaur;

2. Eksport nazwany (named exports)

Gdy mamy wiele elementów do wyeksportowania, używamy eksportów nazwanych:

1// securityUtils.js - Narzędzia do obsługi bezpieczeństwa
2export const SECURITY_LEVELS = {
3  GREEN: "normalny",
4  YELLOW: "podwyższony",
5  ORANGE: "wysoki",
6  RED: "krytyczny"
7};
8
9export function checkFenceStatus(sectorId) {
10  // Symulacja sprawdzania statusu ogrodzenia w danym sektorze
11  const status = Math.random() > 0.1 ? "active" : "failure";
12  return {
13    sector: sectorId,
14    status,
15    timestamp: new Date().toISOString()
16  };
17}
18
19export function triggerAlarm(sector, reason) {
20  console.log(`ALARM! Sektor ${sector}: ${reason}`);
21  // Logika uruchamiania alarmu
22  return {
23    success: true,
24    alarmId: "ALR-" + Math.floor(Math.random() * 10000)
25  };
26}
27
28// Możemy też eksportować funkcję domyślną obok nazwanych eksportów
29export default function getSecurityStatus() {
30  // Zwraca ogólny status bezpieczeństwa parku
31  return {
32    level: SECURITY_LEVELS.GREEN,
33    activeAlarms: 0,
34    lastChecked: new Date().toISOString()
35  };
36}

3. Eksportowanie wcześniej zdefiniowanych zmiennych

Możemy też najpierw zdefiniować nasze zmienne, funkcje lub klasy, a następnie wyeksportować je na końcu pliku:

1// parkStats.js - Statystyki Parku Jurajskiego
2const totalVisitors = 0;
3const revenue = 0;
4const incidents = [];
5
6function addVisitors(count) {
7  totalVisitors += count;
8  return totalVisitors;
9}
10
11function addRevenue(amount) {
12  revenue += amount;
13  return revenue;
14}
15
16function recordIncident(type, location, description) {
17  const incident = {
18    id: incidents.length + 1,
19    type,
20    location,
21    description,
22    timestamp: new Date().toISOString()
23  };
24  incidents.push(incident);
25  return incident;
26}
27
28function getDailyStats() {
29  return {
30    totalVisitors,
31    revenue,
32    incidentCount: incidents.length
33  };
34}
35
36// Eksport wielu elementów naraz
37export {
38  addVisitors,
39  addRevenue,
40  recordIncident,
41  getDailyStats
42};
43
44// Możemy również zmienić nazwę eksportowanego elementu
45export { recordIncident as logParkIncident };

Importowanie elementów z modułów

Po utworzeniu modułów, czas je wykorzystać w głównej aplikacji:

1. Import domyślny

1// app.js
2import Dinosaur from './dinosaur.js';
3
4// Tworzymy nowe instancje dinozaurów
5const trex = new Dinosaur("Rex", "Tyrannosaurus", "carnivore", 9);
6const brachi = new Dinosaur("Longneck", "Brachiosaurus", "herbivore", 4);
7
8console.log(trex.checkStatus());
9console.log(brachi.feed());

2. Import nazwany

1// securityMonitor.js
2import { checkFenceStatus, triggerAlarm, SECURITY_LEVELS } from './securityUtils.js';
3
4// Używamy importowanych funkcji i stałych
5function monitorSecurity() {
6  const sectors = ["A1", "B2", "C3", "D4"];
7
8  // Sprawdzamy każdy sektor
9  sectors.forEach(sector => {
10    const fenceStatus = checkFenceStatus(sector);
11
12    if (fenceStatus.status === "failure") {
13      const alarmResult = triggerAlarm(
14        sector,
15        "Awaria ogrodzenia elektrycznego!"
16      );
17      console.log(`Alarm ${alarmResult.alarmId} uruchomiony w sektorze ${sector}`);
18
19      document.querySelector("#securityStatus").textContent = SECURITY_LEVELS.RED;
20    }
21  });
22}
23
24setInterval(monitorSecurity, 60000); // Sprawdzanie co minutę

3. Import domyślny i nazwany razem

1// dashboard.js
2import getSecurityStatus, { SECURITY_LEVELS, checkFenceStatus } from './securityUtils.js';
3
4// Używamy domyślnego importu
5const parkStatus = getSecurityStatus();
6console.log("Status bezpieczeństwa:", parkStatus.level);
7
8// Używamy nazwanych importów
9if (parkStatus.level === SECURITY_LEVELS.RED) {
10  // Sprawdź wszystkie sektory natychmiast
11  ["A1", "B2", "C3", "D4"].forEach(sector => {
12    console.log(`Sprawdzanie sektora ${sector}: `, checkFenceStatus(sector));
13  });
14}

4. Import ze zmianą nazwy

1// incidentReport.js
2import { recordIncident as logIncident } from './parkStats.js';
3
4// Używamy zaimportowanej funkcji pod nową nazwą
5function reportEmergency(location, details) {
6  logIncident("EMERGENCY", location, details);
7  // Dodatkowa logika obsługi sytuacji awaryjnej
8}

5. Import wszystkiego jako obiekt

1// adminPanel.js
2import * as ParkStats from './parkStats.js';
3
4// Dostęp do wszystkich funkcji z modułu przez obiekt
5function generateDailyReport() {
6  const visitorCount = ParkStats.addVisitors(120); // Dodaje 120 odwiedzających
7  const dailyRevenue = ParkStats.addRevenue(15000); // Dodaje $15,000 przychodu
8
9  const stats = ParkStats.getDailyStats();
10
11  return `
12    Dzienny raport Parku Jurajskiego:
13    - Liczba odwiedzających: ${stats.totalVisitors}
14    - Przychód: $${stats.revenue}
15    - Incydenty: ${stats.incidentCount}
16  `;
17}

Agregacja i re-eksportowanie modułów

W dużych projektach często warto stworzyć punkty wejścia, które agregują i re-eksportują funkcjonalności z wielu mniejszych modułów:

1// models/index.js - Agregacja wszystkich modeli
2export { default as Dinosaur } from './dinosaur.js';
3export { default as Employee } from './employee.js';
4export { default as Facility } from './facility.js';
5export { default as Visitor } from './visitor.js';

Dzięki temu możemy importować wszystkie modele z jednego punktu wejścia:

1// main.js
2import { Dinosaur, Employee, Facility } from './models/index.js';
3
4// Teraz możemy korzystać z wszystkich modeli
5const rex = new Dinosaur("Rex", "Tyrannosaurus", "carnivore", 9);
6const lab = new Facility("Laboratoria InGen", "B3", 12);
7const scientist = new Employee("Dr. Wu", "Główny genetyk", "lab");

Dynamiczne importowanie modułów

ES Modules oferują również mechanizm dynamicznego importowania, co pozwala na ładowanie modułów na żądanie:

1// emergencySystem.js
2async function handleEmergency(type) {
3  let emergencyModule;
4
5  switch(type) {
6    case "dinosaurEscape":
7      emergencyModule = await import('./emergencies/dinosaurEscape.js');
8      break;
9    case "powerOutage":
10      emergencyModule = await import('./emergencies/powerOutage.js');
11      break;
12    case "weatherAlert":
13      emergencyModule = await import('./emergencies/weatherAlert.js');
14      break;
15    default:
16      emergencyModule = await import('./emergencies/generalEmergency.js');
17  }
18
19  // Wykonaj procedurę awaryjną z załadowanego modułu
20  emergencyModule.initiateProtocol();
21}
22
23// Przycisk w UI do uruchomienia procedury awaryjnej
24document.querySelector("#emergencyButton").addEventListener("click", () => {
25  const emergencyType = document.querySelector("#emergencyType").value;
26  handleEmergency(emergencyType);
27});

Specyfika ES Modules w przeglądarce

Aby używać ES Modules bezpośrednio w przeglądarce, musimy dodać atrybut

type="module"
do tagu script:

1<!DOCTYPE html>
2<html>
3<head>
4  <title>Panel Kontrolny Parku Jurajskiego</title>
5</head>
6<body>
7  <h1>Panel Kontrolny Parku Jurajskiego</h1>
8  <div id="securityStatus"></div>
9  <select id="emergencyType">
10    <option value="dinosaurEscape">Ucieczka dinozaura</option>
11    <option value="powerOutage">Awaria zasilania</option>
12    <option value="weatherAlert">Alert pogodowy</option>
13  </select>
14  <button id="emergencyButton">Uruchom procedurę awaryjną</button>
15
16  <!-- Główny skrypt używający modułów -->
17  <script type="module" src="./app.js"></script>
18</body>
19</html>

Zalety modularnego podejścia

Organizowanie aplikacji z wykorzystaniem ES Modules oferuje wiele korzyści:

  1. Enkapsulacja - każdy moduł ma własną przestrzeń nazw, co zapobiega konfliktom nazwowym
  2. Reużywalność - możemy łatwo ponownie używać modułów w różnych częściach aplikacji
  3. Organizacja - łatwiej jest zrozumieć i utrzymać kod, gdy jest podzielony na logiczne części
  4. Współpraca w zespole - różni członkowie zespołu mogą pracować nad odrębnymi modułami
  5. Lazy loading - możemy ładować moduły tylko wtedy, gdy są potrzebne
  6. Lepsze zarządzanie zależnościami - jasno zdefiniowane zależności między modułami

Praktyczny przykład: System monitorowania dinozaurów

Zobaczmy, jak moglibyśmy zorganizować większy system monitorowania dinozaurów w Parku Jurajskim z wykorzystaniem modułów:

1// models/dinosaur.js
2export default class Dinosaur {
3  // ... implementacja klasy Dinosaur (jak wcześniej)
4}
5
6// services/dinoTracking.js
7import Dinosaur from '../models/dinosaur.js';
8
9export async function getAllDinosaurs() {
10  // Pobieranie danych o dinozaurach z API
11  const response = await fetch('/api/dinosaurs');
12  const data = await response.json();
13
14  // Konwersja surowych danych na instancje klasy Dinosaur
15  return data.map(dino => new Dinosaur(
16    dino.name,
17    dino.species,
18    dino.diet,
19    dino.dangerLevel
20  ));
21}
22
23export async function getDinosaursByDangerLevel(level) {
24  const allDinos = await getAllDinosaurs();
25  return allDinos.filter(dino => dino.dangerLevel >= level);
26}
27
28// services/securitySystem.js
29export async function getActiveFences() {
30  // Pobieranie danych o aktywnych ogrodzeniach
31  const response = await fetch('/api/fences/active');
32  return response.json();
33}
34
35export async function activateSectorLockdown(sectorId) {
36  // Uruchamianie blokady w danym sektorze
37  const response = await fetch(`/api/sectors/${sectorId}/lockdown`, {
38    method: 'POST'
39  });
40  return response.json();
41}
42
43// utils/alertSystem.js
44export const ALERT_LEVELS = {
45  INFO: 'info',
46  WARNING: 'warning',
47  DANGER: 'danger',
48  CRITICAL: 'critical'
49};
50
51export function showAlert(message, level = ALERT_LEVELS.INFO) {
52  const alertElement = document.createElement('div');
53  alertElement.className = `alert alert-${level}`;
54  alertElement.textContent = message;
55
56  document.querySelector('#alertContainer').appendChild(alertElement);
57
58  // Usunięcie alertu po 5 sekundach (chyba że jest krytyczny)
59  if (level !== ALERT_LEVELS.CRITICAL) {
60    setTimeout(() => {
61      alertElement.remove();
62    }, 5000);
63  }
64}
65
66// controllers/monitoringDashboard.js
67import { getAllDinosaurs, getDinosaursByDangerLevel } from '../services/dinoTracking.js';
68import { getActiveFences, activateSectorLockdown } from '../services/securitySystem.js';
69import { showAlert, ALERT_LEVELS } from '../utils/alertSystem.js';
70
71export async function initializeDashboard() {
72  try {
73    // Pobierz dane o dinozaurach i statusie ogrodzeń
74    const [allDinos, activeFences] = await Promise.all([
75      getAllDinosaurs(),
76      getActiveFences()
77    ]);
78
79    // Znajdź niebezpieczne dinozaury
80    const dangerousDinos = await getDinosaursByDangerLevel(8);
81
82    // Wyświetl dane na dashboardzie
83    renderDinosaurTable(allDinos);
84    renderSecurityStatus(activeFences);
85
86    // Pokaż alerty dla niebezpiecznych dinozaurów
87    if (dangerousDinos.length > 0) {
88      showAlert(
89        `UWAGA: ${dangerousDinos.length} dinozaurów z wysokim poziomem zagrożenia`,
90        ALERT_LEVELS.WARNING
91      );
92    }
93
94    // Ustaw nasłuchiwanie na zdarzenia awaryjne
95    setupEmergencyHandlers();
96
97  } catch (error) {
98    showAlert(`Błąd inicjalizacji dashboardu: ${error.message}`, ALERT_LEVELS.DANGER);
99  }
100}
101
102function renderDinosaurTable(dinosaurs) {
103  // Logika renderowania tabeli dinozaurów
104}
105
106function renderSecurityStatus(fences) {
107  // Logika renderowania statusu zabezpieczeń
108}
109
110function setupEmergencyHandlers() {
111  // Logika ustawiania obsługi zdarzeń awaryjnych
112  document.querySelector('#emergencyLockdown').addEventListener('click', async () => {
113    const sector = document.querySelector('#sectorSelect').value;
114    try {
115      await activateSectorLockdown(sector);
116      showAlert(`Blokada sektora ${sector} aktywowana`, ALERT_LEVELS.WARNING);
117    } catch (error) {
118      showAlert(`Nie udało się aktywować blokady: ${error.message}`, ALERT_LEVELS.CRITICAL);
119    }
120  });
121}
122
123// app.js - Główny plik aplikacji
124import { initializeDashboard } from './controllers/monitoringDashboard.js';
125
126// Inicjalizacja aplikacji gdy DOM jest gotowy
127document.addEventListener('DOMContentLoaded', () => {
128  initializeDashboard();
129
130  // Dodatkowa logika inicjalizacyjna
131  console.log('System monitorowania Parku Jurajskiego uruchomiony');
132});

Wsparcie przeglądarek i konfiguracja Webpack

Nowsze przeglądarki obsługują ES Modules natywnie, ale dla pełnej kompatybilności z starszymi przeglądarkami często używamy narzędzi takich jak Webpack lub Rollup, które bundlują nasze moduły w kod kompatybilny z starszymi przeglądarkami.

Podstawowa konfiguracja Webpack dla ES Modules

1// webpack.config.js
2const path = require('path');
3
4module.exports = {
5  entry: './src/app.js',
6  output: {
7    filename: 'bundle.js',
8    path: path.resolve(__dirname, 'dist')
9  },
10  module: {
11    rules: [
12      {
13        test: /.js$/,
14        exclude: /node_modules/,
15        use: {
16          loader: 'babel-loader',
17          options: {
18            presets: ['@babel/preset-env']
19          }
20        }
21      }
22    ]
23  }
24};

Dobre praktyki przy pracy z modułami

  1. Jedna odpowiedzialność - każdy moduł powinien mieć jedną, jasno określoną odpowiedzialność
  2. Małe moduły - staraj się, aby moduły były małe i skupione na określonym zadaniu
  3. Spójne nazewnictwo - używaj spójnych konwencji nazewnictwa dla plików modułów
  4. Jawne zależności - wszystkie zależności powinny być jasno zdefiniowane w importach
  5. Minimalizacja modyfikacji stanu - unikaj modyfikowania stanu globalnego w modułach
  6. Dokumentacja - dodawaj komentarze JSDoc do eksportowanych funkcji i klas
  7. Struktura folderów - organizuj moduły w logiczną strukturę folderów (np. models, services, utils)

Podsumowanie

ES Modules to potężny mechanizm organizacji kodu w JavaScript, który pozwala na tworzenie modularnych, dobrze zorganizowanych aplikacji. W świecie tak złożonym jak Park Jurajski, gdzie każdy błąd może mieć katastrofalne konsekwencje, dobra organizacja kodu jest kluczowa dla utrzymania bezpieczeństwa i wydajności.

W następnym ćwiczeniu przyjrzymy się alternatywnemu systemowi modułów - CommonJS, który jest szeroko używany w środowisku Node.js, idealnym do tworzenia serwerowej części naszego systemu zarządzania Parkiem Jurajskim.

Vai a CodeWorlds