Skip to content
OpenSmartRoute
Skillv1.0.0

vite

Use when building or configuring Vite 6/7 projects — dev server, production builds, plugins system, SSR, library mode, HMR, environment variables, build optimization, Vitest testing, and migration fro

by bach619(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from bach619/sc_auditor (.opencode/skills/vite/SKILL.md). Install upstream with npx skills add bach619/sc_auditor --skill vite. Copyright stays with the author.

Skill: vite

Vite Mastery — Dev Server, Build Optimization, Plugins, SSR, and Production Patterns

Core Philosophy

Vite is a build tool that leverages native ES modules during development and Rollup for production bundling. Unlike Webpack which bundles everything upfront, Vite serves source files as native ESM, letting the browser handle module resolution and only transforming files on-demand. This enables sub-second HMR regardless of project size.

┌─────────────────────────────────────────────────────────────────────┐
│                         VITE ARCHITECTURE                            │
│                                                                      │
│   DEV SERVER                        PRODUCTION BUILD                │
│   ───────────                        ───────────────                │
│                                                                      │
│   Request                          ┌─────────────────────┐          │
│     │                              │   Rollup Bundler     │          │
│     ▼                              │                     │          │
│   esbuild (pre-bundle deps) ──────►│  ┌───────────────┐  │          │
│     │                              │  │ Tree Shaking  │  │          │
│     ▼                              │  └───────────────┘  │          │
│   Transform (esbuild/SWC/Babel)    │  ┌───────────────┐  │          │
│     │                              │  │ Code Splitting│  │          │
│     ▼                              │  └───────────────┘  │          │
│   HMR (WebSocket) ◄───────────►    │  ┌───────────────┐  │          │
│     │                              │  │ Minification  │  │          │
│     ▼                              │  └───────────────┘  │          │
│   Browser (native ESM)             │  ┌───────────────┐  │          │
│                                    │  │ CSS inlining  │  │          │
│                                    │  └───────────────┘  │          │
│   ┌─ esbuild pre-bundling ───────┐ │  ┌───────────────┐  │          │
│   │  - Converts CJS to ESM       │ │  │ Chunk Splitting│  │         │
│   │  - Deduplicates deps         │ │  └───────────────┘  │          │
│   │  - Single file per dep       │ │          ▼          │          │
│   └──────────────────────────────┘ │    dist/            │          │
│                                    │    ├── assets/      │          │
│   ┌─ HMR over WebSocket ─────────┐│    ├── index.html   │          │
│   │  - File change detected      ││    └── ...           │          │
│   │  - Transform only changed    ││                      │          │
│   │  - Push update to browser    │└──────────────────────┘          │
│   │  - Hot replace without reload│                                   │
│   └──────────────────────────────┘                                   │
└─────────────────────────────────────────────────────────────────────┘

1. Configuration — vite.config.ts

1.1 Base Configuration

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'node:path'

export default defineConfig({
  // Root directory of the project
  root: process.cwd(),

  // Base public path when served in production
  base: '/',                   // absolute path
  // base: '/my-app/',         // sub-path deployment
  // base: './',               // relative — for Electron or static deploys

  // Public directory (served as-is at root)
  publicDir: 'public',

  // Cache directory for pre-bundled deps
  cacheDir: 'node_modules/.vite',

  // Array of plugins
  plugins: [react()],

  resolve: {
    // Path aliases — maps @/ to src/
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@utils': path.resolve(__dirname, 'src/lib/utils'),
      '#types': path.resolve(__dirname, 'src/types'),
    },
    // Extensions to try in order (default)
    extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'],
    // Main fields in package.json to check
    mainFields: ['module', 'browser', 'jsnext:main', 'jsnext'],
  },

  css: {
    modules: {
      // CSS Modules naming convention
      localsConvention: 'camelCaseOnly',
      scopeBehaviour: 'local',
      generateScopedName: '[name]__[local]___[hash:base64:5]',
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`,
        api: 'modern-compiler', // use sass modern compiler
      },
      less: {
        javascriptEnabled: true,
      },
    },
    // PostCSS config — auto-detected from postcss.config.js
    devSourcemap: true,
  },

  define: {
    // Global constants replaced at build time
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
    __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
    __DEV__: process.env.NODE_ENV !== 'production',
  },

  server: {
    port: 5173,
    strictPort: false,       // auto-increment if port is taken
    host: 'localhost',       // or '0.0.0.0' for network access
    open: true,              // auto-open browser
    cors: true,
    // HTTPS — provide cert and key
    // https: { cert, key },
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
        // WebSocket support
        ws: true,
        // Timeout
        proxyTimeout: 30_000,
        // Custom headers
        headers: {
          'X-Custom-Header': 'value',
        },
      },
      '/ws': {
        target: 'ws://localhost:3000',
        ws: true,
      },
    },
  },
})

1.2 Build Options

export default defineConfig({
  build: {
    // Output directory
    outDir: 'dist',
    // Clean outDir before build
    emptyOutDir: true,
    // Assets directory inside outDir
    assetsDir: 'assets',
    // Sourcemap strategy
    sourcemap: false,               // no sourcemaps
    // sourcemap: 'hidden',         // sourcemaps but no reference in files
    // sourcemap: 'inline',         // inline sourcemaps (large bundles)

    // Target browsers
    target: 'es2020',               // modern browsers
    // target: ['es2020', 'edge88', 'firefox78', 'chrome87', 'safari14'],

    // Minification — esbuild is faster, terser shrinks more
    minify: 'esbuild',              // default, 20-40x faster than terser
    // minify: 'terser',            // smaller output, slower build

    // Terser options (only when minify: 'terser')
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
      format: {
        comments: false,
      },
    },

    // esbuild minification options
    esbuild: {
      drop: ['console', 'debugger'],
      legalComments: 'none',
      treeShaking: true,
    },

    // CSS code splitting — extract CSS per chunk
    cssCodeSplit: true,

    // CSS minification
    cssMinify: 'esbuild',           // 'esbuild' | 'lightningcss'

    // Rollup options
    rollupOptions: {
      // Manual chunk splitting
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom', 'react-router-dom'],
          ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
          utils: ['date-fns', 'zod', 'clsx'],
          // Dynamic splitting with function
          // manualChunks(id) {
          //   if (id.includes('node_modules')) {
          //     const pkg = id.split('node_modules/')[1].split('/')[0]
          //     return `vendor-${pkg}`
          //   }
          // },
        },
        // Chunk file naming
        entryFileNames: 'assets/[name]-[hash].js',
        chunkFileNames: 'assets/[name]-[hash].js',
        assetFileNames: 'assets/[name]-[hash][extname]',
      },
    },

    // Chunk size warning limit
    chunkSizeWarningLimit: 500,      // kB

    // Report compressed sizes
    reportCompressedSize: true,

    // Rollup watch options
    watch: null,                     // disable watch during build

    // Enable/disable emitting CSS into separate files
    cssCodeSplit: true,

    // Copy publicDir to outDir
    copyPublicDir: true,

    // Enable/disable module preload polyfill
    modulePreload: {
      polyfill: true,
      resolveDependencies: (filename, deps, context) => {
        return deps // or filter
      },
    },
  },
})

1.3 Preview Server

export default defineConfig({
  preview: {
    port: 4173,
    strictPort: false,
    host: 'localhost',
    open: false,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
})

1.4 Dependency Optimization

export default defineConfig({
  optimizeDeps: {
    // Force pre-bundle of certain deps
    include: ['react', 'react-dom', 'react-router-dom', 'lodash-es'],
    // Exclude deps from pre-bundling
    exclude: ['fsevents'],
    // Force re-bundle when these deps change
    force: true,
    // esbuild options for dep optimization
    esbuildOptions: {
      target: 'es2020',
      // Custom tsconfig for deps
      tsconfigRaw: {
        compilerOptions: {
          experimentalDecorators: true,
        },
      },
    },
    // Prevent bundling of specific dependencies
    needsInterop: ['some-dep'],
  },
})

2. Plugin System

2.1 Plugin Architecture

Vite plugins extend Rollup's plugin interface with Vite-specific hooks. A plugin is an object with name, hooks, and optional enforce (pre | post).

┌───────────────────────────────────────────────────────────┐
│                   PLUGIN HOOK ORDER                         │
│                                                             │
│   ROLLUP HOOKS                        VITE HOOKS            │
│   ─────────────                        ──────────           │
│   options ◄────── buildStart                                │
│   buildStart ◄───── resolveId ◄────────── resolve           │
│   resolveId ◄────── load ◄────────────── load              │
│   load ◄────────────── transform ◄─────────── transform     │
│   transform ◄─────── moduleParsed                           │
│   moduleParsed                                            │
│   ...                    configResolved                     │
│   generateBundle                                            │
│   writeBundle                                               │
│   closeBundle                                               │
│                                                             │
│   VITE-SPECIFIC:                                            │
│   ┌─────────────────────────────────────────────────────┐  │
│   │  config        — modify config before resolve        │  │
│   │  configResolved — read resolved config               │  │
│   │  configureServer — configure dev server              │  │
│   │  transformIndexHtml — transform index.html           │  │
│   │  handleHotUpdate — custom HMR handling               │  │
│   │  resolve — resolve id (before rollup)                │  │
│   │  load — load module (before rollup)                  │  │
│   │  transform — transform module (before rollup)        │  │
│   └─────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────┘

2.2 Vite-Specific Hooks

import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'
import type { AddressInfo } from 'node:net'

function myPlugin(): Plugin {
  let config: ResolvedConfig
  let server: ViteDevServer

  return {
    name: 'my-plugin',

    // enforce — 'pre' (run before built-in), 'post' (run after)
    enforce: 'post',

    // Modify config before it's resolved
    config(userConfig, { command, mode, ssrBuild }) {
      return {
        define: {
          __MY_PLUGIN_VERSION__: JSON.stringify('1.0.0'),
        },
      }
    },

    // Read the resolved config
    configResolved(resolvedConfig) {
      config = resolvedConfig
      // Store resolved paths
      const cacheDir = config.cacheDir
    },

    // Configure the dev server
    configureServer(devServer) {
      server = devServer

      // Access the underlying http server (Vite 6+)
      // server.httpServer is deprecated in Vite 7
      const listen = server.listen.bind(server)
      server.listen = async (port, isRestart) => {
        await listen(port, isRestart)
        const addr = server.resolvedUrls?.local[0]
        console.log(`  ➜  Custom: http://localhost:${port}/custom`)
      }

      // Middleware mode — add connect middleware
      return () => {
        // Return a middleware function
        server.middlewares.use('/custom', (req, res, next) => {
          res.writeHead(200, { 'Content-Type': 'text/plain' })
          res.end('Hello from Vite plugin!')
        })
      }
    },

    // Transform index.html
    transformIndexHtml(html, { filename, server, bundle, chunk }) {
      return {
        html,
        tags: [
          {
            tag: 'link',
            attrs: {
              rel: 'preload',
              href: '/assets/preloaded-asset.js',
              as: 'script',
            },
            injectTo: 'head',
          },
          {
            tag: 'script',
            attrs: { src: '/analytics.js', 'data-id': 'UA-XXXXX-Y' },
            injectTo: 'head-prepend',
          },
          // InjectTo positions:
          // 'head' | 'head-prepend' | 'body' | 'body-prepend'
        ],
      }
    },

    // Custom HMR handling
    handleHotUpdate(ctx) {
      // ctx: { file, server, modules, read, timestamp }
      // Filter which modules to update
      if (ctx.file.includes('node_modules')) {
        return [] // ignore node_modules changes
      }
      if (ctx.file.endsWith('.graphql')) {
        // Custom handling for .graphql files
        return ctx.modules
      }
      // Return nothing = default behavior
    },

    // Resolve module specifiers (runs before Rollup's resolveId)
    resolve(id, importer, options) {
      if (id.startsWith('virtual:')) {
        return { id: '\0' + id, external: false }
      }
    },

    // Load modules (runs before Rollup's load)
    load(id, options) {
      if (id === '\0virtual:my-module') {
        return {
          code: `export const msg = 'Hello from virtual module'`,
          map: null,
        }
      }
    },

    // Transform module code
    transform(code, id, options) {
      if (id.endsWith('.custom')) {
        // Transform custom file types
        return {
          code: `export default ${JSON.stringify(code)}`,
          map: null,
        }
      }
    },

    // Close server — cleanup
    closeBundle() {
      // Cleanup resources
    },
  }
}

