Loading...
Loading...
Vite-specific bundle optimization patterns. Use when configuring builds, code splitting, managing dependencies, or troubleshooting slow Vite builds.
npx skill4agent add patternsdev/skills vite-bundle-optimizationnpx vite-bundle-visualizernpx vite-bundle-visualizerindex.tsimport { Button, TextField } from '@/components'
// Loads ALL components in the barrel, even unused ones
import { Check, X, Menu } from 'lucide-react'
// Loads all 1,500+ icons (~2.8s in dev)import { Button } from '@/components/Button'
import { TextField } from '@/components/TextField'
import Check from 'lucide-react/dist/esm/icons/check'
import X from 'lucide-react/dist/esm/icons/x'
import Menu from 'lucide-react/dist/esm/icons/menu'vite-plugin-barrel// vite.config.ts
import barrel from 'vite-plugin-barrel'
export default defineConfig({
plugins: [
react(),
barrel({
packages: ['lucide-react', '@mui/material', '@mui/icons-material'],
}),
],
})lucide-react@mui/material@mui/icons-material@tabler/icons-reactreact-icons@radix-ui/react-*lodashdate-fnsrxjs// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {
// Core React — rarely changes
'vendor-react': ['react', 'react-dom'],
// Router — changes infrequently
'vendor-router': ['react-router-dom'],
// Data layer — changes occasionally
'vendor-query': ['@tanstack/react-query'],
// UI framework — changes with design updates
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
},
})manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('react-dom')) return 'vendor-react'
if (id.includes('react-router')) return 'vendor-router'
if (id.includes('@tanstack')) return 'vendor-query'
return 'vendor' // everything else
}
},React.lazy()import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}const Dashboard = lazy(() =>
import(/* webpackChunkName: "dashboard" */ './pages/Dashboard')
)import { lazy, Suspense, useState } from 'react'
const RichTextEditor = lazy(() => import('./components/RichTextEditor'))
const ChartPanel = lazy(() => import('./components/ChartPanel'))
function ArticlePage() {
const [editing, setEditing] = useState(false)
return (
<article>
<h1>Article Title</h1>
<p>Content visible immediately...</p>
{editing && (
<Suspense fallback={<EditorSkeleton />}>
<RichTextEditor />
</Suspense>
)}
<Suspense fallback={<ChartSkeleton />}>
<ChartPanel />
</Suspense>
</article>
)
}// main.tsx
import * as Sentry from '@sentry/react'
import posthog from 'posthog-js'
Sentry.init({ dsn: '...' })
posthog.init('...')// main.tsx — defer to idle time
function initThirdParty() {
import('@sentry/react').then(Sentry => {
Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN })
})
import('posthog-js').then(({ default: posthog }) => {
posthog.init(import.meta.env.VITE_POSTHOG_KEY)
})
}
if ('requestIdleCallback' in window) {
requestIdleCallback(initThirdParty)
} else {
setTimeout(initThirdParty, 2000)
}deferfunction loadScript(src: string) {
const script = document.createElement('script')
script.src = src
script.async = true
document.body.appendChild(script)
}function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
const preload = () => {
// Vite creates a module preload for dynamic imports
switch (to) {
case '/dashboard':
import('./pages/Dashboard')
break
case '/settings':
import('./pages/Settings')
break
}
}
return (
<Link to={to} onMouseEnter={preload} onFocus={preload}>
{children}
</Link>
)
}<link rel="modulepreload"><!-- Preload critical route chunks -->
<link rel="modulepreload" href="/assets/Home-abc123.js" /><link rel="modulepreload">node_modules// vite.config.ts
export default defineConfig({
optimizeDeps: {
// Force pre-bundle these (useful for CJS deps or deep imports)
include: [
'react',
'react-dom',
'react-router-dom',
'@tanstack/react-query',
'date-fns/format',
'date-fns/parseISO',
],
// Skip pre-bundling for these (already ESM, or causes issues)
exclude: ['@vite-pwa/assets-generator'],
},
})include// vite.config.ts
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
react(),
viteCompression({ algorithm: 'gzip' }),
viteCompression({ algorithm: 'brotliCompress' }),
],
}).gz.brnpx vite-bundle-visualizer{
"scripts": {
"build": "vite build",
"analyze": "vite build && npx vite-bundle-visualizer"
}
}node_modulesimport.meta.envimport.meta.env.*// This code is completely removed in production
if (import.meta.env.DEV) {
console.log('Debug info:', data)
window.__DEBUG_DATA__ = data
}
// Feature flags eliminated at build time
if (import.meta.env.VITE_FEATURE_NEW_DASHBOARD === 'true') {
// Only included when flag is set
initNewDashboard()
}.env# .env.production
VITE_FEATURE_NEW_DASHBOARD=true
VITE_API_URL=https://api.example.com// vite.config.ts
export default defineConfig({
build: {
assetsInlineLimit: 4096, // Inline assets < 4KB as base64
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
// Organize assets by type
if (/\.(png|jpe?g|gif|svg|webp|avif)$/.test(assetInfo.name ?? '')) {
return 'images/[name]-[hash][extname]'
}
if (/\.(woff2?|ttf|eot)$/.test(assetInfo.name ?? '')) {
return 'fonts/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
},
},
},
},
})vite-plugin-image-optimizerimport { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
export default defineConfig({
plugins: [
react(),
ViteImageOptimizer({
png: { quality: 80 },
jpeg: { quality: 80 },
webp: { quality: 80 },
}),
],
})server.proxy// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
},
'/auth': {
target: 'http://localhost:3001',
changeOrigin: true,
},
// WebSocket support for real-time features
'/ws': {
target: 'ws://localhost:3001',
ws: true,
},
},
},
})fetch('/api/users')vite-plugin-pwavite-plugin-pwa// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.svg', 'robots.txt'],
manifest: {
name: 'My App',
short_name: 'App',
theme_color: '#ffffff',
icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
runtimeCaching: [
{
urlPattern: /^https:\/\/api\./,
handler: 'NetworkFirst',
options: { cacheName: 'api-cache', expiration: { maxEntries: 50 } },
},
],
},
}),
],
})registerType: 'autoUpdate'registerType: 'prompt'// Button.module.css → automatically scoped
import styles from './Button.module.css'
function Button({ children }: { children: React.ReactNode }) {
return <button className={styles.primary}>{children}</button>
}// vite.config.ts — no plugin needed, Tailwind uses PostCSS
// Just install tailwindcss and add postcss.config.js
// postcss.config.js
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}useMemouseCallbackReact.memonpm install -D babel-plugin-react-compiler// vite.config.ts
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
}),
],
})useMemouseCallbackReact.memo'use memo'