Apprendre JavaScript de zéro jusqu'au niveau expert : syntaxe moderne, DOM, asynchrone et event loop, modules, prototypes, métaprogrammation, performance et mémoire.
Un projet JavaScript moderne ne se limite pas au langage. Il y a un système de modules, un gestionnaire de paquets, un bundler, un linter, un framework de test. Ce chapitre installe cet environnement et explique le rôle de chaque pièce, pour ne plus copier des configurations sans les comprendre.
Chaque fichier est un module avec sa propre portée. On exporte explicitement ce qui doit être visible.
export const PI = 3.14159
export function aire(r) {
return PI * r ** 2
}
export default function perimetre(r) {
return 2 * PI * r
}import perimetre, { aire, PI as pi } from './math.js'
import * as math from './math.js'
console.log(aire(2), perimetre(2), math.PI)Je préfère les exports nommés aux exports par défaut : l'autocomplétion fonctionne mieux et un renommage se propage partout.
bouton.addEventListener('click', async () => {
const { genererPDF } = await import('./pdf.js') // chargé seulement au clic
genererPDF(document.body)
})C'est la base du code splitting : les bundlers créent un fichier séparé pour chaque import() dynamique.
this vaut undefined au niveau supérieurL'ancien système de Node, encore très présent :
const fs = require('node:fs')
module.exports = { lire }Dans un nouveau projet, ajoutez "type": "module" dans package.json et utilisez partout import / export.
npm init -y
npm install zod # dépendance de production
npm install -D vitest eslint # dépendance de développement
npm uninstall zod
npm outdated
npm audit{
"name": "mon-projet",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"lint": "eslint ."
},
"dependencies": {
"zod": "^4.0.0"
},
"devDependencies": {
"vitest": "^4.0.0"
}
}MAJEUR.MINEUR.CORRECTIF. Le préfixe ^4.0.0 accepte toute version 4.x.x supérieure ou égale : pas de changement majeur. ~4.0.0 n'accepte que les correctifs 4.0.x.
package-lock.json fige les versions exactes de tout l'arbre de dépendances. Il se committe. En CI, utilisez npm ci qui installe exactement le lockfile et échoue s'il est désynchronisé.
npx create-vite@latest mon-app
npx serve distExécute un paquet sans l'installer globalement.
npm create vite@latest mon-app -- --template vanilla
cd mon-app
npm install
npm run devEn développement, Vite sert les modules ES natifs directement au navigateur, sans bundler : le démarrage est instantané. En production, vite build produit des fichiers optimisés (minification, tree shaking, hash dans les noms pour le cache).
Le tree shaking élimine le code non importé. Il ne fonctionne qu'avec les modules ES, car leurs imports sont statiques et analysables.
npm init @eslint/config@latestimport js from '@eslint/js'
import globals from 'globals'
export default [
js.configs.recommended,
{
languageOptions: { globals: { ...globals.browser, ...globals.node } },
rules: {
eqeqeq: 'error',
'no-var': 'error',
'prefer-const': 'error',
},
},
]ESLint trouve les bugs probables, Prettier gère la forme. Ne demandez pas à ESLint de faire du formatage.
TypeScript ajoute un système de types statiques à JavaScript, retiré à la compilation. Sur tout projet qui dépasse quelques centaines de lignes, je le considère comme indispensable. L'article TypeScript pour les développeurs JavaScript couvre la transition.
Sans passer à TypeScript, JSDoc donne déjà l'autocomplétion et la vérification dans VS Code :
// @ts-check
/**
* @param {number} prix
* @param {number} pourcentage
* @returns {number}
*/
export function appliquerRemise(prix, pourcentage) {
return Math.round(prix * (1 - pourcentage / 100) * 100) / 100
}npm install -D vitestexport function appliquerRemise(prix, pourcentage) {
if (pourcentage < 0 || pourcentage > 100) throw new RangeError('Pourcentage invalide')
return Math.round(prix * (1 - pourcentage / 100) * 100) / 100
}import { describe, it, expect } from 'vitest'
import { appliquerRemise } from './remise.js'
describe('appliquerRemise', () => {
it('applique 20 %', () => {
expect(appliquerRemise(100, 20)).toBe(80)
})
it('rejette un pourcentage invalide', () => {
expect(() => appliquerRemise(100, 150)).toThrow(RangeError)
})
it.each([
[100, 0, 100],
[100, 100, 0],
[19.99, 10, 17.99],
])('%s avec %s %% donne %s', (prix, pct, attendu) => {
expect(appliquerRemise(prix, pct)).toBe(attendu)
})
})npx vitest # mode watch
npx vitest run --coverageimport { vi, it, expect } from 'vitest'
it('appelle l\'API', async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ id: 1 }) })
vi.stubGlobal('fetch', fetchMock)
const data = await getJSON('/api/user')
expect(fetchMock).toHaveBeenCalledWith('/api/user', expect.any(Object))
expect(data).toEqual({ id: 1 })
})
it('debounce attend 300 ms', () => {
vi.useFakeTimers()
const fn = vi.fn()
const d = debounce(fn, 300)
d(); d(); d()
vi.advanceTimersByTime(299)
expect(fn).not.toHaveBeenCalled()
vi.advanceTimersByTime(1)
expect(fn).toHaveBeenCalledOnce()
})Pour une petite bibliothèque, pas besoin de dépendance :
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { appliquerRemise } from './remise.js'
test('applique 20 %', () => {
assert.equal(appliquerRemise(100, 20), 80)
})node --testLes tests unitaires vérifient les fonctions isolées. Pour tester l'application entière dans un vrai navigateur, voir les tests end-to-end avec Playwright.
Créer un projet Vite vanilla, y déplacer la todo list du chapitre 6 en séparant l'état (store.js), le rendu (render.js) et le point d'entrée (main.js). Écrire des tests Vitest pour les fonctions de store.js (ajouter, basculer, supprimer), qui doivent être pures.
export const ajouter = (todos, texte) => [...todos, { id: crypto.randomUUID(), texte, fait: false }]
export const basculer = (todos, id) => todos.map(t => (t.id === id ? { ...t, fait: !t.fait } : t))
export const supprimer = (todos, id) => todos.filter(t => t.id !== id)Modules ES, lockfile committé, Vite, ESLint et Vitest : c'est la base commune de presque tous les projets JavaScript actuels. Avec cet outillage en place, on peut attaquer les mécanismes plus profonds du langage.