Comment remplacer Webpack par Vite ?

Comment remplacer Webpack par Vite ?

Enfin un successeur à Webpack

Si vous aussi, vous avez été convaincu par Vite ou alors vous êtes simplement curieux; vous trouverez dans cet article tout ce qu'il faut pour changer rapidement de bundler.

L'existant:

Si votre application utilise React, il y a de fortes chances que vous vous soyez retrouvé à utiliser Webpack par défaut.

Lorsque vous créez une application avec CRA (create-react-app), celui-ci utilise Webpack pour transformer les fichiers de votre projet en un seul. Il s'agit d'un outil très pratique, mais qui peut s'avérer de plus en plus lent à longueur que votre projet s'étoffe.

Afin de présenter au mieux cet outil, je vous propose le code source généré par CRA avec le template Typescript parce qu'on adore Typescript chez NIJI (●'◡'●)

Vite: Quésaco ?

Vite a été créé afin de remplacer l'utilisation des autres bundlers tels que : Webpack, Rollup ou Parcel. Ces derniers ont grandement amélioré l'expérience des développeurs front end, mais un nouveau concurrent propose encore mieux.

Vite a pour but de proposer des solutions à deux problèmes majeurs: le démarrage lent du serveur et les lenteurs lors des mises à jour du code.

Améliorer la vitesse du démarrage du serveur

Afin d'obtenir un serveur de développement, un bundler classique analyse entièrement tout le code de votre application puis le regroupe en un fichier pour qu'il soit accessible.

Vite fonctionne autrement pour réduire le temps du démarrage du serveur, en séparant les dépendances du code source de votre application.

D'un côté, les dépendances de votre projet sont pour la majorité du JavaScript simple, mis à plat, qui ne change pas pendant le développement. Certaines peuvent être assez importantes, coûteuses à traiter et leurs formats peuvent varier, par exemple: ESM ou CommonJS.

De l'autre, le code source de votre application n'est pas souvent à plat, en contenant du JSX, du CSS ou même des composants Vue/Svelte qui seront souvent modifiés. De plus, votre code n'est pas obligé d'être chargé en un seul morceau si par exemple son utilisation est séparée par différentes routes.

Le choix d'avoir des modules JavaScript natifs permet de faire au navigateur la plupart des tâches qu'un bundler classique devrait faire. Vite ne transforme et n'envoie que lorsque le code est demandé par le navigateur.

Le préregroupement des dépendances

Vite préregroupe les dépendances en utilisant Esbuild, un bundler écrit en Go, qui permet de faire cette action 10 à 100 fois plus vite que les bundlers écrit en JavaScript.

Ce préregroupement effectue d'abord la conversion des dépendances, livrées en CommonJS ou UMD, en modules JavaScript natifs. Par la suite, Vite convertit les modules internes des modules JavaScript en un seul module, afin d'améliorer les performances de chargement.

Un des meilleurs exemples est la librairie aux fonctions utilitaires "lodash-es" qui contient plus de 600 modules internes.

Lors de l'import d'un de ses modules, le navigateur envoie plus de 600 requêtes HTTP en même temps, qui sont prises en charge sans difficulté par le serveur, mais qui engendrent une congestion du réseau du côté du navigateur et donc ralentit sensiblement le chargement de la page. Alors qu'avec le préregroupement, il suffirait d'une seule requête HTTP.

De plus, un système de cache est présent dans le navigateur ainsi que dans le système des fichiers. Ce cache est mis à jour seulement si la liste des dépendances est mise à jour dans le package.json, si le fichier de verrouillage du gestionnaire de paquets a changé (package-lock.json, yarn.lock...) ou si une propriété particulière du fichier de configuration de Vite change.

Le préregroupement ne s'applique seulement qu'au mode développement. Pour le mode production, le plugin CommonJS de Rollup est utilisé.

Avoir des mises à jour plus rapides

Dans une configuration avec un bundler, lorsque un fichier est modifié, il faudrait reconstruire entièrement le fichier final afin d'obtenir la modification. Ce qui pose des problèmes de performances au fur et à mesure que le fichier grossit.

C'est pour cela que certains bundlers utilisent un serveur de développement pour regrouper les fichiers en un seul en mémoire afin d'invalider seulement la partie du module qui a été modifiée. Il sera quand même nécessaire de construire un nouveau fichier final et de recharger la page pour que le navigateur soit au courant de la modification. Ce qui peut être coûteux, et entraine la perte de l'état actuel de l'application.

Vient alors, le support du HMR (Hot Module Replacement) par certains bundlers, comme Webpack, qui permet à un module de "se remplacer à chaud" lorsqu'il a été modifié, sans affecter le reste de la page. Cela améliore considérablement l'expérience développeur.

Cependant, dans la pratique, un constat s'est dressé que même la vitesse de mise à jour HMR se détériore de manière significative à mesure que la taille de l'application augmente.

Dans Vite, le HMR est utilisé en se basant sur les modules JavaScript natifs (ESM). Lorsqu'un changement est effectué, seule la chaine entre le module modifié et le module le plus proche est invalidée. Cela permet de garder la même rapidité de mise à jour peu importe la taille de l'application.