2.3 Writing a Virtual Module Plugin

import type { Plugin } from 'vite'
import fs from 'node:fs'

const VIRTUAL_PREFIX = 'virtual:config'
const RESOLVED_PREFIX = '\0' + VIRTUAL_PREFIX

function virtualConfigPlugin(): Plugin {
  return {
    name: 'virtual-config',
    resolve(id) {
      if (id === VIRTUAL_PREFIX) {
        return RESOLVED_PREFIX
      }
    },
    load(id) {
      if (id === RESOLVED_PREFIX) {
        const env = {
          NODE_ENV: process.env.NODE_ENV || 'development',
          API_URL: process.env.VITE_API_URL || 'http://localhost:3000',
          BUILD_TIME: new Date().toISOString(),
        }
        return {
          code: `export const config = ${JSON.stringify(env)}`,
          map: null,
        }
      }
    },
  }
}

3. Environment Variables

3.1 Built-in Variables

// Available at build time via import.meta.env
import.meta.env.MODE          // 'development' | 'production'
import.meta.env.BASE_URL      // base path (default '/')
import.meta.env.PROD          // true in production
import.meta.env.DEV           // true in development
import.meta.env.SSR           // true when running on server

3.2 Custom Env Variables (VITE_ prefix)

// Only variables with VITE_ prefix are exposed to client code
// .env
VITE_API_URL=http://localhost:3000
VITE_APP_TITLE=My App
VITE_FEATURE_FLAG_NEW_DASHBOARD=true

