INCAYA

Migrer une application React de CRA à Vite

Rapide retour d'expérience sur la migration d'une application React gérée avec l'outillage CRA vers l'outillage Vite

Par une maintenant vieille habitude - au moins 4 ou 5 ans - j’utilise le projet Create React App (CRA pour la suite) lorsque je démarre une application de type SPA (single page application) en React. C’est ce que j’avais fait pour une application client datant d’un peu plus d’un an et demi : lancement de npx create-react-app client-app, immédiatement suivi d’un npm run eject. Force est de constater que c’est devenu très pénible à maintenir tant il y a de dépendances tombant dans le deprecated, sans espoir d’amélioration puisque le projet CRA n’est plus maintenu.

Une rapide revue de web m’a conduit à m’orienter vers un outil semblant être le plus stable pour continuer à faire vivre mon projet dans de bonnes conditions : Vite.

Voici un rapide retour d’expérience sur cette migration.

Une première approche naïve

J’ai donc tout bêtement créé une nouvelle application avec npm create vite@latest et en choisissant parmi les options une application React en JavaScript. Ensuite, j’ai rajouté les dépendances propres à mon application legacy, lancé un npm install, puis un npm run dev, et enfin remplacé le répertoire src auto-généré par celui de ma SPA.

Et là, j’ai découvert l’affichage des erreurs de Vite 😁

L’affichage des erreurs de Vite

(Je n’ai pas eu le réflexe de faire une capture d’écran, il s’agit donc d’une image trouvée sur le web… Mais le problème était le même.)

Les ajustements

Le traitement des fichiers .js

Tout d’abord, Vite ne va pas traiter les fichiers .js comme des .jsx. C’était le cas avec CRA, et du coup tous les fichiers sont en .js (ce qui est discutable, c’est vrai). Mais comme cela faisait beaucoup de fichiers à traiter, sans pouvoir l’automatiser (tous les fichiers ne sont pas des jsx), il a fallu retrouver le même comportement qu’avec CRA. Cela se passe dans le fichier vite.config.js :

import { defineConfig, transformWithEsbuild } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
  // treat .js files as .jsx files
  plugins: [
    {
      name: 'treat-js-files-as-jsx',
      async transform(code, id) {
        if (!id.match(/src\/.*\.js$/))  return null

        // Use the exposed transform from vite, instead of directly
        // transforming with esbuild
        return transformWithEsbuild(code, id, {
          loader: 'jsx',
          jsx: 'automatic',
        })
      },
    },
    react(),
  ],
  optimizeDeps: {
    force: true,
    esbuildOptions: {
      loader: {
        '.js': 'jsx',
      },
    },
  },
})

L’import des fichiers .css

Ensuite, le code legacy importe des fichiers .css pour les utiliser comme des CSS Modules , du genre :

import styles from './style.css';

element.innerHTML = '<div class="' + styles.className + '">';

C’est tout à fait faisable avec Vite, mais nativement, pour que cela fonctionne, il faut que les fichiers importés respectent la syntaxe nomdefichier.module.css. Cela doit être possible de configurer Vite pour qu’il considère tous les imports de css comme des modules, mais pour le coup, j’ai préféré renommer mes fichiers qui n’étaient pas nombreux (une dizaine).

La configuration du serveur de développement

Mon application tourne en développement dans un conteneur Docker derrière un autre conteneur Nginx jouant le rôle de proxy. Pour que cela fonctionne, il faut une configuration adéquate du Nginx

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 3000;
    ## if you want to use HTTPS:
    # listen 443 ssl;
    # ssl_certificate      server.crt;
    # ssl_certificate_key  server.key;
    # ssl_session_cache    shared:SSL:1m;
    # ssl_session_timeout  5m;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;

        proxy_pass  http://vite:5173;

        # proxy ws
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 86400;
    }
}

et du serveur de développement de Vite.

import { defineConfig } from 'vite'
import dns from 'dns'

dns.setDefaultResultOrder('verbatim')

// https://vitejs.dev/config/
export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 5173,
    strictPort: true
  }
})

Ce cas est documenté.

L’absence de global

Afin de pouvoir laisser quelques console.log dans le code, CRA permettait d’écrire des global.console.log. Ce n’est plus le cas avec Vite qui ne définit pas de propriété global dans le window, comme le fait webpack. Par contre, la configuration du linter de Vite permet l’écriture du console. Le problème est donc vite réglé.

Les variables d’environnement

On peut avec CRA définir des variables d’environnement qui seront prises en compte au moment du build, en les préfixant par REACT_APP_ et en les appelant de la manière suivante :

process.env.REACT_APP_MY_ENV_VAR

C’est aussi possible avec Vite si ce n’est qu’il faut les préfixer par VITE_ et les appeler de la manière suivante :

import.meta.env.VITE_MY_ENV_VAR

La destination de l’application final

Dernier détail : le projet final généré par CRA est construit dans un répertoire build, celui dans Vite dans un répertoire dist. Comme cette référence au répertoire build se trouvait dans pas mal de mes scripts de déploiement et/ou de fichiers Docker, j’ai préféré configurer Vite plutôt que d’aller modifier ces scripts :

import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
  build: {
    outDir: 'build'
  },
})

Conclusion

La migration de CRA à Vite est finalement étonnamment simple, même pour un projet qui n’utilise pas TypeScript (qui aurait sans doute imposé un formalisme facilitant cette migration).

Je ne peux que la conseiller, tant l’économie en termes de dépendances par rapport à un projet CRA éjecté est importante (mon fichier package.json est passé de 172 à 88 lignes). Et même si ce n’était pas ma motivation, Vite est effectivement beaucoup, beaucoup plus rapide, aussi bien lors de développement que pour le build final, que la stack du CRA.

Je pense que la prochaine étape pour rendre mon projet encore plus rapide, léger et maintenable sera sans doute de me passer de la dépendance à React 🤩