0%
Modern JavaScript: ES Modules

ES Modules

Modular Code Organization

10-15 min

Modern JavaScript: ES Modules

ES (ECMAScript) modules are a standardized system for organizing and sharing JavaScript code across different files. They let you build applications that are more maintainable and modular.

Introduction to ES Modules

Why use modules?

  • Encapsulation: Each module has its own scope
  • Reusability: Code can be easily shared between files
  • Explicit dependencies: Relationships between modules are clearly defined
  • Organization: Clear code structure

Basic configuration

<!-- In your HTML -->
<script type="module" src="main.js"></script>
// In your package.json
{
    "type": "module"
}

Exporting from Modules

Named exports

// mathematiques.js
export const PI = 3.14159;

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

export function soustraction(a, b) {
    return a - b;
}

// Alternative: grouped export
const multiplication = (a, b) => a * b;
const division = (a, b) => a / b;

export { multiplication, division };

Default export

// calculateur.js
class Calculateur {
    addition(a, b) {
        return a + b;
    }
    
    soustraction(a, b) {
        return a - b;
    }
}

export default Calculateur;

Combining exports

// utils.js
export const VERSION = "1.0.0";

export function formaterNombre(nombre) {
    return nombre.toFixed(2);
}

const Utils = {
    VERSION,
    formaterNombre
};

export default Utils;

Importing Modules

Named imports

// app.js
import { addition, soustraction } from './mathematiques.js';
import { PI as MATH_PI } from './mathematiques.js';

console.log(addition(2, 3));      // 5
console.log(MATH_PI);             // 3.14159

Default import

// app.js
import Calculateur from './calculateur.js';

const calc = new Calculateur();
console.log(calc.addition(5, 3)); // 8

Combined import

// app.js
import Utils, { VERSION } from './utils.js';
// or
import Utils, * as UtilsAll from './utils.js';

console.log(Utils.formaterNombre(3.14159)); // "3.14"
console.log(VERSION);                       // "1.0.0"

Dynamic import

// app.js
async function chargerModule() {
    try {
        const module = await import('./grand-module.js');
        console.log(module.fonctionImportante());
    } catch (erreur) {
        console.error('Erreur de chargement:', erreur);
    }
}

Organizing Modules

Typical project structure

src/
├── index.js
├── modules/
│   ├── utilisateurs/
│   │   ├── index.js
│   │   ├── utilisateur.js
│   │   └── validation.js
│   ├── produits/
│   │   ├── index.js
│   │   └── produit.js
│   └── utils/
│       ├── index.js
│       ├── format.js
│       └── validation.js

Barrel modules

// modules/utilisateurs/index.js
export { default as Utilisateur } from './utilisateur.js';
export { validerEmail, validerMotDePasse } from './validation.js';

// app.js
import { Utilisateur, validerEmail } from './modules/utilisateurs';

Aggregator modules

// modules/index.js
export * from './utilisateurs';
export * from './produits';
export * from './utils';

// app.js
import { Utilisateur, Produit, formaterPrix } from './modules';

Patterns and Best Practices

Singleton pattern

// config.js
class Config {
    constructor() {
        this.api = 'https://api.example.com';
        this.timeout = 5000;
    }
    
    setApi(url) {
        this.api = url;
    }
}

export default new Config();

Factory pattern

// loggerFactory.js
const createLogger = (prefix = '') => ({
    log: (message) => console.log(`${prefix}${message}`),
    error: (message) => console.error(`${prefix}${message}`),
    warn: (message) => console.warn(`${prefix}${message}`)
});

export default createLogger;

Adapter pattern

// apiAdapter.js
import axios from 'axios';
import { formatResponse } from './utils.js';

export const api = {
    async get(url) {
        const response = await axios.get(url);
        return formatResponse(response.data);
    },
    
    async post(url, data) {
        const response = await axios.post(url, data);
        return formatResponse(response.data);
    }
};

Practical Use Cases

Modular application

// modules/auth/auth.service.js
export class AuthService {
    async login(credentials) {
        // Authentication logic
    }
    
    async logout() {
        // Logout logic
    }
}

// modules/auth/index.js
export { AuthService } from './auth.service.js';

// app.js
import { AuthService } from './modules/auth';
const auth = new AuthService();

Modular state management

// store/counter.js
let count = 0;
const listeners = new Set();

export const getCount = () => count;

export const increment = () => {
    count++;
    notifyListeners();
};

export const decrement = () => {
    count--;
    notifyListeners();
};

export const subscribe = (listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
};

function notifyListeners() {
    listeners.forEach(listener => listener(count));
}

// app.js
import { getCount, increment, subscribe } from './store/counter.js';

subscribe((newCount) => {
    console.log('Nouveau compte:', newCount);
});

Modular plugins

// plugins/logger.js
export const loggerPlugin = {
    install(app) {
        app.logger = {
            log: console.log,
            error: console.error
        };
    }
};

// plugins/storage.js
export const storagePlugin = {
    install(app) {
        app.storage = {
            get: key => localStorage.getItem(key),
            set: (key, value) => localStorage.setItem(key, value)
        };
    }
};

// app.js
import { loggerPlugin } from './plugins/logger.js';
import { storagePlugin } from './plugins/storage.js';

class App {
    constructor() {
        this.plugins = new Map();
    }
    
    use(plugin) {
        plugin.install(this);
        this.plugins.set(plugin.name, plugin);
    }
}

const app = new App();
app.use(loggerPlugin);
app.use(storagePlugin);

Practical Exercises

  1. Creating a utility library
// utils/string.js
export const capitalize = (str) => 
    str.charAt(0).toUpperCase() + str.slice(1);

export const reverse = (str) => 
    str.split('').reverse().join('');

// utils/array.js
export const unique = (arr) => 
    [...new Set(arr)];

export const groupBy = (arr, key) => 
    arr.reduce((acc, item) => {
        (acc[item[key]] = acc[item[key]] || []).push(item);
        return acc;
    }, {});

// utils/index.js
export * from './string.js';
export * from './array.js';

// app.js
import { capitalize, unique } from './utils';

console.log(capitalize('hello')); // "Hello"
console.log(unique([1, 2, 2, 3])); // [1, 2, 3]
  1. Modular plugin system
// plugin-system.js
export class PluginSystem {
    constructor() {
        this.plugins = new Map();
    }
    
    register(name, plugin) {
        if (this.plugins.has(name)) {
            throw new Error(`Plugin ${name} déjà enregistré`);
        }
        this.plugins.set(name, plugin);
        
        if (plugin.install) {
            plugin.install(this);
        }
    }
    
    use(name) {
        const plugin = this.plugins.get(name);
        if (!plugin) {
            throw new Error(`Plugin ${name} non trouvé`);
        }
        return plugin;
    }
}

// plugins/validation.js
export const validationPlugin = {
    name: 'validation',
    install(system) {
        system.validate = {
            email: (email) => /^.+@.+\..+$/.test(email),
            required: (value) => value !== undefined && value !== ''
        };
    }
};

// app.js
import { PluginSystem } from './plugin-system.js';
import { validationPlugin } from './plugins/validation.js';

const system = new PluginSystem();
system.register(validationPlugin.name, validationPlugin);

console.log(system.validate.email('test@example.com')); // true

Conclusion

ES modules are a fundamental tool for:

  • Organizing code in a modular, maintainable way
  • Efficiently managing dependencies between different parts of the application
  • Building extensible systems through plugins
  • Improving code reusability

In the next tutorial, we’ll explore modern data structures such as Map and Set.


Need to review Promises and async/await? Check out our previous tutorial on asynchronous programming.

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!