Vite Patterns
Build-Tool- und Dev-Server-Patterns für Vite 8+ Projekte. Deckt Konfiguration, Umgebungsvariablen, Proxy-Setup, Library-Modus, Abhängigkeits-Vorab-Bündelung und häufige Produktionsfallstricke ab.
Wann verwenden
- Konfigurieren von
vite.config.tsodervite.config.js - Einrichten von Umgebungsvariablen oder
.env-Dateien - Konfigurieren des Dev-Server-Proxys für API-Backends
- Optimieren der Build-Ausgabe (Chunks, Minifizierung, Assets)
- Veröffentlichen von Bibliotheken mit
build.lib - Fehlersuche bei Abhängigkeits-Vorab-Bündelung oder CJS/ESM-Interop
- Debuggen von HMR, Dev-Server oder Build-Fehlern
- Auswählen oder Anordnen von Vite-Plugins
Wie es funktioniert
- Dev-Modus dient Quelldateien als natives ESM — keine Bündelung. Transformationen erfolgen on-demand 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 mit Tree-Shaking, Code-Splitting und Oxc-basierter Minifizierung zu bündeln.
- Dependency Pre-Bundling konvertiert CJS/UMD-Abhängigkeiten einmalig in ESM via esbuild und cached das Ergebnis unter
node_modules/.vite, sodass nachfolgende Starts die Arbeit überspringen. - Plugins teilen eine einheitliche Schnittstelle für Dev und Build — das gleiche Plugin-Objekt funktioniert sowohl für die On-Demand-Transformationen des Dev-Servers als auch für die Produktions-Pipeline.
- Umgebungsvariablen werden zur Build-Zeit statisch inline eingefügt. Mit
VITE_-präfixierte Variablen werden zu öffentlichen Konstanten im Bundle; alles ohne Präfix ist für Client-Code unsichtbar.
Beispiele
Konfigurationsstruktur
Einfache 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 | '.' | Projektstamm (wo index.html liegt) |
base | '/' | Öffentlicher Basispfad für bereitgestellte Assets |
envPrefix | 'VITE_' | Präfix für Client-exponierte 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 einer Handvoll gut gewarteter Pakete abgedeckt. Greifen Sie zu diesen, bevor Sie eigene schreiben.
| Plugin | Zweck | Wann verwenden |
|---|---|---|
@vitejs/plugin-react-swc | React HMR + Fast Refresh via SWC | Standard für React-Apps (schneller als Babel-Variante) |
@vitejs/plugin-react | React HMR + Fast Refresh via Babel | Nur wenn Sie Babel-Plugins benötigen (Emotion, MobX Decorators) |
@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 Aliasse aus tsconfig.json paths | Immer wenn Sie bereits Aliasse in tsconfig.json haben |
vite-plugin-dts | Erzeugt .d.ts-Dateien im Library-Modus | Beim Veröffentlichen 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-Audits (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, es sei denn, Sie fügen vite-plugin-checker hinzu oder führen tsc --noEmit in CI aus.
Erstellen eigener Plugins
Das Erstellen eigener Plugins ist selten — die meisten Anforderungen werden von vorhandenen Plugins abgedeckt. Wenn Sie doch 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 modifizieren), resolveId + load (virtuelle Module), transformIndexHtml (in HTML einfügen), configureServer (Dev-Middleware hinzufügen), hotUpdate (benutzerdefiniertes HMR — ersetzt das in v7+ deprecated handleHotUpdate).
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'.
Die vollständige Plugin-API finden Sie unter vite.dev/guide/api-plugin. Verwenden Sie vite-plugin-inspect während der Entwicklung, um die Transform-Pipeline zu debuggen.
HMR API
Framework-Plugins (@vitejs/plugin-react, @vitejs/plugin-vue usw.) kümmern sich automatisch um HMR. 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 — manuelles HMR für ein Vanilla-Modul
if (import.meta.hot) {
// Zustand über Aktualisierungen hinweg beibehalten (MUSS mutiert, NIEMALS .data neu zugewiesen werden)
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 in Produktions-Builds durch Tree-Shaking entfernt — 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 gitignored und für lokale Geheimnisse gedacht.
Clientseitiger Zugriff
Nur VITE_-präfixierte Variablen sind für Clientcode sichtbar:
import.meta.env.VITE_API_URL // string
import.meta.env.MODE // 'development' | 'production' | custom
import.meta.env.BASE_URL // base config value
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 Deaktivieren von Source Maps 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) kommen in VITE_-Variablen. Geheimnisse (API-Tokens, Datenbank-URLs, private Schlüssel) MÜSSEN serverseitig hinter einer API oder serverlosen 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 via `define` in Client-Code eingefügt zu werden.
const env = loadEnv(mode, process.cwd(), '')
// GUT: Explizite Präfixliste
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
Source Maps in der Produktion
Produktions-Source Maps geben Ihren ursprünglichen Quellcode preis. Deaktivieren Sie sie, es sei denn, Sie laden sie zu einem Fehler-Tracker (Sentry, Bugsnag) hoch und löschen sie danach lokal:
build: {
sourcemap: false, // Standard – so belassen
}
.gitignore-Checkliste
.env.local,.env.*.local— lokale Geheimnis-Überschreibungendist/— Build-Ausgabenode_modules/.vite— Vorab-Bündelungs-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 nur ein einzelnes Symbol importieren. Dies ist die Nr. 1 der Verlangsamung des Dev-Servers, die in der offiziellen Dokumentation genannt wird.
// SCHLECHT — Das Importieren eines einzigen Utils 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'
Seien Sie explizit mit Import-Erweiterungen
Jede implizite Erweiterung erzwingt bis zu 6 Dateisystem-Überprüfungen via resolve.extensions. In großen Codebasen summiert sich das.
// SCHLECHT
import Component from './Component'
// GUT
import Component from './Component.tsx'
Schränken Sie tsconfig.json allowImportingTsExtensions + resolve.extensions auf nur die Erweiterungen ein, die Sie tatsächlich verwenden.
Aufwärmen von Hot-Path-Routen
server.warmup.clientFiles transformiert bekannte heiße Einträge vorab, bevor der Browser sie anfordert — dies eliminiert den Kaltstart-Wasserfall von Anfragen bei großen Apps.
// vite.config.ts
server: {
warmup: {
clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
},
}
Profiling langsamer Dev-Server
Wenn sich vite dev langsam anfühlt, beginnen Sie mit vite --profile, interagieren Sie mit der App, drücken Sie dann p+Enter, um eine .cpuprofile-Datei zu speichern. Laden Sie sie in Speedscope, um herauszufinden, welche Plugins Zeit fressen — normalerweise die Hooks buildStart, config oder configResolved 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 Benutzern 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 Externalisierungen
Reine 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 belassen
noExternal: ['esm-only-package'], // erzwungen in SSR-Ausgabe bündeln (behebt die meisten SSR-Fehler)
target: 'node', // 'node' oder 'webworker'
}
Abhängigkeits-Vorab-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 erzwingen
'cjs-package', // CJS-Abhängigkeiten, die Interop-Probleme verursachen
'deep-lib/components/**', // Glob für tiefe Imports
],
exclude: ['local-esm-package'], // muss gültiges ESM sein, wenn ausgeschlossen
force: true, // Cache ignorieren, neu optimieren (temporäres Debugging)
}
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 vor der Bereitstellung.
Veraltete Chunks nach der Bereitstellung
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:
- Alte
dist/assets/-Dateien für ein Bereitstellungsfenster aktiv lassen - Dynamische Importfehler in Ihrem Router abfangen und einen Seitenneuladen erzwingen
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, // an 0.0.0.0 binden
hmr: { clientPort: 3000 }, // falls hinter einem Reverse-Proxy
}
Monorepo-Dateizugriff
Vite beschränkt das Bereitstellen von Dateien auf das Projektstammverzeichnis. Pakete außerhalb des Stammverzeichnisses werden blockiert:
// vite.config.ts — Monorepo-Dateizugriff
server: {
fs: {
allow: ['..'], // übergeordnetes Verzeichnis erlauben (Workspace-Stamm)
},
}
Anti-Patterns
// SCHLECHT: Das Setzen von envPrefix auf '' legt ALLE Umgebungsvariablen (einschließlich Geheimnisse) offen
envPrefix: ''
// SCHLECHT: Annahme, dass require() im Browser funktioniert