Typescript fondamentaux
Thème :
Junior
1 vues ·3/14 leçons

03 - Modules et Build Tools : Architecture Moderne et Performance

3.1 Introduction aux Modules TypeScript

Évolution des Modules JavaScript

L’écosystème JavaScript a connu une évolution remarquable dans la gestion des modules. Des scripts simples chargés globalement aux systèmes de modules sophistiqués, cette transformation a fondamentalement changé la manière dont nous structurons nos applications.

En 2025, les modules sont devenus la pierre angulaire du développement JavaScript moderne. Ils permettent de :

  • Organiser le code en unités logiques et réutilisables
  • Gérer les dépendances de manière déclarative
  • Optimiser le chargement avec le code splitting
  • Faciliter la maintenance à travers l’encapsulation

TypeScript et les Modules

TypeScript apporte une couche supplémentaire de sécurité aux modules JavaScript en ajoutant :

  • Typage statique des imports/exports
  • Analyse de dépendances à la compilation
  • Refactoring sécurisé à travers les modules
  • Auto-complétion intelligente des imports

Philosophie de l’Architecture Modulaire

Une bonne architecture modulaire suit plusieurs principes :

  1. Single Responsibility : Chaque module a une seule responsabilité claire
  2. Loose Coupling : Les modules dépendent d’interfaces, pas d’implémentations
  3. High Cohesion : Les fonctionnalités liées sont regroupées
  4. Explicit Dependencies : Toutes les dépendances sont déclarées explicitement

3.2 ES Modules (ESM) : Le Standard Moderne

Syntaxe des ES Modules

Les ES Modules (ESM) sont devenus le standard officiel pour la modularisation en JavaScript. TypeScript supporte nativement cette syntaxe depuis ses premières versions.

Export Nommé

// utils/math.ts
export const PI = 3.14159;

export function addition(a: number, b: number): number {
    return a + b;
}

export function multiplication(a: number, b: number): number {
    return a * b;
}

export class Calculatrice {
    constructor(private valeurInitiale: number = 0) {}

    ajouter(valeur: number): this {
        this.valeurInitiale += valeur;
        return this;
    }

    resultat(): number {
        return this.valeurInitiale;
    }
}

// Export d'un type
export type Operation = 'addition' | 'multiplication' | 'division';

Export par Défaut

// services/calculatrice.ts
import { Calculatrice, Operation } from '../utils/math';

export default class ServiceCalculatrice {
    private calculatrice: Calculatrice;

    constructor(valeurInitiale: number = 0) {
        this.calculatrice = new Calculatrice(valeurInitiale);
    }

    executer(operation: Operation, valeur: number): number {
        switch (operation) {
            case 'addition':
                return this.calculatrice.ajouter(valeur).resultat();
            case 'multiplication':
                // Implémentation...
                return this.calculatrice.resultat() * valeur;
            default:
                throw new Error(`Opération non supportée: ${operation}`);
        }
    }
}

Import des Modules

// main.ts
// Import nommé
import { PI, addition, multiplication, Calculatrice } from './utils/math';
import type { Operation } from './utils/math';

// Import par défaut
import ServiceCalculatrice from './services/calculatrice';

// Import combiné
import ServiceCalculatrice, { Calculatrice as Calc } from './services/calculatrice';

// Import de tout l'espace de noms
import * as MathUtils from './utils/math';

// Utilisation
const resultat = addition(10, 20);
const service = new ServiceCalculatrice();
const calc = new Calc(5);

Types et Modules

TypeScript permet d’exporter et d’importer des types de manière optimisée :

// types/user.ts
export interface Utilisateur {
    id: number;
    nom: string;
    email: string;
    dateCreation: Date;
}

export type StatutUtilisateur = 'actif' | 'inactif' | 'suspendu';

export type Permission = 'lire' | 'ecrire' | 'supprimer' | 'administrer';

// Import dans un autre fichier
import type { Utilisateur, StatutUtilisateur } from './types/user';
import { Permission } from './types/user'; // Import de valeur

Modules Dynamiques

Les imports dynamiques permettent de charger des modules à la demande, ce qui est essentiel pour le code splitting :

// Chargement dynamique d'un module
async function chargerModuleUtilisateur() {
    const { Utilisateur, StatutUtilisateur } = await import('./types/user');

    const utilisateur: Utilisateur = {
        id: 1,
        nom: 'Alice',
        email: 'alice@example.com',
        dateCreation: new Date(),
    };

    return utilisateur;
}