// .env.development
VITE_API_URL=http://localhost:3000
VITE_DEBUG=true

// .env.production
VITE_API_URL=https://api.example.com

// .env.local — git-ignored, for local overrides
VITE_API_KEY=sk-local-dev-key

// Priority (highest to lowest):
// 1. .env.[mode].local      — mode-specific, local
// 2. .env.[mode]             — mode-specific
// 3. .env.local              — general local overrides
// 4. .env                    — general defaults

// In code:
const apiUrl = import.meta.env.VITE_API_URL
const isDev = import.meta.env.DEV

3.3 Type Safety for Env Variables

// src/vite-env.d.ts — augment ImportMeta
interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_APP_TITLE: string
  readonly VITE_FEATURE_FLAG_NEW_DASHBOARD: 'true' | 'false'
  readonly VITE_DEBUG: string
  // optional with ?
  readonly VITE_SENTRY_DSN?: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

// src/env.d.ts — alternative approach with triple-slash
/// <reference types="vite/client" />

3.4 Runtime Env Variables (docker/K8s)

// For runtime env injection (not at build time):
// index.html — load env from window object
// <script>
//   window.__ENV__ = {
//     VITE_API_URL: '${VITE_API_URL}',
//     VITE_APP_TITLE: '${VITE_APP_TITLE}',
//   }
// </script>

// src/env.ts — runtime env resolver
function getEnv(key: string, fallback?: string): string {
  if (typeof window !== 'undefined' && (window as any).__ENV__?.[key]) {
    return (window as any).__ENV__[key]
  }
  if (import.meta.env[key]) {
    return import.meta.env[key]
  }
  if (fallback !== undefined) return fallback
  throw new Error(`Missing env: ${key}`)
}

export const env = {
  apiUrl: getEnv('VITE_API_URL', 'http://localhost:3000'),
  appTitle: getEnv('VITE_APP_TITLE', 'My App'),
}

3.5 Mode-Specific Config

// vite.config.ts — access mode
export default defineConfig(({ mode, command }) => {
  const isDev = mode === 'development'
  const isStaging = mode === 'staging'

  return {
    // mode flag: vite build --mode staging
    base: isDev ? '/' : '/app/',
    build: {
      sourcemap: isDev || isStaging,
      minify: isDev ? false : 'esbuild',
    },
    define: {
      __STAGING__: JSON.stringify(isStaging),
    },
  }
})

4. Essential Plugins

4.1 @vitejs/plugin-react — SWC vs Babel

// Babel-based (default, more customizable)
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      // Babel configuration
      babel: {
        plugins: ['@emotion/babel-plugin'],
        presets: ['@babel/preset-typescript'],
        babelrc: false,
        configFile: false,
      },
      // Fast Refresh
      fastRefresh: true,
      // JSX runtime — 'automatic' (React 18+) or 'classic'
      jsxRuntime: 'automatic',
      // Exclude specific files from transformation
      exclude: /\.stories\.(t|j)sx?$/,
      // Include specific files
      include: /\.(t|j)sx?$/,
    }),
  ],
})
// SWC-based (faster, less configurable)
import react from '@vitejs/plugin-react-swc'

export default defineConfig({
  plugins: [
    react({
      // SWC configuration
      tsDecorators: true,
      parserConfig: {
        syntax: 'typescript',
        tsx: true,
        decorators: true,
      },
      // Exclude stories
      exclude: /\.stories\.(t|j)sx?$/,
    }),
  ],
})

4.2 @vitejs/plugin-vue

import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      // Template compilation options
      template: {
        compilerOptions: {
          // Treat specific tags as custom elements
          isCustomElement: (tag) => tag.startsWith('ion-'),
        },
      },
      // Reactive transform (Vue 3.3+)
      reactivityTransform: true,
      // Custom SFC blocks
      customBlocks: ['i18n'],
    }),
  ],
})

4.3 unplugin-auto-import

import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  plugins: [
    AutoImport({
      // Auto-import from known libraries
      imports: [
        'react',
        'react-router-dom',
        'react-i18next',
        { 'date-fns': ['format', 'parseISO', 'addDays'] },
        { 'clsx': [['default', 'cn']] },
      ],
      // Include type declarations
      dts: 'src/auto-imports.d.ts',
      // Directories to auto-import from
      dirs: [
        'src/hooks/**',
        'src/utils/**',
      ],
      // ESLint globals
      eslintrc: {
        enabled: true,
        filepath: './.eslintrc-auto-import.json',
      },
    }),
  ],
})

4.4 unplugin-icons

import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'

export default defineConfig({
  plugins: [
    Icons({
      // Icon collections — install as devDeps
      // npm i -D @iconify-json/mdi @iconify-json/logos
      compiler: 'jsx',          // 'jsx' | 'tsx' | 'vue' | 'svelte' | 'solid'
      jsx: 'react',             // framework
      scale: 1.2,               // scale 1.2x
      defaultStyle: 'display: inline-block',
      autoInstall: true,        // install missing collections on the fly
    }),
  ],
})

// Usage:
// import IconAccessPoint from '~icons/mdi/access-point'
// import IconLogoGithub from '~icons/logos/github-icon'

4.5 vite-tsconfig-paths

import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  plugins: [
    tsconfigPaths({
      // Match paths from tsconfig.json
      projects: ['./tsconfig.json'],
      extensions: ['.ts', '.tsx', '.js', '.jsx'],
    }),
  ],
})

// Instead of manual resolve.alias
// tsconfig.json:
// {
//   "compilerOptions": {
//     "paths": {
//       "@/*": ["./src/*"],
//       "@components/*": ["./src/components/*"]
//     }
//   }
// }

4.6 vite-plugin-pwa

import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'robots.txt', 'apple-touch-icon.png'],
      manifest: {
        name: 'My App',
        short_name: 'MyApp',
        description: 'My amazing app',
        theme_color: '#ffffff',
        background_color: '#ffffff',
        display: 'standalone',
        scope: '/',
        start_url: '/',
        icons: [
          {
            src: 'icons/icon-192x192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: 'icons/icon-512x512.png',
            sizes: '512x512',
            type: 'image/png',
          },
        ],
      },
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/api\.example\.com\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 60 * 60 * 24, // 24 hours
              },
              networkTimeoutSeconds: 10,
            },
          },
        ],
      },
      // For dev — test PWA in development
      devOptions: {
        enabled: true,
        type: 'module',
      },
    }),
  ],
})

