Skip to content

Vite ​

A comprehensive guide to Vite, the next-generation frontend build tool.

Introduction to Vite ​

Vite (French word for "fast", pronounced /vit/) is a modern frontend build tool created by Evan You, the creator of Vue.js. It provides a faster and leaner development experience for modern web projects. Vite consists of two major parts:

  1. A dev server that serves your source files over native ES modules, with rich built-in features and astonishingly fast Hot Module Replacement (HMR).
  2. A build command that bundles your code with Rollup, pre-configured to output highly optimized static assets for production.

Key Features ​

  • Lightning-fast cold start: Vite leverages native ES modules in the browser to avoid bundling during development.
  • Instant hot module replacement (HMR): Updates are reflected in the browser instantly without reloading the page.
  • True on-demand compilation: Only the modules being edited are transformed and updated.
  • Out-of-the-box support for TypeScript, JSX, CSS preprocessors, and more.
  • Optimized build: Production builds are highly optimized using Rollup.
  • Framework-agnostic: Works with Vue, React, Preact, Lit, Svelte, and vanilla JavaScript.
  • Plugin ecosystem: Extensible via Rollup-compatible plugins.

Getting Started with Vite ​

Installation ​

You can create a new Vite project using npm, yarn, or pnpm:

bash
# npm
npm create vite@latest my-vite-app -- --template vanilla

# yarn
yarn create vite my-vite-app --template vanilla

# pnpm
pnpm create vite my-vite-app --template vanilla

Available templates include:

  • vanilla: Plain JavaScript
  • vanilla-ts: TypeScript
  • vue: Vue
  • vue-ts: Vue + TypeScript
  • react: React
  • react-ts: React + TypeScript
  • preact: Preact
  • preact-ts: Preact + TypeScript
  • lit: Lit
  • lit-ts: Lit + TypeScript
  • svelte: Svelte
  • svelte-ts: Svelte + TypeScript

Project Structure ​

A typical Vite project structure looks like this:

my-vite-app/
├── node_modules/
├── public/             # Static assets that will be served as-is
│   └── favicon.ico
├── src/                # Application source code
│   ├── assets/         # Assets that will be processed by Vite
│   │   └── logo.png
│   ├── components/     # Application components
│   │   └── HelloWorld.vue
│   ├── App.vue         # Root component (for Vue projects)
│   └── main.js         # Application entry point
├── index.html          # HTML entry point
├── package.json        # Project dependencies and scripts
├── vite.config.js      # Vite configuration
└── README.md           # Project documentation

Development Server ​

To start the development server:

bash
# Navigate to your project directory
cd my-vite-app

# Install dependencies
npm install

# Start the development server
npm run dev

The development server will start at http://localhost:5173 by default.

Core Concepts ​

ES Modules ​

Vite leverages native ES modules in the browser, which allows it to serve source code without bundling during development. This results in faster startup times and more efficient hot module replacement.

html
<!-- index.html -->
<script type="module" src="/src/main.js"></script>
javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

createApp(App).mount('#app')

Hot Module Replacement (HMR) ​

Vite provides an HMR API that allows modules to exchange and update without reloading the page:

javascript
// Counter module with HMR support
export let count = 0

export function increment() {
  count++
}

// HMR interface
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    count = newModule.count
  })
}

Static Asset Handling ​

Vite automatically processes and optimizes static assets:

javascript
// Importing assets
import imgUrl from './img.png'               // Resolved to public URL
import svgContent from './icon.svg?raw'      // Raw content as string
import Component from './Component.vue?url'  // Get the URL of the component

// Using assets in templates (Vue example)
<img :src="imgUrl" alt="Image">

Environment Variables ​

Vite exposes environment variables on the special import.meta.env object:

javascript
console.log(import.meta.env.MODE)          // 'development' or 'production'
console.log(import.meta.env.VITE_API_URL)  // Custom variables prefixed with VITE_

Custom environment variables can be defined in .env files:

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

Configuration ​

Basic Configuration ​

Vite is configured through a vite.config.js file in the project root:

javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  },
  server: {
    port: 3000,
    open: true
  },
  build: {
    outDir: 'dist',
    minify: 'terser'
  }
})

Common Configuration Options ​

Plugins ​

