Was macht vite-patterns?
Build-Tool- und Dev-Server-Patterns für Vite 8+-Projekte. Behandelt Konfiguration, Umgebungsvariablen, Proxy-Setup, Library-Modus, Abhängigkeits-Pre-Bündelung und häufige Produktionsfallstricke.
Verwendung
- Konfigurieren von
vite.config.tsodervite.config.js - Einrichten von Umgebungsvariablen oder
.env-Dateien - Konfigurieren des Dev-Server-Proxys für API-Backends
- Optimieren des Build-Outputs (Chunks, Minifizierung, Assets)
- Publizieren von Bibliotheken mit
build.lib - Fehlersuche bei Abhängigkeits-Pre-Bündelung oder CJS/ESM-Interop
- Debuggen von HMR, Dev-Server oder Build-Fehlern
- Auswählen oder Anordnen von Vite-Plugins
Funktionsweise
- Dev-Modus liefert Quelldateien als natives ESM – ohne Bündelung. Transformationen erfolgen bei Bedarf pro Modulanfrage, daher sind Kaltstarts schnell und HMR präzise.
- Build-Modus verwendet Rolldown (v7+) oder Rollup (v5–v6), um die App für die Produktion zu bündeln, mit Tree-Shaking, Code-Splitting und Oxc-basierter Minifizierung.
- Abhängigkeits-Pre-Bündelung konvertiert CJS/UMD-Abhängigkeiten einmalig über esbuild zu ESM und speichert das Ergebnis unter
node_modules/.vite, sodass nachfolgende Starts diesen Schritt überspringen. - Plugins teilen eine einheitliche Schnittstelle für Dev und Build – dasselbe Plugin-Objekt funktioniert sowohl für die bedarfsgesteuerten Transformationen des Dev-Servers als auch für die Produktions-Pipeline.
- Umgebungsvariablen werden zur Build-Zeit statisch inline eingefügt.
VITE_-präfixierte Variablen werden zu öffentlichen Konstanten im Bundle; alles ohne Präfix ist für Client-Code unsichtbar.
Beispiele
Konfigurationsstruktur
Basis-Konfiguration
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': new URL('./src', import.meta.url).pathname },
},
})
Bedingte Konfiguration
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd()) // nur VITE_-Präfix (sicher)
return {
plugins: [react()],
server: command === 'serve' ? { port: 3000 } : undefined,
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
Wichtige Konfigurationsoptionen
| Schlüssel | Standard | Beschreibung |
|---|---|---|
root | '.' | Projektwurzel (wo index.html liegt) |
base | '/' | Öffentlicher Basispfad für bereitgestellte Assets |
envPrefix | 'VITE_' | Präfix für client-seitige Umgebungsvariablen |
build.outDir | 'dist' | Ausgabeverzeichnis |
build.minify | 'oxc' | Minifizierer ('oxc', 'terser' oder false) |
build.sourcemap | false | true, 'inline' oder 'hidden' |
Plugins
Essentielle Plugins
Die meisten Plugin-Anforderungen werden von einigen wenigen gut gewarteten Paketen abgedeckt. Greifen Sie zu diesen, bevor Sie eigene Plugins schreiben.
| Plugin | Zweck | Verwendung |
|---|---|---|
@vitejs/plugin-react-swc | React HMR + Fast Refresh über SWC | Standard für React-Apps (schneller als Babel-Variante) |
@vitejs/plugin-react | React HMR + Fast Refresh über Babel | Nur wenn Sie Babel-Plugins benötigen (Emotion, MobX-Dekorateure) |
@vitejs/plugin-vue | Vue 3 SFC-Unterstützung | Vue-Apps |
vite-plugin-checker | Führt tsc + ESLint in einem Worker-Thread mit HMR-Overlay aus | Jede TypeScript-App – Vite führt KEINE Typprüfung während vite build durch |
vite-tsconfig-paths | Berücksichtigt tsconfig.json paths-Aliase | Immer wenn Sie bereits Aliase in tsconfig.json haben |
vite-plugin-dts | Erzeugt .d.ts-Dateien im Library-Modus | Beim Publizieren von TypeScript-Bibliotheken |
vite-plugin-svgr | Importiert SVGs als React-Komponenten | React-Apps, die SVGs als Komponenten verwenden |
rollup-plugin-visualizer | Bundle-Treemap/Sunburst-Bericht | Regelmäßige Bundle-Größenüberprüfungen (mit enforce: 'post' verwenden) |
vite-plugin-pwa | Zero-Config PWA + Workbox | Offline-fähige Apps |
Wichtiger Hinweis: vite build transpiliert, führt aber KEINE Typprüfung durch. Typfehler gelangen stillschweigend in die Produktion, wenn Sie nicht vite-plugin-checker hinzufügen oder tsc --noEmit in CI ausführen.
Eigene Plugins erstellen
Das Erstellen eigener Plugins ist selten – die meisten Anforderungen werden durch vorhandene Plugins abgedeckt. Wenn Sie dennoch eines benötigen, beginnen Sie inline in vite.config.ts und extrahieren Sie es nur bei Wiederverwendung.
// vite.config.ts – minimales Inline-Plugin
function myPlugin(): Plugin {
return {
name: 'my-plugin', // erforderlich, muss eindeutig sein
enforce: 'pre', // 'pre' | 'post' (optional)
apply: 'build', // 'build' | 'serve' (optional)
transform(code, id) {
if (!id.endsWith('.custom')) return
return { code: transformCustom(code), map: null }
},
}
}
Wichtige Hooks: transform (Quelle ändern), resolveId + load (virtuelle Module), transformIndexHtml (in HTML einfügen), configureServer (Dev-Middleware hinzufügen), hotUpdate (benutzerdefinierte HMR – ersetzt das veraltete handleHotUpdate in v7+).
Virtuelle Module verwenden die \0-Präfix-Konvention – resolveId gibt '\0virtual:my-id' zurück, sodass andere Plugins es überspringen. Benutzercode importiert 'virtual:my-id'.
Für die vollständige Plugin-API siehe vite.dev/guide/api-plugin. Verwenden Sie während der Entwicklung vite-plugin-inspect, um die Transformations-Pipeline zu debuggen.
HMR-API
Framework-Plugins (@vitejs/plugin-react, @vitejs/plugin-vue usw.) behandeln HMR automatisch. Greifen Sie nur dann direkt auf import.meta.hot zu, wenn Sie benutzerdefinierte Zustandsspeicher, Entwicklerwerkzeuge oder framework-agnostische Dienstprogramme erstellen, die den Zustand über Aktualisierungen hinweg beibehalten müssen.
// src/store.ts – manuelle HMR für ein Vanilla-Modul
if (import.meta.hot) {
// Zustand über Aktualisierungen hinweg beibehalten (muss MUTIEREN, .data niemals neu zuweisen)
import.meta.hot.data.count = import.meta.hot.data.count ?? 0
// Nebenwirkungen bereinigen, bevor das Modul ersetzt wird
import.meta.hot.dispose((data) => clearInterval(data.intervalId))
// Eigene Aktualisierungen dieses Moduls akzeptieren
import.meta.hot.accept()
}
Der gesamte import.meta.hot-Code wird aus Produktions-Builds herausgetree-shaked – keine Guard-Entfernung erforderlich.
Umgebungsvariablen
Vite lädt .env, .env.local, .env.[mode] und .env.[mode].local in dieser Reihenfolge (spätere überschreiben frühere); *.local-Dateien sind in gitignore und für lokale Geheimnisse gedacht.
Client-seitiger Zugriff
Nur VITE_-präfixierte Variablen werden dem Client-Code ausgesetzt:
import.meta.env.VITE_API_URL // string
import.meta.env.MODE // 'development' | 'production' | benutzerdefiniert
import.meta.env.BASE_URL // base-Konfigurationswert
import.meta.env.DEV // boolean
import.meta.env.PROD // boolean
import.meta.env.SSR // boolean
Verwendung von Env in der Konfiguration
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd()) // nur VITE_-Präfix (sicher)
return {
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
Sicherheit
VITE_-Präfix ist KEINE Sicherheitsgrenze
Jede Variable mit dem Präfix VITE_ wird zur Build-Zeit statisch in das Client-Bundle eingefügt. Minifizierung, Base64-Kodierung und das Deaktivieren von Sourcemaps verbergen sie NICHT. Ein entschlossener Angreifer kann jede VITE_-Variable aus dem ausgelieferten JavaScript extrahieren.
Regel: Nur öffentliche Werte (API-URLs, Feature-Flags, öffentliche Schlüssel) gehören in VITE_-Variablen. Geheimnisse (API-Tokens, Datenbank-URLs, private Schlüssel) MÜSSEN serverseitig hinter einer API oder Serverless-Funktion leben.
Die loadEnv('')-Falle
// SCHLECHT: Übergabe von '' als drittes Argument lädt ALLE Umgebungsvariablen – einschließlich Server-Geheimnisse –
// und macht sie verfügbar, um über `define` in Client-Code eingefügt zu werden.
const env = loadEnv(mode, process.cwd(), '')
// GUT: Explizite Präfix-Liste
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
Sourcemaps in der Produktion
Produktions-Sourcemaps leaken Ihren ursprünglichen Quellcode. Deaktivieren Sie sie, es sei denn, Sie laden sie in einen Fehler-Tracker (Sentry, Bugsnag) hoch und löschen sie anschließend lokal:
build: {
sourcemap: false, // Standard – so belassen
}
.gitignore-Checkliste
.env.local,.env.*.local– lokale Geheimnis-Überschreibungendist/– Build-Ausgabenode_modules/.vite– Pre-Bundle-Cache (veraltete Einträge verursachen Phantomfehler)
Server-Proxy
// vite.config.ts – server.proxy
server: {
proxy: {
'/foo': 'http://localhost:4567', // String-Kurzform
'/api': {
target: 'http://localhost:8080',
changeOrigin: true, // erforderlich für virtuelle Host-Backends
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}
Für WebSocket-Proxying fügen Sie ws: true zur Routenkonfiguration hinzu.
Build-Optimierung
Manuelle Chunks
// vite.config.ts – build.rolldownOptions
build: {
rolldownOptions: {
output: {
// Objektform: Bestimmte Pakete gruppieren
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
},
},
},
}
// Funktionsform: Nach Heuristik aufteilen
manualChunks(id) {
if (id.includes('node_modules/react')) return 'react-vendor'
if (id.includes('node_modules')) return 'vendor'
}
Leistung
Barrel-Dateien vermeiden
Barrel-Dateien (index.ts, die alles aus einem Verzeichnis re-exportieren) zwingen Vite, jede re-exportierte Datei zu laden, selbst wenn Sie ein einzelnes Symbol importieren. Dies ist die #1-Verlangsamung des Dev-Servers, die in den offiziellen Dokumenten genannt wird.
// SCHLECHT – das Importieren eines Dienstprogramms zwingt Vite, das gesamte Barrel zu laden
import { slash } from '@/utils'
// GUT – Direkter Import, nur die eine Datei wird geladen
import { slash } from '@/utils/slash'
Explizite Import-Erweiterungen verwenden
Jede implizite Erweiterung erzwingt bis zu 6 Dateisystemprüfungen über resolve.extensions. In großen Codebasen summiert sich das.
// SCHLECHT
import Component from './Component'
// GUT
import Component from './Component.tsx'
Verengen Sie tsconfig.json allowImportingTsExtensions + resolve.extensions auf nur die Erweiterungen, die Sie tatsächlich verwenden.
Hot-Path-Routen aufwärmen
server.warmup.clientFiles transformiert bekannte Hot-Einträge vor, bevor der Browser sie anfordert – dies eliminiert den Cold-Load-Anforderungswasserfall bei großen Apps.
// vite.config.ts
server: {
warmup: {
clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
},
}
Langsame Dev-Server profilieren
Wenn vite dev sich langsam anfühlt, starten Sie mit vite --profile, interagieren Sie mit der App und drücken Sie dann p+Eingabe, um eine .cpuprofile-Datei zu speichern. Laden Sie sie in Speedscope, um herauszufinden, welche Plugins Zeit fressen – normalerweise die buildStart-, config- oder configResolved-Hooks in Community-Plugins.
Library-Modus
Wenn Sie ein npm-Paket veröffentlichen, verwenden Sie build.lib. Zwei Fallstricke sind wichtiger als die Konfigurationsdetails:
- Typen werden nicht ausgegeben – fügen Sie
vite-plugin-dtshinzu oder führen Sietsc --emitDeclarationOnlyseparat aus. - Peer-Abhängigkeiten MÜSSEN externalisiert werden – nicht aufgeführte Peers werden in Ihre Bibliothek gebündelt, was bei Verwendern doppelte Laufzeitfehler verursacht.
// vite.config.ts
build: {
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => `my-lib.${format}.js`,
},
rolldownOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'], // jede Peer-Abhängigkeit
},
}
SSR-Externalisierung
Bare createServer({ middlewareMode: true })-Setups sind Framework-Autoren-Territorium. Die meisten Apps sollten stattdessen Nuxt, Remix, SvelteKit, Astro oder TanStack Start verwenden. Was Sie als Framework-Benutzer anpassen werden, ist die Externalisierungskonfiguration, wenn Abhängigkeiten in SSR brechen:
// vite.config.ts – ssr-Optionen
ssr: {
external: ['node-native-package'], // als require() im SSR-Bundle beibehalten
noExternal: ['esm-only-package'], // in SSR-Output erzwingen (behebt die meisten SSR-Fehler)
target: 'node', // 'node' oder 'webworker'
}
Abhängigkeits-Pre-Bündelung
Vite bündelt Abhängigkeiten vor, um CJS/UMD in ESM zu konvertieren und die Anzahl der Anfragen zu reduzieren.
// vite.config.ts – optimizeDeps
optimizeDeps: {
include: [
'lodash-es', // bekannte schwere Abhängigkeiten vor-bündeln
'cjs-package', // CJS-Abhängigkeiten, die Interop-Probleme verursachen
'deep-lib/components/**', // Glob für tiefe Importe
],
exclude: ['local-esm-package'], // muss gültiges ESM sein, wenn ausgeschlossen
force: true, // Cache ignorieren, neu optimieren (temporäres Debuggen)
}
Häufige Fallstricke
Dev stimmt nicht mit Build überein
Dev verwendet esbuild/Rolldown für Transformationen; Build verwendet Rolldown für die Bündelung. CJS-Bibliotheken können sich zwischen beiden unterschiedlich verhalten. Überprüfen Sie immer mit vite build && vite preview, bevor Sie bereitstellen.
Veraltete Chunks nach Deployment
Neue Builds erzeugen neue Chunk-Hashes. Benutzer mit aktiven Sitzungen fordern alte Dateinamen an, die nicht mehr existieren. Vite hat keine integrierte Lösung. Minderungsmaßnahmen:
- Behalten Sie alte
dist/assets/-Dateien für ein Deployment-Fenster live - Fangen Sie dynamische Importfehler in Ihrem Router ab und erzwingen Sie ein erneutes Laden der Seite
Docker und Container
Vite bindet standardmäßig an localhost, was von außerhalb eines Containers nicht erreichbar ist:
// vite.config.ts – Docker/Container-Setup
server: {
host: true, // bind 0.0.0.0
hmr: { clientPort: 3000 }, // wenn hinter einem Reverse-Proxy
}
Monorepo-Dateizugriff
Vite beschränkt die Dateiauslieferung auf das Projektstammverzeichnis. Pakete außerhalb des Stammverzeichnisses werden blockiert:
// vite.config.ts – Monorepo-Dateizugriff
server: {
fs: {
allow: ['..'], // übergeordnetes Verzeichnis zulassen (Workspace-Root)
},
}
Anti-Patterns
// SCHLECHT: Setzen von envPrefix auf '' legt ALLE Umgebungsvariablen (einschließlich Geheimnisse) offen
envPrefix: ''
// SCHLECHT: Annahme, dass require() in Vite funktioniert (es funktioniert nicht nativ; verwenden Sie Importe)
// (Anmerkung: require() wird in modernem Vite nicht unterstützt; verwenden Sie ESM-Importe)