4.7 vite-plugin-svgr

import svgr from 'vite-plugin-svgr'

export default defineConfig({
  plugins: [
    svgr({
      // SVGR options
      svgrOptions: {
        exportType: 'default',
        ref: true,
        svgo: true,
        titleProp: true,
        plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'],
      },
      // Include/exclude
      include: '**/*.svg?react',
      exclude: '',
    }),
  ],
})

// Usage:
// import Logo from './logo.svg?react'
// const App = () => <Logo width={100} height={100} />

4.8 Other Essential Plugins

// vite-plugin-compression — gzip/brotli
import compression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    compression({
      algorithm: 'brotliCompress',
      ext: '.br',
      threshold: 10240,          // only files > 10KB
      deleteOriginFile: false,
    }),
  ],
})
// vite-plugin-inspect — inspect plugin output
import Inspect from 'vite-plugin-inspect'

export default defineConfig({
  plugins: [Inspect()],
  // Visit http://localhost:5173/__inspect in dev
})
// vite-plugin-checker — type check + lint in a separate worker
import checker from 'vite-plugin-checker'

export default defineConfig({
  plugins: [
    checker({
      typescript: {
        tsconfigPath: './tsconfig.json',
      },
      eslint: {
        lintCommand: 'eslint src --ext .ts,.tsx',
        dev: {
          logLevel: ['error'],
        },
      },
    }),
  ],
})

5. Build Optimization

5.1 Code Splitting — manualChunks Strategies

// Strategy 1: Framework chunk
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
        },
      },
    },
  },
})

// Strategy 2: Granular by package group
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            // Group by top-level package
            const pkg = id.split('node_modules/')[1].split('/')[0]

            // Put React and its ecosystem together
            if (['react', 'react-dom', 'react-router-dom', 'scheduler'].includes(pkg)) {
              return 'vendor-react'
            }
            // Put UI libraries together
            if (['@radix-ui', '@emotion', 'framer-motion'].includes(pkg)) {
              return 'vendor-ui'
            }
            // Default: per-package chunk
            return `vendor-${pkg}`
          }
          // Source code — group by feature
          if (id.includes('/src/pages/dashboard/')) {
            return 'feature-dashboard'
          }
          if (id.includes('/src/pages/admin/')) {
            return 'feature-admin'
          }
        },
      },
    },
  },
})

// Strategy 3: Use rollup-plugin-visualizer to analyze
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    visualizer({
      filename: 'stats.html',
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
})

5.2 Dynamic Imports — Lazy Loading

// React.lazy + Suspense for route-level splitting
import { lazy, Suspense } from 'react'

const Dashboard = lazy(() => import('./pages/Dashboard'))
const AdminPanel = lazy(() => import('./pages/AdminPanel'))
const Settings = lazy(() => import('./pages/Settings'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/admin" element={<AdminPanel />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}

// Named exports — use with /* @vite-ignore */ for dynamic
const Page = lazy(() => import(/* @vite-ignore */ `./pages/${pageName}`))

// Prefetch on hover
// const loadDashboard = () => import('./pages/Dashboard')
// <Link onMouseEnter={loadDashboard} to="/dashboard">Dashboard</Link>

// Magic comments for chunk naming:
// const Admin = lazy(() => import(/* webpackChunkName: "admin" */ './pages/Admin'))
// Vite converts webpackChunkName to chunk names automatically

5.3 Tree Shaking

// Vite tree-shakes by default using Rollup. Ensure:

// 1. Use ESM imports (not CJS)
import { map, filter } from 'lodash-es'      // ✓ ESM — tree-shakable
// const _ = require('lodash')                // ❌ CJS — full bundle

// 2. Use barrel exports carefully
// utils/index.ts
export { formatDate } from './date'           // ✓ tree-shakable
export { validateEmail } from './validation'  // ✓ tree-shakable
// export * from './date'                     // exports ALL from date (fine if tree-shaken)

// 3. Side effect flags in package.json
// {
//   "sideEffects": false,                     // tell bundler this package is side-effect-free
//   "sideEffects": ["*.css"]                  // only CSS has side effects
// }

// 4. Mark modules as pure
const result = /*#__PURE__*/ createFactory()

5.4 Chunk Size Warning Configuration

export default defineConfig({
  build: {
    chunkSizeWarningLimit: 1000,        // increase warning threshold to 1MB
    rollupOptions: {
      output: {
        // Aggressively split large chunks
        experimentalMinChunkSize: 20000, // merge chunks smaller than 20KB (Rollup 4+)
      },
    },
    // Report compressed sizes (default: true)
    reportCompressedSize: true,
  },
})

5.5 CSS Optimization

export default defineConfig({
  build: {
    // Extract CSS into separate files per chunk
    cssCodeSplit: true,

    // CSS minification engine
    cssMinify: 'lightningcss',   // faster than esbuild for CSS

    // Inline small CSS (< 4KB)
    cssMinify: 'esbuild',

    // PostCSS config auto-detected
  },
  css: {
    // CSS modules
    modules: {
      localsConvention: 'camelCase',
    },
    // LightningCSS options
    lightningcss: {
      // targets: browserslist to query
      drafts: {
        customMedia: true,
      },
    },
    // PostCSS
    postcss: './postcss.config.js',
  },
})

6. HMR (Hot Module Replacement)

6.1 How Vite HMR Works

┌─────────┐     File Change      ┌───────────┐
│  Editor  │ ──────────────────►  │  File     │
└─────────┘                       │  Watcher  │
                                  └─────┬─────┘
                                        │
                                   ┌────▼────┐
                                   │  Vite   │
                                   │  Dev    │
                                   │  Server │
                                   └────┬────┘
                                        │
                              ┌─────────▼──────────┐
                              │  Invalidate module  │
                              │  chain in module    │
                              │  graph              │
                              └─────────┬──────────┘
                                        │
                              ┌─────────▼──────────┐
                              │  Transform only     │
                              │  changed module     │
                              │  (esbuild/SWC)      │
                              └─────────┬──────────┘
                                        │
                              ┌─────────▼──────────┐
                              │  Send over          │
                              │  WebSocket          │
                              │  (full update /     │
                              │   partial update)   │
                              └─────────┬──────────┘
                                        │
                              ┌─────────▼──────────┐
                              │  Browser receives   │
                              │  hot update         │
                              │  → re-render        │
                              │  without full reload│
                              └────────────────────┘

6.2 HMR API

// Accept module self-updates
if (import.meta.hot) {
  import.meta.hot.accept()

  // Accept with callback
  import.meta.hot.accept((newModule) => {
    // newModule is the updated module
    console.log('Module updated:', newModule)
  })
}

// Accept dependencies
import { foo } from './foo'

if (import.meta.hot) {
  import.meta.hot.accept('./foo', (newFoo) => {
    // Called when ./foo is updated
    foo = newFoo.foo
  })
}

// Self-dispose — cleanup before module is re-executed
if (import.meta.hot) {
  import.meta.hot.dispose(() => {
    // Cleanup: remove event listeners, timers, etc.
    cleanup()
  })
}

// Decline — do not accept updates (force full reload)
if (import.meta.hot) {
  import.meta.hot.decline()
}

// Invalidate — force parent modules to re-execute
if (import.meta.hot) {
  import.meta.hot.invalidate()
}

// Custom events — send data to dev server
if (import.meta.hot) {
  import.meta.hot.send('custom:event', { data: 'hello' })
}

// Status checks
if (import.meta.hot) {
  console.log(import.meta.hot.status) // 'idle' | 'check' | 'apply' | 'fail'
}

6.3 Custom HMR Boundaries

// For stateful modules (e.g., stores, contexts)
// src/store.ts
let store = {
  user: null,
  theme: 'light',
}

export function getStore() {
  return store
}

export function setStore(newStore: Partial<typeof store>) {
  store = { ...store, ...newStore }
}

// HMR — preserve store state across hot updates
if (import.meta.hot) {
  const prevStore = store // save state

  import.meta.hot.accept((newModule) => {
    // Restore state in new module
    store = prevStore
  })

  import.meta.hot.dispose(() => {
    // Save state before dispose
    sessionStorage.setItem('__hmr_store__', JSON.stringify(store))
  })
}

6.4 HMR in Plugins

function hmrPlugin(): Plugin {
  return {
    name: 'hmr-plugin',
    handleHotUpdate(ctx) {
      // Filter — only update specific modules
      if (ctx.file.endsWith('.css')) {
        // CSS updates are handled natively
        return ctx.modules
      }
      if (ctx.file.endsWith('.graphql')) {
        // Transform before HMR
        return ctx.server.transformRequest(ctx.file).then(() => {
          return ctx.modules
        })
      }
      // Return empty to suppress update
      if (ctx.file.includes('__tests__')) {
        return []
      }
    },
  }
}

7. SSR (Server-Side Rendering)

7.1 SSR Build Configuration

// vite.config.ts — dual build
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  build: {
    // Client build
    outDir: 'dist/client',
    rollupOptions: {
      input: 'src/entry-client.tsx',
    },
  },
  // SSR-specific config
  ssr: {
    // Externalize these deps (not bundled for SSR)
    external: ['react', 'react-dom/server'],
    // Vite will bundle everything else
    noExternal: ['@my-company/*'],
    // Target
    target: 'node',
    // Resolve conditions
    resolve: {
      conditions: ['node', 'module', 'import'],
    },
  },
})
// vite.config.ts — SSR build config
export default defineConfig({
  build: {
    outDir: 'dist/server',
    ssr: 'src/entry-server.tsx',
    rollupOptions: {
      output: {
        format: 'esm',
        entryFileNames: '[name].mjs',
      },
    },
    // Minify is usually off for SSR bundles
    minify: false,
  },
})