// Import conditionnel
async function chargerService(service: 'auth' | 'database' | 'api') {
    switch (service) {
        case 'auth':
            return await import('./services/auth');
        case 'database':
            return await import('./services/database');
        case 'api':
            return await import('./services/api');
        default:
            throw new Error(`Service inconnu: ${service}`);
    }
}

3.3 Configuration TypeScript pour Modules

tsconfig.json pour Modules Modernes

Une configuration appropriée est essentielle pour tirer le meilleur parti des modules TypeScript :

{
    "compilerOptions": {
        // Cible moderne pour les modules
        "target": "ES2024",

        // Système de modules
        "module": "ESNext",
        "moduleResolution": "bundler",

        // Support des chemins absolus
        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"],
            "@/components/*": ["src/components/*"],
            "@/utils/*": ["src/utils/*"],
            "@/types/*": ["src/types/*"],
            "@/services/*": ["src/services/*"]
        },

        // Génération des déclarations
        "declaration": true,
        "declarationMap": true,

        // Génération des source maps
        "sourceMap": true,

        // Options de sortie
        "outDir": "./dist",
        "rootDir": "./src",

        // Interopérabilité
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,

        // Options strictes
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,

        // Options de module
        "forceConsistentCasingInFileNames": true,
        "skipLibCheck": true,
        "isolatedModules": true,
        "verbatimModuleSyntax": false
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

Configuration Multi-Projets

Pour les projets complexes, TypeScript supporte les workspaces :

// tsconfig.json (racine)
{
  "files": [],
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/ui" },
    { "path": "./packages/utils" },
    { "path": "./apps/web" },
    { "path": "./apps/mobile" }
  ]
}

// packages/core/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "node_modules"]
}

Import Maps

Les import maps permettent de mapper des URLs à des modules spécifiques :

// importmap.json
{
    "imports": {
        "react": "/node_modules/react/index.js",
        "react-dom": "/node_modules/react-dom/index.js",
        "lodash": "/node_modules/lodash/lodash.js",
        "@mon-app/utils": "/src/utils/index.js",
        "@mon-app/components": "/src/components/index.js"
    }
}

3.4 Build Tools Modernes pour TypeScript

Vite : Le Build Tool Ultra-Rapide

Vite est devenu le standard de facto pour les projets TypeScript en 2025 grâce à sa vitesse exceptionnelle et son excellente intégration TypeScript.

Installation et Configuration

# Création d'un projet Vite + TypeScript
npm create vite@latest mon-app -- --template react-ts

# Installation manuelle
npm install -D vite @vitejs/plugin-react typescript

Configuration Vite

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
    plugins: [react()],

    // Configuration des chemins
    resolve: {
        alias: {
            '@': resolve(__dirname, 'src'),
            '@/components': resolve(__dirname, 'src/components'),
            '@/utils': resolve(__dirname, 'src/utils'),
            '@/types': resolve(__dirname, 'src/types'),
            '@/services': resolve(__dirname, 'src/services'),
        },
    },

    // Configuration TypeScript
    esbuild: {
        target: 'es2024',
    },

    // Optimisation du build
    build: {
        target: 'es2024',
        minify: 'esbuild',
        sourcemap: true,
        rollupOptions: {
            output: {
                manualChunks: {
                    vendor: ['react', 'react-dom'],
                    utils: ['lodash', 'date-fns'],
                },
            },
        },
    },

    // Configuration du serveur de développement
    server: {
        port: 3000,
        open: true,
        cors: true,
    },
});

esbuild : Compilation Ultra-Rapide

esbuild est un compilateur JavaScript extrêmement rapide écrit en Go, excellent pour les projets TypeScript :

// build.js
import esbuild from 'esbuild';
import path from 'path';

const build = async () => {
    try {
        await esbuild.build({
            entryPoints: ['src/index.ts'],
            bundle: true,
            outfile: 'dist/bundle.js',
            format: 'esm',
            target: 'es2024',
            sourcemap: true,
            minify: true,
            treeShaking: true,
            define: {
                'process.env.NODE_ENV': '"production"',
            },
            external: ['react', 'react-dom'],
            loader: {
                '.ts': 'ts',
                '.tsx': 'tsx',
            },
        });

        console.log('Build terminé avec succès !');
    } catch (error) {
        console.error('Erreur de build:', error);
        process.exit(1);
    }
};

