Du bundling à Vite

Pourquoi regrouper le JavaScript ?

Quand une application grandit, on répartit son code dans plusieurs modules : un fichier pour les dates, un autre pour le DOM, un autre pour l'affichage. Cette organisation facilite la lecture et permet de réutiliser les fonctions. Une bibliothèque externe peut, elle aussi, dépendre de nombreux fichiers.

Les navigateurs modernes savent charger ces modules grâce à import. Mais ils doivent découvrir leurs dépendances et récupérer les fichiers correspondants. Une application organisée en centaines de petits modules peut ainsi nécessiter beaucoup d'échanges avant de pouvoir s'exécuter.

Un bundler analyse les imports et rassemble le code dans un fichier JavaScript, appelé bundle. On conserve des sources séparées pour travailler, tout en livrant au navigateur une version regroupée.

Sources                       Fichier livré au navigateur
main.js
├── utils/dom.js       ──►    assets/index-Bx7k2.js
├── utils/date.js
└── utils/schedule.js

Ce regroupement réduit le nombre de fichiers à demander et permet d'optimiser l'ensemble du code. Il ne s'agit pas de copier les fichiers bout à bout : le bundler doit préserver le fonctionnement des imports, des exports et des variables de chaque module.

Un seul gros fichier n'est toutefois pas toujours idéal. Il ferait télécharger des fonctionnalités inutiles à la page consultée et devrait être récupéré à nouveau dès qu'une partie change. Les outils peuvent donc produire plusieurs chunks, c'est-à-dire des morceaux de l'application. Le but est de livrer le code utile efficacement ; les protocoles HTTP modernes permettent aussi de charger plusieurs fichiers en parallèle.

Pourquoi ajouter une phase de build ?

Le bundling peut faire partie d'une étape plus large : le build. Les sources deviennent l'entrée d'un processus qui fabrique la version à mettre en production.

Cette étape peut retirer le code inutilisé (tree shaking), réduire la taille du JavaScript et de la CSS (minification), préparer les assets et réécrire leurs références. On peut aussi transformer certaines syntaxes pour les navigateurs ciblés. Le résultat garde le comportement de l'application, mais son organisation peut être très différente de celle des sources.

On distingue alors deux versions : les sources, conçues pour être comprises et modifiées, et le résultat du build, conçu pour être distribué et exécuté. La mise en production consiste à déployer ce résultat. Le serveur peut servir des fichiers déjà préparés : il n'a pas à refaire ces transformations pour chaque visiteur.

Cette séparation a un coût. Il faut installer et maintenir les outils, attendre le build et parfois comprendre des erreurs de transformation. Le code généré est moins lisible, même si des source maps peuvent aider à retrouver les sources pendant le débogage. Une modification locale n'arrive plus directement en production : elle doit passer par un nouveau build et un déploiement.

Pour une petite page, servir directement les sources peut suffire. L'intérêt du build augmente avec les dépendances, les transformations nécessaires et la taille du projet.

La particularité de Vite : deux façons de servir le projet

Vite cherche à rendre les modifications rapides à observer pendant le développement. Au lieu de reconstruire toute l'application avant chaque affichage, son serveur s'appuie sur les modules natifs du navigateur et transforme les fichiers à la demande. Les dépendances externes peuvent être préparées à l'avance.

Quand un fichier change, Vite peut remplacer le module concerné dans la page : c'est le Hot Module Replacement, ou HMR. Selon la modification et la manière dont le projet gère les mises à jour, une actualisation complète peut être nécessaire. On évite ainsi d'attendre un build de production à chaque retouche.

Pour la production, Vite effectue le build et écrit les fichiers générés dans dist/. Le développement privilégie donc la rapidité de modification ; le build prépare la version destinée aux visiteurs. Vérifier ce résultat avant de le déployer permet de tester les fichiers qui seront réellement servis.

Les fichiers du projet

Fichier ou dossierRôle
index.htmlPoint d'entrée de la page et, par défaut, du build ; référence les scripts et la CSS.
main.jsCode de l'application, réparti si nécessaire dans plusieurs modules.
package.jsonDéclare les dépendances et les commandes du projet.
package-lock.jsonConsigne les versions exactes des dépendances pour retrouver un environnement cohérent.
node_modules/Contient les dépendances installées ; se recrée avec npm et ne se versionne pas dans Git.
vite.config.jsPermet d'adapter les réglages de Vite ; inutile tant que les valeurs par défaut conviennent.
dist/Contient le résultat du build, à déployer en production.

Les assets font aussi partie du build

Le JavaScript dépend souvent d'autres ressources : CSS, images, polices ou données. Pour produire une version cohérente, l'outil doit savoir quels fichiers sont utilisés et comment ils sont référencés.

Vite suit notamment les références du HTML, les url() de la CSS et les imports JavaScript. Il peut émettre un asset comme fichier séparé ou intégrer un petit fichier directement au contenu. Les chemins sont adaptés au résultat généré.

Le dossier public/ répond au besoin inverse : ses fichiers sont copiés tels quels, sans transformation ni renommage. Par exemple, public/robots.txt devient dist/robots.txt.

Garder le cache sans garder une ancienne version

Le cache du navigateur évite de télécharger à nouveau une ressource déjà reçue. Mais comment conserver longtemps un fichier tout en garantissant que les visiteurs recevront sa prochaine version ?

Vite ajoute une empreinte liée au contenu dans les noms des fichiers générés, comme index-Bx7k2.js. Si le contenu change, l'empreinte change aussi. Le nouveau HTML référence alors une nouvelle URL, que le navigateur télécharge. Les fichiers inchangés peuvent continuer à être réutilisés.

Quelques commandes de base

Dans un projet créé avec Vite, les commandes npm run … exécutent les scripts définis dans package.json.

CommandeIntention
npm create vite@latestObtenir une base de projet avec les fichiers et scripts nécessaires.
npm installInstaller les dépendances pour travailler sur le projet.
npm run devObserver les sources en cours de modification avec le serveur de développement.
npm run buildFabriquer la version à déployer dans dist/.
npm run previewExaminer localement cette version construite avant sa mise en production.

npm et les alternatives

npm est le gestionnaire de paquets fourni avec Node.js. Il installe les dépendances déclarées dans package.json et permet d'exécuter les scripts du projet. Vite est l'un des outils installés et lancés par son intermédiaire.

D'autres gestionnaires, comme pnpm et Yarn, remplissent le même rôle. Bun propose également un gestionnaire de paquets, en plus de son environnement d'exécution JavaScript. Les commandes et les fichiers de verrouillage varient selon l'outil choisi, mais le rôle de Vite reste le même.

Références