7.2 SSR Entry Points

// src/entry-client.tsx — client-side hydration
import { StrictMode } from 'react'
import { hydrateRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

hydrateRoot(
  document.getElementById('root')!,
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>,
)
// src/entry-server.tsx — server-side render
import { StrictMode } from 'react'
import { renderToPipeableStream, renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom/server'
import App from './App'

// For streaming SSR (React 18+)
export function render(url: string, opts?: { onShellReady?: () => void }) {
  const stream = renderToPipeableStream(
    <StrictMode>
      <StaticRouter location={url}>
        <App />
      </StaticRouter>
    </StrictMode>,
    {
      bootstrapScripts: ['/client/assets/entry-client.js'],
      onShellReady() {
        opts?.onShellReady?.()
      },
      onError(err) {
        console.error(err)
      },
    },
  )
  return stream
}

// For synchronous SSR
export function renderToString(url: string) {
  return renderToString(
    <StrictMode>
      <StaticRouter location={url}>
        <App />
      </StaticRouter>
    </StrictMode>,
  )
}

7.3 SSR Server

// server.js — production SSR server
import express from 'express'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const app = express()

// Serve static files from client build
app.use(
  '/client/assets',
  express.static(path.resolve(__dirname, 'dist/client/assets'), {
    maxAge: 31536000,
    immutable: true,
  }),
)
app.use('/client', express.static(path.resolve(__dirname, 'dist/client')))

// Import SSR renderer
const { render } = await import('./dist/server/entry-server.mjs')

app.get('*', async (req, res) => {
  const url = req.originalUrl
  const template = fs.readFileSync(
    path.resolve(__dirname, 'dist/client/index.html'),
    'utf-8',
  )

  try {
    // React streaming SSR
    const stream = render(url)
    const htmlStart = template.indexOf('<div id="root">')
    const head = template.slice(0, htmlStart)
    const tail = template.slice(htmlStart)

    res.setHeader('Content-Type', 'text/html')
    res.write(head)
    stream.pipe(res, { end: false })
    stream.on('end', () => {
      res.write(tail)
      res.end()
    })
  } catch (err) {
    console.error(err)
    res.status(500).send('Internal Server Error')
  }
})

app.listen(3000, () => {
  console.log('SSR server listening on http://localhost:3000')
})

7.4 SSR with Data Fetching

// src/entry-server.tsx — with data preloading
import { StaticRouter } from 'react-router-dom/server'
import { renderToString } from 'react-dom/server'
import App from './App'

// Data fetching context
interface SSRContext {
  url: string
  state: Record<string, unknown>
}

export async function render(url: string): Promise<{ html: string; state: Record<string, unknown> }> {
  const ctx: SSRContext = { url, state: {} }

  const html = renderToString(
    <StaticRouter location={url}>
      <App ssrContext={ctx} />
    </StaticRouter>,
  )

  return { html, state: ctx.state }
}

// In components:
// function Dashboard({ ssrContext }: { ssrContext?: SSRContext }) {
//   useEffect(() => {
//     if (!ssrContext) fetchData()
//   }, [])
//   if (ssrContext) {
//     ssrContext.state.dashboard = fetchDataSync()
//   }
//   // ...
// }

8. Library Mode

8.1 Configuration

// vite.config.ts — build a library
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import dts from 'vite-plugin-dts'
import path from 'node:path'

export default defineConfig({
  plugins: [
    react(),
    // Generate .d.ts files
    dts({
      insertTypesEntry: true,
      include: ['src'],
      exclude: ['src/**/*.test.*', 'src/**/*.stories.*'],
      rollupTypes: true, // rollup types into single file (TS 5+)
    }),
  ],
  build: {
    // Library mode
    lib: {
      // Entry point
      entry: path.resolve(__dirname, 'src/index.ts'),
      // Name for UMD/IIFE globals
      name: 'MyLibrary',
      // Output formats
      formats: ['es', 'cjs', 'umd'],
      // File names for each format
      fileName: (format) => {
        switch (format) {
          case 'es':
            return 'my-library.mjs'
          case 'cjs':
            return 'my-library.cjs'
          case 'umd':
            return 'my-library.umd.js'
          default:
            return 'my-library.[format].js'
        }
      },
    },
    // Externalize peer deps (don't bundle React)
    rollupOptions: {
      external: [
        'react',
        'react-dom',
        'react/jsx-runtime',
      ],
      output: {
        // Globals for UMD/IIFE builds
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
          'react/jsx-runtime': 'jsxRuntime',
        },
        // Preserve modules structure
        preserveModules: false,
        // Sourcemaps for library
        sourcemap: true,
      },
    },
    // Don't empty outDir for lib builds
    emptyOutDir: true,
    // Sourcemap
    sourcemap: true,
    // Minify for distribution
    minify: 'esbuild',
  },
})

8.2 Library Entry Point

// src/index.ts — library public API
export { Button } from './components/Button'
export { Input } from './components/Input'
export { Card, CardHeader, CardContent } from './components/Card'
export { ThemeProvider, useTheme } from './context/ThemeContext'
export { cn } from './lib/utils'

// Types
export type { ButtonProps } from './components/Button'
export type { InputProps } from './components/Input'
export type { Theme } from './types'

// Internal types — not exported

8.3 Package.json for Libraries

{
  "name": "@my-company/my-library",
  "version": "1.0.0",
  "type": "module",
  "files": ["dist"],
  "main": "./dist/my-library.cjs",
  "module": "./dist/my-library.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.mts",
        "default": "./dist/my-library.mjs"
      },
      "require": {
        "types": "./dist/index.d.ts",
        "default": "./dist/my-library.cjs"
      }
    },
    "./styles.css": "./dist/styles.css"
  },
  "sideEffects": ["**/*.css"],
  "scripts": {
    "build": "vite build",
    "prepublishOnly": "npm run build"
  },
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0",
    "react-dom": "^18.0.0 || ^19.0.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.0.0",
    "vite": "^6.0.0",
    "vite-plugin-dts": "^4.0.0"
  }
}