Maintenant : le changement

Mise à jour du fichier package.json

Code source: https://github.com/abouteiller-niji/cra-to-vite/tree/step-1

  • Remplacer les scripts "start, build, test, eject" par ceux ci-dessous
"scripts": {
   "start": "vite",
   "build": "tsc && vite build",
   "serve": "vite preview"
},
  • Supprimer la librairie qui était utilisée par les anciens scripts: react-scripts

    npm remove react-scripts
    
  • Installer les nouvelles librairies en tant que dépendances de développement

    npm i --save-dev vite @vitejs/plugin-react vite-plugin-svgr
    

    La librairie "vite-plugin-svgr" est facultative mais permet d'avoir le même comportement qu'avec CRA, qui permet lors de l'import d'un fichier ".svg" d'utiliser directement celui-ci comme un composant React.

Ajout d'une configuration Vite

Code source: https://github.com/abouteiller-niji/cra-to-vite/tree/step-2

Copiez-collez ce contenu dans un fichier vite.config.js

import { defineConfig } from 'vite' 
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr'

// https://vitejs.dev/config/ 
export default defineConfig({ 
    build: { 
        outDir: 'build', 
    }, 
    plugins: [ 
        react(),
        svgr(),
    ], 
})

La propriété outDir définit le nom du dossier qui contiendra les fichiers rassemblés. Avec CRA, le nom utilisé est "build", il faut donc le configurer ici sinon le nom du dossier sera "dist" par défaut.

Mise à jour du fichier tsconfig.json

Code source: https://github.com/abouteiller-niji/cra-to-vite/tree/step-3

Si vous n'utilisez pas Typescript dans votre projet, vous pouvez sauter cette étape.

Certaines propriétés sont nécessaires dans le fichier tsconfig.json pour fonctionner avec Vite.

  • Il faut que "target", "lib" et "module" contiennent la valeur "esnext".
  • Ajoutez la propriété "isolatedModules" à true, car certaines fonctionnalités modernes de Typescript ne sont pas prises en compte par Vite. Si cet ajout rend la migration compliquée, Vite conseille d'utiliser ce plugin pour continuer une transition facile.

Voici le contenu en entier d'un exemple de configuration:

{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "lib": ["dom", "dom.iterable", "esnext"],
    "types": ["vite/client"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}

Pour être sûr que la compilation prenne en compte les types de Vite, la documentation officielle recommande d'avoir un fichier de déclaration ".d.ts" qui contient cette ligne de code:

/// <reference types="vite/client" />

Cette information permet au compilateur TypeScript d'aller chercher les bons types d'une librairie spécifique.

Déplacer votre fichier index.html

Code source: https://github.com/abouteiller-niji/cra-to-vite/tree/step-4

Votre fichier index.html n'a plus besoin d'être dans le dossier public avec vite. Vous pouvez le déplacer à la racine de votre application. Les autres fichiers tels que les images, les icônes qui étaient dans le dossier "public" peuvent être intégrés au dossier "src".

La structure de votre application devrait ressembler à cette image: image.png

Si votre architecture est plus profonde avec plusieurs dossiers avant d'accéder au fichier index.html alors vous devrez modifier la configuration de Vite. La propriété "root" est celle qui définit notamment le dossier où se trouve le fichier index.html

Mise à jour du contenu du fichier index.html

Code source: https://github.com/abouteiller-niji/cra-to-vite/tree/step-5

Supprimez toutes les chaînes de caractères "%PUBLIC_URL%" dans le fichier index.html. Cette partie d'information n'est plus nécessaire avec Vite.

Ajoutez cette ligne afin que votre code soit chargé par Vite:

<script type="module" src="/src/index.tsx"></script>

Mise à jour des variables d'environnements

Code source: https://github.com/abouteiller-niji/cra-to-vite/tree/step-6

Toutes les variables d'environnements qui commencent par "REACT_APP_" doivent être changées par "VITE_" pour être exposées de la même manière dans le fichier final. Les autres variables d'environnements qui ne sont pas préfixées comme "DB_PASSWORD" ne seront pas exposées.

Vous devez remplacer tous les "process.env.VAR" par "import.meta.env.VAR" où VAR représente le nom de votre variable d'environnement.

Comparaison: Performances avant, après

Script Build

Avec CRA: 10,68s Avec Vite: 3,67s
image.png image.png

Ce qui représente une diminution de 65% du temps nécessaire à la construction du fichier final utilisé par le navigateur.

Script Start

Un script intéressant à comparer est celui du "start". Nous allons calculer le temps minimum nécessaire avant que l'application s'affiche.

Avec CRA: 10,08s Avec Vite: 0,94s
image.png image.png

La diminution ici est encore plus impressionnante avec ce script: 10 fois moins de temps passés en moins !

Conclusion

Pour un projet de petite taille, la transition est assez simple et rapide.

Lorsqu'il s'agit de configurations entières faites avec Webpack, le challenge est souvent plus important, mais le résultat en vaut la chandelle.