Un seul fichier, c'est comme une pièce où tout traîne. Les modules, c'est ranger dans des tiroirs étiquetés. Un pour les armes, un pour les faux papiers, un pour les contacts. Propre. Sécurisé. Maintenable.
Avec ES6, JavaScript a enfin ses propres modules natifs.
13.1 import / export - Le Système de Compartiments
Exports Nommés - Plusieurs Pièces par Tiroir
// Fichier : armes.js
export const pistolet = "Glock 17";
export const couteau = "K-Bar";
export const silencieux = true;
export function nettoyer(arme) {
return `${arme} nettoyée.`;
}
// Export groupé à la fin (alternative)
const grenade = "M67";
const c4 = "Plastique";
export { grenade, c4 };
// Export avec alias
export { grenade as explosifMain, c4 as explosifLourd };
Imports Nommés - Prendre ce dont tu as Besoin
// Fichier : mission.js
import { pistolet, couteau, nettoyer } from './armes.js';
console.log(`Équipement : ${pistolet}, ${couteau}`);
console.log(nettoyer(pistolet));
// Importer avec alias (si conflit de noms)
import { pistolet as armePrincipale } from './armes.js';
// Importer tout dans un objet
import * as arsenal from './armes.js';
console.log(arsenal.c4); // "Plastique"
console.log(arsenal.nettoyer(arsenal.couteau));
// Imports mixtes (nommés + défaut si présent)
import defaultExport, { pistolet, couteau } from './armes.js';
Export par Défaut - Le Tiroir Principal
// Fichier : contact.js
const contact = {
nom: "L'Ombre",
niveau: 9,
contacter() {
return "Canal sécurisé établi.";
}
};
export default contact; // UN SEUL export default par fichier
// Syntaxe alternative (export direct)
// export default { nom: "L'Ombre", ... };
Import par Défaut - Prendre le Paquet Principal
// Fichier : operation.js
import monContact from './contact.js'; // Pas d'accolades pour le défaut
console.log(monContact.nom); // "L'Ombre"
monContact.contacter();
// Avec alias (utile pour les noms génériques)
import contactPrincipal from './contact.js';
// Mixte : défaut + nommés (si le fichier en a)
import contact, { autreChose } from './contact.js';
Exports Combinés - Les Tiroirs à Double Fond
// Fichier : outils.js
// Exports nommés
export const lampe = "Torche puissante";
export const lockpick = "Jeu de crochets";
// Export par défaut
const kitDeBase = {
lampe: "Torche standard",
multioutil: "Leatherman"
};
export default kitDeBase;
// Fichier : infiltration.js
import kit, { lampe, lockpick } from './outils.js';
// kit = kitDeBase (défaut)
// lampe = "Torche puissante" (nommé)
Ré-exporter - Transférer Sans Ouvrir
// Fichier : equipement.js (fichier barrière/façade)
// Ré-export de armes.js
export { pistolet, couteau } from './armes.js';
// Ré-export avec alias
export { grenade as g } from './armes.js';
// Ré-export TOUT
export * from './armes.js';
// Ré-export du défaut (délicat)
export { default } from './contact.js';
// Maintenant, un seul point d'entrée
import { pistolet, couteau, g } from './equipement.js';
// Tout vient d'armes.js, mais via equipement.js
13.2 Modules par Défaut vs Nommés - Quand Utiliser Quoi
// CAS D'UN EXPORT NOMmé :
// Quand le fichier contient PLUSIEURS choses utiles
// ex: utils.js avec plein de fonctions helpers
// CAS D'UN EXPORT PAR DéFAUT :
// 1. Quand le fichier représente UNE SEULE entité principale
// ex: Une classe Agent, un composant React/Vue
// 2. Quand le fichier est une configuration/instance
// 3. Pour les bibliothèques/plugins
// Bonne pratique :
// - Préfère les exports nommés pour la transparence (sait ce qu'on importe)
// - Utilise le défaut pour le "point d'entrée" principal d'un module
// Exemple de structure cohérente :
// agents/
// index.js (export par défaut ? ou ré-export de tout)
// Agent.js (classe principale, export par défaut)
// types.js (exports nommés: AgentType, MissionType)
// utils.js (exports nommés: validateAgent, encryptName)
Syntaxe Dynamique import() - Charger à la Demande
// Import classique = statique (toujours chargé)
// import { heavyWeapon } from './heavy.js'; // Chargé même si pas utilisé
// Import dynamique = à la demande (retourne une Promesse)
async function prepareAssault(difficulty) {
if (difficulty > 7) {
// Charge le module lourd SEULEMENT si nécessaire
const heavyModule = await import('./heavyWeapons.js');
heavyModule.nukeTarget();
} else {
const lightModule = await import('./smallArms.js');
lightModule.silentTakeDown();
}
}
// Utile pour :
// - Le code splitting (performances)
// - Charger des polyfills conditionnels
// - Charger des modules selon les permissions
// - Implémenter des plugins
// Avec destructuring
const { sniperRifle, thermalScope } = await import('./sniperKit.js');
13.3 Structuration de Projet - L'Architecture de l'Ombre
Structure de Base (Projet Moderne)
mon-operation/
├── index.html # Point d'entrée HTML
├── package.json # Manifeste (dépendances, scripts)
├── vite.config.js # Config du bundler (ex: Vite)
│
├── public/ # Assets statiques (copiés tels quels)
│ ├── favicon.ico
│ └── robots.txt
│
├── src/ # Code source
│ ├── main.js # Point d'entrée JavaScript
│ ├── style.css # Styles globaux
│ │
│ ├── core/ # Cœur du système
│ │ ├── Agent.js # Classe principale
│ │ ├── Mission.js # Logique métier
│ │ └── Security.js # Chiffrement, validation
│ │
│ ├── utils/ # Outils réutilisables
│ │ ├── formatters.js # Formatage de données
│ │ ├── validators.js # Validation
│ │ └── logger.js # Logging
│ │
│ ├── data/ # Données, modèles
│ │ ├── constants.js # Constantes globales
│ │ ├── schemas.js # Schémas de validation
│ │ └── mockData.js # Données de test
│ │
│ ├── api/ # Communication externe
│ │ ├── endpoints.js # URLs, configurations
│ │ ├── httpClient.js # Wrapper fetch/axios
│ │ └── websocket.js # Connexions temps réel
│ │
│ └── ui/ # Interface utilisateur
│ ├── components/ # Composants réutilisables
│ ├── views/ # Pages/vues complètes
│ └── hooks/ # Hooks personnalisés (React)
│
└── tests/ # Tests
├── unit/ # Tests unitaires
└── integration/ # Tests d'intégration
Fichier index.js - Le Point de Ralliement
// src/core/index.js
// Ré-exporte tout proprement pour un import facile
export { default as Agent } from './Agent.js';
export { default as Mission } from './Mission.js';
export * from './Security.js';
// src/utils/index.js
export { formatDate, formatCurrency } from './formatters.js';
export { isValidEmail, isValidPassword } from './validators.js';
export { default as logger } from './logger.js';
// Maintenant dans ton code :
import { Agent, Mission } from './core';
import { isValidEmail, logger } from './utils';
// Propre. Clair.
Configuration TypeScript (Optionnel mais Recommandé)
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
13.4 Bundlers - La Machine à Emballer (Aperçu)
Pourquoi un Bundler ?
- Combine tous tes modules en un/quelques fichiers
- Optimise (minifie, enlève le code mort)
- Transforme (JS moderne → ancien, TypeScript → JS, etc.)
- Gère les assets (CSS, images, polices)
Les Principaux Contrebandiers
// Webpack (l'ancien, puissant mais complexe)
// webpack.config.js
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]
}
};
// Vite (le nouveau, rapide, simple)
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
outDir: 'dist',
rollupOptions: {
input: './src/main.js'
}
}
});
// Parcel (zéro config)
// Juste lance `parcel src/index.html`
Scripts NPM Typiques
// package.json
{
"scripts": {
"dev": "vite", // Dev server avec HMR
"build": "vite build", // Build pour production
"preview": "vite preview", // Prévisualiser le build
"test": "vitest", // Lancer les tests
"lint": "eslint src/", // Vérifier le code
"format": "prettier --write src/" // Formater le code
}
}
Arbre de Dépendances et Code Splitting
// Sans code splitting : un énorme bundle.js
// Avec code splitting : plusieurs petits fichiers chargés à la demande
// Exemple avec import() dynamique
// → Crée un chunk séparé automatiquement
// Configuration Vite/Webpack pour split automatique
// - Split par route (pages)
// - Split par vendor (node_modules)
// - Split manuel avec commentaires magiques
// import(/* webpackChunkName: "heavy" */ './heavy.js')
Module Federation (Avancé)
// Pour les micro-frontends
// Plusieurs apps indépendantes qui partagent des modules
// Ex: Le dashboard et l'admin chargent la même lib UI
// webpack.config.js (app hôte)
new ModuleFederationPlugin({
name: 'host',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js'
}
});
// Dans le code
const RemoteComponent = React.lazy(() => import('remoteApp/Component'));
13.5 Bonnes Pratiques - Les Règles du Métier
1. Chemins d'Import
// ❌ Mauvais - Relatif compliqué
import { thing } from '../../../../utils/helpers.js';
// ✅ Bon - Alias de chemin configuré
import { thing } from '@/utils/helpers';
// (configuré dans vite.config.js / webpack.config.js)
// ✅ Bon - Import depuis index
import { Agent, Mission } from './core'; // cherche ./core/index.js
2. Arborescence Logique
// Par fonctionnalité (plutôt que par type)
src/
├── agents/ # Tout sur les agents
│ ├── Agent.js
│ ├── AgentList.js
│ ├── AgentForm.js
│ └── api.js # Appels API spécifiques aux agents
├── missions/ # Tout sur les missions
│ ├── Mission.js
│ ├── MissionCard.js
│ └── api.js
└── shared/ # Vraiment partagé
├── components/
└── utils/
3. Éviter les Effets de Bord Cachés
// ❌ Mauvais - Effet de bord à l'import
// logger.js
console.log("Logger initialisé!"); // S'exécute AU MOMENT de l'import
// ✅ Bon - Initialisation explicite
// logger.js
let initialized = false;
export function initLogger(config) {
// initialisation
initialized = true;
}
4. Tests et Modules
// Fichier testable = dépendances injectées
// ❌ Dur à tester
import { api } from './api.js';
export function fetchAgent(id) {
return api.get(`/agents/${id}`);
}
// ✅ Testable
export function createAgentFetcher(apiClient) {
return function fetchAgent(id) {
return apiClient.get(`/agents/${id}`);
};
}
5. Documentation des Exports
// utils/formatters.js
/**
* Formatte une date pour l'affichage
* @param {Date|string} date - Date à formatter
* @param {string} [format='fr-FR'] - Locale
* @returns {string} Date formatée
* @example
* formatDate(new Date()) // "15/03/2024"
*/
export function formatDate(date, format = 'fr-FR') {
// ...
}
Résumé du Module 13 :
Les Modules ES :
export/import: La syntaxe native pour diviser le code.- Nommés : Pour plusieurs exports d'un fichier. Utilise les accolades.
- Par défaut : Pour l'export principal d'un fichier. Pas d'accolades.
- Dynamique :
import()pour charger à la demande (retourne une Promesse).
L'Organisation :
- Une structure logique : par fonctionnalité, pas par type technique.
- Des fichiers
index.jspour créer des points d'entrée propres. - Des chemins d'import clairs (alias configurés).
Les Bundlers (l'essentiel) :
- Combinent les modules pour le navigateur.
- Optimisent pour la production.
- Transforment le code moderne/TypeScript.
La philosophie :
- Un fichier = une responsabilité claire.
- Un dossier = une fonctionnalité cohérente.
- Moins de 200-300 lignes par fichier.
- Exports explicites, imports ciblés.
Divise ton code. Cache les détails. Expose seulement ce qui est nécessaire. Comme une cellule dormante : chaque agent connaît seulement sa mission, pas l'ensemble du réseau.