8.4 Multiple Entry Points (Subpath Exports)

// vite.config.ts — multi-entry library
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
import path from 'node:path'

export default defineConfig({
  plugins: [dts({ rollupTypes: true })],
  build: {
    lib: {
      entry: {
        index: path.resolve(__dirname, 'src/index.ts'),
        components: path.resolve(__dirname, 'src/components/index.ts'),
        hooks: path.resolve(__dirname, 'src/hooks/index.ts'),
        utils: path.resolve(__dirname, 'src/utils/index.ts'),
      },
      formats: ['es', 'cjs'],
      fileName: (format, entryName) => {
        const ext = format === 'es' ? 'mjs' : 'cjs'
        return `${entryName}.${ext}`
      },
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
    },
  },
})
// package.json — subpath exports
{
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    },
    "./components": {
      "import": "./dist/components.mjs",
      "require": "./dist/components.cjs"
    },
    "./hooks": {
      "import": "./dist/hooks.mjs",
      "require": "./dist/hooks.cjs"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "require": "./dist/utils.cjs"
    },
    "./styles.css": "./dist/styles.css"
  }
}

9. Testing with Vitest

9.1 Configuration

// vitest.config.ts — or merge with vite.config.ts
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'

export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      // Test environment
      environment: 'jsdom',
      // Globals like describe, it, expect
      globals: true,
      // Setup files
      setupFiles: ['./src/test/setup.ts'],
      // File patterns
      include: ['src/**/*.{test,spec}.{ts,tsx}'],
      exclude: ['node_modules', 'dist'],
      // Coverage
      coverage: {
        provider: 'v8',       // 'v8' (built-in) or 'istanbul'
        reporter: ['text', 'json', 'html', 'lcov'],
        include: ['src'],
        exclude: [
          'src/**/*.test.*',
          'src/**/*.spec.*',
          'src/test/**',
          'src/**/*.d.ts',
          'src/**/index.ts',
        ],
        thresholds: {
          branches: 80,
          functions: 80,
          lines: 80,
          statements: 80,
        },
        // Report per-file
        reportsDirectory: './coverage',
      },
      // Test timeout
      testTimeout: 10_000,
      // Hooks timeout
      hookTimeout: 10_000,
      // Retry flaky tests
      retry: 2,
      // Parallel
      pool: 'forks',          // 'forks' | 'threads'
      poolOptions: {
        threads: {
          singleThread: false,
        },
        forks: {
          singleFork: false,
        },
      },
      // Sequence
      sequence: {
        shuffle: true,        // randomize test order
        seed: Date.now(),
      },
      // Update snapshots
      update: false,
      // API mocking
      mocks: {
        clearMocks: true,
        restoreMocks: true,
      },
      // Environment options
      environmentOptions: {
        jsdom: {
          url: 'http://localhost:3000',
        },
      },
      // CSS handling
      css: {
        modules: {
          classNameStrategy: 'non-scoped', // for testing CSS modules
        },
      },
      // Alias (inherits from vite.config.ts)
      alias: {
        '@': '/src',
      },
    },
  }),
)

9.2 Basic Test Examples

// src/utils/format.test.ts
import { describe, it, expect } from 'vitest'
import { formatDate, parseISO } from './format'

describe('formatDate', () => {
  it('formats ISO date correctly', () => {
    expect(formatDate('2026-05-17')).toBe('May 17, 2026')
  })

  it('handles invalid dates', () => {
    expect(formatDate('invalid')).toBe('Invalid Date')
  })
})

9.3 React Component Testing

// src/test/setup.ts
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach } from 'vitest'

afterEach(() => {
  cleanup()
})
// src/components/Button.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'

describe('Button', () => {
  it('renders children', () => {
    render(<Button>Click me</Button>)
    expect(screen.getByText('Click me')).toBeInTheDocument()
  })

  it('calls onClick when clicked', () => {
    const onClick = vi.fn()
    render(<Button onClick={onClick}>Click</Button>)
    fireEvent.click(screen.getByText('Click'))
    expect(onClick).toHaveBeenCalledTimes(1)
  })

  it('is disabled when disabled prop is true', () => {
    render(<Button disabled>Disabled</Button>)
    expect(screen.getByText('Disabled')).toBeDisabled()
  })

  it('applies variant classes', () => {
    const { rerender } = render(<Button variant="primary">Primary</Button>)
    expect(screen.getByText('Primary')).toHaveClass('bg-blue-500')

    rerender(<Button variant="secondary">Secondary</Button>)
    expect(screen.getByText('Secondary')).toHaveClass('bg-gray-500')
  })
})

9.4 Async Testing

// src/hooks/useData.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { useData } from './useData'

