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
- 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]
- 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