Développement Web
Vite
Build Tool Ultra-Rapide
Dev server instantané basé sur les ES modules natifs, HMR ultra-rapide, build production avec Rollup, plugins, Vitest — tout ce qu'il faut pour un workflow moderne.
01 — Découverte
Pourquoi Vite ?
⚡
Vite (« vite » en français) est un outil de build créé par Evan You (créateur de Vue). Sa différence fondamentale : en développement, il ne bundle pas le code — il sert les fichiers directement en ES modules natifs, ce qui rend le démarrage instantané, quelle que soit la taille du projet.
Pourquoi c'est si rapide ?
Dev server traditionnel (Webpack/CRA) :
┌─ Lire tous les fichiers
├─ Parser les imports
├─ Transformer (Babel, TS, CSS…)
├─ Bundler en un gros fichier
└─ Servir → 20-60 secondes !
Vite en développement :
┌─ Démarrer le serveur (instantané)
├─ Pré-bundler seulement les node_modules
│ avec esbuild (10-100× plus vite que Babel)
└─ Servir les fichiers source DIRECTEMENT
Le navigateur résout les imports lui-même
→ < 300ms !
HMR (Hot Module Replacement) :
Webpack : rebundle → envoie tout au navigateur
Vite : envoie SEULEMENT le module modifié
→ 50ms quelle que soit la taille du projet
Build de production :
Vite utilise Rollup (stable, mature, tree-shaking)
→ bundles optimisés, code-splitting automatique
02
Démarrage rapide
Créer un projet Vite
# Interactive — choisir le template
npm create vite@latest
# Avec template direct
npm create vite@latest mon-app -- --template react
npm create vite@latest mon-app -- --template react-ts
npm create vite@latest mon-app -- --template vue
npm create vite@latest mon-app -- --template vue-ts
npm create vite@latest mon-app -- --template svelte
npm create vite@latest mon-app -- --template vanilla-ts
npm create vite@latest mon-app -- --template lit
cd mon-app
npm install
npm run dev # → http://localhost:5173
# Scripts disponibles (package.json)
{
"scripts": {
"dev": "vite", ← dev server
"build": "vite build", ← production build
"preview": "vite preview", ← tester le build local
"lint": "eslint ."
}
}
# Avec yarn / pnpm
yarn create vite mon-app --template react-ts
pnpm create vite mon-app --template react-ts
Templates disponibles
Templates officiels :
vanilla vanilla-ts
vue vue-ts
react react-ts
react-swc react-swc-ts ← plus rapide
preact preact-ts
lit lit-ts
svelte svelte-ts
solid solid-ts
qwik qwik-ts
react-swc vs react :
→ swc = Speedy Web Compiler (Rust)
→ Remplace Babel pour les transforms
→ 10-20× plus rapide que Babel
→ Recommandé pour les nouveaux projets
Starters communautaires (via degit) :
npm create vite-extra@latest ← templates extras
Ex : vite + react + tailwind + vitest
degit user/repo mon-app
03
Structure & vite.config.ts
Structure d'un projet Vite+React
mon-app/
├── public/ ← fichiers copiés tels quels dans dist/
│ └── favicon.ico référencés avec / (ex: /favicon.ico)
│
├── src/
│ ├── assets/ ← fichiers importés → traités par Vite
│ │ └── logo.svg (hachage, optimisation)
│ ├── components/
│ ├── App.tsx
│ └── main.tsx ← point d'entrée
│
├── index.html ← RACINE du projet (pas dans public/)
├── vite.config.ts
├── tsconfig.json
└── package.json
index.html — point d'entrée Vite
<!DOCTYPE html>
<html>
<body>
<div id="root"></div>
<!-- Vite injecte le script automatiquement -->
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
vite.config.ts — configuration complète
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
// Alias de chemins
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
},
},
// Serveur de développement
server: {
port: 3000,
open: true, // ouvrir le navigateur
host: true, // accès réseau local
proxy: { // proxy API
'/api': 'http://localhost:8080'
}
},
// Build de production
build: {
outDir: 'dist',
sourcemap: true,
minify: 'terser',
},
// Tests Vitest
test: {
globals: true,
environment: 'jsdom',
},
});
04 — Développement
Dev server & HMR
Options du serveur de développement
server: {
port: 3000, // port (défaut: 5173)
open: true, // ouvrir le navigateur
host: '0.0.0.0', // accès réseau (mobile, VM)
https: true, // HTTPS avec certificat auto
strictPort: true, // erreur si port déjà utilisé
cors: true, // CORS pour les requêtes fetch
// Hmr — Hot Module Replacement
hmr: {
overlay: true, // afficher les erreurs dans le navigateur
port: 24678, // port WebSocket HMR
host: 'localhost',
},
// Surveillance des fichiers
watch: {
ignored: ['**/node_modules/**', '**/dist/**'],
}
}
# Raccourcis dans le terminal Vite
r → forcer le reload complet
u → afficher/masquer l'URL serveur
o → ouvrir dans le navigateur
c → effacer la console
q → quitter
Modules ES natifs — comprendre le fonctionnement
// En dev, chaque import = une requête HTTP au serveur Vite
// Le navigateur résout lui-même le graphe de dépendances
// main.tsx
import React from 'react' // → GET /node_modules/.vite/react.js
import App from './App' // → GET /src/App.tsx (transformé)
import styles from './App.css' // → GET /src/App.css (injecté JS)
// node_modules = pré-bundlés avec esbuild (1 seule fois)
// Stockés dans node_modules/.vite/deps/
// Recalculés seulement si package.json change
// HMR — comment ça marche
// 1. Vous sauvegardez App.tsx
// 2. Vite détecte le changement
// 3. Re-transforme SEULEMENT App.tsx
// 4. Envoie le module via WebSocket
// 5. Le navigateur remplace le module en mémoire
// 6. React Fast Refresh préserve le state
// API HMR manuelle (rarement nécessaire)
if (import.meta.hot) {
import.meta.hot.accept('./dep', (newMod) => {
// re-initialiser avec le nouveau module
});
import.meta.hot.dispose(() => {
// nettoyage avant remplacement
});
}
05
Gestion des assets
Importer des assets
// Images — retourne l'URL transformée
import logoUrl from './assets/logo.png';
import logoSvg from './assets/logo.svg';
return <img src={logoUrl} alt="Logo" />;
// SVG comme composant React (avec plugin)
import { ReactComponent as Logo } from './logo.svg?react';
return <Logo width={100} />;
// URL explicite
import url from './fichier.pdf?url'; // toujours une URL
import raw from './data.txt?raw'; // contenu en string
// Inline (pour les petits fichiers)
import iconUrl from './icon.svg?inline'; // data URI
// Worker Web
import Worker from './worker.ts?worker';
const worker = new Worker();
// WASM
import init from './module.wasm?init';
await init();
// JSON — tree-shaking natif
import config from './config.json';
import { version } from './package.json'; // partiel
public/ vs assets/
public/
→ Fichiers COPIÉS tels quels dans dist/
→ Accessibles via URL absolue : /logo.png
→ Pas de hachage, pas de transformation
→ Usage : favicon, robots.txt, manifest.json,
fichiers référencés dans du code non-bundlé
src/assets/
→ Fichiers IMPORTÉS dans le code
→ Traités par Vite (hachage, optimisation)
→ URL avec hash : /assets/logo-D1a2b3c4.png
→ Usage : images de l'app, fonts, SVG
Limite d'inline automatique :
build: {
assetsInlineLimit: 4096 // 4KB → data URI
}
// Fichiers < 4KB → intégrés en base64 (moins de requêtes)
// Fichiers > 4KB → copiés dans assets/ avec hash
Fonts — importer depuis src/assets/
/* styles.css */
@font-face {
font-family: 'MyFont';
src: url('./assets/fonts/MyFont.woff2') format('woff2');
}
06
Variables d'environnement
Fichiers .env
# Fichiers chargés selon l'environnement :
.env ← toujours chargé
.env.local ← toujours, ignoré par git
.env.development ← npm run dev seulement
.env.production ← npm run build seulement
.env.test ← vitest seulement
.env.development.local ← dev + ignoré par git
# Variables dans .env
# OBLIGATOIRE : préfixe VITE_ pour être exposé au client
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=Mon Application
VITE_FEATURE_FLAG=true
# Sans préfixe = serveur seulement (non exposé)
DB_PASSWORD=secret123 ← JAMAIS accessible côté client
# .gitignore — NE JAMAIS commiter les .env.local
.env.local
.env.*.local
Utiliser les variables
// Accès via import.meta.env
const apiUrl = import.meta.env.VITE_API_URL;
const title = import.meta.env.VITE_APP_TITLE;
// Variables intégrées par Vite :
import.meta.env.MODE // 'development' | 'production' | 'test'
import.meta.env.DEV // true en dev
import.meta.env.PROD // true en prod
import.meta.env.SSR // true si SSR
import.meta.env.BASE_URL // valeur de base (config base:)
// Conditionnement selon l'env
if (import.meta.env.DEV) {
console.log('Mode développement');
}
// Code éliminé en production (tree-shaken)
if (import.meta.env.PROD) {
// Analytiques, Sentry, etc.
initSentry(import.meta.env.VITE_SENTRY_DSN);
}
// TypeScript — typer import.meta.env
// vite-env.d.ts
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
07
CSS & Préprocesseurs
CSS Modules & PostCSS
// CSS classique — importé = injecté globalement
import './global.css';
// CSS Modules — scoped automatiquement
import styles from './Button.module.css';
return <button className={styles.btn}>...</button>;
// → class="Button_btn_a1b2c3" (unique)
/* Button.module.css */
.btn { background: blue; }
.btn:hover { background: darkblue; }
// PostCSS — automatique si postcss.config.js existe
// npm install -D autoprefixer postcss
// postcss.config.js :
export default {
plugins: {
autoprefixer: {}, // préfixes vendeurs automatiques
'postcss-preset-env': { stage: 3 }
}
}
// CSS variables dans vite.config.ts
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "./src/styles/variables.scss";`
}
}
}
SCSS, Less, Stylus & Tailwind
// SCSS — npm install -D sass
import './styles.scss';
import styles from './Component.module.scss';
// Less — npm install -D less
import './styles.less';
// Stylus — npm install -D stylus
import './styles.styl';
// Tailwind CSS — setup complet
# npm install -D tailwindcss postcss autoprefixer
# npx tailwindcss init -p
// tailwind.config.js
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: { extend: {} },
plugins: [],
}
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
// main.tsx
import './index.css';
// UnoCSS (alternative plus rapide)
# npm install -D unocss
// vite.config.ts → plugins: [UnoCSS()]
08
TypeScript
TypeScript dans Vite
// Vite supporte TS nativement SANS config spéciale
// Il transpile avec esbuild (pas tsc)
// → esbuild enlève les types, ne vérifie PAS
// tsconfig.json recommandé
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
}
}
// Vérification de types (CI/CD)
# package.json
{
"scripts": {
"type-check": "tsc --noEmit",
"build": "tsc --noEmit && vite build"
}
}
Alias de chemins
// Sans alias — chemins relatifs imbriqués
import { Button } from '../../../components/ui/Button';
// Avec alias — propre et portable
import { Button } from '@/components/ui/Button';
// vite.config.ts
import { resolve } from 'path';
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@hooks': resolve(__dirname, 'src/hooks'),
'@utils': resolve(__dirname, 'src/utils'),
'@assets': resolve(__dirname, 'src/assets'),
'@types': resolve(__dirname, 'src/types'),
}
}
// tsconfig.json — même mapping pour l'IDE
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@hooks/*": ["src/hooks/*"],
"@utils/*": ["src/utils/*"]
}
// Plugin vite-tsconfig-paths (synchronise automatiquement)
# npm install -D vite-tsconfig-paths
import tsconfigPaths from 'vite-tsconfig-paths';
plugins: [react(), tsconfigPaths()]
09 — Build & Production
Build de production
src/
→
Rollup
→
Tree-shaking
→
Minification
→
dist/
Options de build
build: {
outDir: 'dist', // dossier de sortie
emptyOutDir: true, // vider avant chaque build
sourcemap: true, // pour le debugging prod
minify: 'terser', // 'esbuild' (rapide) ou 'terser'
target: 'es2015', // navigateurs cibles
// Taille des chunks
chunkSizeWarningLimit: 500, // warning si > 500kb
// Assets
assetsInlineLimit: 4096, // inline si < 4kb
assetsDir: 'assets',
// CSS
cssMinify: true,
cssCodeSplit: true, // 1 fichier CSS par chunk
// Compatibilité navigateurs anciens
// npm install -D @vitejs/plugin-legacy
}
# Analyser la taille du bundle
# npm install -D rollup-plugin-visualizer
plugins: [
visualizer({ open: true, gzipSize: true })
]
Contenu du dossier dist/
dist/
├── index.html ← HTML avec tags <script> injectés
├── assets/
│ ├── index-B1c2d3e4.js ← bundle principal (hash = cache)
│ ├── vendor-A5f6g7h8.js ← dépendances (react, etc.)
│ ├── admin-C9d0e1f2.js ← chunk lazy-loaded
│ ├── index-D3e4f5g6.css ← styles
│ ├── logo-E7f8g9h0.png ← image avec hash
│ └── font-F1g2h3i4.woff2
└── favicon.ico ← depuis public/
# Tester le build en local
npm run build
npm run preview → http://localhost:4173
# Déploiement
# dist/ est statique → déployable partout :
# Vercel, Netlify, GitHub Pages, Nginx, Apache
# Base URL pour sous-dossiers
# vite.config.ts
base: '/mon-app/' ← si déployé sur example.com/mon-app/
10
Code splitting
Dynamic imports
// Import dynamique → chunk séparé automatiquement
const module = await import('./heavy-lib');
// React.lazy + Suspense
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
// Nommer les chunks
const Dashboard = lazy(() =>
import(
/* webpackChunkName: "dashboard" */
'./pages/Dashboard'
)
);
// Précharger un module
<link rel="modulepreload" href="/assets/dashboard-xxx.js">
// Ou via Vite : import(/* @vite-ignore */ './dashboard')
Contrôler les chunks (manualChunks)
build: {
rollupOptions: {
output: {
// Séparer les dépendances en chunks nommés
manualChunks: {
// Vendor — bibliothèques rarement changées
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
ui: ['@mui/material', '@mui/icons-material'],
charts: ['recharts'],
},
// Ou via une fonction
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
}
if (id.includes('src/features/admin')) {
return 'admin';
}
}
}
}
}
// Résultat :
// vendor-xxx.js — react + react-dom (mise en cache longue)
// router-xxx.js — react-router-dom
// admin-xxx.js — features/admin (lazy)
// index-xxx.js — code applicatif
11
Optimisations
Pré-bundling & cache
optimizeDeps: {
// Forcer l'inclusion dans le pré-bundle
include: [
'react', 'react-dom',
'lodash-es', // si chargement lent
],
// Exclure du pré-bundle (servir tel quel)
exclude: ['my-local-package'],
// Options esbuild pour le pré-bundle
esbuildOptions: {
target: 'es2020'
}
}
// Cache des dépendances
// Stocké dans node_modules/.vite/deps/
// Recalculé si package-lock.json change
# Forcer la recréation du cache
npx vite --force
# Ou supprimer manuellement
rm -rf node_modules/.vite
Terser & esbuild minification
build: {
minify: 'esbuild', // très rapide (défaut)
minify: 'terser', // plus agressif, plus lent
minify: false, // désactiver
terserOptions: { // si minify: 'terser'
compress: {
drop_console: true, // supprimer console.log
drop_debugger: true,
pure_funcs: ['console.log', 'console.info'],
},
mangle: {
safari10: true
}
}
}
// Compatibilité navigateurs anciens
# npm install -D @vitejs/plugin-legacy
import legacy from '@vitejs/plugin-legacy';
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
]
// Génère un bundle moderne + un fallback pour anciens navigateurs
12 — Configuration
Plugins
@vitejs/plugin-react-swc
Support React avec SWC — Fast Refresh, JSX, décorateurs
@vitejs/plugin-vue
Support Vue 3 — SFC, template compiler
vite-plugin-svgr
Importer des SVG comme composants React
vite-tsconfig-paths
Lire les paths de tsconfig automatiquement
@vitejs/plugin-legacy
Support des navigateurs anciens (IE11, polyfills)
rollup-plugin-visualizer
Analyser la taille du bundle avec une carte interactive
vite-plugin-pwa
Progressive Web App — service worker, manifest
unplugin-auto-import
Auto-import des APIs (React, Vue hooks…) sans import
Écrire un plugin simple
// Un plugin Vite = un objet avec des hooks Rollup/Vite
function monPlugin() {
return {
name: 'mon-plugin', // obligatoire
// Transformer un fichier
transform(code, id) {
if (id.endsWith('.txt')) {
return {
code: `export default ${JSON.stringify(code)}`,
map: null
};
}
},
// Résoudre un module virtuel
resolveId(id) {
if (id === 'virtual:config') return id;
},
load(id) {
if (id === 'virtual:config') {
return `export const version = "1.0.0"`;
}
},
// Hooks de build
buildStart() { console.log('Build démarré'); },
buildEnd() { console.log('Build terminé'); },
// Serveur de dev
configureServer(server) {
server.middlewares.use('/mon-endpoint', (req, res) => {
res.end('Réponse custom');
});
}
};
}
13
Proxy & CORS
Configurer un proxy API
server: {
proxy: {
// Simple — tout /api → backend
'/api': 'http://localhost:8080',
// Avancé — avec options
'/api': {
target: 'http://localhost:8080',
changeOrigin: true, // modifier l'en-tête Host
rewrite: (path) => path.replace(/^\/api/, ''),
// /api/users → http://localhost:8080/users
},
// WebSocket
'/ws': {
target: 'ws://localhost:8080',
ws: true,
},
// HTTPS auto-signé
'/api': {
target: 'https://api.example.com',
secure: false, // accepter certificats auto-signés
},
// Plusieurs endpoints
'/auth': 'http://auth-service:3001',
'/users': 'http://user-service:3002',
'/products': 'http://product-service:3003',
}
}
Configuration selon l'environnement
// vite.config.ts — config conditionnelle
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ command, mode }) => {
// Charger les variables .env
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [react()],
define: {
// Injecter des variables au build time
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
__BUILD_DATE__: JSON.stringify(new Date().toISOString()),
},
server: {
proxy: {
'/api': env.VITE_API_URL || 'http://localhost:8080',
}
},
build: {
sourcemap: mode !== 'production',
},
// Dev uniquement
...(command === 'serve' ? {
// config dev spécifique
} : {
// config build spécifique
}),
};
});
14
Multi-pages & mode bibliothèque
Application multi-pages (MPA)
// Plusieurs index.html = plusieurs points d'entrée
mon-app/
├── index.html ← page principale
├── admin/index.html ← page admin
├── login/index.html ← page login
└── vite.config.ts
// vite.config.ts
import { resolve } from 'path';
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin/index.html'),
login: resolve(__dirname, 'login/index.html'),
}
}
}
// Build → dist/ contient les 3 pages
// Utile pour les sites multi-pages traditionnels
// + les apps qui mixent SPA et pages statiques
Mode bibliothèque (Library mode)
// Construire une bibliothèque NPM avec Vite
// vite.config.ts
import { resolve } from 'path';
import dts from 'vite-plugin-dts'; // génère .d.ts
export default defineConfig({
plugins: [react(), dts()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MaBibliothèque',
formats: ['es', 'cjs', 'umd'],
fileName: (format) => `ma-lib.${format}.js`,
},
rollupOptions: {
// Ne pas bundler les peer dependencies
external: ['react', 'react-dom'],
output: {
globals: { react: 'React', 'react-dom': 'ReactDOM' }
}
}
}
});
// Résultat :
// dist/ma-lib.es.js ← ESM
// dist/ma-lib.cjs.js ← CommonJS
// dist/ma-lib.umd.js ← UMD (navigateur)
// dist/index.d.ts ← Types TypeScript
15 — Tests
Vitest — tests unitaires
Configurer et écrire des tests
# npm install -D vitest @vitest/ui jsdom
# Pour React : npm install -D @testing-library/react
// vite.config.ts
test: {
globals: true, // describe, it, expect sans import
environment: 'jsdom', // DOM simulé
setupFiles: ['./src/setupTests.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['node_modules/', 'src/setupTests.ts'],
}
}
// Écrire un test
// utils/sum.test.ts
import { describe, it, expect } from 'vitest';
import { sum } from './sum';
describe('sum', () => {
it('additionne deux nombres', () => {
expect(sum(1, 2)).toBe(3);
});
it('gère les négatifs', () => {
expect(sum(-1, 1)).toBe(0);
});
});
Tester des composants React
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { vi } from 'vitest';
import Button from './Button';
describe('Button', () => {
it('affiche le label', () => {
render(<Button label="Valider" />);
expect(screen.getByText('Valider')).toBeInTheDocument();
});
it('appelle onClick au clic', () => {
const onClick = vi.fn();
render(<Button label="OK" onClick={onClick} />);
fireEvent.click(screen.getByText('OK'));
expect(onClick).toHaveBeenCalledTimes(1);
});
});
// Commandes Vitest
# package.json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui", ← interface web
"test:run": "vitest run", ← exécuter une fois
"coverage": "vitest run --coverage"
}
}
16 — Référence
Cheat sheet Vite
Commandes
| npm create vite@latest | Créer un projet |
| npm run dev | Dev server (port 5173) |
| npm run build | Build production → dist/ |
| npm run preview | Tester le build local |
| npx vite --force | Recréer le cache deps |
Assets & imports
| import url from './img.png' | URL hachée |
| ?raw | Contenu en string |
| ?url | Toujours une URL |
| ?worker | Web Worker |
| public/ | Copié sans transformation |
Env vars
| VITE_XXX | Exposé au client |
| import.meta.env.VITE_XXX | Lire la variable |
| import.meta.env.MODE | development / production |
| import.meta.env.DEV | true en dev |
| .env.local | Jamais commité |
Optimisations
| react-swc template | SWC > Babel |
| lazy() + Suspense | Code splitting routes |
| manualChunks | Séparer vendor/app |
| drop_console: true | Terser en prod |
| visualizer plugin | Analyser le bundle |