describe('useData', () => {
  beforeEach(() => {
    vi.restoreAllMocks()
  })

  it('fetches and returns data', async () => {
    const mockData = { id: 1, name: 'Test' }
    global.fetch = vi.fn().mockResolvedValue({
      json: () => Promise.resolve(mockData),
      ok: true,
    })

    const { result } = renderHook(() => useData('/api/test'))

    expect(result.current.loading).toBe(true)

    await waitFor(() => {
      expect(result.current.data).toEqual(mockData)
    })

    expect(result.current.loading).toBe(false)
    expect(result.current.error).toBeNull()
  })

  it('handles fetch error', async () => {
    global.fetch = vi.fn().mockRejectedValue(new Error('Network error'))

    const { result } = renderHook(() => useData('/api/test'))

    await waitFor(() => {
      expect(result.current.error).toBe('Network error')
    })
  })
})

9.5 Vitest Workspace

// vitest.workspace.ts
import { defineWorkspace } from 'vitest/config'

export default defineWorkspace([
  // Unit tests (jsdom)
  {
    test: {
      name: 'unit',
      root: './src',
      environment: 'jsdom',
      include: ['**/*.test.ts', '**/*.test.tsx'],
    },
  },
  // Integration tests (node)
  {
    test: {
      name: 'integration',
      root: './tests',
      environment: 'node',
      include: ['**/*.test.ts'],
      globalSetup: './tests/setup.ts',
    },
  },
  // Browser tests (experimental)
  {
    test: {
      name: 'browser',
      browser: {
        enabled: true,
        provider: 'playwright',
        instances: [
          { browser: 'chromium' },
        ],
      },
    },
  },
])

9.6 Benchmark Mode

// src/utils/sort.bench.ts
import { bench, describe } from 'vitest'

describe('sorting algorithms', () => {
  const data = Array.from({ length: 10000 }, () =>
    Math.floor(Math.random() * 10000)
  )

  bench('Array.sort', () => {
    data.slice().sort((a, b) => a - b)
  })

  bench('quick sort', () => {
    quickSort(data.slice())
  })
})

// Run: npx vitest bench

10. Performance Optimization

10.1 Dev Server Cold Start

export default defineConfig({
  // Reduce the number of files scanned
  server: {
    // Exclude directories from file watching
    watch: {
      ignored: [
        '**/node_modules/**',
        '**/dist/**',
        '**/.git/**',
        '**/coverage/**',
        '**/*.test.*',
        '**/*.spec.*',
      ],
      // Use polling if filesystem events are unreliable (Docker/WSL)
      usePolling: false,
      interval: 100,
    },
    // Warmup frequently used modules
    warmup: {
      clientFiles: [
        './src/main.tsx',
        './src/App.tsx',
        './src/routes.tsx',
      ],
    },
  },

  // Pre-bundle strategy
  optimizeDeps: {
    // Include common deps to pre-bundle them on startup
    include: [
      'react',
      'react-dom',
      'react-router-dom',
      'lodash-es',
      'date-fns',
    ],
    // Disable dep discovery scan — use explicit include
    // (faster startup but must keep include list updated)
    // disabled: 'build' | 'dev'
    disabled: false,
  },
})

10.2 Build Time Optimization

export default defineConfig({
  build: {
    // Use esbuild for minification (20-40x faster than terser)
    minify: 'esbuild',

    // Disable sourcemaps for faster build
    sourcemap: false,

    // Don't compress during build (nginx/CDN does this)
    reportCompressedSize: false,

    // Disable CSS code splitting if not needed
    cssCodeSplit: true,

    // Larger chunk size limit reduces number of chunks
    rollupOptions: {
      output: {
        experimentalMinChunkSize: 100000,   // 100KB min chunk size
      },
    },
  },

  // Enable SWC instead of Babel for React (10x faster)
  // plugin-react-swc
})

// Build with --debug for timing info
// vite build --debug

// Profile build
// DEBUG="vite:build*" vite build
// cross-env VITE_PROFILE=vite-build-profile.json vite build

10.3 Dependency Optimization Tuning

export default defineConfig({
  optimizeDeps: {
    // Hold dependencies in cache longer
    // Forces re-optimization only if deps change

    // Force specific deps to use ESM
    needsInterop: [
      'some-cjs-lib',
      'another-lib-that-misidentifies-its-module-type',
    ],

    // Exclude if causing issues
    exclude: [
      '@firebase/app',
      'some-large-dep-that-shouldnt-be-pre-bundled',
    ],

    // esbuild target for optimized deps
    esbuildOptions: {
      target: 'es2020',
    },
  },
})

10.4 Bundle Analysis

# Visualize bundle composition
npm i -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    visualizer({
      filename: 'dist/stats.html',
      open: true,                // auto-open in browser
      gzipSize: true,
      brotliSize: true,
      template: 'treemap',       // 'treemap' | 'sunburst' | 'network'
    }),
  ],
})

11. Migration Guides

11.1 CRA → Vite

// 1. Install Vite
// npm remove react-scripts
// npm i -D vite @vitejs/plugin-react

// 2. Create vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'node:path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': path.resolve(__dirname, 'src') },
  },
  server: { port: 3000 },
})

// 3. Move index.html to root (from public/)
// index.html — must have <script type="module" src="/src/index.tsx">

// 4. Update env variables
// REACT_APP_* → VITE_*
// process.env.REACT_APP_API_URL → import.meta.env.VITE_API_URL

// 5. Update tsconfig.json
// {
//   "compilerOptions": {
//     "types": ["vite/client"],
//     "module": "ESNext",
//     "moduleResolution": "bundler",
//     "target": "ES2020"
//   }
// }

// 6. Update scripts in package.json
// "dev": "vite",
// "build": "tsc && vite build",
// "preview": "vite preview"

// 7. Fix imports
// SVG: import { ReactComponent as Logo } from './logo.svg'
//    → import Logo from './logo.svg?react'
// Images: import logo from './logo.png'
//    → still works (Vite handles assets natively)

// 8. Proxy
// Setup proxy in vite.config.ts instead of package.json "proxy" field

// 9. Remove serviceWorker registration
// CRA's serviceWorker.js is not needed — use vite-plugin-pwa instead

// 10. CSS Modules
// .module.css filenames work identically

11.2 Webpack → Vite

// Key differences:
//    Webpack            → Vite
//    babel-loader        → esbuild (or SWC)
//    css-loader          → native CSS handling
//    file-loader         → native asset handling
//    html-webpack-plugin → index.html in root
//    DefinePlugin        → define in config
//    MiniCssExtractPlugin→ built-in CSS splitting
//    TerserPlugin        → esbuild minification

// Loader equivalents:
// ts-loader / babel-loader → esbuild (default) or SWC
// svg-inline-loader       → vite-plugin-svgr
// svgo-loader             → vite-plugin-svgr (includes SVGO)
// image-webpack-loader    → vite-plugin-imagemin
// webpack-manifest-plugin → built-in manifest