build();

SWC : Compilation Rust pour Performance Maximale

SWC (Speedy Web Compiler) est un compilateur écrit en Rust qui offre des performances exceptionnelles :

// .swcrc
{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "target": "es2024",
    "parser": {
      "syntax": "typescript",
      "tsx": true,
      "decorators": true,
      "dynamicImport": true
    },
    "transform": {
      "react": {
        "pragma": "React.createElement",
        "pragmaFrag": "React.Fragment",
        "throwIfNamespace": true,
        "development": false,
        "useBuiltins": false
      },
      "optimizer": {
        "globals": {
          "vars": {
            "process.env.NODE_ENV": "\"production\""
          }
        }
      }
    },
    "loose": false,
    "externalHelpers": false
  },
  "module": {
    "type": "es6",
    "strict": true,
    "strictMode": true
  },
  "sourceMaps": true,
  "inlineSourcesContent": true
}

3.5 Code Splitting et Lazy Loading

Stratégies de Code Splitting

Le code splitting permet de diviser l’application en morceaux plus petits qui peuvent être chargés à la demande, améliorant ainsi les performances de chargement initial.

Splitting par Route

// App.tsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import LoadingSpinner from './components/LoadingSpinner';

// Lazy loading des composants de route
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));
const ContactPage = lazy(() => import('./pages/ContactPage'));
const DashboardPage = lazy(() => import('./pages/DashboardPage'));

const App: React.FC = () => {
  return (
    <BrowserRouter>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
          <Route path="/contact" element={<ContactPage />} />
          <Route path="/dashboard" element={<DashboardPage />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
};

export default App;

Splitting par Composant

// components/HeavyComponent.tsx
import { lazy } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));
const HeavyTable = lazy(() => import('./HeavyTable'));

interface HeavyComponentProps {
  showChart: boolean;
  showTable: boolean;
}

const HeavyComponent: React.FC<HeavyComponentProps> = ({
  showChart,
  showTable
}) => {
  return (
    <div>
      {showChart && (
        <Suspense fallback={<div>Chargement du graphique...</div>}>
          <HeavyChart />
        </Suspense>
      )}

      {showTable && (
        <Suspense fallback={<div>Chargement du tableau...</div>}>
          <HeavyTable />
        </Suspense>
      )}
    </div>
  );
};

export default HeavyComponent;

Splitting par Fonctionnalité

// utils/dynamicImports.ts
export const loadAuthModule = () => import('./modules/auth');
export const loadAdminModule = () => import('./modules/admin');
export const loadReportingModule = () => import('./modules/reporting');

// Utilisation
class ModuleLoader {
    private loadedModules = new Map<string, any>();

    async loadModule(moduleName: string) {
        if (this.loadedModules.has(moduleName)) {
            return this.loadedModules.get(moduleName);
        }

        let module;
        switch (moduleName) {
            case 'auth':
                module = await loadAuthModule();
                break;
            case 'admin':
                module = await loadAdminModule();
                break;
            case 'reporting':
                module = await loadReportingModule();
                break;
            default:
                throw new Error(`Module inconnu: ${moduleName}`);
        }

        this.loadedModules.set(moduleName, module);
        return module;
    }
}

Préchargement Intelligent

// utils/preloader.ts
class Preloader {
    private preloadQueue: Set<string> = new Set();
    private preloadedModules = new Map<string, any>();

    // Précharger un module
    async preload(moduleName: string): Promise<void> {
        if (this.preloadQueue.has(moduleName) || this.preloadedModules.has(moduleName)) {
            return;
        }

        this.preloadQueue.add(moduleName);

        try {
            // Simuler un préchargement après un délai
            setTimeout(async () => {
                const module = await this.loadModule(moduleName);
                this.preloadedModules.set(moduleName, module);
                this.preloadQueue.delete(moduleName);
            }, 2000);
        } catch (error) {
            console.error(`Erreur de préchargement pour ${moduleName}:`, error);
            this.preloadQueue.delete(moduleName);
        }
    }

    // Précharger en fonction de l'utilisateur
    async preloadForUser(userRole: string): Promise<void> {
        const modules = this.getModulesForRole(userRole);
        await Promise.all(modules.map((module) => this.preload(module)));
    }

