Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript
CommonJS (require/module.exports)
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:
- Funkcja
require()do importowania modułów - Obiekt
module.exportslubexportsdo 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 serweraZobaczmy, 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:
- Przekształć strukturę kodu, aby uniknąć zależności cyklicznych
- Użyj wzorca dependency injection
- 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:
- Używając rozszerzenia
.mjsdla plików z ES Modules - 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.exportslubexportsdo 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: