Cours JavaScript et TypeScript · Module 5: JavaScript avancé

CommonJS (require/module.exports)

15 min de lecture
Dans cette leçon11

Dans l'exercice précédent, nous avons découvert les modules ES - le système de modules moderne de JavaScript. Nous allons maintenant plonger dans un autre système de modules, plus ancien mais toujours très important : CommonJS. Même si notre Jurassic Park a besoin des technologies les plus récentes pour surveiller les dinosaures dans le navigateur, son infrastructure serveur solide repose sur Node.js, où CommonJS domine.

Qu'est-ce que CommonJS ?

CommonJS est un système de modules conçu avant tout pour les environnements côté serveur, et il est devenu le standard de l'écosystème Node.js. Il offre une façon d'organiser le code JavaScript en modules séparés, comme les modules ES, mais avec une syntaxe et un comportement différents.

Contrairement aux modules ES, ajoutés à JavaScript en ES6 (2015), CommonJS existait avant et est devenu le standard de fait pour Node.js. Même aujourd'hui, malgré la prise en charge officielle des modules ES dans Node.js, CommonJS reste le format dominant pour les modules Node.js et dans l'écosystème npm.

Les bases de CommonJS

Les principaux concepts de CommonJS sont :

  1. La fonction require() pour importer des modules
  2. L'objet module.exports ou exports pour exporter des éléments

Voyons comment ils fonctionnent dans le contexte de notre système Jurassic Park.

Exporter des éléments en CommonJS

1. Exporter par assignation à module.exports

La façon la plus simple d'exporter consiste à assigner une valeur à module.exports :

1// dinosaur.js - Modèle de dinosaure
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} a été nourri. Niveau de santé : ${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 ? "NIVEAU DE DANGER ÉLEVÉ" : ""
23    };
24  }
25}
26
27// Exporte la classe entière
28module.exports = Dinosaur;

2. Exporter un objet contenant plusieurs éléments

On peut aussi exporter plusieurs éléments en assignant un objet à module.exports :

1// securityUtils.js - Fonctions utilitaires de sécurité
2const SECURITY_LEVELS = {
3  GREEN: "normal",
4  YELLOW: "elevated",
5  ORANGE: "high",
6  RED: "critical"
7};
8
9function checkFenceStatus(sectorId) {
10  // Simule la vérification de l'état des clôtures dans un secteur donné
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(`ALARME ! Secteur ${sector} : ${reason}`);
21  // Logique d'activation de l'alarme
22  return {
23    success: true,
24    alarmId: "ALR-" + Math.floor(Math.random() * 10000)
25  };
26}
27
28function getSecurityStatus() {
29  // Renvoie l'état de sécurité global du parc
30  return {
31    level: SECURITY_LEVELS.GREEN,
32    activeAlarms: 0,
33    lastChecked: new Date().toISOString()
34  };
35}
36
37// Exporte plusieurs éléments sous forme d'objet
38module.exports = {
39  SECURITY_LEVELS,
40  checkFenceStatus,
41  triggerAlarm,
42  getSecurityStatus
43};

3. Ajouter des exports au fur et à mesure

On peut aussi ajouter les exports un par un via l'objet exports (qui est une référence vers module.exports) :

1// parkStats.js - Statistiques de Jurassic Park
2let totalVisitors = 0;
3let revenue = 0;
4const incidents = [];
5
6// Exporte la première fonction
7exports.addVisitors = function(count) {
8  totalVisitors += count;
9  return totalVisitors;
10};
11
12// Exporte la deuxième fonction
13exports.addRevenue = function(amount) {
14  revenue += amount;
15  return revenue;
16};
17
18// Exporte la troisième fonction
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// Exporte la quatrième fonction
32exports.getDailyStats = function() {
33  return {
34    totalVisitors,
35    revenue,
36    incidentCount: incidents.length
37  };
38};

NOTE : Souviens-toi que exports n'est qu'une référence vers module.exports. Si tu assignes quelque chose directement à module.exports, tu perds cette référence, donc tu ne peux pas mélanger ces styles dans un même fichier :

