Migrer une application React de CRA à 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 😁
(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 🤩