    private getModulesForRole(role: string): string[] {
        switch (role) {
            case 'admin':
                return ['admin', 'reporting', 'analytics'];
            case 'user':
                return ['dashboard', 'profile'];
            default:
                return ['home'];
        }
    }

    private async loadModule(moduleName: string) {
        // Implémentation du chargement...
        return {};
    }
}

export const preloader = new Preloader();

3.6 Gestion des Dépendances

Types pour les Packages Externes

TypeScript utilise DefinitelyTyped pour fournir les types des packages JavaScript :

# Installation des types pour un package
npm install --save-dev @types/node
npm install --save-dev @types/react
npm install --save-dev @types/lodash

# Installation globale des types
npm install -g @types/node

Déclaration de Types Personnalisés

Pour les packages sans types disponibles ou pour les projets internes :

// types/global.d.ts
declare global {
    namespace NodeJS {
        interface ProcessEnv {
            NODE_ENV: 'development' | 'production' | 'test';
            API_URL: string;
            DATABASE_URL: string;
            JWT_SECRET: string;
        }
    }

    interface Window {
        gtag: (command: string, targetId: string, config?: any) => void;
        dataLayer: any[];
    }
}

export {};
// types/vendor.d.ts
// Déclaration pour une librairie sans types
declare module 'custom-lib' {
    export function customFunction(input: string): number;
    export class CustomClass {
        constructor(options: { name: string; value: number });
        getName(): string;
        getValue(): number;
    }
}

// Extension des types existants
declare module 'lodash' {
    interface LoDashStatic {
        customMethod<T>(array: T[], predicate: (item: T) => boolean): T[];
    }
}

Gestion des Versions de Types

// types/versions.ts
// Gestion des versions compatibles
export const COMPATIBLE_VERSIONS = {
    react: '^18.0.0',
    'react-dom': '^18.0.0',
    typescript: '^5.0.0',
    vite: '^5.0.0',
} as const;

// Vérification de compatibilité
export function checkCompatibility(packageName: string, version: string): boolean {
    const compatibleVersion = COMPATIBLE_VERSIONS[packageName as keyof typeof COMPATIBLE_VERSIONS];
    if (!compatibleVersion) {
        console.warn(`Version non spécifiée pour ${packageName}`);
        return true;
    }

    // Logique de vérification de version...
    return true;
}

3.7 Performance et Optimisation

Optimisation de la Compilation

// vite.config.ts - Optimisation avancée
export default defineConfig({
    plugins: [react()],

    // Optimisation du build
    build: {
        target: 'es2024',
        minify: 'esbuild',
        sourcemap: true,

        // Configuration du chunking
        rollupOptions: {
            output: {
                // Chunking par vendor
                manualChunks: {
                    'react-vendor': ['react', 'react-dom'],
                    router: ['react-router-dom'],
                    utils: ['lodash', 'date-fns', 'clsx'],
                    ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
                },

                // Noms de fichiers optimisés
                chunkFileNames: 'assets/[name]-[hash].js',
                entryFileNames: 'assets/[name]-[hash].js',
                assetFileNames: 'assets/[name]-[hash].[ext]',
            },
        },

        // Optimisation du tree shaking
        treeshake: {
            preset: 'recommended',
            moduleSideEffects: false,
        },
    },

    // Optimisation des dépendances
    optimizeDeps: {
        include: ['react', 'react-dom', 'react-router-dom'],
        exclude: ['@types/react'],
    },
});

Analyse des Bundles

// scripts/analyze-bundle.js
import { analyzeBundle } from 'vite-bundle-analyzer';
import { build } from 'vite';

async function analyze() {
    const result = await build({
        configFile: 'vite.config.ts',
        mode: 'production',
    });

    if (result instanceof Error) {
        console.error('Erreur de build:', result);
        return;
    }

    await analyzeBundle({
        bundleDir: './dist',
        reportFilename: './bundle-analysis.html',
        open: true,
    });
}

analyze();

Monitoring des Performances

// utils/performance.ts
interface PerformanceMetrics {
    buildTime: number;
    bundleSize: number;
    chunkCount: number;
    dependenciesCount: number;
}

class PerformanceMonitor {
    private metrics: PerformanceMetrics = {
        buildTime: 0,
        bundleSize: 0,
        chunkCount: 0,
        dependenciesCount: 0,
    };