1// Ceci NE fonctionnera PAS
2exports.someFunction = function() { /* ... */ };
3module.exports = SomeClass; // Ceci écrase l'export précédent
4
5// Ceci fonctionnera
6exports.someFunction = function() { /* ... */ };
7exports.SomeClass = SomeClass;
8
9// Ceci fonctionnera aussi
10module.exports.someFunction = function() { /* ... */ };
11module.exports.SomeClass = SomeClass;
12
13// Et ceci fonctionnera (en assignant l'objet entier)
14module.exports = {
15  someFunction: function() { /* ... */ },
16  SomeClass: SomeClass
17};

Importer des modules en CommonJS

1. Importer un module entier

1// app.js
2const Dinosaur = require('./dinosaur.js');
3
4// Crée de nouvelles instances de dinosaure
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. Importer et déstructurer

1// securityMonitor.js
2const security = require('./securityUtils.js');
3
4// Utilise les fonctions et constantes importées depuis l'objet
5function monitorSecurity() {
6  const sectors = ["A1", "B2", "C3", "D4"];
7
8  // Vérifie chaque secteur
9  sectors.forEach(sector => {
10    const fenceStatus = security.checkFenceStatus(sector);
11
12    if (fenceStatus.status === "failure") {
13      const alarmResult = security.triggerAlarm(
14        sector,
15        "Panne de clôture électrique !"
16      );
17      console.log(`Alarme ${alarmResult.alarmId} déclenchée dans le secteur ${sector}`);
18
19      // Met à jour l'état de sécurité
20      console.log("Changement du niveau de sécurité vers :", security.SECURITY_LEVELS.RED);
21    }
22  });
23}
24
25setInterval(monitorSecurity, 60000); // Vérifie toutes les minutes

3. Déstructurer à l'import

Dans les versions plus récentes de Node.js, on peut aussi déstructurer directement à l'import :

1// dashboard.js
2const { checkFenceStatus, triggerAlarm, SECURITY_LEVELS } = require('./securityUtils.js');
3
4// On peut maintenant utiliser les fonctions directement
5function checkAllSectors() {
6  ["A1", "B2", "C3", "D4"].forEach(sector => {
7    console.log(`Vérification du secteur ${sector} : `, checkFenceStatus(sector));
8
9    // Utilise la constante importée
10    if (Math.random() < 0.1) {
11      console.log(`Définition du niveau de sécurité : ${SECURITY_LEVELS.ORANGE}`);
12    }
13  });
14}

Différences entre CommonJS et les modules ES

Il est important de comprendre les différences clés entre CommonJS et les modules ES :

CaractéristiqueCommonJSModules ES
Syntaxe d'importrequire()import ... from ...
Syntaxe d'exportmodule.exports = ... ou exports.x = ...export ... ou export default ...
ChargementSynchroneAsynchrone (chargé avant l'exécution)
ExécutionCode exécuté à chaque importCode exécuté une seule fois, puis le module est mis en cache
Analyse statiqueNon prise en charge (chemins dynamiques)Prise en charge (chemins statiques)
Support navigateurPas nativement pris en chargePris en charge dans les navigateurs modernes
HoistingLes imports ne sont pas hoistésLes imports sont hoistés (utilisables avant la déclaration)
Tree-shakingPlus difficile à mettre en placePlus facile grâce à l'analyse statique

Structure d'un projet Node.js (backend de Jurassic Park)

Voyons comment on pourrait organiser le backend de notre système de gestion de Jurassic Park avec Node.js et CommonJS :

1jurassic-park-backend/
2├── config/
3│   ├── database.js     # Configuration de la base de données
4│   ├── environment.js  # Variables d'environnement
5│   └── security.js     # Paramètres de sécurité
6├── models/
7│   ├── dinosaur.js     # Modèle de dinosaure
8│   ├── employee.js     # Modèle d'employé
9│   ├── facility.js     # Modèle d'installation
10│   └── index.js        # Agrégateur de modèles
11├── controllers/
12│   ├── dinosaurController.js  # Contrôleur des dinosaures
13│   ├── securityController.js  # Contrôleur de la sécurité
14│   └── employeeController.js  # Contrôleur des employés
15├── routes/
16│   ├── api.js          # Routeur API principal
17│   ├── dinosaurs.js    # Routes liées aux dinosaures
18│   └── security.js     # Routes liées à la sécurité
19├── services/
20│   ├── monitoringService.js   # Service de surveillance
21│   ├── notificationService.js # Service de notifications
22│   └── loggingService.js      # Service de journalisation
23├── utils/
24│   ├── logger.js       # Utilitaire de journalisation
25│   └── validators.js   # Validateurs de données
26├── middleware/
27│   ├── auth.js         # Middleware d'authentification
28│   └── errorHandler.js # Gestion des erreurs
29├── app.js              # Application principale
30└── server.js           # Fichier de démarrage du serveur

Examinons des implémentations d'exemple de ces modules :

Fichier de configuration (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  // Fonction utilitaire pour construire l'URI de connexion
10  getConnectionString: function() {
11    return `mongodb://${this.user}:${this.password}@${this.host}:${this.port}/${this.name}`;
12  }
13};

Modèle (models/dinosaur.js)

1// models/dinosaur.js
2const mongoose = require('mongoose');
3const Schema = mongoose.Schema;
4
5// Schéma du dinosaure
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// Méthodes du schéma
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 ? "NIVEAU DE DANGER ÉLEVÉ" : ""
34  };
35};
36
37// Méthodes statiques
38dinosaurSchema.statics.findDangerous = function() {
39  return this.find({ dangerLevel: { $gte: 8 } });
40};
41
42// Hook pre-save
43dinosaurSchema.pre('save', function(next) {
44  this.updatedAt = Date.now();
45  next();
46});
47
48// Crée le modèle
49const Dinosaur = mongoose.model('Dinosaur', dinosaurSchema);
50
51// Exporte le modèle
52module.exports = Dinosaur;

