Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript

CommonJS (require/module.exports)

13 min czytania
W tej lekcji11

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:

CechaCommonJSES Modules
Składnia importurequire()import ... from ...
Składnia eksportumodule.exports = ... lub exports.x = ...export ... lub export default ...
ŁadowanieSynchroniczneAsynchroniczne (ładowane przed wykonaniem)
WykonanieKod wykonywany przy każdym imporcieKod wykonywany tylko raz, następnie moduł jest buforowany
Statyczna analizaNie wspiera statycznej analizy (dynamiczne ścieżki)Wspiera statyczną analizę (ścieżki są statyczne)
Wsparcie w przeglądarceNie wspierane natywnieWspierane w nowoczesnych przeglądarkach
HoistingImporty nie są hoistowaneImporty są hoistowane (można używać przed deklaracją)
Tree-shakingTrudniejszy 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.

Kod do tej lekcji: main.js
1// CommonJS w Parku Jurajskim - Backend Node.js
2// Demonstracja systemu require/module.exports
3
4console.log("Backend Parku Jurajskiego - CommonJS");
5console.log("System zarządzania serwerem Node.js\n");
6
7// ===========================================
8// Symulacja modułów CommonJS
9// ===========================================
10
11// === dinosaur.js - Model dinozaura (CommonJS) ===
12class Dinosaur {
13  constructor(name, species, diet, dangerLevel) {
14    this.name = name;
15    this.species = species;
16    this.diet = diet;
17    this.dangerLevel = dangerLevel;
18    this.health = 100;
19    this.id = `DINO-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
20  }
21
22  feed() {
23    this.health = Math.min(100, this.health + 20);
24    return { success: true, message: `${this.name} nakarmiony`, health: this.health };
25  }
26
27  getDetails() {
28    return {
29      id: this.id,
30      name: this.name,
31      species: this.species,
32      diet: this.diet,
33      dangerLevel: this.dangerLevel,
34      health: this.health
35    };
36  }
37}
38
39// CommonJS export - główna klasa
40const DinosaurModule = Dinosaur;
41
42// === database.js - Symulacja bazy danych (CommonJS) ===
43const database = {
44  dinosaurs: new Map(),
45  enclosures: new Map(),
46  logs: []
47};
48
49function saveDinosaur(dinosaur) {
50  database.dinosaurs.set(dinosaur.id, dinosaur);
51  database.logs.push({ action: 'SAVE_DINOSAUR', id: dinosaur.id, timestamp: Date.now() });
52  return { success: true, id: dinosaur.id };
53}
54
55function getDinosaur(id) {
56  const dino = database.dinosaurs.get(id);
57  return dino || null;
58}
59
60function getAllDinosaurs() {
61  return Array.from(database.dinosaurs.values());
62}
63
64function deleteDinosaur(id) {
65  const deleted = database.dinosaurs.delete(id);
66  if (deleted) {
67    database.logs.push({ action: 'DELETE_DINOSAUR', id, timestamp: Date.now() });
68  }
69  return { success: deleted };
70}
71
72function getDatabaseStats() {
73  return {
74    dinosaurCount: database.dinosaurs.size,
75    enclosureCount: database.enclosures.size,
76    totalLogs: database.logs.length
77  };
78}
79
80// CommonJS multiple exports
81const DatabaseModule = {
82  saveDinosaur,
83  getDinosaur,
84  getAllDinosaurs,
85  deleteDinosaur,
86  getDatabaseStats
87};
88
89// === config.js - Konfiguracja (CommonJS) ===
90const config = {
91  server: {
92    port: 3000,
93    host: 'localhost',
94    environment: 'development'
95  },
96  database: {
97    url: 'mongodb://localhost:27017/jurassic-park',
98    maxConnections: 10
99  },
100  security: {
101    maxDangerLevel: 10,
102    alertThreshold: 7
103  },
104  park: {
105    maxDinosaurs: 50,
106    maxVisitors: 1000,
107    openingHours: { start: 9, end: 18 }
108  }
109};
110
111// CommonJS config export
112const ConfigModule = config;
113
114// === logger.js - System logowania (CommonJS) ===
115const LOG_LEVELS = {
116  INFO: 'INFO',
117  WARN: 'WARN',
118  ERROR: 'ERROR',
119  DEBUG: 'DEBUG'
120};
121
122function formatLogMessage(level, message) {
123  const timestamp = new Date().toISOString();
124  return `[${timestamp}] [${level}] ${message}`;
125}
126
127function log(level, message) {
128  const formatted = formatLogMessage(level, message);
129  console.log(formatted);
130  database.logs.push({ level, message, timestamp: Date.now() });
131}
132
133function info(message) { log(LOG_LEVELS.INFO, message); }
134function warn(message) { log(LOG_LEVELS.WARN, message); }
135function error(message) { log(LOG_LEVELS.ERROR, message); }
136function debug(message) { log(LOG_LEVELS.DEBUG, message); }
137
138// CommonJS logger exports
139const LoggerModule = {
140  LOG_LEVELS,
141  log,
142  info,
143  warn,
144  error,
145  debug
146};
147
148// === api-routes.js - API endpoints (CommonJS) ===
149class DinosaurAPI {
150  static createDinosaur(req) {
151    const { name, species, diet, dangerLevel } = req.body;
152
153    LoggerModule.info(`Creating dinosaur: ${name}`);
154
155    const dinosaur = new DinosaurModule(name, species, diet, dangerLevel);
156    DatabaseModule.saveDinosaur(dinosaur);
157
158    LoggerModule.info(`Dinosaur created: ${dinosaur.id}`);
159
160    return {
161      status: 201,
162      data: dinosaur.getDetails()
163    };
164  }
165
166  static getDinosaur(req) {
167    const { id } = req.params;
168    LoggerModule.debug(`Fetching dinosaur: ${id}`);
169
170    const dinosaur = DatabaseModule.getDinosaur(id);
171
172    if (!dinosaur) {
173      LoggerModule.warn(`Dinosaur not found: ${id}`);
174      return {
175        status: 404,
176        error: 'Dinosaur not found'
177      };
178    }
179
180    return {
181      status: 200,
182      data: dinosaur.getDetails()
183    };
184  }
185
186  static listDinosaurs() {
187    LoggerModule.debug('Listing all dinosaurs');
188
189    const dinosaurs = DatabaseModule.getAllDinosaurs();
190
191    return {
192      status: 200,
193      data: dinosaurs.map(d => d.getDetails()),
194      count: dinosaurs.length
195    };
196  }
197
198  static deleteDinosaur(req) {
199    const { id } = req.params;
200    LoggerModule.info(`Deleting dinosaur: ${id}`);
201
202    const result = DatabaseModule.deleteDinosaur(id);
203
204    if (!result.success) {
205      return {
206        status: 404,
207        error: 'Dinosaur not found'
208      };
209    }
210
211    LoggerModule.info(`Dinosaur deleted: ${id}`);
212
213    return {
214      status: 200,
215      data: { deleted: true }
216    };
217  }
218
219  static getServerStats() {
220    LoggerModule.debug('Fetching server stats');
221
222    const dbStats = DatabaseModule.getDatabaseStats();
223
224    return {
225      status: 200,
226      data: {
227        ...dbStats,
228        config: {
229          maxDinosaurs: ConfigModule.park.maxDinosaurs,
230          environment: ConfigModule.server.environment
231        }
232      }
233    };
234  }
235}
236
237// CommonJS API export
238const APIModule = DinosaurAPI;
239
240// ===========================================
241// DEMONSTRACJA SYSTEMU COMMONJS
242// ===========================================
243console.log("=== INICJALIZACJA SERWERA ===\n");
244
245LoggerModule.info('Server starting...');
246LoggerModule.info(`Port: ${ConfigModule.server.port}`);
247LoggerModule.info(`Environment: ${ConfigModule.server.environment}`);
248
249console.log("\n=== TWORZENIE DINOZAURÓW (API CALLS) ===\n");
250
251// Symulacja requestów API
252const req1 = {
253  body: { name: 'Rexy', species: 'T-Rex', diet: 'carnivore', dangerLevel: 9 }
254};
255const response1 = APIModule.createDinosaur(req1);
256console.log('POST /api/dinosaurs:', JSON.stringify(response1, null, 2));
257
258const req2 = {
259  body: { name: 'Blue', species: 'Velociraptor', diet: 'carnivore', dangerLevel: 7 }
260};
261const response2 = APIModule.createDinosaur(req2);
262console.log('\nPOST /api/dinosaurs:', JSON.stringify(response2, null, 2));
263
264const req3 = {
265  body: { name: 'Gentle Giant', species: 'Brachiosaurus', diet: 'herbivore', dangerLevel: 2 }
266};
267const response3 = APIModule.createDinosaur(req3);
268console.log('\nPOST /api/dinosaurs:', JSON.stringify(response3, null, 2));
269
270console.log("\n=== POBIERANIE DANYCH (API CALLS) ===\n");
271
272// Lista wszystkich dinozaurów
273const listResponse = APIModule.listDinosaurs();
274console.log('GET /api/dinosaurs:', JSON.stringify(listResponse, null, 2));
275
276console.log("\n=== STATYSTYKI SERWERA ===\n");
277
278const statsResponse = APIModule.getServerStats();
279console.log('GET /api/stats:', JSON.stringify(statsResponse, null, 2));
280
281console.log("\n=== RÓŻNICE: CommonJS vs ES Modules ===\n");
282
283console.log("CommonJS (Node.js):");
284console.log("  - Używa: require() i module.exports");
285console.log("  - Synchroniczne ładowanie");
286console.log("  - Dynamiczne importy w runtime");
287console.log("  - Wartość jest kopiowana\n");
288
289console.log("ES Modules (ES6+):");
290console.log("  - Używa: import i export");
291console.log("  - Asynchroniczne ładowanie");
292console.log("  - Statyczne importy w compile-time");
293console.log("  - Wartość jest referencją (live binding)\n");
294
295console.log("Przykład CommonJS:");
296console.log("  // dinosaur.js");
297console.log("  module.exports = class Dinosaur { ... };");
298console.log("");
299console.log("  // main.js");
300console.log("  const Dinosaur = require('./dinosaur');\n");
301
302console.log("Przykład ES Modules:");
303console.log("  // dinosaur.js");
304console.log("  export default class Dinosaur { ... }");
305console.log("");
306console.log("  // main.js");
307console.log("  import Dinosaur from './dinosaur.js';\n");
308
309LoggerModule.info('Demo completed successfully!');
310
311console.log("\nBackend Parku Jurajskiego gotowy!");

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Układanie w poziomie

    Ułóż elementy eksportu funkcji:

Przydatne artykuły