← All articles
BUILD TOOLS Vite: The Build Tool That Makes Frontend Development... 2026-03-04 · 4 min read · vite · build tool · frontend

Vite: The Build Tool That Makes Frontend Development Fast

Build Tools 2026-03-04 · 4 min read vite build tool frontend javascript typescript hmr webpack alternative esbuild rollup

Vite (French for "fast") solved a real pain point: Webpack-based dev servers took 30-60 seconds to start for large projects and several seconds to reflect changes. Vite's dev server starts in under a second and updates the browser in under 100ms. It's now the default build tool for Vue, Svelte, SvelteKit, SolidJS, and Astro, and a popular alternative for React.

How Vite Works

Development mode: Vite doesn't bundle during development. It uses native ES modules (ESM) in the browser — each file is served individually via HTTP. The browser requests files as needed; Vite transforms them on-demand with esbuild.

Result: Server start is O(1) — no upfront bundling. Changes are instant because only the changed module is retransformed.

Production mode: Vite uses Rollup for production builds. Rollup produces well-optimized bundles with code splitting, tree-shaking, and efficient chunk splitting.

This two-mode approach (ESM for dev, Rollup for prod) gives you both fast development and optimized output.

Getting Started

# Create a new project
bun create vite my-app --template react-ts
# or: vanilla-ts, vue-ts, svelte-ts, solid-ts, etc.

cd my-app
bun install
bun run dev   # dev server
bun run build # production build
bun run preview # preview production build locally

Project Structure

my-app/
├── index.html          ← Entry point (not in src/)
├── vite.config.ts
├── tsconfig.json
└── src/
    ├── main.ts         ← Application entry
    └── ...

index.html is the entry point. Vite uses it directly, unlike Webpack which starts from a JS entry and generates HTML.

vite.config.ts

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],

  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },

  server: {
    port: 3000,
    open: true,  // Open browser on start
    proxy: {
      // Proxy API calls to avoid CORS during development
      "/api": {
        target: "http://localhost:8080",
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },

  build: {
    target: "es2020",
    outDir: "dist",
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ["react", "react-dom"],
          // Split large libraries into separate chunks
        },
      },
    },
  },

  test: {
    // Vitest configuration (if using Vitest)
    environment: "jsdom",
    globals: true,
    setupFiles: "./src/test/setup.ts",
  },
});

Environment Variables

Vite uses .env files with a security model: only variables prefixed with VITE_ are exposed to client-side code.

# .env
VITE_API_URL=http://localhost:8080
DB_PASSWORD=secret  # NOT exposed to client code
// Access in code
const apiUrl = import.meta.env.VITE_API_URL;

// Type safety: add to vite-env.d.ts
interface ImportMetaEnv {
  readonly VITE_API_URL: string;
}
interface ImportMeta {
  readonly env: ImportMetaEnv;
}

Files:

HMR (Hot Module Replacement)

Vite's HMR is near-instant because it only sends the changed module to the browser, not the entire bundle.

React projects using @vitejs/plugin-react (with Babel Fast Refresh) or @vitejs/plugin-react-swc (with SWC) get component-level HMR: state is preserved on update.

# Use SWC plugin for faster transforms
bun add -D @vitejs/plugin-react-swc
import react from "@vitejs/plugin-react-swc";

SWC is a Rust-based transformer that's ~5-10x faster than Babel for large codebases.

Library Mode

Build a library instead of an application:

import { defineConfig } from "vite";
import { resolve } from "path";

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, "src/index.ts"),
      name: "MyLib",
      fileName: "my-lib",
      formats: ["es", "umd"],  // ES module + UMD
    },
    rollupOptions: {
      // Don't bundle peer dependencies
      external: ["react", "react-dom"],
      output: {
        globals: {
          react: "React",
          "react-dom": "ReactDOM",
        },
      },
    },
  },
});

Output:

dist/
├── my-lib.js       ← ES module
├── my-lib.umd.cjs  ← UMD (CommonJS compatible)
└── my-lib.d.ts     ← TypeScript types (with vite-plugin-dts)

Key Plugins

@vitejs/plugin-react / @vitejs/plugin-react-swc: React support with Fast Refresh HMR.

vite-plugin-dts: Generate TypeScript declaration files from library builds.

@vitejs/plugin-vue: Vue 3 support.

vite-plugin-svgr: Import SVGs as React components.

vite-tsconfig-paths: Use TypeScript path aliases in Vite without manual alias config.

import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ plugins: [tsconfigPaths()] });

unplugin-icons: Auto-import icons from any icon set.

Static Assets

// Import as URL
import logoUrl from "./logo.svg";

// Import as string (raw content)
import svgContent from "./icon.svg?raw";

// Import as URL without processing
import jsonUrl from "./data.json?url";

Images and other assets in /public are served as-is without hashing.

Vite vs Webpack vs esbuild

Vite Webpack esbuild
Dev server startup <1s 10-60s N/A
HMR speed <100ms 1-5s N/A
Production bundler Rollup Webpack esbuild
Code splitting Limited
Plugin ecosystem Large Largest Small
Config complexity Low High Low
Tree shaking
ESM output Limited

Esbuild is the fastest bundler but lacks advanced code-splitting and Rollup's plugin ecosystem. Vite uses esbuild for transforms/dev and Rollup for production — combining their strengths.

Webpack still has the widest plugin ecosystem and handles very complex bundling scenarios. For new projects, Vite is the better starting point unless you have specific Webpack requirements.

Vitest: Testing with Vite

Vitest is the test runner designed for Vite projects:

bun add -D vitest @vitest/ui jsdom

Vitest reuses your Vite config and transforms, so test setup is minimal. Jest-compatible API — most Jest tests migrate with no changes.

// component.test.ts
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import { MyComponent } from "./MyComponent";

describe("MyComponent", () => {
  it("renders correctly", () => {
    const { getByText } = render(<MyComponent name="World" />);
    expect(getByText("Hello, World!")).toBeDefined();
  });
});

Run tests:

vitest           # watch mode
vitest run       # single run
vitest --ui      # web UI

Vitest's integration with Vite means transforms, aliases, and env variables all work the same way in tests.