Plugins extend Vite's functionality:

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
    // Custom plugin
    {
      name: 'my-plugin',
      transform(code, id) {
        if (id.endsWith('.special')) {
          return { code: transformSpecialFile(code), map: null }
        }
      }
    }
  ]
})

Development Server ​

Configure the development server:

javascript
export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 3000,
    strictPort: true,
    https: true,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

Build Options ​

Configure production builds:

javascript
export default defineConfig({
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    assetsInlineLimit: 4096, // 4kb
    cssCodeSplit: true,
    sourcemap: true,
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    },
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router'],
          utils: ['./src/utils/index.js']
        }
      }
    }
  }
})

Resolve ​

Configure module resolution:

javascript
export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '~': path.resolve(__dirname, './node_modules')
    },
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
  }
})

Mode-specific Configuration ​

You can specify different configurations for development and production:

javascript
export default defineConfig(({ command, mode }) => {
  const isProduction = mode === 'production'
  
  return {
    define: {
      __APP_VERSION__: JSON.stringify('1.0.0'),
      __API_URL__: isProduction
        ? JSON.stringify('https://api.example.com')
        : JSON.stringify('http://localhost:8080')
    },
    build: {
      minify: isProduction ? 'terser' : false
    }
  }
})

Advanced Features ​

CSS Processing ​

Vite has built-in support for CSS preprocessing:

javascript
// Import CSS
import './style.css'

// CSS Modules
import styles from './style.module.css'
element.className = styles.heading

// CSS Preprocessors (requires installing the appropriate preprocessor)
import './style.scss'
import './style.less'
import './style.styl'

Configure CSS in vite.config.js:

javascript
export default defineConfig({
  css: {
    modules: {
      scopeBehaviour: 'local',
      localsConvention: 'camelCaseOnly'
    },
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/styles/variables.scss";`
      },
      less: {
        javascriptEnabled: true
      }
    },
    postcss: {
      plugins: [autoprefixer()]
    }
  }
})

TypeScript Integration ​

Vite supports TypeScript out of the box:

typescript
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

interface User {
  name: string
  age: number
}

const user: User = {
  name: 'John',
  age: 30
}

console.log(user)
createApp(App).mount('#app')

Configure TypeScript in vite.config.js:

javascript
export default defineConfig({
  esbuild: {
    target: 'es2020'
  }
})

WebAssembly ​

Import WebAssembly modules directly:

javascript
import init, { add } from './add.wasm'

init().then(() => {
  console.log(add(1, 2)) // 3
})

Web Workers ​

Import Web Workers directly:

javascript
// Standard worker
import Worker from './worker.js?worker'
const worker = new Worker()

// Shared worker
import SharedWorker from './worker.js?sharedworker'
const sharedWorker = new SharedWorker()

// Inline worker
import InlineWorker from './worker.js?worker&inline'

Server-Side Rendering (SSR) ​

Vite provides first-class support for server-side rendering:

javascript
// vite.config.js
export default defineConfig({
  ssr: {
    // SSR-specific options
    external: ['some-external-dependency'],
    noExternal: ['some-dependency-to-bundle']
  }
})

Basic SSR setup:

javascript
// server.js
import fs from 'fs'
import path from 'path'
import express from 'express'
import { createServer as createViteServer } from 'vite'

async function createServer() {
  const app = express()
  
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom'
  })
  
  app.use(vite.middlewares)
  
  app.use('*', async (req, res) => {
    const url = req.originalUrl
    
    try {
      // Read index.html
      let template = fs.readFileSync(
        path.resolve(process.cwd(), 'index.html'),
        'utf-8'
      )
      
      // Apply Vite HTML transforms
      template = await vite.transformIndexHtml(url, template)
      
      // Load the server entry
      const { render } = await vite.ssrLoadModule('/src/entry-server.js')
      
      // Render the app HTML
      const { html: appHtml, preloadLinks } = await render(url)
      
      // Inject the app-rendered HTML into the template
      const html = template
        .replace(`<!--preload-links-->`, preloadLinks)
        .replace(`<!--app-html-->`, appHtml)
      
      // Send the rendered HTML back
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (e) {
      vite.ssrFixStacktrace(e)
      console.error(e)
      res.status(500).end(e.message)
    }
  })
  
  app.listen(3000)
}