    startBuild(): void {
        this.metrics.buildTime = Date.now();
    }

    endBuild(): void {
        this.metrics.buildTime = Date.now() - this.metrics.buildTime;
        this.reportMetrics();
    }

    setBundleSize(size: number): void {
        this.metrics.bundleSize = size;
    }

    setChunkCount(count: number): void {
        this.metrics.chunkCount = count;
    }

    setDependenciesCount(count: number): void {
        this.metrics.dependenciesCount = count;
    }

    private reportMetrics(): void {
        console.table(this.metrics);

        // Alertes de performance
        if (this.metrics.bundleSize > 1024 * 1024) {
            // > 1MB
            console.warn('⚠️ Bundle size > 1MB - Consider code splitting');
        }

        if (this.metrics.chunkCount > 50) {
            console.warn('⚠️ Too many chunks - Consider bundling strategy');
        }
    }
}

export const performanceMonitor = new PerformanceMonitor();

3.8 Tests et Qualité

Configuration des Tests avec TypeScript

// jest.config.js
module.exports = {
    preset: 'ts-jest',
    testEnvironment: 'jsdom',
    roots: ['<rootDir>/src'],
    testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/*.(test|spec).+(ts|tsx|js)'],
    transform: {
        '^.+\\.(ts|tsx)$': 'ts-jest',
    },
    collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts', '!src/index.ts'],
    coverageThreshold: {
        global: {
            branches: 80,
            functions: 80,
            lines: 80,
            statements: 80,
        },
    },
    moduleNameMapping: {
        '^@/(.*)$': '<rootDir>/src/$1',
    },
    setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
};

Tests de Modules

// __tests__/utils/math.test.ts
import { addition, multiplication, PI } from '@/utils/math';

describe('Math Utils', () => {
    describe('addition', () => {
        it('should add two positive numbers', () => {
            expect(addition(2, 3)).toBe(5);
        });

        it('should handle negative numbers', () => {
            expect(addition(-2, 3)).toBe(1);
        });

        it('should return correct type', () => {
            const result = addition(1, 2);
            expect(typeof result).toBe('number');
        });
    });

    describe('multiplication', () => {
        it('should multiply two numbers', () => {
            expect(multiplication(3, 4)).toBe(12);
        });
    });

    describe('PI constant', () => {
        it('should have correct value', () => {
            expect(PI).toBeCloseTo(3.14159, 5);
        });
    });
});

🎯 TP 3 : Application Modulaire avec Architecture Micro-Frontend

Objectifs

  • Maîtriser les modules ES et l’architecture modulaire
  • Configurer un build tool moderne (Vite)
  • Implémenter le code splitting et le lazy loading
  • Créer une architecture micro-frontend
  • Optimiser les performances de chargement

Contexte du Projet

Vous allez créer une application d’entreprise avec une architecture micro-frontend. L’application sera composée de plusieurs modules indépendants qui peuvent être développés et déployés séparément.

Architecture Cible

Structure des Modules

micro-frontend-app/
├── packages/
│   ├── shared/
│   │   ├── types/
│   │   ├── utils/
│   │   └── components/
│   ├── shell/
│   │   ├── src/
│   │   └── public/
│   ├── auth/
│   │   ├── src/
│   │   └── public/
│   ├── dashboard/
│   │   ├── src/
│   │   └── public/
│   └── admin/
│       ├── src/
│       └── public/
├── apps/
│   ├── web/
│   └── mobile/
├── tools/
│   ├── build/
│   └── deploy/
└── docs/

Fonctionnalités Requises

1. Module Shared

Créer un module partagé avec les types et utilitaires communs :

// packages/shared/types/user.ts
export interface Utilisateur {
    id: string;
    email: string;
    nom: string;
    prenom: string;
    role: RoleUtilisateur;
    permissions: Permission[];
    dateCreation: Date;
    derniereConnexion?: Date;
}

export type RoleUtilisateur = 'admin' | 'manager' | 'user' | 'guest';
export type Permission = 'read' | 'write' | 'delete' | 'admin';

// packages/shared/utils/auth.ts
export interface AuthState {
    utilisateur: Utilisateur | null;
    token: string | null;
    isAuthenticated: boolean;
    isLoading: boolean;
}

export interface AuthService {
    login(email: string, password: string): Promise<AuthState>;
    logout(): Promise<void>;
    refreshToken(): Promise<string>;
    hasPermission(permission: Permission): boolean;
}

// packages/shared/components/Button/Button.tsx
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
    variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
    size?: 'sm' | 'md' | 'lg';
    loading?: boolean;
    icon?: React.ReactNode;
    fullWidth?: boolean;
}

2. Module Shell

Créer le shell principal qui orchestre les micro-frontends :

// packages/shell/src/App.tsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { MicroFrontendLoader } from './components/MicroFrontendLoader';

const AuthModule = lazy(() => import('auth/App'));
const DashboardModule = lazy(() => import('dashboard/App'));
const AdminModule = lazy(() => import('admin/App'));

const App: React.FC = () => {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Chargement...</div>}>
        <Routes>
          <Route path="/auth/*" element={<AuthModule />} />
          <Route path="/dashboard/*" element={<DashboardModule />} />
          <Route path="/admin/*" element={<AdminModule />} />
          <Route path="/" element={<MicroFrontendLoader module="home" />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
};

export default App;

3. Configuration des Build Tools

Configurer Vite pour chaque module avec optimisation :

// vite.config.ts (root)
import { defineConfig } from 'vite';
import { resolve } from 'path';

export default defineConfig({
    build: {
        rollupOptions: {
            output: {
                manualChunks: {
                    vendor: ['react', 'react-dom'],
                    router: ['react-router-dom'],
                    shared: ['@mon-app/shared'],
                },
            },
        },
    },

    resolve: {
        alias: {
            '@shared': resolve(__dirname, 'packages/shared/src'),
        },
    },

    optimizeDeps: {
        include: ['@mon-app/shared'],
    },
});

4. Système de Communication Inter-Modules

Implémenter un système de communication entre les micro-frontends :

// packages/shared/utils/eventBus.ts
export interface EventBusEvent {
    type: string;
    payload: any;
    timestamp: number;
    source: string;
}

class EventBus {
    private listeners = new Map<string, Set<(event: EventBusEvent) => void>>();

    subscribe(eventType: string, callback: (event: EventBusEvent) => void): () => void {
        if (!this.listeners.has(eventType)) {
            this.listeners.set(eventType, new Set());
        }

        this.listeners.get(eventType)!.add(callback);

        // Retourner une fonction de désabonnement
        return () => {
            this.listeners.get(eventType)?.delete(callback);
        };
    }

    publish(event: Omit<EventBusEvent, 'timestamp'>): void {
        const fullEvent: EventBusEvent = {
            ...event,
            timestamp: Date.now(),
        };

        const callbacks = this.listeners.get(event.type);
        if (callbacks) {
            callbacks.forEach((callback) => callback(fullEvent));
        }
    }

    // Communication spécifique entre modules
    sendToModule(targetModule: string, event: Omit<EventBusEvent, 'source'>): void {
        this.publish({
            ...event,
            source: targetModule,
        });
    }
}

export const eventBus = new EventBus();

5. Gestion des États Globaux

Créer un système de gestion d’état partagé :

// packages/shared/store/globalStore.ts
export interface GlobalState {
    utilisateur: Utilisateur | null;
    theme: 'light' | 'dark';
    notifications: Notification[];
    loading: Record<string, boolean>;
}

export interface Notification {
    id: string;
    type: 'success' | 'error' | 'warning' | 'info';
    message: string;
    timestamp: number;
    read: boolean;
}

class GlobalStore {
    private state: GlobalState = {
        utilisateur: null,
        theme: 'light',
        notifications: [],
        loading: {},
    };

    private subscribers = new Set<(state: GlobalState) => void>();

    getState(): GlobalState {
        return { ...this.state };
    }

    setState(updates: Partial<GlobalState>): void {
        this.state = { ...this.state, ...updates };
        this.notifySubscribers();
    }

    subscribe(callback: (state: GlobalState) => void): () => void {
        this.subscribers.add(callback);
        callback(this.getState());

        return () => this.subscribers.delete(callback);
    }

    private notifySubscribers(): void {
        this.subscribers.forEach((callback) => callback(this.getState()));
    }

    // Actions spécifiques
    setUtilisateur(utilisateur: Utilisateur | null): void {
        this.setState({ utilisateur });
    }

    addNotification(notification: Omit<Notification, 'id' | 'timestamp'>): void {
        const newNotification: Notification = {
            ...notification,
            id: Date.now().toString(),
            timestamp: Date.now(),
            read: false,
        };

        this.setState({
            notifications: [...this.state.notifications, newNotification],
        });
    }

    setLoading(key: string, loading: boolean): void {
        this.setState({
            loading: { ...this.state.loading, [key]: loading },
        });
    }
}

export const globalStore = new GlobalStore();

Instructions d’Implémentation

Phase 1 : Structure Initiale

  1. Créer la structure monorepo avec les packages de base
  2. Configurer TypeScript pour les workspaces
  3. Mettre en place Vite pour chaque module
  4. Créer le module shared avec types et utilitaires

Phase 2 : Module Shell

  1. Développer le shell principal avec routing
  2. Implémenter le lazy loading des modules
  3. Créer le système de communication inter-modules
  4. Ajouter la gestion des états globaux

Phase 3 : Modules Spécifiques

  1. Développer le module auth avec login/logout
  2. Créer le module dashboard avec widgets
  3. Implémenter le module admin avec gestion
  4. Ajouter les tests pour chaque module

Phase 4 : Optimisation

  1. Optimiser le bundle splitting
  2. Implémenter le préchargement intelligent
  3. Ajouter le monitoring des performances
  4. Configurer le déploiement indépendant

Étapes Détaillées

Étape 1 : Configuration du Monorepo

# Initialisation du projet
npm init -y

# Installation des dépendances de développement
npm install -D typescript vite @vitejs/plugin-react
npm install -D @types/node @types/react @types/react-dom

# Configuration des workspaces
npm install -D lerna nx

# Création de la structure
mkdir -p packages/{shared,shell,auth,dashboard,admin}
mkdir -p apps/{web,mobile}

Étape 2 : Module Shared

  1. Créer les interfaces et types partagés
  2. Développer les utilitaires communs
  3. Créer les composants réutilisables
  4. Ajouter les tests unitaires

Étape 3 : Configuration Vite

  1. Configurer Vite pour chaque module
  2. Mettre en place le code splitting
  3. Optimiser les dépendances
  4. Ajouter les source maps

Étape 4 : Communication Inter-Modules

  1. Implémenter le système d’événements
  2. Créer le store global
  3. Ajouter la sécurité inter-modules
  4. Tester la communication

Critères de Réussite

✅ Architecture Modulaire

  • Structure monorepo bien organisée
  • Modules indépendants et réutilisables
  • Communication inter-modules efficace
  • Dépendances clairement définies

✅ Performance

  • Build rapide avec Vite
  • Code splitting optimisé
  • Lazy loading fonctionnel
  • Bundle size minimal

✅ Qualité du Code

  • TypeScript strict activé
  • Tests unitaires complets
  • Couverture de code > 80%
  • Documentation complète

✅ Déploiement

  • Déploiement indépendant des modules
  • CI/CD configuré
  • Monitoring en place
  • Rollback automatique

Extensions Possibles

1. Module Federation

  • Intégration avec Webpack Module Federation
  • Partage de dépendances entre modules
  • Runtime dynamic loading

2. Edge Computing

  • Déploiement sur CDN edge
  • Fonctions serverless par module
  • Optimisation globale

3. Testing Avancé

  • Tests E2E inter-modules
  • Tests de charge distribués
  • Tests de compatibilité

4. Sécurité

  • Authentification centralisée
  • Permissions granulaires
  • Audit de sécurité

5. Monitoring

  • Métriques par module
  • Alertes automatiques
  • Dashboard de monitoring

Ressources Complémentaires

Documentation

Outils

  • Lerna : Gestion de monorepos
  • Nx : Outils de développement monorepo
  • Bundle Analyzer : Analyse des bundles

Bonnes Pratiques

  • Modules petits et focalisés
  • Interfaces claires et stables
  • Tests automatisés continus
  • Documentation des APIs

Cette leçon explore l’architecture modulaire moderne avec TypeScript, permettant de créer des applications scalables et performantes grâce aux modules et aux build tools optimisés.

Prêt à passer à la suite ?

Vous avez terminé cette leçon sur 03 - Modules et Build Tools : Architecture Moderne et Performance. Continuez votre apprentissage avec la prochaine leçon ou explorez d'autres formations.