Vite
03 / 03

Vite Features & Optimization

Vite Features & Optimization

Vite provides HMR, code splitting, library mode, and multiple build targets. This page covers advanced configuration for production-ready applications.

HMR — Hot Module Replacement

// Vite's HMR API — accept updates for a module
if (import.meta.hot) {
  // Accept self — when this module changes, re-execute it
  import.meta.hot.accept((newModule) => {
    if (newModule) {
      newModule.render();
    }
  });

  // Accept updates from a dependency
  import.meta.hot.accept('./dep.ts', (newDep) => {
    console.log('dep updated', newDep);
  });

  // Cleanup side effects before module is replaced
  import.meta.hot.dispose((data) => {
    data.intervalId = setInterval(() => {}, 1000);
  });

  // Decline HMR for this module — full page reload instead
  import.meta.hot.decline();

  // Invalidate — force full reload
  import.meta.hot.invalidate('module state is stale');
}

// HMR data — persist data across updates
import.meta.hot.data.count ??= 0;
import.meta.hot.data.count++;

Code Splitting & Dynamic Imports

// Dynamic import — creates a separate chunk automatically
const { default: Chart } = await import('./Chart');

// React lazy + Suspense
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

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

// Prefetch hint — loads chunk in background
const prefetch = () => import(/* @vite-ignore */ './HeavyPage');
onMouseEnter={() => prefetch()};

Build Optimization & Manual Chunks

// vite.config.ts — advanced build optimization
build: {
  rollupOptions: {
    output: {
      // Manual chunk splitting
      manualChunks(id) {
        if (id.includes('node_modules')) {
          if (id.includes('react')) return 'react-vendor';
          if (id.includes('@radix-ui')) return 'radix-vendor';
          if (id.includes('framer-motion')) return 'animation-vendor';
          return 'vendor';             // everything else into vendor
        }
      },
      // Or use object form for explicit grouping:
      // manualChunks: {
      //   vendor: ['react', 'react-dom', 'react-router-dom'],
      // },
      chunkFileNames: 'assets/[name]-[hash].js',
      entryFileNames: 'assets/[name]-[hash].js',
      assetFileNames: 'assets/[name]-[hash][extname]',
    },
  },
  // Dependency pre-bundling control
  optimizeDeps: {
    include: ['lodash-es', 'date-fns'],    // force pre-bundle
    exclude: ['your-local-lib'],           // skip pre-bundling
  },
}

Library Mode

// Build a reusable library (component library, SDK)
import { resolve } from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';     // generates .d.ts declarations

export default defineConfig({
  plugins: [dts({ include: ['src'] })],
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'MyLib',
      formats: ['es', 'cjs', 'umd'],   // output formats
      fileName: (format) => `my-lib.${format}.js`,
    },
    rollupOptions: {
      // Externalize peer deps — don't bundle React into the lib
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
  },
});

// package.json for the library
// {
//   "main": "./dist/my-lib.cjs.js",
//   "module": "./dist/my-lib.es.js",
//   "types": "./dist/index.d.ts",
//   "exports": {
//     ".": {
//       "import": "./dist/my-lib.es.js",
//       "require": "./dist/my-lib.cjs.js"
//     }
//   }
// }

Vitest — Co-located Test Runner

// Vitest config inside vite.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,                    // no need to import describe/it/expect
    environment: 'jsdom',             // 'node' | 'jsdom' | 'happy-dom'
    setupFiles: ['./src/test/setup.ts'],
    include: ['**/*.{test,spec}.{ts,tsx}'],
    exclude: ['node_modules', 'dist', 'e2e'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov', 'html'],
      thresholds: { lines: 80, branches: 75 },
    },
    // Mock CSS modules and static assets
    css: false,
  },
});

// src/test/setup.ts
import '@testing-library/jest-dom';

// Example test
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Button } from '@/components/Button';

describe('Button', () => {
  it('calls onClick when clicked', async () => {
    const onClick = vi.fn();
    render(<Button onClick={onClick}>Click me</Button>);
    await userEvent.click(screen.getByRole('button'));
    expect(onClick).toHaveBeenCalledOnce();
  });
});

// CLI
// vitest              — watch mode
// vitest run          — single run (CI)
// vitest --ui         — browser UI
// vitest --coverage   — with coverage

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free