// webpack.config.js → vite.config.ts
// const webpackConfig = {
//   entry: './src/index.tsx',
//   output: { path: './dist' },
//   module: { rules: [...] },
//   plugins: [...],
//   resolve: { alias: {...} },
//   devServer: { ... },
// }

// Becomes:
// export default defineConfig({
//   plugins: [react()],
//   resolve: { alias: {...} },
//   server: { ... },
//   build: { outDir: 'dist' },
// })

11.3 Migration Cookbook — Common Patterns

// CRA: import { ReactComponent as Icon } from './icon.svg'
// Vite: import Icon from './icon.svg?react'
// (requires vite-plugin-svgr)

// CRA: process.env.PUBLIC_URL + '/static/image.png'
// Vite: import imageUrl from './image.png'
//    or: new URL('./image.png', import.meta.url).href

// CRA: require.context('./icons', false, /\.svg$/)
// Vite: const icons = import.meta.glob('./icons/*.svg')
//    for (const [path, loader] of Object.entries(icons)) {
//      const module = await loader()
//    }

// CRA: lazy(() => import('./Dashboard'))
// Vite: same — works identically

// CRA: CSS Modules: styles.someClass
// Vite: same — works identically

// CRA: Sass variables — @use 'styles/variables' as *;
// Vite: same, but configure via css.preprocessorOptions

// CRA: process.env.NODE_ENV
// Vite: import.meta.env.MODE (or import.meta.env.DEV / PROD)

// CRA: .env.development.local
// Vite: same — file priority is identical

// CRA: sourceMap in config
// Vite: build.sourcemap

// CRA: proxy in package.json
// Vite: server.proxy in vite.config.ts

12. File Convention

my-project/
├── index.html                  # Entry HTML with <script type="module" src="/src/main.tsx">
├── vite.config.ts              # Vite configuration
├── vitest.config.ts            # Vitest configuration (optional, can merge with above)
├── tsconfig.json               # TypeScript config
├── tsconfig.node.json          # TS config for vite.config.ts
├── postcss.config.js           # PostCSS config (auto-detected)
├── .env                        # Default env variables
├── .env.development            # Dev-specific env
├── .env.production             # Production-specific env
├── .env.local                  # Local overrides (git-ignored)
├── .env.staging                # Staging mode (vite build --mode staging)
├── public/                     # Served as-is at root
│   ├── favicon.ico
│   ├── robots.txt
│   └── manifest.json
├── src/
│   ├── main.tsx                # Entry point (client)
│   ├── entry-server.tsx        # Entry point (SSR) — optional
│   ├── App.tsx                 # Root component
│   ├── vite-env.d.ts           # Vite client types + ImportMetaEnv
│   ├── components/             # Shared components
│   │   ├── ui/                 # Primitive UI components
│   │   └── layout/             # Layout components
│   ├── pages/                  # Route pages
│   ├── hooks/                  # Custom React hooks
│   ├── lib/                    # Utility libraries
│   ├── stores/                 # State management
│   ├── services/               # API services
│   ├── types/                  # Shared TypeScript types
│   ├── assets/                 # Static assets (images, fonts)
│   ├── styles/                 # Global styles
│   │   ├── globals.css
│   │   └── variables.css
│   └── test/                   # Test setup
│       ├── setup.ts
│       └── mocks/
├── tests/                      # Integration/E2E tests
├── dist/                       # Build output (git-ignored)
│   ├── client/                 # SSR client build
│   └── server/                 # SSR server build
└── scripts/                    # Build/deploy scripts

13. Anti-Patterns

❌ Disabling Dependency Optimization

// ❌ BAD — disabling dep optimization cripples dev performance
export default defineConfig({
  optimizeDeps: {
    disabled: 'dev',        // causes: 1000s of requests instead of bundled deps
    exclude: ['react'],     // causes: React HMR will be slow, lots of requests
  },
})

// ✅ GOOD — let Vite handle dep optimization; only exclude if absolutely needed
export default defineConfig({
  optimizeDeps: {
    include: ['react', 'react-dom'],
    exclude: ['@firebase/app'], // only when it causes issues
  },
})

❌ Misconfigured Proxy

// ❌ BAD — missing changeOrigin, wrong target
export default defineConfig({
  server: {
    proxy: {
      '/api': 'http://localhost:3000',  // no changeOrigin → CORS issues
    },
  },
})

// ❌ BAD — accidental path rewrite
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        rewrite: (path) => path.replace(/^\/api/, ''),  // strips /api prefix!
        // Client expects /api/users but server receives /users
      },
    },
  },
})

// ✅ GOOD
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        // No rewrite — /api/users → /api/users
      },

*Truncated - read the full file at https://github.com/bach619/sc_auditor/blob/f8868056596d084689fdcd359aa0da0925e30a94/.opencode/skills/vite/SKILL.md.*

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/bach619-sc-auditor-vite/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

bach619-sc-auditor-vite.ocm.jsonjson
{
  "ocm": "1",
  "id": "bach619-sc-auditor-vite",
  "kind": "skill",
  "name": "vite",
  "description": "Use when building or configuring Vite 6/7 projects — dev server, production builds, plugins system, SSR, library mode, HMR, environment variables, build optimization, Vitest testing, and migration from CRA/Webpack. Covers all aspects of the Vite build tool.",
  "publisher": "bach619",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "math"
    ],
    "tags": [
      "skill-md",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Use when building or configuring Vite 6/7 projects — dev server, production builds, plugins system, SSR, library mode, HMR, environment variables, build optimization, Vitest testing, and migration from CRA/Webpack. Covers all aspects of the Vite build tool."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/bach619/sc_auditor",
      "path": ".opencode/skills/vite/SKILL.md",
      "ref": "f8868056596d084689fdcd359aa0da0925e30a94",
      "url": "https://github.com/bach619/sc_auditor/blob/f8868056596d084689fdcd359aa0da0925e30a94/.opencode/skills/vite/SKILL.md",
      "key": "bach619/sc_auditor/.opencode/skills/vite/SKILL.md"
    }
  },
  "instructions": "# Skill: vite\n\n## Vite Mastery — Dev Server, Build Optimization, Plugins, SSR, and Production Patterns\n\n### Core Philosophy\n\nVite is a **build tool that leverages native ES modules** during development and **Rollup** for production bundling. Unlike Webpack which bundles everything upfront, Vite serves source files as native ESM, letting the browser handle module resolution and only transforming files on-demand. This enables **sub-second HMR** regardless of project size.\n\n```\n┌─────────────────────────────────────────────────────────────────────┐\n│                         VITE ARCHITECTURE     ",
  "cost": {
    "context_tokens": 17749
  }
}

Fetch it by URL: GET /api/v1/registry/bach619-sc-auditor-vite/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.