Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

CommonJS (require/module.exports)

W poprzednim ćwiczeniu poznaliśmy ES Modules - nowoczesny system modułów w JavaScript. Teraz zagłębimy się w inny, starszy, ale wciąż bardzo ważny system modułów: CommonJS. Choć nasz Park Jurajski potrzebuje najnowszych technologii do monitorowania dinozaurów w przeglądarce, jego solidne zaplecze serwerowe opiera się na Node.js, gdzie dominuje właśnie CommonJS.

Czym jest CommonJS?

CommonJS to system modułów, który został stworzony głównie z myślą o środowisku serwerowym i stał się standardem w ekosystemie Node.js. Zapewnia sposób na organizację kodu JavaScript w odrębne moduły, podobnie jak ES Modules, ale z inną składnią i logiką działania.

W przeciwieństwie do ES Modules, które zostały dodane do języka JavaScript dopiero w ES6 (2015), CommonJS istniał wcześniej i stał się de facto standardem dla Node.js. Nawet teraz, mimo oficjalnego wsparcia dla ES Modules w Node.js, CommonJS pozostaje dominującym formatem dla modułów Node.js i w ekosystemie npm.

Podstawy CommonJS

Główne koncepcje CommonJS to:

  1. Funkcja
    require()
    do importowania modułów
  2. Obiekt
    module.exports
    lub
    exports
    do eksportowania elementów

Przyjrzyjmy się, jak te elementy działają w kontekście naszego systemu Parku Jurajskiego.

Eksportowanie elementów w CommonJS

1. Eksportowanie przez przypisanie do module.exports

Najprostszy sposób eksportowania to przypisanie wartości do

module.exports
:

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 całej klasy
28module.exports = Dinosaur;

2. Eksportowanie obiektu z wieloma elementami

Możemy również eksportować wiele elementów, przypisując obiekt do

module.exports
:

1// securityUtils.js - Narzędzia do obsługi bezpieczeństwa
2const SECURITY_LEVELS = {
3  GREEN: "normalny",
4  YELLOW: "podwyższony",
5  ORANGE: "wysoki",
6  RED: "krytyczny"
7};
8
9function 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
19function 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
28function getSecurityStatus() {
29  // Zwraca ogólny status bezpieczeństwa parku
30  return {
31    level: SECURITY_LEVELS.GREEN,
32    activeAlarms: 0,
33    lastChecked: new Date().toISOString()
34  };
35}
36
37// Eksportowanie wielu elementów jako obiekt
38module.exports = {
39  SECURITY_LEVELS,
40  checkFenceStatus,
41  triggerAlarm,
42  getSecurityStatus
43};

3. Stopniowe dodawanie do exports

Możemy też dodawać eksporty pojedynczo, używając obiektu

exports
(który jest referencją do
module.exports
):

1// parkStats.js - Statystyki Parku Jurajskiego
2let totalVisitors = 0;
3let revenue = 0;
4const incidents = [];
5
6// Eksport pierwszej funkcji
7exports.addVisitors = function(count) {
8  totalVisitors += count;
9  return totalVisitors;
10};
11
12// Eksport drugiej funkcji
13exports.addRevenue = function(amount) {
14  revenue += amount;
15  return revenue;
16};
17
18// Eksport trzeciej funkcji
19exports.recordIncident = function(type, location, description) {
20  const incident = {
21    id: incidents.length + 1,
22    type,
23    location,
24    description,
25    timestamp: new Date().toISOString()
26  };
27  incidents.push(incident);
28  return incident;
29};
30
31// Eksport czwartej funkcji
32exports.getDailyStats = function() {
33  return {
34    totalVisitors,
35    revenue,
36    incidentCount: incidents.length
37  };
38};

UWAGA: Należy pamiętać, że

exports
jest tylko referencją do
module.exports
. Jeśli przypisujemy coś bezpośrednio do
module.exports
, tracimy tę referencję, dlatego nie możemy mieszać tych stylów w jednym pliku:

1// To NIE zadziała
2exports.someFunction = function() { /* ... */ };
3module.exports = SomeClass; // To nadpisuje poprzedni eksport
4
5// To zadziała
6exports.someFunction = function() { /* ... */ };
7exports.SomeClass = SomeClass;
8
9// To też zadziała
10module.exports.someFunction = function() { /* ... */ };
11module.exports.SomeClass = SomeClass;
12
13// I to zadziała (przypisanie całego obiektu)
14module.exports = {
15  someFunction: function() { /* ... */ },
16  SomeClass: SomeClass
17};

Importowanie modułów w CommonJS

1. Importowanie całego modułu

1// app.js
2const Dinosaur = require('./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. Importowanie i destrukturyzacja obiektu

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

3. Destrukturyzacja przy imporcie

W nowszych wersjach Node.js możemy również używać destrukturyzacji bezpośrednio przy imporcie:

1// dashboard.js
2const { checkFenceStatus, triggerAlarm, SECURITY_LEVELS } = require('./securityUtils.js');
3
4// Teraz możemy używać funkcji bezpośrednio
5function checkAllSectors() {
6  ["A1", "B2", "C3", "D4"].forEach(sector => {
7    console.log(`Sprawdzanie sektora ${sector}: `, checkFenceStatus(sector));
8
9    // Używamy zaimportowanej stałej
10    if (Math.random() < 0.1) {
11      console.log(`Ustawianie poziomu bezpieczeństwa: ${SECURITY_LEVELS.ORANGE}`);
12    }
13  });
14}

Różnice między CommonJS a ES Modules

Warto zrozumieć kluczowe różnice między CommonJS a ES Modules:

| Cecha | CommonJS | ES Modules | |-------|----------|------------| | Składnia importu |

require()
|
import ... from ...
| | Składnia eksportu |
module.exports = ...
lub
exports.x = ...
|
export ...
lub
export default ...
| | Ładowanie | Synchroniczne | Asynchroniczne (ładowane przed wykonaniem) | | Wykonanie | Kod wykonywany przy każdym imporcie | Kod wykonywany tylko raz, następnie moduł jest buforowany | | Statyczna analiza | Nie wspiera statycznej analizy (dynamiczne ścieżki) | Wspiera statyczną analizę (ścieżki są statyczne) | | Wsparcie w przeglądarce | Nie wspierane natywnie | Wspierane w nowoczesnych przeglądarkach | | Hoisting | Importy nie są hoistowane | Importy są hoistowane (można używać przed deklaracją) | | Tree-shaking | Trudniejszy do zaimplementowania | Łatwiejszy dzięki statycznej analizie |

Struktura projektu Node.js (backend Parku Jurajskiego)

Teraz przyjrzyjmy się, jak moglibyśmy zorganizować backend naszego systemu zarządzania Parkiem Jurajskim używając Node.js i CommonJS:

1jurassic-park-backend/
2├── config/
3│   ├── database.js     # Konfiguracja bazy danych
4│   ├── environment.js  # Zmienne środowiskowe
5│   └── security.js     # Ustawienia bezpieczeństwa
6├── models/
7│   ├── dinosaur.js     # Model dinozaura
8│   ├── employee.js     # Model pracownika
9│   ├── facility.js     # Model placówki
10│   └── index.js        # Agregator modeli
11├── controllers/
12│   ├── dinosaurController.js  # Kontroler dinozaurów
13│   ├── securityController.js  # Kontroler bezpieczeństwa
14│   └── employeeController.js  # Kontroler pracowników
15├── routes/
16│   ├── api.js          # Główny router API
17│   ├── dinosaurs.js    # Trasy związane z dinozaurami
18│   └── security.js     # Trasy związane z bezpieczeństwem
19├── services/
20│   ├── monitoringService.js   # Usługa monitorowania
21│   ├── notificationService.js # Usługa powiadomień
22│   └── loggingService.js      # Usługa logowania
23├── utils/
24│   ├── logger.js       # Narzędzie do logowania
25│   └── validators.js   # Walidatory danych
26├── middleware/
27│   ├── auth.js         # Middleware autentykacji
28│   └── errorHandler.js # Obsługa błędów
29├── app.js              # Główna aplikacja
30└── server.js           # Plik startowy serwera

Zobaczmy, jak wyglądałyby przykładowe implementacje tych modułów:

Plik konfiguracyjny (config/database.js)

1// config/database.js
2module.exports = {
3  host: process.env.DB_HOST || 'localhost',
4  port: process.env.DB_PORT || 27017,
5  name: process.env.DB_NAME || 'jurassic_park',
6  user: process.env.DB_USER || 'admin',
7  password: process.env.DB_PASSWORD || 'secret',
8
9  // Funkcja pomocnicza do tworzenia URI połączenia
10  getConnectionString: function() {
11    return `mongodb://${this.user}:${this.password}@${this.host}:${this.port}/${this.name}`;
12  }
13};

Model (models/dinosaur.js)

1// models/dinosaur.js
2const mongoose = require('mongoose');
3const Schema = mongoose.Schema;
4
5// Schemat dinozaura
6const dinosaurSchema = new Schema({
7  name: { type: String, required: true },
8  species: { type: String, required: true },
9  diet: { type: String, enum: ['carnivore', 'herbivore', 'omnivore'], required: true },
10  dangerLevel: { type: Number, min: 1, max: 10, required: true },
11  enclosureId: { type: Schema.Types.ObjectId, ref: 'Enclosure' },
12  health: { type: Number, default: 100 },
13  status: {
14    isContained: { type: Boolean, default: true },
15    lastCheck: { type: Date, default: Date.now }
16  },
17  createdAt: { type: Date, default: Date.now },
18  updatedAt: { type: Date, default: Date.now }
19});
20
21// Metody schematu
22dinosaurSchema.methods.feed = function() {
23  this.health = Math.min(100, this.health + 20);
24  this.updatedAt = Date.now();
25  return this.save();
26};
27
28dinosaurSchema.methods.checkStatus = function() {
29  return {
30    name: this.name,
31    health: this.health,
32    isContained: this.status.isContained,
33    warning: this.dangerLevel > 7 ? "WYSOKI POZIOM ZAGROŻENIA" : ""
34  };
35};
36
37// Metody statyczne
38dinosaurSchema.statics.findDangerous = function() {
39  return this.find({ dangerLevel: { $gte: 8 } });
40};
41
42// Pre-save hook
43dinosaurSchema.pre('save', function(next) {
44  this.updatedAt = Date.now();
45  next();
46});
47
48// Utworzenie modelu
49const Dinosaur = mongoose.model('Dinosaur', dinosaurSchema);
50
51// Eksport modelu
52module.exports = Dinosaur;

Kontroler (controllers/dinosaurController.js)

1// controllers/dinosaurController.js
2const Dinosaur = require('../models/dinosaur');
3const logger = require('../utils/logger');
4
5// Obiekt kontrolera z wieloma metodami
6const dinosaurController = {
7  // Pobierz wszystkie dinozaury
8  getAllDinosaurs: async (req, res) => {
9    try {
10      const dinosaurs = await Dinosaur.find().populate('enclosureId');
11      res.json(dinosaurs);
12    } catch (error) {
13      logger.error('Błąd podczas pobierania dinozaurów:', error);
14      res.status(500).json({ error: 'Błąd serwera' });
15    }
16  },
17
18  // Pobierz dinozaura po ID
19  getDinosaurById: async (req, res) => {
20    try {
21      const dinosaur = await Dinosaur.findById(req.params.id).populate('enclosureId');
22
23      if (!dinosaur) {
24        return res.status(404).json({ error: 'Dinozaur nie znaleziony' });
25      }
26
27      res.json(dinosaur);
28    } catch (error) {
29      logger.error(`Błąd podczas pobierania dinozaura ID=${req.params.id}: `, error);
30      res.status(500).json({ error: 'Błąd serwera' });
31    }
32  },
33
34  // Dodaj nowego dinozaura
35  createDinosaur: async (req, res) => {
36    try {
37      const { name, species, diet, dangerLevel, enclosureId } = req.body;
38
39      const newDinosaur = new Dinosaur({
40        name,
41        species,
42        diet,
43        dangerLevel,
44        enclosureId
45      });
46
47      await newDinosaur.save();
48      logger.info(`Dodano nowego dinozaura: ${name} (${species})`);
49
50      res.status(201).json(newDinosaur);
51    } catch (error) {
52      logger.error('Błąd podczas tworzenia dinozaura:', error);
53      res.status(400).json({ error: error.message });
54    }
55  },
56
57  // Nakarm dinozaura
58  feedDinosaur: async (req, res) => {
59    try {
60      const dinosaur = await Dinosaur.findById(req.params.id);
61
62      if (!dinosaur) {
63        return res.status(404).json({ error: 'Dinozaur nie znaleziony' });
64      }
65
66      await dinosaur.feed();
67      logger.info(`Nakarmiono dinozaura: ${dinosaur.name}`);
68
69      res.json({
70        message: `${dinosaur.name} został nakarmiony. Poziom zdrowia: ${dinosaur.health}%`,
71        health: dinosaur.health
72      });
73    } catch (error) {
74      logger.error(`Błąd podczas karmienia dinozaura ID=${req.params.id}: `, error);
75      res.status(500).json({ error: 'Błąd serwera' });
76    }
77  },
78
79  // Znajdź niebezpieczne dinozaury
80  getDangerousDinosaurs: async (req, res) => {
81    try {
82      const dangerousDinos = await Dinosaur.findDangerous();
83      res.json(dangerousDinos);
84    } catch (error) {
85      logger.error('Błąd podczas pobierania niebezpiecznych dinozaurów:', error);
86      res.status(500).json({ error: 'Błąd serwera' });
87    }
88  }
89};
90
91// Eksport całego obiektu kontrolera
92module.exports = dinosaurController;

Trasy (routes/dinosaurs.js)

1// routes/dinosaurs.js
2const express = require('express');
3const router = express.Router();
4const dinosaurController = require('../controllers/dinosaurController');
5const authMiddleware = require('../middleware/auth');
6
7// Trasy związane z dinozaurami
8router.get('/', dinosaurController.getAllDinosaurs);
9router.get('/dangerous', dinosaurController.getDangerousDinosaurs);
10router.get('/:id', dinosaurController.getDinosaurById);
11
12// Trasy wymagające uwierzytelnienia
13router.post('/', authMiddleware.verifyToken, dinosaurController.createDinosaur);
14router.post('/:id/feed', authMiddleware.verifyToken, dinosaurController.feedDinosaur);
15
16// Eksport routera
17module.exports = router;

Główny plik aplikacji (app.js)

1// app.js
2const express = require('express');
3const mongoose = require('mongoose');
4const cors = require('cors');
5const morgan = require('morgan');
6const helmet = require('helmet');
7
8// Importy konfiguracji i middleware
9const dbConfig = require('./config/database');
10const errorHandler = require('./middleware/errorHandler');
11const logger = require('./utils/logger');
12
13// Importy tras
14const apiRouter = require('./routes/api');
15const dinosaurRoutes = require('./routes/dinosaurs');
16const securityRoutes = require('./routes/security');
17
18// Inicjalizacja aplikacji Express
19const app = express();
20
21// Middleware
22app.use(helmet()); // Bezpieczeństwo
23app.use(cors()); // Obsługa Cross-Origin Resource Sharing
24app.use(morgan('dev')); // Logowanie żądań HTTP
25app.use(express.json()); // Parsowanie JSON
26app.use(express.urlencoded({ extended: false })); // Parsowanie formularzy
27
28// Połączenie z bazą danych
29mongoose.connect(dbConfig.getConnectionString(), {
30  useNewUrlParser: true,
31  useUnifiedTopology: true
32})
33.then(() => {
34  logger.info('Połączono z bazą danych MongoDB');
35})
36.catch(err => {
37  logger.error('Błąd połączenia z bazą danych:', err);
38  process.exit(1);
39});
40
41// Trasy
42app.use('/api', apiRouter);
43app.use('/api/dinosaurs', dinosaurRoutes);
44app.use('/api/security', securityRoutes);
45
46// Obsługa błędów 404
47app.use((req, res, next) => {
48  res.status(404).json({ error: 'Nie znaleziono' });
49});
50
51// Centralna obsługa błędów
52app.use(errorHandler);
53
54// Eksport aplikacji
55module.exports = app;

Plik startowy serwera (server.js)

1// server.js
2const http = require('http');
3const app = require('./app');
4const logger = require('./utils/logger');
5
6// Ustawienia portu
7const port = process.env.PORT || 3000;
8app.set('port', port);
9
10// Utworzenie serwera HTTP
11const server = http.createServer(app);
12
13// Nasłuchiwanie na połączenia
14server.listen(port, () => {
15  logger.info(`Serwer Parku Jurajskiego uruchomiony na porcie ${port}`);
16});
17
18// Obsługa błędów serwera
19server.on('error', (error) => {
20  if (error.syscall !== 'listen') {
21    throw error;
22  }
23
24  logger.error(`Błąd serwera: ${error}`);
25  process.exit(1);
26});

Circular Dependencies (Zależności cykliczne)

Jednym z wyzwań przy pracy z CommonJS (i modułami ogólnie) są zależności cykliczne, które występują, gdy moduł A importuje moduł B, a moduł B importuje moduł A:

1// dinosaurUtils.js
2const Dinosaur = require('./dinosaur');
3
4function isDangerous(dino) {
5  return dino.dangerLevel > 7;
6}
7
8module.exports = { isDangerous };
9
10// dinosaur.js
11const dinosaurUtils = require('./dinosaurUtils'); // Zależność cykliczna!
12
13class Dinosaur {
14  // ... implementacja klasy
15
16  isHighRisk() {
17    return dinosaurUtils.isDangerous(this);
18  }
19}
20
21module.exports = Dinosaur;

CommonJS radzi sobie z zależnościami cyklicznymi, ale może to prowadzić do dziwnych zachowań, jak niekompletne obiekty. Aby uniknąć takich problemów:

  1. Przekształć strukturę kodu, aby uniknąć zależności cyklicznych
  2. Użyj wzorca dependency injection
  3. Przenieś współdzielony kod do trzeciego modułu, z którego importują oba

Transpilacja CommonJS do ES Modules i odwrotnie

W wielu projektach musimy obsługiwać zarówno CommonJS, jak i ES Modules. Na przykład frontend naszego Parku Jurajskiego może używać ES Modules, a backend CommonJS. Co zrobić, gdy chcemy współdzielić kod?

Babel do transpilacji

Możemy użyć Babel do transpilacji między formatami:

1// babel.config.js dla ES Modules -> CommonJS
2module.exports = {
3  presets: [
4    ['@babel/preset-env', {
5      targets: {
6        node: 'current'
7      },
8      modules: 'commonjs' // Konwertuj import/export na require/exports
9    }]
10  ]
11};

Dual Package Hazard

Innym podejściem jest tworzenie pakietów obsługujących oba formaty modułów. W package.json możesz zdefiniować:

1{
2  "name": "jurassic-park-utils",
3  "version": "1.0.0",
4  "main": "dist/index.js", // CommonJS entry
5  "module": "dist/index.mjs", // ES Module entry
6  "exports": {
7    "import": "./dist/index.mjs",
8    "require": "./dist/index.js"
9  }
10}

Nowoczesne podejście: Node.js z ES Modules

Nowsze wersje Node.js (od 13.2.0) oficjalnie wspierają ES Modules. Możemy włączyć to wsparcie na kilka sposobów:

  1. Używając rozszerzenia
    .mjs
    dla plików z ES Modules
  2. Ustawiając
    "type": "module"
    w package.json

Jednak wiele pakietów npm nadal używa CommonJS, więc prawdopodobnie będziesz musiał pracować z oboma systemami.

Praktyczny przykład: Migracja z CommonJS do ES Modules

Załóżmy, że chcemy zmigrować część naszego backendu z CommonJS do ES Modules. Oto jak mogłaby wyglądać taka migracja:

Oryginalna wersja CommonJS (dinosaurController.js)

1// controllers/dinosaurController.js (CommonJS)
2const Dinosaur = require('../models/dinosaur');
3const logger = require('../utils/logger');
4
5function getAllDinosaurs(req, res) {
6  // implementacja
7}
8
9function getDinosaurById(req, res) {
10  // implementacja
11}
12
13module.exports = {
14  getAllDinosaurs,
15  getDinosaurById
16};

Migracja do ES Modules (dinosaurController.mjs)

1// controllers/dinosaurController.mjs (ES Modules)
2import Dinosaur from '../models/dinosaur.mjs';
3import logger from '../utils/logger.mjs';
4
5export function getAllDinosaurs(req, res) {
6  // implementacja
7}
8
9export function getDinosaurById(req, res) {
10  // implementacja
11}
12
13// Możemy też eksportować domyślnie obiekt z funkcjami
14export default {
15  getAllDinosaurs,
16  getDinosaurById
17};

Wrapper do kompatybilności

Czasami potrzebujemy utworzyć wrapper, który pozwoli na import modułu ES Module z kodu CommonJS:

1// controllers/dinosaurControllerWrapper.js (CommonJS)
2// Ten plik ładuje moduł ES Module i eksportuje go jako CommonJS
3const importESM = async () => {
4  const controller = await import('./dinosaurController.mjs');
5  return controller.default; // lub wybrane funkcje
6};
7
8// Eksportujemy funkcję, która zwraca Promise z kontrolerem
9module.exports = importESM();

Podsumowanie

CommonJS to powszechnie używany system modułów w Node.js, który pozwala na organizację kodu w odrębne moduły, co jest kluczowe dla budowania skalowalnych aplikacji, takich jak backend naszego systemu zarządzania Parkiem Jurajskim.

Główne koncepcje CommonJS to:

  • Funkcja
    require()
    do importowania modułów
  • Obiekt
    module.exports
    lub
    exports
    do eksportowania elementów
  • Synchroniczne ładowanie modułów
  • Niezbędne buforowanie modułów dla wydajności

Mimo rosnącej popularności ES Modules, CommonJS pozostaje istotnym systemem modułów, szczególnie w ekosystemie Node.js. Zrozumienie różnic między CommonJS a ES Modules jest kluczowe dla efektywnego rozwoju aplikacji JavaScript, które mogą działać zarówno w przeglądarce, jak i na serwerze.

W naszym Parku Jurajskim używamy CommonJS dla backendu (monitoring, baza danych, kontrola bezpieczeństwa) i ES Modules dla frontendu (interfejs użytkownika, dashboardy, powiadomienia). Ta kombinacja zapewnia nam najlepsze z obu światów i pozwala efektywnie zarządzać systemem, który musi być niezawodny - w końcu bezpieczeństwo parku i jego gości zależy od naszego kodu!

W następnym ćwiczeniu przyjrzymy się dynamicznemu importowaniu modułów, które pozwala na jeszcze większą elastyczność w ładowaniu kodu na żądanie, co może być kluczowe w sytuacjach awaryjnych w Parku Jurajskim.

Vai a CodeWorlds