Contrôleur (controllers/dinosaurController.js)

1// controllers/dinosaurController.js
2const Dinosaur = require('../models/dinosaur');
3const logger = require('../utils/logger');
4
5// Objet contrôleur avec plusieurs méthodes
6const dinosaurController = {
7  // Récupère tous les dinosaures
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('Erreur lors de la récupération des dinosaures :', error);
14      res.status(500).json({ error: 'Erreur serveur' });
15    }
16  },
17
18  // Récupère un dinosaure par son 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: 'Dinosaure introuvable' });
25      }
26
27      res.json(dinosaur);
28    } catch (error) {
29      logger.error(`Erreur lors de la récupération du dinosaure ID=${req.params.id} : `, error);
30      res.status(500).json({ error: 'Erreur serveur' });
31    }
32  },
33
34  // Ajoute un nouveau dinosaure
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(`Nouveau dinosaure ajouté : ${name} (${species})`);
49
50      res.status(201).json(newDinosaur);
51    } catch (error) {
52      logger.error('Erreur lors de la création du dinosaure :', error);
53      res.status(400).json({ error: error.message });
54    }
55  },
56
57  // Nourrit un dinosaure
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: 'Dinosaure introuvable' });
64      }
65
66      await dinosaur.feed();
67      logger.info(`Dinosaure nourri : ${dinosaur.name}`);
68
69      res.json({
70        message: `${dinosaur.name} a été nourri. Niveau de santé : ${dinosaur.health}%`,
71        health: dinosaur.health
72      });
73    } catch (error) {
74      logger.error(`Erreur lors du nourrissage du dinosaure ID=${req.params.id} : `, error);
75      res.status(500).json({ error: 'Erreur serveur' });
76    }
77  },
78
79  // Trouve les dinosaures dangereux
80  getDangerousDinosaurs: async (req, res) => {
81    try {
82      const dangerousDinos = await Dinosaur.findDangerous();
83      res.json(dangerousDinos);
84    } catch (error) {
85      logger.error('Erreur lors de la récupération des dinosaures dangereux :', error);
86      res.status(500).json({ error: 'Erreur serveur' });
87    }
88  }
89};
90
91// Exporte l'objet contrôleur entier
92module.exports = dinosaurController;

Routes (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// Routes liées aux dinosaures
8router.get('/', dinosaurController.getAllDinosaurs);
9router.get('/dangerous', dinosaurController.getDangerousDinosaurs);
10router.get('/:id', dinosaurController.getDinosaurById);
11
12// Routes nécessitant une authentification
13router.post('/', authMiddleware.verifyToken, dinosaurController.createDinosaur);
14router.post('/:id/feed', authMiddleware.verifyToken, dinosaurController.feedDinosaur);
15
16// Exporte le routeur
17module.exports = router;

Fichier principal de l'application (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// Imports de configuration et de middleware
9const dbConfig = require('./config/database');
10const errorHandler = require('./middleware/errorHandler');
11const logger = require('./utils/logger');
12
13// Imports des routes
14const apiRouter = require('./routes/api');
15const dinosaurRoutes = require('./routes/dinosaurs');
16const securityRoutes = require('./routes/security');
17
18// Initialise l'application Express
19const app = express();
20
21// Middleware
22app.use(helmet()); // Sécurité
23app.use(cors()); // Partage des ressources entre origines (CORS)
24app.use(morgan('dev')); // Journalisation des requêtes HTTP
25app.use(express.json()); // Analyse du JSON
26app.use(express.urlencoded({ extended: false })); // Analyse des formulaires
27
28// Connexion à la base de données
29mongoose.connect(dbConfig.getConnectionString(), {
30  useNewUrlParser: true,
31  useUnifiedTopology: true
32})
33.then(() => {
34  logger.info('Connecté à la base de données MongoDB');
35})
36.catch(err => {
37  logger.error('Erreur de connexion à la base de données :', err);
38  process.exit(1);
39});
40
41// Routes
42app.use('/api', apiRouter);
43app.use('/api/dinosaurs', dinosaurRoutes);
44app.use('/api/security', securityRoutes);
45
46// Gère les erreurs 404
47app.use((req, res, next) => {
48  res.status(404).json({ error: 'Introuvable' });
49});
50
51// Gestion centralisée des erreurs
52app.use(errorHandler);
53
54// Exporte l'application
55module.exports = app;

Fichier de démarrage du serveur (server.js)

1// server.js
2const http = require('http');
3const app = require('./app');
4const logger = require('./utils/logger');
5
6// Paramètres du port
7const port = process.env.PORT || 3000;
8app.set('port', port);
9
10// Crée le serveur HTTP
11const server = http.createServer(app);
12
13// Écoute les connexions
14server.listen(port, () => {
15  logger.info(`Serveur Jurassic Park démarré sur le port ${port}`);
16});
17
18// Gère les erreurs du serveur
19server.on('error', (error) => {
20  if (error.syscall !== 'listen') {
21    throw error;
22  }
23
24  logger.error(`Erreur du serveur : ${error}`);
25  process.exit(1);
26});

Dépendances circulaires

L'un des défis du travail avec CommonJS (et avec les modules en général) est celui des dépendances circulaires, qui se produisent quand le module A importe le module B et que le module B importe le module 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'); // Dépendance circulaire !
12
13class Dinosaur {
14  // ... implémentation de la classe
15
16  isHighRisk() {
17    return dinosaurUtils.isDangerous(this);
18  }
19}
20
21module.exports = Dinosaur;

CommonJS gère les dépendances circulaires, mais cela peut entraîner des comportements étranges comme des objets incomplets. Pour éviter ce genre de problème :

  1. Restructure le code pour éviter les dépendances circulaires
  2. Utilise le pattern d'injection de dépendances
  3. Déplace le code partagé dans un troisième module dont les deux dépendent

Transpiler de CommonJS vers les modules ES et inversement

Dans de nombreux projets, on doit gérer à la fois CommonJS et les modules ES. Par exemple, le frontend de notre Jurassic Park pourrait utiliser les modules ES tandis que le backend utilise CommonJS. Que faire quand on veut partager du code ?

Babel pour la transpilation

On peut utiliser Babel pour transpiler entre les formats :

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

Dual Package Hazard (piège du double package)

Une autre approche consiste à créer des packages qui prennent en charge les deux formats de modules. Dans package.json, tu peux définir :

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

Approche moderne : Node.js avec les modules ES

Les versions plus récentes de Node.js (à partir de 13.2.0) prennent officiellement en charge les modules ES. On peut activer ce support de plusieurs façons :

  1. En utilisant l'extension .mjs pour les fichiers de modules ES
  2. En définissant "type": "module" dans package.json

Cependant, de nombreux packages npm utilisent encore CommonJS, donc tu devras probablement travailler avec les deux systèmes.

Exemple pratique : migration de CommonJS vers les modules ES

Disons qu'on veut migrer une partie de notre backend de CommonJS vers les modules ES. Voici à quoi pourrait ressembler une telle migration :

Version CommonJS d'origine (dinosaurController.js)

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

Migration vers les modules ES (dinosaurController.mjs)

1// controllers/dinosaurController.mjs (Modules ES)
2import Dinosaur from '../models/dinosaur.mjs';
3import logger from '../utils/logger.mjs';
4
5export function getAllDinosaurs(req, res) {
6  // implémentation
7}
8
9export function getDinosaurById(req, res) {
10  // implémentation
11}
12
13// On peut aussi exporter un objet par défaut contenant les fonctions
14export default {
15  getAllDinosaurs,
16  getDinosaurById
17};

Wrapper de compatibilité

Parfois, on doit créer un wrapper qui permet d'importer un module ES depuis du code CommonJS :

1// controllers/dinosaurControllerWrapper.js (CommonJS)
2// Ce fichier charge un module ES et l'exporte en CommonJS
3const importESM = async () => {
4  const controller = await import('./dinosaurController.mjs');
5  return controller.default; // ou des fonctions sélectionnées
6};
7
8// Exporte une fonction qui renvoie une Promise contenant le contrôleur
9module.exports = importESM();

Résumé

CommonJS est un système de modules très répandu dans Node.js qui permet d'organiser le code en modules séparés, ce qui est essentiel pour construire des applications évolutives comme le backend de notre système de gestion de Jurassic Park.

Les principaux concepts de CommonJS sont :

  • La fonction require() pour importer des modules
  • L'objet module.exports ou exports pour exporter des éléments
  • Le chargement synchrone des modules
  • La mise en cache des modules, essentielle pour les performances

Malgré la popularité croissante des modules ES, CommonJS reste un système de modules important, surtout dans l'écosystème Node.js. Comprendre les différences entre CommonJS et les modules ES est essentiel pour développer efficacement des applications JavaScript capables de tourner aussi bien dans le navigateur que sur le serveur.

Dans notre Jurassic Park, on utilise CommonJS pour le backend (surveillance, base de données, contrôle de la sécurité) et les modules ES pour le frontend (interface utilisateur, tableaux de bord, notifications). Cette combinaison nous donne le meilleur des deux mondes et nous permet de gérer efficacement un système qui doit être fiable - après tout, la sécurité du parc et de ses visiteurs dépend de notre code !

Dans l'exercice suivant, nous examinerons l'import dynamique de modules, qui offre une flexibilité encore plus grande pour charger du code à la demande, ce qui peut être crucial dans les situations d'urgence à Jurassic Park.

Code de cette leçon : 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("Node.js server management system\n");
6
7// ===========================================
8// CommonJS module simulation
9// ===========================================
10
11// === dinosaur.js - Model dinosaura (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 - main class
40const DinosaurModule = Dinosaur;
41
42// === database.js - Simulation 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// DEMONSTRATION DU SYSTEME 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=== CREATING DINOSAURS (API CALLS) ===\n");
250
251// API request simulation
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=== RÉCUPÉRATION DES DONNÉES (API CALLS) ===\n");
271
272// Liste de tous les dinosaures
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=== DIFFERENCES: CommonJS vs ES Modules ===\n");
282
283console.log("CommonJS (Node.js):");
284console.log("  - Uses: require() and module.exports");
285console.log("  - Synchronous loading");
286console.log("  - Dynamiczne importy w runtime");
287console.log("  - Value is copied\n");
288
289console.log("ES Modules (ES6+):");
290console.log("  - Uses: import and export");
291console.log("  - Asynchronous loading");
292console.log("  - Statyczne importy w compile-time");
293console.log("  - Value is a reference (live binding)\n");
294
295console.log("CommonJS example:");
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("ES Modules example:");
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!");

Tu as repéré une erreur dans cette leçon ?

Tâches pratiques dans le jeu

  • Tri horizontal

    Ordonne les éléments de l'export d'une fonction :

Articles utiles