Cours JavaScript et TypeScript · Module 5: JavaScript avancé
Structurer le code dans un projet plus grand
Dans cette leçon11
Après avoir parcouru toutes les techniques JavaScript les plus importantes, il est temps de prendre du recul - comment organiser le code dans des projets plus grands. Une mauvaise structure de code peut mener à une catastrophe comparable à celle qu'on a vécue à Jurassic Park - chaos, erreurs et difficulté à gérer le système.
Pourquoi la structure du projet est importante
Imagine toute l'infrastructure de Jurassic Park gérée par un seul système gigantesque. Toute la sécurité des enclos, la surveillance de la santé des dinosaures, la gestion du flux des visiteurs, la restauration, les laboratoires de génétique - tout dans un seul programme monolithique. La catastrophe n'est pas loin, n'est-ce pas ?
Une structure de projet bien conçue garantit :
- Maintenabilité - plus facile de trouver et corriger les bugs
- Évolutivité - possibilité d'ajouter de nouvelles fonctionnalités sans casser celles qui existent
- Testabilité - possibilité de tester les composants de manière isolée
- Facilité de collaboration - plusieurs développeurs peuvent travailler en parallèle sur différentes parties
- Lisibilité - plus facile de comprendre comment le système fonctionne
Organisation des dossiers et des fichiers
Il existe de nombreuses approches pour organiser le code dans les grands projets JavaScript. Examinons quelques patterns éprouvés qui fonctionneraient dans un système de gestion de Jurassic Park.
1. Structure par type
Une structure populaire, surtout dans les petites applications, regroupe les fichiers par leur type :
1jurassic-park-management/
2├── src/
3│ ├── components/ # Composants d'interface
4│ ├── services/ # Services de communication avec l'API
5│ ├── utils/ # Outils utilitaires
6│ ├── models/ # Définitions de types et d'interfaces
7│ ├── hooks/ # Hooks personnalisés
8│ ├── constants/ # Constantes de l'application
9│ ├── assets/ # Images, polices, etc.
10│ ├── styles/ # Styles CSS/SCSS
11│ └── tests/ # Tests
12└── package.json2. Structure par fonctionnalité
À mesure que le projet grandit, une structure par fonctionnalité est souvent préférable :
1jurassic-park-management/
2├── src/
3│ ├── features/
4│ │ ├── dinosaurs/
5│ │ │ ├── DinosaurList.jsx
6│ │ │ ├── DinosaurDetail.jsx
7│ │ │ ├── dinosaurService.js
8│ │ │ ├── dinosaurReducer.js
9│ │ │ └── dinosaur.test.js
10│ │ ├── security/
11│ │ │ ├── SecurityDashboard.jsx
12│ │ │ ├── securityService.js
13│ │ │ └── security.test.js
14│ │ └── monitoring/
15│ │ ├── MonitoringPanel.jsx
16│ │ ├── monitoringService.js
17│ │ └── monitoring.test.js
18│ ├── shared/
19│ │ ├── components/ # Composants partagés
20│ │ ├── utils/ # Utilitaires partagés
21│ │ └── hooks/ # Hooks partagés
22│ └── app/
23│ ├── store.js
24│ └── router.js
25└── package.json3. Structure modulaire (orientée domaine)
Pour les projets de niveau entreprise :
1jurassic-park-management/
2├── modules/
3│ ├── DinosaurModule/
4│ │ ├── domain/ # Logique métier
5│ │ ├── infrastructure/ # Communication externe
6│ │ ├── presentation/ # Interface utilisateur
7│ │ └── index.js # API publique
8│ ├── SecurityModule/
9│ └── MonitoringModule/
10├── shared/
11└── app/Architecture de l'application
MVC (Model-View-Controller)
1// Modèle
2class DinosaurModel {
3 constructor(data) {
4 this.id = data.id;
5 this.name = data.name;
6 this.species = data.species;
7 this.health = data.health;
8 }
9
10 isHealthy() { return this.health > 60; }
11 isDangerous() { return this.dangerLevel > 7; }
12}
13
14// Vue
15class DinosaurView {
16 render(dinosaur) {
17 return `<div class="dino-card ${dinosaur.isHealthy() ? 'healthy' : 'sick'}">
18 <h3>${dinosaur.name}</h3>
19 <p>${dinosaur.species}</p>
20 <p>Santé : ${dinosaur.health}%</p>
21 </div>`;
22 }
23}
24
25// Contrôleur
26class DinosaurController {
27 constructor(model, view, service) {
28 this.model = model;
29 this.view = view;
30 this.service = service;
31 }
32
33 async loadAndDisplay(id) {
34 const data = await this.service.getDinosaur(id);
35 const dinosaur = new this.model(data);
36 return this.view.render(dinosaur);
37 }
38}Modularisation du code
Quelle que soit la structure de dossiers et l'architecture, l'essentiel est de bien découper le code en modules plus petits et cohérents.
1. Modularisation par responsabilité
Chaque module ne devrait avoir qu'une responsabilité claire et unique :
1// dinosaurHealthMonitor.js - responsable uniquement de la surveillance de la santé des dinosaures
2export function checkVitalSigns(dinosaurId) {
3 //
4}
5
6export function alertHealthIssue(dinosaurId, issue) {
7 //
8}
9
10// enclosureSecurity.js - responsable uniquement de la sécurité des enclos
11export function checkEnclosureSecurity(enclosureId) {
12 //
13}
14
15export function activateEmergencyProtocol(enclosureId) {
16 //
17}2. Interfaces propres entre les modules
Les modules devraient communiquer via des interfaces propres et bien définies :
1// Le module de plus haut niveau utilise les interfaces des modules de plus bas niveau
2import { checkVitalSigns } from './dinosaurHealthMonitor';
3import { checkEnclosureSecurity } from './enclosureSecurity';
4
5// parkSafetySystem.js
6export async function performSafetyCheck() {
7 const dinosaurs = await fetchAllDinosaurs();
8 const enclosures = await fetchAllEnclosures();
9
10 // Utilise les interfaces des autres modules
11 const healthIssues = dinosaurs.map(checkVitalSigns);
12 const securityIssues = enclosures.map(checkEnclosureSecurity);
13
14 return {
15 healthIssues: healthIssues.filter(issue => issue !== null),
16 securityIssues: securityIssues.filter(issue => issue !== null)
17 };
18}3. Injection de dépendances
L'injection de dépendances est une technique qui permet d'inverser les dépendances entre les modules, ce qui facilite les tests et les modifications :
1// Sans DI
2function DinosaurMonitor() {
3 // Dépendance directe
4 const vitalSignsMonitor = new VitalSignsMonitor();
5
6 return {
7 checkDinosaur(dinosaur) {
8 return vitalSignsMonitor.check(dinosaur);
9 }
10 };
11}
12
13// Avec DI
14function DinosaurMonitor(vitalSignsMonitor) {
15 return {
16 checkDinosaur(dinosaur) {
17 return vitalSignsMonitor.check(dinosaur);
18 }
19 };
20}
21
22// On peut maintenant injecter différentes implémentations
23const realMonitor = DinosaurMonitor(new VitalSignsMonitor());
24const testMonitor = DinosaurMonitor(new MockVitalSignsMonitor());Gérer les imports
Dans les grands projets, gérer les imports peut devenir problématique. Voici quelques techniques qui aident à les garder organisés :
1. Barrel exports
Des fichiers index qui ré-exportent tous les éléments publics d'un répertoire donné :
1// dinosaurMonitoring/index.js
2export * from './vitalSigns';
3export * from './behaviorAnalysis';
4export * from './healthAlerts';
5
6// Dans un autre module
7import { checkVitalSigns, analyzeBehavior } from './dinosaurMonitoring';
8// Au lieu de
9import { checkVitalSigns } from './dinosaurMonitoring/vitalSigns';
10import { analyzeBehavior } from './dinosaurMonitoring/behaviorAnalysis';2. Alias d'import
De nombreux outils de bundling (comme webpack, Vite) permettent de définir des alias pour les chemins d'import :
1// webpack.config.js
2module.exports = {
3 resolve: {
4 alias: {
5 '@core': path.resolve(__dirname, 'src/core'),
6 '@features': path.resolve(__dirname, 'src/features'),
7 '@shared': path.resolve(__dirname, 'src/shared'),
8 }
9 }
10};
11
12// Dans le code
13import { activateProtocol } from '@core/security';
14import { DinosaurCard } from '@features/dinosaurs/components';Cela élimine le problème des chemins relatifs (../../..) et rend les imports plus lisibles.
Gestion de l'état
Dans les applications plus grandes, la gestion de l'état devient un défi majeur. Voici plusieurs approches :
1. Gestion d'état centralisée (Redux, MobX)
Stocker tout l'état de l'application en un seul endroit :
1// Actions Redux pour le système de surveillance des dinosaures
2const UPDATE_DINOSAUR_HEALTH = 'UPDATE_DINOSAUR_HEALTH';
3const ALERT_HEALTH_ISSUE = 'ALERT_HEALTH_ISSUE';
4
5// Reducer
6function dinosaurHealthReducer(state = initialState, action) {
7 switch (action.type) {
8 case UPDATE_DINOSAUR_HEALTH:
9 return {
10 ...state,
11 dinosaurs: state.dinosaurs.map(dino =>
12 dino.id === action.payload.id
13 ? { ...dino, health: action.payload.health }
14 : dino
15 )
16 };
17 case ALERT_HEALTH_ISSUE:
18 return {
19 ...state,
20 alerts: [...state.alerts, action.payload]
21 };
22 default:
23 return state;
24 }
25}2. État modularisé (Redux Toolkit, Zustand)
Découper l'état en plus petits « slices » indépendants :
1// Slice d'état responsable des dinosaures
2const dinosaurSlice = createSlice({
3 name: 'dinosaurs',
4 initialState: { list: [], loading: false, error: null },
5 reducers: {
6 fetchStart: (state) => {
7 state.loading = true;
8 },
9 fetchSuccess: (state, action) => {
10 state.list = action.payload;
11 state.loading = false;
12 },
13 updateHealth: (state, action) => {
14 const { id, health } = action.payload;
15 const dinosaur = state.list.find(d => d.id === id);
16 if (dinosaur) dinosaur.health = health;
17 }
18 }
19});
20
21// Slice d'état responsable des enclos
22const enclosureSlice = createSlice({
23 name: 'enclosures',
24 initialState: { list: [], securityStatus: {} },
25 reducers: {
26 //
27 }
28});3. État serveur vs état client
Distinguer les données provenant du serveur de l'état de l'interface :
1// Données serveur - gérées par React Query
2const { data: dinosaurs, isLoading, error } = useQuery(
3 'dinosaurs',
4 () => fetchDinosaurs()
5);
6
7// État de l'interface - géré localement
8const [selectedDinosaurId, setSelectedDinosaurId] = useState(null);Gérer les effets de bord
Dans les grandes applications, gérer les effets de bord (requêtes API, opérations d'E/S, etc.) devient complexe. Voici plusieurs approches :
1. Gestion centralisée (Redux Saga, Redux Thunk)
1// Redux Saga pour la surveillance de la santé des dinosaures
2function* monitorDinosaurHealthSaga() {
3 while (true) {
4 try {
5 // Récupère tous les dinosaures
6 const dinosaurs = yield call(api.fetchAllDinosaurs);
7
8 // Vérifie la santé de chaque dinosaure
9 for (const dinosaur of dinosaurs) {
10 const healthData = yield call(api.fetchDinosaurHealth, dinosaur.id);
11
12 // Met à jour l'état
13 yield put(updateDinosaurHealth(dinosaur.id, healthData));
14
15 // Si la santé est critique, signale une alerte
16 if (healthData.status === 'critical') {
17 yield put(alertHealthIssue(dinosaur.id, healthData));
18 }
19 }
20
21 // Attend 5 minutes avant la prochaine vérification
22 yield delay(5 * 60 * 1000);
23 } catch (error) {
24 console.error('Erreur de surveillance de la santé :', error);
25 yield put(monitoringError(error));
26 // Attend 1 minute avant de réessayer
27 yield delay(60 * 1000);
28 }
29 }
30}2. Hooks d'effets (React Query, SWR)
1// Hook React Query pour gérer les données de santé des dinosaures
2function useDinosaurHealth(dinosaurId) {
3 return useQuery(
4 ['dinosaurHealth', dinosaurId],
5 () => api.fetchDinosaurHealth(dinosaurId),
6 {
7 // Rafraîchit les données toutes les minutes
8 refetchInterval: 60 * 1000,
9 // Réagit aux changements de santé
10 onSuccess: (data) => {
11 if (data.status === 'critical') {
12 notifyHealthIssue(dinosaurId, data);
13 }
14 }
15 }
16 );
17}
18
19// Dans un composant
20function DinosaurMonitor({ dinosaurId }) {
21 const { data, isLoading, error } = useDinosaurHealth(dinosaurId);
22
23 if (isLoading) return <Loading />;
24 if (error) return <Error message={error.message} />;
25
26 return (
27 <div className={`health-status ${data.status}`}>
28 <h2>Santé du dinosaure : {data.status}</h2>
29 <div>Température : {data.temperature}°C</div>
30 <div>Rythme cardiaque : {data.heartRate} BPM</div>
31 <div>Mis à jour : {formatTime(data.timestamp)}</div>
32 </div>
33 );
34}Tester les grandes applications
Dans les grands projets, des tests complets sont essentiels. Voici les niveaux de tests à considérer :
1. Tests unitaires
Tester des modules individuels de manière isolée :
1// Test de la fonction checkVitalSigns de dinosaurHealthMonitor
2describe('dinosaurHealthMonitor', () => {
3 describe('checkVitalSigns', () => {
4 it('devrait correctement identifier les dinosaures en bonne santé', () => {
5 const healthyDino = {
6 id: 'trex1',
7 temperature: 38,
8 heartRate: 70,
9 bloodOxygen: 95
10 };
11
12 const result = checkVitalSigns(healthyDino);
13 expect(result.status).toBe('healthy');
14 expect(result.issues).toHaveLength(0);
15 });
16
17 it('devrait détecter une température élevée', () => {
18 const feverishDino = {
19 id: 'trex1',
20 temperature: 42, // Trop élevée
21 heartRate: 70,
22 bloodOxygen: 95
23 };
24
25 const result = checkVitalSigns(feverishDino);
26 expect(result.status).toBe('issue');
27 expect(result.issues).toContain('high temperature');
28 });
29 });
30});2. Tests d'intégration
Tester la coopération entre les modules :
1// Test de la coopération entre le module de santé et le module d'alertes
2describe('Intégration de la surveillance de la santé', () => {
3 it('devrait générer des alertes pour les problèmes de santé', async () => {
4 // Prépare les mocks
5 const mockAlertSystem = {
6 sendAlert: jest.fn()
7 };
8
9 const mockDinosaurData = {
10 id: 'raptor2',
11 temperature: 43, // Critiquement élevée
12 heartRate: 120, // Élevé
13 bloodOxygen: 85 // Niveau d'oxygène bas
14 };
15
16 // Crée le système de surveillance avec le système d'alertes mocké
17 const healthMonitor = createHealthMonitor(mockAlertSystem);
18
19 // Appelle la fonction de surveillance
20 await healthMonitor.checkAndAlert(mockDinosaurData);
21
22 // Vérifie que l'alerte a bien été envoyée
23 expect(mockAlertSystem.sendAlert).toHaveBeenCalledWith(
24 expect.objectContaining({
25 dinosaurId: 'raptor2',
26 severity: 'critical',
27 issues: expect.arrayContaining([
28 expect.stringMatching(/temperature/i),
29 expect.stringMatching(/heart rate/i),
30 expect.stringMatching(/oxygen/i)
31 ])
32 })
33 );
34 });
35});3. Tests de bout en bout (E2E)
Tester des parcours utilisateur complets :
1// Test E2E du processus de réponse à une brèche d'enclos
2describe("Réponse à une brèche d'enclos", () => {
3 it("devrait activer le protocole d'urgence en cas de panne de clôture", async () => {
4 // Se connecte en tant qu'administrateur système
5 await login('admin', 'password');
6
7 // Navigue vers le panneau de gestion des enclos
8 await navigateTo('enclosures');
9
10 // Simule une panne de clôture
11 await simulateFenceFailure('enclosure-b12');
12
13 // Vérifie que l'alarme a été activée
14 const alarmStatus = await getElement('.alarm-status');
15 expect(alarmStatus).toHaveClass('active');
16
17 // Vérifie que le protocole d'urgence a été lancé
18 const protocolStatus = await getElement('.emergency-protocol');
19 expect(protocolStatus.textContent).toContain('Active');
20
21 // Vérifie que les notifications ont été envoyées
22 const notifications = await getElements('.notification');
23 expect(notifications).toHaveLength(2); // À l'équipe de sécurité et à la direction
24
25 // Simule la réparation de la clôture
26 await repairFence('enclosure-b12');
27
28 // Vérifie que l'alarme a été désactivée
29 const updatedAlarmStatus = await getElement('.alarm-status');
30 expect(updatedAlarmStatus).not.toHaveClass('active');
31 });
32});Documentation du code
Dans les grands projets, la documentation devient essentielle. Voici différents niveaux de documentation à considérer :
1. Documentation du code (commentaires, JSDoc)
1/**
2 * Vérifie l'état de santé d'un dinosaure.
3 * @param {string} dinoId - L'identifiant unique du dinosaure
4 * @param {Object} [options] - Options supplémentaires
5 * @param {boolean} [options.detailed=false] - Renvoyer un rapport détaillé
6 * @returns {Promise<HealthStatus>} Objet d'état de santé
7 * @throws {DinoNotFoundError} Quand le dinosaure est introuvable
8 * @example
9 * const status = await checkHealth('TRX-001', { detailed: true });
10 */
11async function checkHealth(dinoId, options = {}) {
12 const dino = await getDinosaurById(dinoId);
13 if (!dino) throw new DinoNotFoundError(dinoId);
14
15 return {
16 healthy: dino.health > 60,
17 level: dino.health,
18 lastChecked: new Date().toISOString()
19 };
20}2. Documentation des modules (README.md)
Chaque module devrait avoir son propre fichier README.md décrivant ses fonctionnalités, son interface et son utilisation :
1# Module de surveillance de la santé des dinosaures
2
3Ce module est responsable de la surveillance et de l'évaluation de l'état de santé des dinosaures de Jurassic Park.
4
5## Fonctionnalités
6
7- Vérification régulière des signes vitaux des dinosaures
8- Détection des problèmes de santé et génération d'alertes
9- Suivi de l'historique de l'état de santé et des tendances
10- Notifications automatiques du personnel vétérinaire
11
12## API
13
14### `checkVitalSigns(dinosaur)`
15
16Vérifie la santé du dinosaure d'après ses signes vitaux.
17
18### `monitorHealth(dinosaurId, interval)`
19
20Démarre une surveillance périodique de la santé du dinosaure.
21
22### `stopMonitoring(dinosaurId)`
23
24Arrête la surveillance de la santé du dinosaure.
25
26## Exemples d'utilisation
27
28~~~js
29// Vérification ponctuelle de la santé
30const healthStatus = checkVitalSigns(dinosaur);
31
32// Démarre une surveillance continue
33const stopMonitoring = monitorHealth('trex1', 60000); // toutes les minutes
34
35// Arrête la surveillance
36stopMonitoring();Intégration avec d'autres modules
Ce module utilise :
- Les API du système de capteurs (src/modules/sensors)
- Le système d'alertes (src/modules/alerts)
Utilisé par :
- Le tableau de bord de gestion du parc (src/modules/dashboard)
- Le système de sécurité (src/modules/security)
1
2### 3. Documentation de l'architecture (diagrammes, ADR)
3
4Documenter les décisions architecturales est essentiel pour comprendre pourquoi le système a été conçu d'une certaine façon. Parmi les outils utiles :
5
6- **Diagrammes d'architecture** (C4, UML)
7- **Architecture Decision Records (ADR)** - documents décrivant les décisions architecturales importantes, leur contexte et leurs conséquences
8
9~~~text
10# ADR 001 : Choix de Redux comme système de gestion d'état
11
12## Contexte
13
14L'application de gestion de Jurassic Park a besoin d'un mécanisme de gestion d'état qui gère :
15- Un état global complexe (données des dinosaures, des enclos, des visiteurs)
16- Des opérations asynchrones (communication avec les systèmes du parc)
17- Le suivi de l'historique des changements d'état (audit de sécurité)
18
19## Décision
20
21Nous avons décidé d'utiliser Redux avec Redux Toolkit et Redux Saga comme mécanisme principal de gestion d'état.
22
23## Justification
24
25- **Prévisibilité** : Redux assure un flux de données unidirectionnel
26- **Middleware** : Redux Saga simplifie la gestion des opérations asynchrones complexes
27- **DevTools** : les outils de développement Redux facilitent le débogage
28- **Écosystème** : un riche écosystème de bibliothèques et d'outils
29- **Time Travel Debugging** : possibilité de suivre l'historique des changements d'état
30
31## Conséquences
32
33### Positives
34- Flux de données clair dans l'application
35- Débogage plus facile des problèmes d'état
36- Possibilité d'auditer les actions des utilisateurs
37
38### Négatives
39- Augmentation de la quantité de code (actions, reducers, selectors)
40- Courbe d'apprentissage plus élevée pour les nouveaux membres de l'équipe
41- Surcoût de performance potentiel s'il est mal utiliséBonnes pratiques pour les grands projets JavaScript
1. Établir des standards de code
Définis et fais respecter des standards de code avec des outils comme ESLint, Prettier :
1// .eslintrc.json
2{
3 "extends": [
4 "eslint:recommended",
5 "plugin:react/recommended"
6 ],
7 "rules": {
8 "react/prop-types": "error",
9 "no-unused-vars": "error",
10 "no-console": ["warn", { "allow": ["warn", "error"] }],
11 "prefer-const": "error"
12 }
13}2. Utiliser le typage (TypeScript, PropTypes)
1// typescript
2interface DinosaurVitalSigns {
3 id: string;
4 temperature: number;
5 heartRate: number;
6 bloodOxygen: number;
7}
8
9interface HealthAssessment {
10 status: 'healthy' | 'issue' | 'critical';
11 issues: string[];
12 lastChecked: Date;
13}
14
15function checkVitalSigns(dinosaur: DinosaurVitalSigns): HealthAssessment {
16 // implémentation...
17}3. Automatiser les tests et les déploiements (CI/CD)
Mets en place des pipelines CI/CD qui exécutent automatiquement les tests, l'analyse statique du code et déploient l'application :
1# .github/workflows/ci.yml
2name: CI/CD
3
4on:
5 push:
6 branches: [ main, develop ]
7 pull_request:
8 branches: [ main, develop ]
9
10jobs:
11 test:
12 runs-on: ubuntu-latest
13 steps:
14 - uses: actions/checkout@v2
15 - name: Set up Node.js
16 uses: actions/setup-node@v2
17 with:
18 node-version: '14'
19 - name: Install dependencies
20 run: npm ci
21 - name: Run tests
22 run: npm test
23 - name: Run linting
24 run: npm run lint
25 - name: Build
26 run: npm run build
27
28 deploy:
29 needs: test
30 if: github.ref == 'refs/heads/main'
31 runs-on: ubuntu-latest
32 steps:
33 - uses: actions/checkout@v2
34 # ... étapes de déploiement4. Pratiquer les revues de code
Mets en place un processus de Code Review qui garantit que le code est relu par au moins un autre développeur avant d'être fusionné dans la branche principale.
5. Surveiller les performances
Implémente une surveillance des performances de l'application pour détecter et corriger rapidement les problèmes :
1// Exemple de configuration d'un outil de surveillance des performances personnalisé
2const performanceMonitor = {
3 eventTiming: {},
4
5 startTiming(eventName) {
6 this.eventTiming[eventName] = {
7 start: performance.now()
8 };
9 },
10
11 endTiming(eventName) {
12 if (this.eventTiming[eventName]) {
13 const start = this.eventTiming[eventName].start;
14 const duration = performance.now() - start;
15
16 // Journalise vers le serveur de monitoring
17 logPerformanceMetric(eventName, duration);
18
19 // Génère une alerte si l'opération a pris trop de temps
20 if (duration > performanceThresholds[eventName]) {
21 alertPerformanceIssue(eventName, duration);
22 }
23
24 delete this.eventTiming[eventName];
25 }
26 }
27};
28
29// Utilisation
30function loadDinosaurData(id) {
31 performanceMonitor.startTiming('loadDinosaurData');
32
33 return fetchDinosaurData(id)
34 .then(data => {
35 performanceMonitor.endTiming('loadDinosaurData');
36 return data;
37 })
38 .catch(error => {
39 performanceMonitor.endTiming('loadDinosaurData');
40 throw error;
41 });
42}Résumé
Structurer le code dans les grands projets JavaScript est un sujet complexe qui demande une approche réfléchie. Les aspects clés sont :
- Organisation des dossiers/fichiers - choisir la bonne structure selon la taille et la nature du projet
- Architecture de l'application - définir comment les différentes parties de l'application interagissent
- Modularisation du code - découper le code en modules plus petits et cohérents avec des responsabilités claires
- Gestion des imports - garder des imports lisibles et efficaces
- Gestion de l'état - choisir la bonne approche pour gérer l'état
- Gestion des effets de bord - organiser les opérations asynchrones et autres effets de bord
- Tests - mettre en place une stratégie de test complète
- Documentation - assurer un code et une architecture bien documentés
En suivant ces principes, tu peux éviter le chaos qui pourrait mener à une « catastrophe » dans ton projet JavaScript - tout comme une organisation et une gestion correctes auraient permis d'éviter la catastrophe à Jurassic Park !
Code de cette leçon : main.js
1// Modules ES dans le navigateur - Jurassic Park
2console.log("Modules ES dans le navigateur\n");
3
4// Demonstracja <script type="module">
5console.log("=== Exemple HTML ===\n");
6console.log(`<!DOCTYPE html>
7<html>
8<head>
9 <title>Park Jurajski</title>
10</head>
11<body>
12 <!-- Module ES dans le navigateur -->
13 <script type="module">
14 import { Dinosaur } from './dinosaur.js';
15
16 const rex = new Dinosaur('Rex');
17 console.log(rex);
18 </script>
19
20 <!-- Solution de repli pour les anciens navigateurs -->
21 <script nomodule>
22 console.log('Ton navigateur ne prend pas en charge les modules ES');
23 </script>
24</body>
25</html>`);
26
27// Import dynamique dans le navigateur
28console.log("\n=== Import dynamique ===\n");
29
30async function loadDinosaurModule() {
31 try {
32 console.log("⏳ Chargement du module...");
33
34 // Simulation d'un import dynamique
35 const module = {
36 Dinosaur: class {
37 constructor(name) {
38 this.name = name;
39 }
40 }
41 };
42
43 console.log("Module charge !");
44 const dino = new module.Dinosaur('Blue');
45 console.log("Cree :", dino);
46
47 } catch (error) {
48 console.error("Erreur de chargement :", error);
49 }
50}
51
52loadDinosaurModule();
53
54// Import maps
55console.log("\n=== Import Maps ===\n");
56console.log(`<script type="importmap">
57{
58 "imports": {
59 "dinosaurs": "/modules/dinosaurs.js",
60 "security": "/modules/security.js",
61 "lodash": "https://cdn.skypack.dev/lodash"
62 }
63}
64</script>
65
66<script type="module">
67 // On peut maintenant utiliser des alias
68 import { Dinosaur } from 'dinosaurs';
69 import { Security } from 'security';
70 import _ from 'lodash';
71</script>`);
72
73console.log("\nModules ES dans le navigateur :");
74console.log("Prise en charge native (sans bundler)");
75console.log("Defer automatique");
76console.log("Mode strict par defaut");
77console.log("Import maps pour les alias");
78
79console.log("\nNavigateurs modernes = modules natifs !");Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Quelle est la différence entre une organisation par type et une organisation par fonctionnalité ?
Tâches pratiques dans le jeu
- Éditeur de code
Crée une structure modulaire avec séparation des responsabilités et une API claire pour chaque module.