createServer()

Testing with Vite ​

Unit Testing with Vitest ​

Vitest is a Vite-native unit test framework:

bash
# Install Vitest
npm install -D vitest

Configure Vitest in vite.config.js:

javascript
import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    coverage: {
      reporter: ['text', 'json', 'html']
    }
  }
})

Write tests:

javascript
// src/utils.test.js
import { describe, it, expect } from 'vitest'
import { sum } from './utils'

describe('sum function', () => {
  it('adds two numbers correctly', () => {
    expect(sum(1, 2)).toBe(3)
  })
  
  it('handles negative numbers', () => {
    expect(sum(-1, -2)).toBe(-3)
  })
})

Run tests:

bash
npx vitest

Component Testing ​

Test Vue components with Vitest and Vue Test Utils:

bash
npm install -D @vue/test-utils jsdom
javascript
// src/components/Counter.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Counter from './Counter.vue'

describe('Counter.vue', () => {
  it('increments count when button is clicked', async () => {
    const wrapper = mount(Counter)
    expect(wrapper.text()).toContain('Count: 0')
    
    await wrapper.find('button').trigger('click')
    expect(wrapper.text()).toContain('Count: 1')
  })
})

End-to-End Testing ​

Use Cypress with Vite:

bash
npm install -D cypress

Configure Cypress for Vite:

javascript
// cypress.config.js
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:5173',
    supportFile: false
  }
})

Write E2E tests:

javascript
// cypress/e2e/basic.cy.js
describe('Basic test', () => {
  it('visits the app root url', () => {
    cy.visit('/')
    cy.contains('h1', 'Hello Vite')
  })
  
  it('increments counter', () => {
    cy.visit('/')
    cy.contains('Count: 0')
    cy.get('button').contains('Increment').click()
    cy.contains('Count: 1')
  })
})

Deployment ​

Static Site Deployment ​

Build your Vite project for production:

bash
npm run build

The build output will be in the dist directory by default, which you can deploy to any static hosting service:

  • Netlify
  • Vercel
  • GitHub Pages
  • AWS S3 + CloudFront
  • Firebase Hosting
  • Cloudflare Pages

Example deployment to Netlify:

bash
# Install Netlify CLI
npm install -g netlify-cli

# Deploy to Netlify
netlify deploy --prod --dir=dist

Base Path Configuration ​

If your site is deployed to a subdirectory, configure the base path:

javascript
// vite.config.js
export default defineConfig({
  base: '/my-app/'
})

Environment Variables for Different Environments ​

Create environment-specific files:

.env                # Loaded in all environments
.env.local          # Loaded in all environments, ignored by git
.env.development    # Loaded in development mode
.env.production     # Loaded in production mode

Performance Optimization ​

Code Splitting ​

Vite automatically performs code splitting for dynamic imports:

javascript
// Async component loading
const UserProfile = () => import('./components/UserProfile.vue')

// Dynamic import with comments for chunk naming
import(/* webpackChunkName: "admin" */ './admin.js')

Preloading ​

Use <link rel="modulepreload"> to preload modules:

javascript
// vite.config.js
export default defineConfig({
  build: {
    modulePreload: {
      polyfill: true
    }
  }
})

Asset Optimization ​

Optimize assets during build:

javascript
// vite.config.js
export default defineConfig({
  build: {
    assetsInlineLimit: 4096, // 4kb
    cssCodeSplit: true,
    rollupOptions: {
      output: {
        assetFileNames: 'assets/[name].[hash].[ext]'
      }
    }
  }
})

Integrations with Frameworks ​

Vue ​

bash
# Create a Vue project with Vite
npm create vite@latest my-vue-app -- --template vue

Configure Vue in vite.config.js:

javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
  plugins: [
    vue({
      reactivityTransform: true
    }),
    vueJsx()
  ]
})

React ​

bash
# Create a React project with Vite
npm create vite@latest my-react-app -- --template react

Configure React in vite.config.js:

javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      // Use React plugin options
      babel: {
        plugins: ['babel-plugin-macros']
      },
      jsxImportSource: '@emotion/react'
    })
  ]
})

Svelte ​

bash
# Create a Svelte project with Vite
npm create vite@latest my-svelte-app -- --template svelte

