
Vite Advanced
- 39 installs
- 213 repo stars
- Updated August 4, 2026
- yonatangross/skillforge-claude-plugin
Helps with ai & agent building tasks.
About
vite-advanced is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- vite-advanced
- AI & Agent Building
- AI-coding skill
Vite Advanced by the numbers
- 39 all-time installs (skills.sh)
- Ranked #8,302 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yonatangross/skillforge-claude-plugin --skill vite-advancedAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 39 |
|---|---|
| repo stars | ★ 213 |
| Last updated | August 4, 2026 |
| Repository | yonatangross/skillforge-claude-plugin ↗ |
What it does
Helps with ai & agent building tasks.
Files
Vite Advanced Patterns
Advanced configuration for Vite 8 (Rolldown-powered, stable Mar 2026) with Environment API from Vite 7.
Vite 8: Rolldown-Powered Builds (Default)
Vite 8 replaces esbuild+Rollup with Rolldown (Rust-based unified bundler), delivering 7.7x faster builds and 50% less memory. This is now the default for all new projects.
npm install vite@8 # Direct upgradeKey improvements:
| Metric | Vite 7 | Vite 8 | Improvement |
|---|---|---|---|
| Build time (Linear) | 46s | 6s | 7.7x faster |
| Dev server startup | ~3s | ~1s | 3x faster |
| HMR updates | ~100ms | ~60ms | 40% faster |
| Memory usage | ~800MB | ~400MB | 50% reduction |
Breaking changes from Vite 7:
build.rollupOptions→build.rolldownOptions(auto-converted with deprecation warning)transformWithEsbuild→transformWithOxc- Plugin
transform()returning JS must addmoduleType: 'js'to return value manualChunksobject form removed — use function form oradvancedChunks'system'/'amd'output formats no longer supported- Lightning CSS is now standard (replaces esbuild for CSS minification)
- Browser target bump: Chrome 107→111, Firefox 104→114, Safari 16.0→16.4
advancedChunks (Replaces manualChunks)
Vite 8 introduces declarative chunk grouping with priority control and size constraints:
export default defineConfig({
build: {
rolldownOptions: {
output: {
advancedChunks: {
groups: [
{ name: 'react-vendor', test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, priority: 20 },
{ name: 'ui-vendor', test: /[\\/]node_modules[\\/]@radix-ui[\\/]/, priority: 15, minShareCount: 2 },
{ name: 'vendor', test: /[\\/]node_modules[\\/]/, priority: 10, maxSize: 500000 },
],
},
},
},
},
})Load Read("${CLAUDE_SKILL_DIR}/references/vite8-rolldown.md") for full migration options, benchmarks, advancedChunks syntax, Oxc benefits, and migration checklist.
Environment API (from Vite 7, stable)
Multi-environment support (client/SSR/edge) is first-class:
export default defineConfig({
environments: {
client: { build: { outDir: 'dist/client' } },
ssr: { build: { outDir: 'dist/server', ssr: true } },
},
});Load Read("${CLAUDE_SKILL_DIR}/references/environment-api.md") for full configuration, per-environment plugins, Builder API, and buildApp hook.
Plugin Development
Hooks for config, transform, resolveId, load, virtual modules, HMR, and environment-aware transforms.
Load Read("${CLAUDE_SKILL_DIR}/references/plugin-development.md") for plugin structure, hook execution order, and advanced patterns.
SSR Configuration
Middleware mode for dev, separate client/server builds for prod, streaming SSR support.
Load Read("${CLAUDE_SKILL_DIR}/references/ssr-configuration.md") for entry points, dev/prod server setup, and streaming patterns.
Build Optimization
Chunk splitting with advancedChunks (Vite 8, preferred) or manualChunks (Vite 7 legacy), tree shaking, minification, and bundle analysis.
Load Read("${CLAUDE_SKILL_DIR}/references/chunk-optimization.md") for chunk strategies, build targets, and optimization checklist.
Quick Reference
| Feature | Vite 8 Status |
|---|---|
| Rolldown bundler | Default (replaces esbuild+Rollup) |
| Environment API | Stable (from Vite 7) |
| ESM-only distribution | Default |
| Node.js requirement | 20.19+ or 22.12+ |
advancedChunks | New (replaces manualChunks) |
buildApp hook | Stable (for multi-env plugins) |
createBuilder | Multi-env builds |
| Oxc integration | Parsing 3x faster than SWC |
Key Decisions
| Decision | Recommendation |
|---|---|
| New projects | Vite 8 (default) |
| Existing production apps | Evaluate Vite 8, use rolldown-vite for gradual migration |
| Multi-env builds | Environment API (environments config) |
| Plugin scope | Use this.environment for env-aware plugins |
| SSR | Middleware mode for dev, separate builds for prod |
| Chunks | advancedChunks for Vite 8, manualChunks for Vite 7 compat |
Related Skills
biome-linting- Fast linting alongside Viteork:react-server-components-framework- SSR integrationedge-computing-patterns- Edge environment buildsork:storybook-testing- Component testing with Vitest
References
Load on demand with Read("${CLAUDE_SKILL_DIR}/references/<file>"):
| File | Content |
|---|---|
vite8-rolldown.md | Rolldown migration, benchmarks, advancedChunks, Oxc benefits |
environment-api.md | Multi-environment builds, Builder API, per-env plugins |
plugin-development.md | Plugin hooks, virtual modules, HMR, env-aware transforms |
ssr-configuration.md | Entry points, dev/prod servers, streaming SSR |
library-mode.md | Building publishable npm packages |
chunk-optimization.md | advancedChunks, manualChunks, tree shaking, bundle analysis |
Vite Production Build Checklist
Pre-deployment build verification.
Configuration
- [ ]
build.targetset appropriately for audience - [ ]
build.sourcemapdisabled or set to'hidden' - [ ]
build.minifyenabled (esbuild or terser) - [ ] Environment variables properly defined
Bundle Optimization
- [ ] Run bundle analyzer:
npx vite-bundle-visualizer - [ ] No unexpected large chunks (> 500kb)
- [ ] Vendor chunks split appropriately
- [ ] No duplicate dependencies
- [ ] Tree shaking working (check for dead code)
Chunk Strategy
// Recommended manual chunks
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router': ['react-router-dom'],
'ui': ['@radix-ui/*'],
}- [ ] React/framework in separate vendor chunk
- [ ] Route-based code splitting enabled
- [ ] Large libraries in separate chunks
Assets
- [ ] Images optimized (WebP, AVIF)
- [ ]
assetsInlineLimitset appropriately (default 4kb) - [ ] Static assets in
public/folder - [ ] Asset filenames include hash for caching
CSS
- [ ] CSS minified
- [ ] CSS code split by entry point (if needed)
- [ ] No unused CSS (PurgeCSS or similar)
- [ ] PostCSS processing complete
Environment Variables
- [ ]
.env.productionexists with production values - [ ] No secrets in client-side env vars
- [ ]
VITE_*prefix used for client vars - [ ] Build-time vars validated
TypeScript
- [ ]
tsc --noEmitpasses - [ ] No type errors in build
- [ ] Source maps working if enabled
SSR (If Applicable)
- [ ] Server entry point builds correctly
- [ ] External dependencies configured
- [ ] Client manifest generated
- [ ] Hydration working
Testing
- [ ] Build completes without errors
- [ ] Preview mode works:
vite preview - [ ] All routes accessible
- [ ] No console errors in production build
- [ ] Performance metrics acceptable
Output Verification
# Check build output
ls -la dist/
# Check chunk sizes
du -sh dist/assets/*
# Preview locally
vite preview
# Test production server
NODE_ENV=production node server.jsPerformance Targets
| Metric | Target | Current |
|---|---|---|
| Initial JS | < 200kb gzipped | ___ |
| Main chunk | < 150kb | ___ |
| Vendor chunk | < 100kb | ___ |
| CSS | < 50kb | ___ |
| LCP | < 2.5s | ___ |
| TTI | < 3.5s | ___ |
Pre-Deploy Commands
# Full build
npm run build
# Type check
npm run typecheck
# Preview
npm run preview
# Analyze bundle
npx vite-bundle-visualizerCommon Issues
Large Bundle
- Check for duplicate dependencies
- Ensure tree shaking is working
- Split vendor chunks
- Use dynamic imports
Build Failures
- Clear
.vitecache:rm -rf node_modules/.vite - Check TypeScript errors
- Verify all imports resolve
Missing Assets
- Check
public/folder structure - Verify asset paths in code
- Check
baseconfig if using subpath
Sign-Off
- [ ] Build size within budget
- [ ] Preview works correctly
- [ ] No console errors
- [ ] Performance acceptable
- [ ] Ready for deployment
Vite Build Optimization
Chunk splitting and build performance.
advancedChunks (Vite 8+)
Vite 8 introduces advancedChunks as the recommended approach for chunk splitting. It provides declarative configuration with priority control and size constraints.
Full Syntax
export default defineConfig({
build: {
rolldownOptions: {
output: {
advancedChunks: {
groups: [
{
// Required: Chunk name
name: 'react-vendor',
// Required: Module matching (regex or function)
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
// Optional: Higher priority wins when module matches multiple groups
priority: 20,
// Optional: Minimum chunk size in bytes (default: 0)
minSize: 20000, // 20KB
// Optional: Maximum chunk size in bytes (auto-splits if exceeded)
maxSize: 250000, // 250KB
// Optional: Minimum number of chunks that must share this module
minShareCount: 1,
},
],
},
},
},
},
})Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | Required | Output chunk name |
test | `RegExp \ | (id: string) => boolean` | Required |
priority | number | 0 | Higher wins on conflicts |
minSize | number | 0 | Skip if chunk would be smaller |
maxSize | number | Infinity | Auto-split if exceeded |
minShareCount | number | 1 | Required shared imports |
Complete Example
// vite.config.ts - Production-ready advancedChunks
export default defineConfig({
build: {
rolldownOptions: {
output: {
advancedChunks: {
groups: [
// Framework core - highest priority, always separate
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 30,
minSize: 20000,
maxSize: 200000,
},
// Router - medium-high priority
{
name: 'router',
test: /[\\/]node_modules[\\/](react-router|react-router-dom|@remix-run)[\\/]/,
priority: 25,
},
// UI library - group all Radix components
{
name: 'radix-ui',
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
priority: 20,
minShareCount: 2, // Only if used by 2+ routes
},
// Charts - large, load on demand
{
name: 'charts',
test: /[\\/]node_modules[\\/](recharts|d3|victory)[\\/]/,
priority: 15,
maxSize: 300000, // Split if charts exceed 300KB
},
// Date libraries
{
name: 'dates',
test: /[\\/]node_modules[\\/](date-fns|dayjs|luxon)[\\/]/,
priority: 15,
},
// Catch-all vendor chunk
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 5,
maxSize: 500000, // Auto-split large vendor chunks
},
],
},
},
},
},
})Function-Based Test
For complex matching logic:
advancedChunks: {
groups: [
{
name: 'heavy-deps',
test: (id) => {
if (!id.includes('node_modules')) return false
const heavyPackages = ['three', 'monaco-editor', 'pdf-lib']
return heavyPackages.some(pkg => id.includes(`node_modules/${pkg}`))
},
priority: 25,
},
],
}---
Manual Chunks (Vite 7 and Earlier)
Deprecation Notice (Vite 8):manualChunksstill works in Vite 8 for backward compatibility, butadvancedChunksis the recommended approach. Consider migrating toadvancedChunksfor new projects or when updating existing configurations.
Split large dependencies into separate chunks:
// vite.config.ts
export default defineConfig({
build: {
rolldownOptions: {
output: {
manualChunks: {
// Vendor chunk for React
'react-vendor': ['react', 'react-dom'],
// Router chunk
'router': ['react-router-dom'],
// UI library chunk
'ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
// Chart libraries
'charts': ['recharts', 'd3'],
},
},
},
},
})Dynamic Manual Chunks
export default defineConfig({
build: {
rolldownOptions: {
output: {
manualChunks(id) {
// All node_modules in vendor chunk
if (id.includes('node_modules')) {
// Split by package name
const match = id.match(/node_modules\/([^/]+)/)
if (match) {
const packageName = match[1]
// Group related packages
if (['react', 'react-dom', 'scheduler'].includes(packageName)) {
return 'react-vendor'
}
if (packageName.startsWith('@radix-ui')) {
return 'radix-vendor'
}
// Large packages get their own chunk
if (['lodash', 'moment', 'three'].includes(packageName)) {
return packageName
}
// Everything else in common vendor
return 'vendor'
}
}
},
},
},
},
})Build Target
Vite 7 default: 'baseline-widely-available'
export default defineConfig({
build: {
target: 'baseline-widely-available', // Default in Vite 7
// Or specific targets:
// target: 'esnext',
// target: 'es2022',
// target: ['es2022', 'edge88', 'firefox78', 'chrome87', 'safari14'],
},
})Minification
export default defineConfig({
build: {
minify: 'esbuild', // Default, fastest
// minify: 'terser', // More aggressive, slower
// Terser options
terserOptions: {
compress: {
drop_console: true, // Remove console.log
drop_debugger: true,
},
},
},
})Source Maps
export default defineConfig({
build: {
sourcemap: false, // No source maps (production)
// sourcemap: true, // Separate .map files
// sourcemap: 'inline', // Inline in JS (dev)
// sourcemap: 'hidden', // Maps for error reporting only
},
})Tree Shaking
Ensure packages support tree shaking:
// package.json of your lib
{
"sideEffects": false,
// Or specify files with side effects:
"sideEffects": ["**/*.css", "./src/polyfills.js"]
}Analyze Bundle
# Install visualizer
npm install -D rollup-plugin-visualizer
# Or use npx
npx vite-bundle-visualizerimport { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true,
filename: 'stats.html',
gzipSize: true,
brotliSize: true,
}),
],
})CSS Optimization
export default defineConfig({
build: {
cssCodeSplit: true, // Split CSS per entry point
cssMinify: 'lightningcss', // Faster CSS minification
},
css: {
devSourcemap: true, // CSS source maps in dev
},
})Asset Inlining
export default defineConfig({
build: {
assetsInlineLimit: 4096, // Inline assets < 4kb as base64
},
})Chunk Size Warnings
export default defineConfig({
build: {
chunkSizeWarningLimit: 500, // Warn if chunk > 500kb
},
})Dependency Optimization
export default defineConfig({
optimizeDeps: {
// Pre-bundle these dependencies
include: ['lodash-es', 'axios'],
// Don't pre-bundle these
exclude: ['@my/local-package'],
// Force re-optimization
force: true,
},
})Quick Optimization Checklist
1. [ ] Use advancedChunks (Vite 8+) or manualChunks (Vite 7) 2. [ ] Use dynamic imports for routes 3. [ ] Set appropriate target for audience 4. [ ] Remove console in production 5. [ ] Analyze bundle with visualizer 6. [ ] Check for duplicate dependencies 7. [ ] Ensure tree-shakeable imports 8. [ ] Set sideEffects: false in package.json 9. [ ] Consider CSS code splitting 10. [ ] Adjust assetsInlineLimit as needed
---
Migration: manualChunks to advancedChunks
When upgrading to Vite 8, convert your manualChunks configuration to advancedChunks.
Example 1: Object-Based manualChunks
Before (Vite 7):
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router': ['react-router-dom'],
'ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
}After (Vite 8):
advancedChunks: {
groups: [
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
priority: 20,
},
{
name: 'router',
test: /[\\/]node_modules[\\/]react-router-dom[\\/]/,
priority: 15,
},
{
name: 'ui',
test: /[\\/]node_modules[\\/]@radix-ui[\\/]react-(dialog|dropdown-menu)[\\/]/,
priority: 10,
},
],
}Example 2: Function-Based manualChunks
Before (Vite 7):
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('react') || id.includes('react-dom')) {
return 'react-vendor'
}
if (id.includes('@radix-ui')) {
return 'radix-vendor'
}
if (id.includes('lodash') || id.includes('moment')) {
return 'utils'
}
return 'vendor'
}
}After (Vite 8):
advancedChunks: {
groups: [
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 30,
},
{
name: 'radix-vendor',
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
priority: 20,
},
{
name: 'utils',
test: /[\\/]node_modules[\\/](lodash|moment)[\\/]/,
priority: 15,
},
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 5,
},
],
}Example 3: Adding Size Constraints
One key benefit of advancedChunks is automatic chunk splitting based on size.
Before (Vite 7): No built-in size control
manualChunks: {
'vendor': ['lodash', 'moment', 'axios', 'd3', 'three'],
// This could produce a 2MB chunk with no warning
}After (Vite 8): Automatic splitting with maxSize
advancedChunks: {
groups: [
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 5,
maxSize: 250000, // Auto-split into vendor, vendor-1, vendor-2, etc.
},
],
}Example 4: Shared Module Optimization
Use minShareCount to only chunk modules used by multiple entry points.
After (Vite 8):
advancedChunks: {
groups: [
{
name: 'shared-ui',
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
priority: 15,
minShareCount: 2, // Only if imported by 2+ chunks
},
{
name: 'shared-utils',
test: /[\\/]src[\\/]utils[\\/]/,
priority: 10,
minShareCount: 3, // Common utilities used across 3+ pages
minSize: 5000, // At least 5KB
},
],
}Migration Tips
1. Start with existing chunk names - Keep the same name values for cache consistency 2. Convert package arrays to regex - ['react', 'react-dom'] becomes /[\\/]node_modules[\\/](react|react-dom)[\\/]/ 3. Add priorities - Earlier items in manualChunks had implicit priority; make it explicit 4. Add size constraints - Use maxSize to prevent oversized chunks 5. Test thoroughly - Compare bundle output before/after with rollup-plugin-visualizer
Compatibility Note
Both manualChunks and advancedChunks can coexist during migration, but advancedChunks takes precedence when both match the same module. For cleanest results, fully migrate to advancedChunks.
Vite 7 Environment API
Multi-environment builds for client, SSR, and edge runtimes.
Concept
Vite 6+ formalizes environments as a first-class concept. Each environment has its own:
- Module graph
- Configuration
- Plugin pipeline
- Build output
Basic Configuration
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
// Shared config (inherited by all environments)
build: {
sourcemap: false,
},
environments: {
// Browser client (default)
client: {
build: {
outDir: 'dist/client',
manifest: true,
},
},
// Server-side rendering (Node.js)
ssr: {
build: {
outDir: 'dist/server',
target: 'node20',
rollupOptions: {
output: { format: 'esm' },
},
},
},
// Edge runtime (Cloudflare Workers, etc.)
edge: {
resolve: {
noExternal: true, // Bundle all dependencies
conditions: ['edge', 'worker'],
},
build: {
outDir: 'dist/edge',
rollupOptions: {
external: ['cloudflare:workers'],
},
},
},
},
})Accessing Environments in Plugins
Plugins can access the current environment via this.environment:
export function myPlugin(): Plugin {
return {
name: 'my-plugin',
transform(code, id) {
// Environment available in all hooks
const env = this.environment
if (env.name === 'ssr') {
// SSR-specific transform
return transformForSSR(code)
}
if (env.name === 'edge') {
// Edge-specific transform
return transformForEdge(code)
}
// Default client transform
return transformForClient(code)
},
configureServer(server) {
// Access specific environments
const ssrEnv = server.environments.ssr
const clientEnv = server.environments.client
},
}
}Per-Environment Plugins
Use perEnvironmentPlugin helper:
import { perEnvironmentPlugin } from 'vite'
// Plugin only for SSR environment
export const ssrOnlyPlugin = perEnvironmentPlugin('ssr-only', (environment) => {
if (environment.name !== 'ssr') {
return null // Don't apply to other environments
}
return {
transform(code, id) {
// SSR-only transformation
},
}
})Builder API
For coordinated multi-environment builds:
import { createBuilder } from 'vite'
async function build() {
const builder = await createBuilder({
environments: {
client: { build: { outDir: 'dist/client' } },
ssr: { build: { outDir: 'dist/server' } },
},
})
// Build all environments in parallel
await builder.build()
// Or build individually
await builder.build(builder.environments.client)
await builder.build(builder.environments.ssr)
// Cleanup
await builder.close()
}buildApp Hook (Vite 7)
Plugins can coordinate environment builds:
export function frameworkPlugin(): Plugin {
return {
name: 'framework-plugin',
// Order: 'pre' runs before builder.buildApp, 'post' after
buildApp: {
order: 'pre',
async handler(builder) {
// Pre-build setup
await prepareAssets()
// Build specific environments
await builder.build(builder.environments.client)
// Check if environment already built
if (!builder.environments.ssr.isBuilt) {
await builder.build(builder.environments.ssr)
}
},
},
}
}Environment Instance API
// In dev server
const server = await createServer()
const clientEnv = server.environments.client
const ssrEnv = server.environments.ssr
// Transform module in specific environment
const result = await ssrEnv.transformRequest('/src/app.js')
// Hot module handling
ssrEnv.hot.send({ type: 'custom', event: 'reload' })ModuleRunner (SSR)
Execute modules in the SSR environment:
const ssrEnv = server.environments.ssr
// Import module through runner
const { render } = await ssrEnv.runner.import('/src/entry-server.js')
// Execute render
const html = await render(url)Real-World: Cloudflare Workers
The Cloudflare Vite plugin demonstrates Environment API:
// Cloudflare plugin creates a custom environment
export default defineConfig({
plugins: [cloudflare()],
environments: {
// Cloudflare Workers environment
worker: {
resolve: {
conditions: ['workerd', 'worker', 'browser'],
},
build: {
outDir: 'dist/worker',
rollupOptions: {
external: ['cloudflare:workers'],
},
},
},
},
})Node.js Requirements
Vite 7 requires:
- Node.js 20.19+ or
- Node.js 22.12+
These versions support require(esm) without a flag, enabling ESM-only distribution.
Vite Library Mode
Building publishable npm packages.
Basic Library Config
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
import react from '@vitejs/plugin-react'
import dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
react(),
dts({ include: ['src'] }), // Generate .d.ts files
],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLib', // Global variable name for UMD
fileName: (format) => `my-lib.${format}.js`,
},
rolldownOptions: {
// Externalize dependencies that shouldn't be bundled
external: ['react', 'react-dom'],
output: {
// Global variables for UMD build
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
},
},
})Package.json Setup
{
"name": "my-lib",
"version": "1.0.0",
"type": "module",
"main": "./dist/my-lib.umd.js",
"module": "./dist/my-lib.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/my-lib.es.js",
"require": "./dist/my-lib.umd.js",
"types": "./dist/index.d.ts"
},
"./styles.css": "./dist/style.css"
},
"files": [
"dist"
],
"sideEffects": [
"**/*.css"
],
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"vite": "^7.0.0",
"vite-plugin-dts": "^4.0.0"
},
"scripts": {
"build": "vite build",
"dev": "vite"
}
}Multiple Entry Points
// vite.config.ts
export default defineConfig({
build: {
lib: {
entry: {
index: resolve(__dirname, 'src/index.ts'),
utils: resolve(__dirname, 'src/utils/index.ts'),
hooks: resolve(__dirname, 'src/hooks/index.ts'),
},
formats: ['es', 'cjs'],
},
rolldownOptions: {
external: ['react', 'react-dom'],
},
},
})With matching exports:
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.js",
"require": "./dist/utils.cjs",
"types": "./dist/utils.d.ts"
},
"./hooks": {
"import": "./dist/hooks.js",
"require": "./dist/hooks.cjs",
"types": "./dist/hooks.d.ts"
}
}
}CSS Handling
// vite.config.ts
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
},
cssCodeSplit: false, // Bundle all CSS into one file
rolldownOptions: {
external: ['react', 'react-dom'],
},
},
})For CSS modules with TypeScript:
// vite.config.ts
export default defineConfig({
css: {
modules: {
localsConvention: 'camelCase',
},
},
})Preserving File Structure
// vite.config.ts
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['es'],
},
rolldownOptions: {
external: ['react', 'react-dom'],
output: {
preserveModules: true, // Keep file structure
preserveModulesRoot: 'src',
entryFileNames: '[name].js',
},
},
},
})TypeScript Declarations
Install and configure vite-plugin-dts:
npm install -D vite-plugin-dtsimport dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
dts({
include: ['src'],
exclude: ['src/**/*.test.ts', 'src/**/*.stories.tsx'],
rollupTypes: true, // Bundle .d.ts files
}),
],
})Development Testing
// vite.config.ts
export default defineConfig(({ command }) => ({
plugins: [react()],
// Only apply library config for build
...(command === 'build' && {
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
},
},
}),
}))Pre-publish Checklist
# 1. Build
npm run build
# 2. Check output
ls -la dist/
# 3. Verify types
cat dist/index.d.ts
# 4. Test locally
cd ../test-project
npm link ../my-lib
# 5. Publish
npm publishVite Plugin Development
Creating custom Vite plugins.
Basic Plugin Structure
import type { Plugin } from 'vite'
export function myPlugin(options?: { debug?: boolean }): Plugin {
return {
name: 'my-plugin', // Required: unique plugin name
// Hooks listed in execution order
config(config, env) {
// Modify config before resolution
return {
define: {
__MY_PLUGIN__: JSON.stringify(true),
},
}
},
configResolved(config) {
// Called once config is fully resolved
if (options?.debug) {
console.log('Resolved config:', config)
}
},
configureServer(server) {
// Add middleware or modify dev server
server.middlewares.use((req, res, next) => {
if (req.url === '/my-plugin-endpoint') {
res.end('Hello from plugin')
return
}
next()
})
},
buildStart() {
// Called at build start
},
resolveId(id) {
// Custom module resolution
if (id === 'virtual:my-module') {
return '\0virtual:my-module'
}
},
load(id) {
// Load virtual modules
if (id === '\0virtual:my-module') {
return `export const data = ${JSON.stringify({ version: '1.0' })}`
}
},
transform(code, id) {
// Transform individual modules
if (id.endsWith('.custom')) {
return {
code: transformCustomFormat(code),
map: null,
}
}
},
buildEnd() {
// Called after build completes
},
closeBundle() {
// Cleanup after bundle is written
},
}
}Hook Execution Order
1. config - Modify/extend config
2. configResolved - Access final config
3. configureServer - Dev server setup (dev only)
4. buildStart - Build begins
5. resolveId - Resolve import paths
6. load - Load module content
7. transform - Transform module code
8. buildEnd - Build complete
9. closeBundle - Bundle writtenVirtual Modules
Generate modules at runtime:
const virtualModuleId = 'virtual:my-data'
const resolvedVirtualModuleId = '\0' + virtualModuleId
export function virtualDataPlugin(data: Record<string, unknown>): Plugin {
return {
name: 'virtual-data',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export default ${JSON.stringify(data)}`
}
},
}
}
// Usage in app:
// import data from 'virtual:my-data'Transform Hook Patterns
export function transformPlugin(): Plugin {
return {
name: 'transform-plugin',
transform(code, id) {
// Only transform specific files
if (!id.endsWith('.special.ts')) {
return null
}
// Return transformed code
return {
code: code.replace(/PLACEHOLDER/g, 'REPLACED'),
map: null, // Or source map
}
},
}
}Using Rollup Plugins
Many Rollup plugins work in Vite:
import { defineConfig } from 'vite'
import commonjs from '@rollup/plugin-commonjs'
export default defineConfig({
plugins: [
// Rollup plugins work in Vite
commonjs(),
],
})Environment-Aware Plugins (Vite 7)
export function envAwarePlugin(): Plugin {
return {
name: 'env-aware',
transform(code, id) {
// Access current environment
const env = this.environment
if (env.name === 'ssr') {
return transformForServer(code)
}
return transformForClient(code)
},
}
}Applying Plugins Conditionally
export function conditionalPlugin(): Plugin {
let isDev: boolean
return {
name: 'conditional',
configResolved(config) {
isDev = config.command === 'serve'
},
transform(code, id) {
if (isDev) {
// Dev-only transformation
return injectDevHelpers(code)
}
return null
},
}
}Plugin Ordering
export function orderedPlugin(): Plugin {
return {
name: 'ordered',
enforce: 'pre', // Run before core plugins
// enforce: 'post' // Run after core plugins
}
}Hot Module Replacement
export function hmrPlugin(): Plugin {
return {
name: 'hmr-plugin',
handleHotUpdate({ file, server }) {
if (file.endsWith('.custom')) {
// Custom HMR handling
server.ws.send({
type: 'custom',
event: 'custom-update',
data: { file },
})
return [] // Prevent default HMR
}
},
}
}
// Client-side HMR handling:
// if (import.meta.hot) {
// import.meta.hot.on('custom-update', (data) => {
// console.log('Custom file updated:', data.file)
// })
// }TypeScript Declarations
// Type declarations for virtual module
declare module 'virtual:my-data' {
const data: { version: string }
export default data
}Vite SSR Configuration
Server-side rendering setup for development and production.
Project Structure
project/
├── index.html
├── src/
│ ├── main.tsx # Client entry
│ ├── entry-client.tsx # Client-specific setup
│ ├── entry-server.tsx # Server render function
│ └── App.tsx
├── server.js # Production server
└── vite.config.tsEntry Points
Client Entry (entry-client.tsx)
import { hydrateRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
hydrateRoot(
document.getElementById('root')!,
<BrowserRouter>
<App />
</BrowserRouter>
)Server Entry (entry-server.tsx)
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom/server'
import App from './App'
export function render(url: string) {
return renderToString(
<StaticRouter location={url}>
<App />
</StaticRouter>
)
}Development Server
// server-dev.js
import fs from 'node:fs'
import path from 'node:path'
import express from 'express'
import { createServer as createViteServer } from 'vite'
async function createServer() {
const app = express()
// Create Vite server in middleware mode
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'custom',
})
// Use Vite's middleware
app.use(vite.middlewares)
app.use('*', async (req, res, next) => {
const url = req.originalUrl
try {
// 1. Read index.html
let template = fs.readFileSync(
path.resolve('index.html'),
'utf-8'
)
// 2. Apply Vite HTML transforms (HMR client, etc.)
template = await vite.transformIndexHtml(url, template)
// 3. Load server entry with HMR support
const { render } = await vite.ssrLoadModule('/src/entry-server.tsx')
// 4. Render app HTML
const appHtml = await render(url)
// 5. Inject into template
const html = template.replace('<!--ssr-outlet-->', appHtml)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite.ssrFixStacktrace(e as Error)
next(e)
}
})
app.listen(5173)
}
createServer()Production Build
Build Scripts (package.json)
{
"scripts": {
"dev": "node server-dev.js",
"build": "npm run build:client && npm run build:server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/entry-server.tsx",
"preview": "node server-prod.js"
}
}Production Server
// server-prod.js
import fs from 'node:fs'
import path from 'node:path'
import express from 'express'
const app = express()
// Serve static assets
app.use(express.static('dist/client', { index: false }))
app.use('*', async (req, res) => {
const url = req.originalUrl
// Read pre-built template
const template = fs.readFileSync(
path.resolve('dist/client/index.html'),
'utf-8'
)
// Import pre-built server bundle
const { render } = await import('./dist/server/entry-server.js')
const appHtml = await render(url)
const html = template.replace('<!--ssr-outlet-->', appHtml)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})
app.listen(3000)Vite Config for SSR
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
// Shared build options
sourcemap: true,
},
// SSR-specific options
ssr: {
// Externalize dependencies (not bundled)
external: ['express'],
// Force bundle specific packages
noExternal: ['some-ssr-unfriendly-package'],
},
})Vite 7 Environment API (Alternative)
export default defineConfig({
environments: {
client: {
build: {
outDir: 'dist/client',
manifest: true,
},
},
ssr: {
build: {
outDir: 'dist/server',
ssr: 'src/entry-server.tsx',
target: 'node20',
},
},
},
})Streaming SSR
// entry-server.tsx with streaming
import { renderToPipeableStream } from 'react-dom/server'
export function render(url: string, res: Response) {
const { pipe, abort } = renderToPipeableStream(
<StaticRouter location={url}>
<App />
</StaticRouter>,
{
onShellReady() {
res.setHeader('Content-Type', 'text/html')
pipe(res)
},
onError(error) {
console.error(error)
},
}
)
setTimeout(abort, 10000) // Timeout
}index.html Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SSR App</title>
</head>
<body>
<div id="root"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.tsx"></script>
</body>
</html>Vite 8: Rolldown-Powered Builds
Vite 8 replaces the esbuild+Rollup pipeline with Rolldown, a Rust-based unified bundler delivering dramatic performance improvements.
Migration Options
Option 1: Direct Upgrade to Vite 8
npm install vite@8Best for: New projects, smaller codebases, teams ready to adopt cutting-edge tooling.
Option 2: Gradual Migration with rolldown-vite
npm install rolldown-vite// vite.config.ts - swap import only
import { defineConfig } from 'rolldown-vite' // instead of 'vite'
export default defineConfig({
// Existing config works unchanged
})Best for: Large production apps, risk-averse teams, testing Rolldown before full commitment.
Performance Benchmarks
Real-world improvements from production deployments:
| Metric | Before (Vite 7) | After (Vite 8) | Improvement |
|---|---|---|---|
| Linear build time | 46s | 6s | 7.7x faster |
| Dev server startup | ~3s | ~1s | 3x faster |
| HMR updates | ~100ms | ~60ms | 40% faster |
| Memory usage | ~800MB | ~400MB | 50% reduction |
advancedChunks (Replaces manualChunks)
Vite 8 introduces advancedChunks with declarative grouping, priority control, and size constraints:
export default defineConfig({
build: {
rolldownOptions: {
output: {
advancedChunks: {
groups: [
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 20,
minSize: 20000,
maxSize: 250000,
},
{
name: 'ui-vendor',
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
priority: 15,
minShareCount: 2,
},
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 10,
maxSize: 500000,
},
],
},
},
},
},
})Key Differences from manualChunks:
| Feature | manualChunks | advancedChunks |
|---|---|---|
| Syntax | Function or object | Declarative groups array |
| Priority control | Manual ordering | Explicit priority field |
| Size constraints | None | minSize, maxSize |
| Shared module handling | Manual | minShareCount |
| Regex support | Via function | Native test field |
When to Use Vite 7 vs Vite 8
| Scenario | Recommendation | Reason |
|---|---|---|
| New greenfield project | Vite 8 | Latest features, best performance |
| Existing stable production app | Vite 7 (evaluate 8) | Stability, proven track record |
| Build times > 30s | Vite 8 | Significant improvement |
| Complex plugin ecosystem | Vite 7 (test 8) | Some plugins may need updates |
| Monorepo with many packages | Vite 8 | Memory and speed benefits |
| Enterprise with strict stability | Vite 7 | LTS-style support |
Full Bundle Mode (Upcoming)
Vite 8.1+ will introduce optional Full Bundle Mode for production builds:
export default defineConfig({
build: {
// Preview API - may change
fullBundleMode: true,
},
})Benefits: Single unified bundle (no code splitting), optimal for small apps/libraries/embedded contexts, eliminates chunk loading overhead, better for offline-first applications.
Breaking Changes from Vite 7
Config Renames
build.rollupOptions → build.rolldownOptions // auto-converted w/ deprecation
worker.rollupOptions → worker.rolldownOptions
transformWithEsbuild → transformWithOxc
esbuild.jsx → oxc.jsx
esbuild.define → oxc.definePlugin API Changes
// Plugins converting non-JS to JS MUST add moduleType
transform(code, id) {
return { code: transformedCode, moduleType: 'js' } // NEW — required in v8
}Removed hooks: shouldTransformCachedModule, resolveImportMeta, renderDynamicImport, resolveFileUrl
Other Breaking Changes
manualChunksobject form removed — use function form oradvancedChunks'system'/'amd'output formats removedimport.meta.hot.accept(url)→accept(id)(relative module id, not full URL)- Lightning CSS is now standard (replaces esbuild for CSS minification)
- Browser target bump: Chrome 107→111, Firefox 104→114, Safari 16.0→16.4
@vitejs/plugin-reactv6: Babel removed, Oxc handles transforms
New Config Options
export default {
devtools: true, // built-in Vite Devtools
resolve: { tsconfigPaths: true }, // auto-resolve tsconfig paths
server: { forwardConsole: true }, // forward browser console to CLI
}Oxc Integration Benefits
Rolldown is built on Oxc (Oxidation Compiler), providing:
- Parsing: 3x faster than SWC, 100x faster than Babel
- Transformation: Unified transform pipeline (replaces Babel in plugin-react v6)
- Tree-shaking: More aggressive dead code elimination
- Scope hoisting: Better than Rollup's implementation
- Minification: Oxc minifier (replaces esbuild minifier)
Migration Checklist
[ ] build.rollupOptions → build.rolldownOptions
[ ] Plugin transform() returning JS: add moduleType: 'js'
[ ] Remove plugins using deleted hooks (shouldTransformCachedModule, etc.)
[ ] manualChunks object form → function form or advancedChunks
[ ] Remove 'system'/'amd' format usage
[ ] transformWithEsbuild → transformWithOxc
[ ] @vitejs/plugin-react → v6 (Babel config changes if using React Compiler)
[ ] Browser target: Chrome 111+, Firefox 114+, Safari 16.4+
[ ] Test with rolldown-vite first if risk-averse
[ ] Update CI/CD build time expectations
[ ] Verify source maps work correctlyStatus: Vite 8.0.0 stable as of Mar 12, 2026. Recommended for all new projects; evaluate for existing production apps.
Rule Categories
1. Build Optimization (vite) — HIGH — 2 rules
Bundle splitting and library publishing configuration.
vite-advanced-chunks.md— Manual chunk splitting, vendor separation, avoiding circular chunksvite-lib-config.md— Library mode with proper externals and dual ESM/CJS output
2. Plugin & Environment (vite) — MEDIUM — 2 rules
Plugin development patterns and multi-environment configuration.
vite-plugin-hooks.md— Hook execution order, enforce/apply modifiersvite-environments.md— Environment-specific config for client, SSR, and custom targets
[Rule Name]
[Brief description — 1-2 sentences.]
Incorrect:
// Bad patternCorrect:
// Good patternKey rules:
- [Rule 1]
- [Rule 2]
- [Rule 3]
Reference: [link]
Vite: Chunk Optimization
Use advancedChunks (Vite 8+) or manualChunks (Vite 7) to split vendor and application code into separate, cacheable chunks. Assign priorities to resolve conflicts and use maxSize to prevent oversized bundles.
Incorrect:
// No chunk config — everything in a single monolithic bundle
export default defineConfig({
build: { rolldownOptions: {} }, // No advancedChunks or manualChunks
})Correct (Vite 8+ — advancedChunks):
export default defineConfig({
build: {
rolldownOptions: {
output: {
advancedChunks: {
groups: [
{
name: 'react-vendor',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 30,
minSize: 20000,
maxSize: 200000,
},
{
name: 'router',
test: /[\\/]node_modules[\\/](react-router|react-router-dom)[\\/]/,
priority: 25,
},
{
name: 'vendor',
test: /[\\/]node_modules[\\/]/,
priority: 5,
maxSize: 500000, // Auto-splits into vendor, vendor-1, etc.
},
],
},
},
},
},
})Correct (Vite 7 — manualChunks):
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router': ['react-router-dom'],
},
},
},
},
})Key rules:
- Separate framework deps into a dedicated vendor chunk with the highest priority so it caches independently from app code.
- Add a catch-all
vendorgroup at lowest priority withmaxSizeto prevent oversized bundles. - Use
minShareCountfor shared UI libraries — only extract when imported by 2+ routes. - When migrating Vite 7 to 8, convert package arrays to regex and make implicit ordering explicit via
priority. manualChunksis deprecated in Vite 8 — preferadvancedChunksfor new projects.
Reference: references/chunk-optimization.md
Vite: Environment API
Vite 6+ treats environments (client, SSR, edge) as first-class concepts, each with its own module graph, config, plugin pipeline, and build output. Use environments config instead of mixing targets in top-level config.
Incorrect:
// Flat config — SSR and client share the same target and externals
export default defineConfig({
build: {
outDir: 'dist',
target: 'node20', // Wrong for client!
rolldownOptions: { external: ['cloudflare:workers'] }, // Wrong for client!
},
ssr: { noExternal: ['some-package'] }, // Legacy SSR config
})Correct:
export default defineConfig({
build: { sourcemap: false }, // Shared config
environments: {
client: {
build: { outDir: 'dist/client', manifest: true },
},
ssr: {
build: {
outDir: 'dist/server',
target: 'node20',
rolldownOptions: { output: { format: 'esm' } },
},
},
edge: {
resolve: { noExternal: true, conditions: ['edge', 'worker'] },
build: {
outDir: 'dist/edge',
rolldownOptions: { external: ['cloudflare:workers'] },
},
},
},
})Correct — environment-aware plugins:
export function envAwarePlugin(): Plugin {
return {
name: 'env-aware',
transform(code, id) {
const env = this.environment // Available in Vite 6+
if (env.name === 'ssr') return transformForSSR(code)
if (env.name === 'edge') return transformForEdge(code)
return transformForClient(code)
},
}
}Key rules:
- Put only shared settings at the top level; environment-specific settings go under
environments.client,environments.ssr, etc. - Each environment gets its own module graph and build output — never share
outDirbetween environments. - Use
this.environmentin hooks to branch per environment; useperEnvironmentPlugin()to skip environments entirely. - For edge runtimes, set
resolve.noExternal: trueandresolve.conditionsfor edge-specific package exports. - Vite 7 requires Node.js 20.19+ or 22.12+ for
require(esm)support.
Reference: references/environment-api.md
Vite: Library Mode
Configure build.lib with proper entry points, externalize peer dependencies, and provide dual ESM/CJS output with TypeScript declarations.
Incorrect:
// Bundles React into the library — consumers get duplicate React
export default defineConfig({
build: {
lib: { entry: resolve(__dirname, 'src/index.ts'), formats: ['es'] },
// Missing rolldownOptions.external — peer deps are bundled
},
})Correct:
import { defineConfig } from 'vite'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [dts({ include: ['src'], rollupTypes: true })],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLib',
fileName: (format) => `my-lib.${format}.js`,
},
rolldownOptions: {
external: ['react', 'react-dom'],
output: {
globals: { react: 'React', 'react-dom': 'ReactDOM' },
},
},
},
}){
"name": "my-lib",
"type": "module",
"main": "./dist/my-lib.umd.js",
"module": "./dist/my-lib.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/my-lib.es.js",
"require": "./dist/my-lib.umd.js",
"types": "./dist/index.d.ts"
},
"./styles.css": "./dist/style.css"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"sideEffects": ["**/*.css"]
}Key rules:
- Always externalize peer dependencies via
rolldownOptions.external— never bundle them. - Provide dual formats: ESM (
module) for bundlers and UMD/CJS (main) for legacy consumers; useexportsmap. - Generate TypeScript declarations with
vite-plugin-dts; set"types"in top-level and eachexportsentry. - Mark CSS in
"sideEffects"so bundlers preserve styles during tree-shaking. - For multi-entry libraries, use an object
entryand match keys toexportssubpaths.
Reference: references/library-mode.md
Vite: Plugin Hooks
Vite plugins follow a strict hook execution order inherited from Rollup. Choose the correct hook for each task and use enforce/apply to control when a plugin runs.
Hook execution order:
1. config — Modify config before resolution
2. configResolved — Access final config (read-only)
3. configureServer — Dev server setup (dev only)
4. buildStart — Build begins
5. resolveId — Resolve import paths to module IDs
6. load — Provide module content for a resolved ID
7. transform — Transform loaded module source code
8. buildEnd / closeBundle — CleanupIncorrect:
// Wrong: transform can't create modules — virtual modules need resolveId + load
export function brokenVirtualPlugin(): Plugin {
return {
name: 'broken-virtual',
transform(code, id) {
if (id === 'virtual:my-data') {
return `export default ${JSON.stringify({ key: 'value' })}`
}
},
}
}Correct:
const VIRTUAL_ID = 'virtual:my-data'
const RESOLVED_ID = '\0' + VIRTUAL_ID
export function virtualDataPlugin(data: Record<string, unknown>): Plugin {
return {
name: 'virtual-data',
resolveId(id) {
if (id === VIRTUAL_ID) return RESOLVED_ID
},
load(id) {
if (id === RESOLVED_ID) return `export default ${JSON.stringify(data)}`
},
}
}Correct — enforce and apply modifiers:
export function preProcessPlugin(): Plugin {
return {
name: 'pre-process',
enforce: 'pre', // Run BEFORE core Vite plugins
apply: 'build', // Only during vite build (not dev)
transform(code, id) {
if (!id.endsWith('.special.ts')) return null
return { code: code.replace(/PLACEHOLDER/g, 'REPLACED'), map: null }
},
}
}Key rules:
- Use
resolveId+loadfor virtual modules;transformonly modifies already-loaded source. - Prefix resolved virtual IDs with
\0to exclude them from other plugins and filesystem resolution. - Set
enforce: 'pre'to run before core plugins,enforce: 'post'to run after. - Set
apply: 'build'orapply: 'serve'to restrict a plugin to one mode. - Access
this.environmentin hooks (Vite 6+) for environment-specific transforms.
Reference: references/plugin-development.md
// Custom Vite Plugin Template
// Copy and customize for your project
import type { Plugin, ResolvedConfig } from 'vite'
// =========================================
// Plugin Options Interface
// =========================================
interface MyPluginOptions {
debug?: boolean
include?: string[]
exclude?: string[]
}
// =========================================
// Main Plugin Function
// =========================================
export function myPlugin(options: MyPluginOptions = {}): Plugin {
// Plugin state
let config: ResolvedConfig
let isDev: boolean
// Virtual module handling
const virtualModuleId = 'virtual:my-plugin-data'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
// Required: unique plugin name
name: 'my-plugin',
// Optional: run before/after core plugins
// enforce: 'pre' | 'post',
// =========================================
// Configuration Hooks
// =========================================
// Modify config before resolution
config(userConfig, env) {
if (options.debug) {
console.log('[my-plugin] config hook', env.mode)
}
// Return config modifications
return {
define: {
__MY_PLUGIN_VERSION__: JSON.stringify('1.0.0'),
},
}
},
// Access resolved config
configResolved(resolvedConfig) {
config = resolvedConfig
isDev = config.command === 'serve'
if (options.debug) {
console.log('[my-plugin] resolved config', {
root: config.root,
mode: config.mode,
command: config.command,
})
}
},
// =========================================
// Dev Server Hooks
// =========================================
// Configure dev server
configureServer(server) {
// Add custom middleware
server.middlewares.use((req, res, next) => {
if (req.url === '/__my-plugin') {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ status: 'ok' }))
return
}
next()
})
// Log startup
if (options.debug) {
console.log('[my-plugin] dev server configured')
}
},
// =========================================
// Build Hooks
// =========================================
buildStart() {
if (options.debug) {
console.log('[my-plugin] build started')
}
},
// =========================================
// Module Resolution Hooks
// =========================================
// Custom module resolution
resolveId(id) {
// Handle virtual module
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
// Handle custom file extension
if (id.endsWith('.custom')) {
return id
}
return null // Let other plugins handle it
},
// Load module content
load(id) {
// Load virtual module
if (id === resolvedVirtualModuleId) {
return `
export const version = '1.0.0'
export const isDev = ${isDev}
export const config = ${JSON.stringify(options)}
`
}
return null
},
// =========================================
// Transform Hooks
// =========================================
// Transform module code
transform(code, id) {
// Skip excluded files
if (options.exclude?.some((pattern) => id.includes(pattern))) {
return null
}
// Only transform included files
if (options.include && !options.include.some((pattern) => id.includes(pattern))) {
return null
}
// Example: Replace placeholders
if (code.includes('__TIMESTAMP__')) {
return {
code: code.replace(/__TIMESTAMP__/g, Date.now().toString()),
map: null, // Or generate source map
}
}
return null
},
// =========================================
// HMR Hooks
// =========================================
handleHotUpdate({ file, server }) {
if (file.endsWith('.custom')) {
// Custom HMR handling
server.ws.send({
type: 'custom',
event: 'my-plugin:update',
data: { file },
})
return [] // Prevent default HMR
}
},
// =========================================
// Build End Hooks
// =========================================
buildEnd(error) {
if (error) {
console.error('[my-plugin] build failed', error)
} else if (options.debug) {
console.log('[my-plugin] build completed')
}
},
closeBundle() {
// Cleanup after bundle is written
if (options.debug) {
console.log('[my-plugin] bundle closed')
}
},
}
}
// =========================================
// TypeScript Declarations
// =========================================
declare module 'virtual:my-plugin-data' {
export const version: string
export const isDev: boolean
export const config: Record<string, unknown>
}
// =========================================
// Usage
// =========================================
/*
// vite.config.ts
import { defineConfig } from 'vite'
import { myPlugin } from './plugins/my-plugin'
export default defineConfig({
plugins: [
myPlugin({
debug: true,
include: ['src/**'],
exclude: ['node_modules'],
}),
],
})
// In your app:
import { version, isDev } from 'virtual:my-plugin-data'
console.log('Plugin version:', version, 'Dev mode:', isDev)
*/
// Vite Library Mode Configuration Template
// For building publishable npm packages
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import dts from 'vite-plugin-dts'
import { resolve } from 'path'
export default defineConfig({
plugins: [
react(),
// Generate TypeScript declarations
dts({
include: ['src'],
exclude: ['src/**/*.test.ts', 'src/**/*.stories.tsx'],
rollupTypes: true, // Bundle all declarations
}),
],
build: {
// Library mode configuration
lib: {
// Entry point
entry: resolve(__dirname, 'src/index.ts'),
// Global variable name for UMD/IIFE builds
name: 'MyLib',
// Output file naming
fileName: (format) => `my-lib.${format}.js`,
// Output formats
// formats: ['es', 'cjs', 'umd', 'iife'],
},
rollupOptions: {
// Externalize peer dependencies
external: ['react', 'react-dom', 'react/jsx-runtime'],
output: {
// Global variables for UMD build
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'react/jsx-runtime': 'jsxRuntime',
},
// Preserve export names
exports: 'named',
},
},
// Build optimizations
sourcemap: true,
minify: 'esbuild',
// CSS handling
cssCodeSplit: false, // Single CSS file
},
})
// =========================================
// Multiple Entry Points Configuration
// =========================================
/*
export default defineConfig({
build: {
lib: {
entry: {
index: resolve(__dirname, 'src/index.ts'),
utils: resolve(__dirname, 'src/utils/index.ts'),
hooks: resolve(__dirname, 'src/hooks/index.ts'),
components: resolve(__dirname, 'src/components/index.ts'),
},
formats: ['es', 'cjs'],
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
// Preserve module structure
preserveModules: true,
preserveModulesRoot: 'src',
entryFileNames: '[name].js',
},
},
},
})
*/
// =========================================
// Package.json Configuration
// =========================================
/*
{
"name": "my-lib",
"version": "1.0.0",
"type": "module",
"main": "./dist/my-lib.cjs.js",
"module": "./dist/my-lib.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/my-lib.es.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/my-lib.cjs.js"
}
},
"./styles.css": "./dist/style.css"
},
"files": [
"dist"
],
"sideEffects": [
"**\/*.css"
],
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"typescript": "^5.0.0",
"vite": "^7.0.0",
"vite-plugin-dts": "^4.0.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build"
}
}
*/
// =========================================
// Exports for Multiple Entry Points
// =========================================
/*
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.js",
"require": "./dist/utils.cjs",
"types": "./dist/utils.d.ts"
},
"./hooks": {
"import": "./dist/hooks.js",
"require": "./dist/hooks.cjs",
"types": "./dist/hooks.d.ts"
},
"./components": {
"import": "./dist/components.js",
"require": "./dist/components.cjs",
"types": "./dist/components.d.ts"
}
}
}
*/
// Vite 7 Multi-Environment Configuration Template
// Copy and customize for your project
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
// Shared configuration (inherited by all environments)
build: {
sourcemap: process.env.NODE_ENV !== 'production',
target: 'baseline-widely-available', // Vite 7 default
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
// Environment-specific configuration
environments: {
// =========================================
// Client Environment (Browser)
// =========================================
client: {
build: {
outDir: 'dist/client',
manifest: true, // Generate manifest for SSR
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router': ['react-router-dom'],
},
},
},
},
},
// =========================================
// SSR Environment (Node.js)
// =========================================
ssr: {
build: {
outDir: 'dist/server',
ssr: 'src/entry-server.tsx',
target: 'node20',
rollupOptions: {
output: {
format: 'esm',
},
},
},
// SSR-specific externals
resolve: {
// Don't bundle Node.js built-ins
external: ['fs', 'path', 'http', 'stream'],
},
},
// =========================================
// Edge Environment (Workers)
// =========================================
edge: {
resolve: {
// Bundle all dependencies for edge
noExternal: true,
// Prioritize edge-compatible exports
conditions: ['edge', 'worker', 'browser', 'import'],
},
build: {
outDir: 'dist/edge',
rollupOptions: {
// Externalize platform-specific APIs
external: ['cloudflare:workers', 'node:*'],
output: {
format: 'esm',
// Single file output for edge
inlineDynamicImports: true,
},
},
},
},
},
})
// =========================================
// Usage with createBuilder (build script)
// =========================================
/*
// build.ts
import { createBuilder } from 'vite'
import config from './vite.config'
async function build() {
const builder = await createBuilder(config)
// Build all environments
await builder.build()
// Or build individually
// await builder.build(builder.environments.client)
// await builder.build(builder.environments.ssr)
// await builder.build(builder.environments.edge)
await builder.close()
}
build()
*/
// =========================================
// Package.json scripts
// =========================================
/*
{
"scripts": {
"dev": "vite",
"build": "vite build",
"build:client": "vite build --environment client",
"build:ssr": "vite build --environment ssr",
"build:edge": "vite build --environment edge",
"preview": "vite preview"
}
}
*/
{
"skill": "vite-advanced",
"version": "1.0.0",
"testCases": [
{
"id": "basic-i-need-to-configure",
"rule": "",
"query": "I need to configure Vite to build for three targets: browser client, Node.js SSR server, and Cloudflare edge workers. How do I set up multi-environment builds?",
"expectedBehavior": [
"Claude uses the Vite 7 Environment API with the environments config key",
"Configures separate client, ssr, and edge environment blocks",
"Sets appropriate build targets (e.g., node20 for SSR, edge/worker conditions for edge)",
"Uses separate outDir for each environment (dist/client, dist/server, dist/edge)",
"Mentions that each environment has its own module graph"
]
},
{
"id": "edge-our-vite-7-build",
"rule": "",
"query": "Our Vite 7 build takes 46 seconds and we're considering Vite 8 with Rolldown. Show me how to migrate manualChunks to the new advancedChunks API.",
"expectedBehavior": [
"Claude explains the migration from manualChunks to advancedChunks with declarative groups",
"Provides advancedChunks config with priority, minSize, maxSize, and minShareCount fields",
"Mentions the rolldown-vite gradual migration path as an option",
"References performance benchmarks (7.7x build speed improvement)",
"Includes the migration checklist items for verifying a complete upgrade"
]
},
{
"id": "negative-write-a-react-component",
"rule": "",
"query": "Write a React component that displays a data table with sorting and filtering",
"expectedBehavior": [
"Claude does NOT invoke the vite-advanced skill",
"Implements a standard React data table component",
"Uses UI component patterns without build configuration concerns"
]
},
{
"id": "vite-advanced-chunks",
"rule": "vite-advanced-chunks",
"query": "Split our Vite 8 build into separate vendor chunks for React, router, and remaining dependencies with size limits.",
"expectedBehavior": [
"Uses advancedChunks groups with regex test patterns to match node_modules packages",
"Assigns priority values to resolve conflicts between overlapping chunk groups",
"Sets maxSize on the catch-all vendor group to auto-split oversized bundles",
"Creates a dedicated react-vendor chunk for react, react-dom, and scheduler at highest priority",
"Recommends minShareCount for shared UI libraries imported by multiple routes"
]
},
{
"id": "vite-environments",
"rule": "vite-environments",
"query": "Configure separate Vite environments for client browser, Node.js SSR, and Cloudflare edge worker builds.",
"expectedBehavior": [
"Uses the environments config key with separate client, ssr, and edge blocks",
"Puts only shared settings at the top level and environment-specific settings under each block",
"Sets separate outDir for each environment to prevent build output conflicts",
"Configures resolve.noExternal true and edge conditions for the edge runtime environment",
"References this.environment in plugin hooks for environment-aware transformations"
]
},
{
"id": "vite-lib-config",
"rule": "vite-lib-config",
"query": "Configure Vite to build a React component library with proper externals, dual ESM/CJS output, and TypeScript declarations.",
"expectedBehavior": [
"Externalizes peer dependencies like react and react-dom via rollupOptions.external",
"Provides dual format output with ESM for bundlers and UMD or CJS for legacy consumers",
"Uses vite-plugin-dts to generate TypeScript declaration files alongside build output",
"Configures package.json exports map with import, require, and types subpath entries",
"Marks CSS files in sideEffects to preserve styles during consumer tree-shaking"
]
},
{
"id": "vite-plugin-hooks",
"rule": "vite-plugin-hooks",
"query": "Create a Vite plugin with a virtual module that injects build-time data and only runs during production builds.",
"expectedBehavior": [
"Uses resolveId and load hooks together for virtual module creation instead of transform",
"Prefixes resolved virtual module IDs with the null byte character to exclude from filesystem",
"Sets apply to build to restrict the plugin to vite build mode only",
"Uses enforce pre or post to control plugin execution order relative to core plugins",
"Returns valid JavaScript module source from the load hook with JSON serialized data"
]
}
]
}