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:
- A dev server that serves your source files over native ES modules, with rich built-in features and astonishingly fast Hot Module Replacement (HMR).
- 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:
# 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 JavaScriptvanilla-ts
: TypeScriptvue
: Vuevue-ts
: Vue + TypeScriptreact
: Reactreact-ts
: React + TypeScriptpreact
: Preactpreact-ts
: Preact + TypeScriptlit
: Litlit-ts
: Lit + TypeScriptsvelte
: Sveltesvelte-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:
# 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.
<!-- index.html -->
<script type="module" src="/src/main.js"></script>
// 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:
// 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:
// 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:
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:
// 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:
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:
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:
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:
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:
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:
// 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
:
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:
// 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
:
export default defineConfig({
esbuild: {
target: 'es2020'
}
})
WebAssembly
Import WebAssembly modules directly:
import init, { add } from './add.wasm'
init().then(() => {
console.log(add(1, 2)) // 3
})
Web Workers
Import Web Workers directly:
// 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:
// vite.config.js
export default defineConfig({
ssr: {
// SSR-specific options
external: ['some-external-dependency'],
noExternal: ['some-dependency-to-bundle']
}
})
Basic SSR setup:
// 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:
# Install Vitest
npm install -D vitest
Configure Vitest in vite.config.js
:
import { defineConfig } from 'vite'
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
coverage: {
reporter: ['text', 'json', 'html']
}
}
})
Write tests:
// 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:
npx vitest
Component Testing
Test Vue components with Vitest and Vue Test Utils:
npm install -D @vue/test-utils jsdom
// 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:
npm install -D cypress
Configure Cypress for Vite:
// cypress.config.js
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:5173',
supportFile: false
}
})
Write E2E tests:
// 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:
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:
# 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:
// 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:
// 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:
// vite.config.js
export default defineConfig({
build: {
modulePreload: {
polyfill: true
}
}
})
Asset Optimization
Optimize assets during build:
// vite.config.js
export default defineConfig({
build: {
assetsInlineLimit: 4096, // 4kb
cssCodeSplit: true,
rollupOptions: {
output: {
assetFileNames: 'assets/[name].[hash].[ext]'
}
}
}
})
Integrations with Frameworks
Vue
# Create a Vue project with Vite
npm create vite@latest my-vue-app -- --template vue
Configure Vue in vite.config.js
:
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
# Create a React project with Vite
npm create vite@latest my-react-app -- --template react
Configure React in vite.config.js
:
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
# Create a Svelte project with Vite
npm create vite@latest my-svelte-app -- --template svelte
Configure Svelte in vite.config.js
:
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
Use dynamic imports for code splitting:
javascriptconst AdminPanel = () => import('./components/AdminPanel.vue')
Lazy load images with Intersection Observer:
javascriptimport { useIntersectionObserver } from '@vueuse/core' const image = ref(null) const isVisible = ref(false) useIntersectionObserver(image, ([{ isIntersecting }]) => { if (isIntersecting) { isVisible.value = true } })
Optimize dependencies:
bash# Analyze bundle size npx vite-bundle-analyzer
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
Sanitize user input to prevent XSS attacks:
javascriptimport DOMPurify from 'dompurify' const sanitizedHTML = DOMPurify.sanitize(userInput)
Use environment variables for sensitive information:
javascriptconst apiKey = import.meta.env.VITE_API_KEY
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
HMR not working:
- Check if the file is properly imported
- Ensure the module has proper HMR acceptance code
- Check for syntax errors
Build errors:
- Check for circular dependencies
- Verify that all imports are correct
- Look for missing dependencies in package.json
CSS processing issues:
- Ensure preprocessor packages are installed
- Check preprocessor syntax
- Verify configuration in vite.config.js
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
- Ensure variables are prefixed with
Debugging Tips
Enable source maps:
javascript// vite.config.js export default defineConfig({ build: { sourcemap: true } })
Use Vite's debug logging:
bash# Set the DEBUG environment variable DEBUG=vite:* npm run dev
Inspect network requests in the browser's developer tools
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:
- Install Vite and necessary plugins
- Create a
vite.config.js
file - Update import statements to use ES modules syntax
- Adjust the HTML entry point
- Update scripts in package.json
How do I handle legacy browsers?
Use the @vitejs/plugin-legacy
plugin:
// 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
// vite.config.js
export default defineConfig({
optimizeDeps: {
include: ['@my-org/shared-lib']
},
resolve: {
preserveSymlinks: true
}
})
Related Resources
- Vite Official Documentation
- Awesome Vite
- Vite GitHub Repository
- Vitest - Vite-native Testing Framework
- VitePress - Vite & Vue Powered Static Site Generator
- More Tutorials
This document will be continuously updated. If you have any questions, please provide feedback through GitHub Issues.