Configure Svelte in vite.config.js:

javascript
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
  plugins: [
    svelte({
      compilerOptions: {
        dev: !process.env.PRODUCTION
      }
    })
  ]
})

Best Practices ​

Project Structure ​

Organize your Vite project for maintainability:

src/
├── assets/         # Static assets
├── components/     # Reusable components
├── composables/    # Composition API functions (Vue)
├── hooks/          # Custom hooks (React)
├── layouts/        # Layout components
├── pages/          # Page components
├── router/         # Routing configuration
├── stores/         # State management
├── styles/         # Global styles
├── utils/          # Utility functions
└── main.js         # Entry point

Performance Tips ​

  1. Use dynamic imports for code splitting:

    javascript
    const AdminPanel = () => import('./components/AdminPanel.vue')
  2. Lazy load images with Intersection Observer:

    javascript
    import { useIntersectionObserver } from '@vueuse/core'
    
    const image = ref(null)
    const isVisible = ref(false)
    
    useIntersectionObserver(image, ([{ isIntersecting }]) => {
      if (isIntersecting) {
        isVisible.value = true
      }
    })
  3. Optimize dependencies:

    bash
    # Analyze bundle size
    npx vite-bundle-analyzer
  4. Use tree-shaking friendly imports:

    javascript
    // ❌ Bad - imports the entire library
    import lodash from 'lodash'
    
    // ✅ Good - imports only what's needed
    import { debounce } from 'lodash-es'

Security Best Practices ​

  1. Sanitize user input to prevent XSS attacks:

    javascript
    import DOMPurify from 'dompurify'
    
    const sanitizedHTML = DOMPurify.sanitize(userInput)
  2. Use environment variables for sensitive information:

    javascript
    const apiKey = import.meta.env.VITE_API_KEY
  3. Set proper CSP headers in production:

    javascript
    // server middleware
    app.use((req, res, next) => {
      res.setHeader(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self'"
      )
      next()
    })

Troubleshooting ​

Common Issues and Solutions ​

  1. HMR not working:

    • Check if the file is properly imported
    • Ensure the module has proper HMR acceptance code
    • Check for syntax errors
  2. Build errors:

    • Check for circular dependencies
    • Verify that all imports are correct
    • Look for missing dependencies in package.json
  3. CSS processing issues:

    • Ensure preprocessor packages are installed
    • Check preprocessor syntax
    • Verify configuration in vite.config.js
  4. Environment variables not working:

    • Ensure variables are prefixed with VITE_
    • Check that .env files are in the project root
    • Restart the dev server after changing .env files

Debugging Tips ​

  1. Enable source maps:

    javascript
    // vite.config.js
    export default defineConfig({
      build: {
        sourcemap: true
      }
    })
  2. Use Vite's debug logging:

    bash
    # Set the DEBUG environment variable
    DEBUG=vite:* npm run dev
  3. Inspect network requests in the browser's developer tools

  4. Check for plugin conflicts by disabling plugins one by one

Frequently Asked Questions ​

How does Vite compare to webpack? ​

Vite differs from webpack in several key ways:

  • Vite uses native ES modules during development, while webpack bundles everything
  • Vite has a faster startup time and HMR
  • Webpack has a larger ecosystem and more mature tooling
  • Vite uses Rollup for production builds, which can be more efficient for modern code

Can I use Vite with existing projects? ​

Yes, you can migrate existing projects to Vite:

  1. Install Vite and necessary plugins
  2. Create a vite.config.js file
  3. Update import statements to use ES modules syntax
  4. Adjust the HTML entry point
  5. Update scripts in package.json

How do I handle legacy browsers? ​

Use the @vitejs/plugin-legacy plugin:

javascript
// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11']
    })
  ]
})

How do I configure Vite for monorepos? ​

For monorepos, you can use:

  • Workspace packages with npm/yarn/pnpm
  • Vite's optimizeDeps.include option to include workspace dependencies
  • Shared configuration with defineConfig and composition
javascript
// vite.config.js
export default defineConfig({
  optimizeDeps: {
    include: ['@my-org/shared-lib']
  },
  resolve: {
    preserveSymlinks: true
  }
})

This document will be continuously updated. If you have any questions, please provide feedback through GitHub Issues.

VitePress Development Guide