
Vite
- 648 installs
- 93 repo stars
- Updated March 1, 2026
- uni-helper/skills
vite is a Claude Code skill that helps developers configure Vite, add plugins, run the dev server, and create optimized production builds for modern frontend projects.
About
vite is a Claude Code skill generated from the official Vite repository, based on Vite 6.x with metadata version 2026.1.28. The skill guides agents through instant dev-server startup with native ES modules, lightning-fast HMR, TypeScript and JSX support, CSS pre-processors, and production builds via Rolldown and Rollup. Developers reach for vite when scaffolding vite.config.ts, adding plugins, debugging dev-server behavior, or tuning optimized production bundles for React, Vue, or vanilla frontends without digging through upstream docs mid-session.
- Instant server start with native ES modules and lightning-fast HMR
- Optimized production builds using Rolldown/Rollup
- Built-in support for TypeScript, JSX, CSS modules, and pre-processors
- Rich plugin ecosystem with easy adding, configuring, and ordering
- Comprehensive CLI commands for dev, build, and preview
Vite by the numbers
- 648 all-time installs (skills.sh)
- +15 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #540 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/uni-helper/skills --skill viteAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 648 |
|---|---|
| repo stars | ★ 93 |
| Last updated | March 1, 2026 |
| Repository | uni-helper/skills ↗ |
How do you configure Vite plugins and production builds?
Configure Vite, add plugins, run the dev server, and create optimized production builds for modern frontend projects.
Who is it for?
Frontend developers using Vite 6.x who need agent help configuring plugins, HMR dev server, or Rolldown and Rollup production builds.
Skip if: Webpack or Next.js App Router projects, or backend-only services with no Vite frontend toolchain.
When should I use this skill?
A developer asks to configure Vite, add Vite plugins, fix dev-server issues, or optimize Vite production builds.
What you get
Working vite.config.ts, plugin setup, dev-server commands, and optimized production build configuration.
- vite.config.ts
- plugin configuration
- production build scripts
By the numbers
- Skill is based on Vite 6.x with metadata version 2026.1.28
- Generated from the official vitejs/vite repository via antfu/skills scripts
Files
Vite is a modern build tool for frontend development featuring instant server start with native ES modules, lightning-fast HMR, and optimized production builds using Rolldown/Rollup. It supports TypeScript, JSX, CSS pre-processors out of the box and has a rich plugin ecosystem.
The skill is based on Vite 6.x, generated at 2026-01-28.
Core
| Topic | Description | Reference |
|---|---|---|
| Configuration | Config file setup, defineConfig, conditional and async configs | core-config |
| CLI Commands | Dev server, build, preview commands and options | core-cli |
| Core Features | TypeScript, JSX, CSS, HTML processing, JSON handling | core-features |
| Using Plugins | Adding, configuring, and ordering plugins | core-plugins |
Features
| Topic | Description | Reference |
|---|---|---|
| CSS Handling | CSS modules, pre-processors, PostCSS, Lightning CSS | features-css |
| Static Assets | Asset imports, public directory, URL handling | features-assets |
| Glob Import | import.meta.glob, dynamic imports, batch loading | features-glob-import |
| Environment Variables | .env files, modes, import.meta.env constants | features-env |
| HMR API | Hot Module Replacement client API | features-hmr |
| Web Workers | Worker imports and configuration | features-workers |
| Dependency Pre-Bundling | optimizeDeps, caching, monorepo setup | features-dep-bundling |
Build
| Topic | Description | Reference |
|---|---|---|
| Production Build | Build options, browser targets, multi-page apps | build-production |
| Library Mode | Building libraries with proper package exports | build-library |
| SSR | Server-side rendering setup and configuration | build-ssr |
Advanced
| Topic | Description | Reference |
|---|---|---|
| JavaScript API | createServer, build, preview programmatic APIs | advanced-api |
| Plugin API | Creating Vite plugins, hooks, virtual modules | advanced-plugin-api |
| Performance | Optimization tips for dev server and builds | advanced-performance |
| Backend Integration | Integrating Vite with traditional backends | advanced-backend |
Generation Info
- Source:
sources/vite - Git SHA:
b40292ce6a7dbbbbac9c6dae5f126b7f44c3e1b7 - Generated: 2026-01-28
MIT License
Copyright (c) 2025-PRESENT Anthony Fu <https://github.com/antfu>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
JavaScript API
Vite's APIs are fully typed. Use TypeScript or enable JS type checking for intellisense.
createServer
Create a development server programmatically:
import { createServer } from 'vite'
const server = await createServer({
configFile: false,
root: __dirname,
server: {
port: 1337
}
})
await server.listen()
server.printUrls()
server.bindCLIShortcuts({ print: true })ViteDevServer Interface
interface ViteDevServer {
config: ResolvedConfig
middlewares: Connect.Server // Connect app for custom middleware
httpServer: http.Server | null // Node HTTP server
watcher: FSWatcher // Chokidar watcher
ws: WebSocketServer // WebSocket for HMR
moduleGraph: ModuleGraph // Module import relationships
// Transform without HTTP
transformRequest(url: string): Promise<TransformResult | null>
// Apply HTML transforms
transformIndexHtml(url: string, html: string): Promise<string>
// Load module for SSR
ssrLoadModule(url: string): Promise<Record<string, any>>
// Fix SSR error stack traces
ssrFixStacktrace(e: Error): void
// Control
listen(port?: number): Promise<ViteDevServer>
restart(): Promise<void>
close(): Promise<void>
}build
Build for production:
import { build } from 'vite'
await build({
root: './project',
base: '/foo/',
build: {
rolldownOptions: {
// ...
}
}
})preview
Preview production build locally:
import { preview } from 'vite'
const previewServer = await preview({
preview: {
port: 8080,
open: true
}
})
previewServer.printUrls()resolveConfig
Resolve config without starting server:
import { resolveConfig } from 'vite'
const config = await resolveConfig(
{ root: './project' },
'serve', // 'serve' | 'build'
'development' // default mode
)mergeConfig
Deep merge two configs:
import { mergeConfig } from 'vite'
const merged = mergeConfig(baseConfig, overrideConfig)Merge callback config:
import { defineConfig, mergeConfig } from 'vite'
export default defineConfig((env) =>
mergeConfig(configAsCallback(env), configAsObject)
)loadEnv
Load .env files:
import { loadEnv } from 'vite'
// Load VITE_* vars
const env = loadEnv('development', process.cwd())
// Load all vars (empty prefix)
const allEnv = loadEnv('development', process.cwd(), '')searchForWorkspaceRoot
Find monorepo workspace root:
import { searchForWorkspaceRoot } from 'vite'
const workspaceRoot = searchForWorkspaceRoot(process.cwd())normalizePath
Normalize paths for cross-platform:
import { normalizePath } from 'vite'
normalizePath('foo\\bar') // 'foo/bar'transformWithOxc
Transform JS/TS with Oxc Transformer:
import { transformWithOxc } from 'vite'
const result = await transformWithOxc(
code,
'file.ts',
{ target: 'es2020' }
)preprocessCSS
Pre-process CSS files:
import { preprocessCSS, resolveConfig } from 'vite'
const config = await resolveConfig({}, 'serve')
const result = await preprocessCSS(code, 'styles.scss', config)
// result.code - plain CSS
// result.modules - CSS modules mappingloadConfigFromFile
Load config file manually:
import { loadConfigFromFile } from 'vite'
const result = await loadConfigFromFile(
{ command: 'serve', mode: 'development' },
'vite.config.ts'
)
// result.config, result.path, result.dependenciesInlineConfig
Extends UserConfig with:
interface InlineConfig extends UserConfig {
configFile?: string | false // Config file path or false to skip
mode?: string
}<!-- Source references:
- https://vite.dev/guide/api-javascript.html
-->
Backend Integration
Integrate Vite with traditional backends (Rails, Laravel, etc.) for asset serving.
Configuration
// vite.config.ts
export default defineConfig({
server: {
cors: {
origin: 'http://my-backend.example.com'
}
},
build: {
manifest: true, // Generate .vite/manifest.json
rolldownOptions: {
input: '/path/to/main.js' // Override HTML entry
}
}
})Import polyfill in entry:
// main.js
import 'vite/modulepreload-polyfill'Development
Inject Vite client and entry in your backend template:
<!-- Development only -->
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/main.js"></script>React Setup
Add before other scripts:
<script type="module">
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>Asset Proxying
Either: 1. Proxy static asset requests to Vite 2. Set server.origin:
export default defineConfig({
server: {
origin: 'http://localhost:5173'
}
})Production
Build generates .vite/manifest.json:
{
"views/foo.js": {
"file": "assets/foo-BRBmoGS9.js",
"src": "views/foo.js",
"isEntry": true,
"imports": ["_shared-B7PI925R.js"],
"css": ["assets/foo-5UjPuW-k.css"]
},
"_shared-B7PI925R.js": {
"file": "assets/shared-B7PI925R.js",
"css": ["assets/shared-ChJ_j-JJ.css"]
}
}Rendering Tags
For entry views/foo.js, render in this order:
<!-- 1. Entry CSS -->
<link rel="stylesheet" href="/assets/foo-5UjPuW-k.css" />
<!-- 2. Imported chunks' CSS (recursive) -->
<link rel="stylesheet" href="/assets/shared-ChJ_j-JJ.css" />
<!-- 3. Entry script -->
<script type="module" src="/assets/foo-BRBmoGS9.js"></script>
<!-- 4. Optional: preload imports -->
<link rel="modulepreload" href="/assets/shared-B7PI925R.js" />Manifest Structure
interface ManifestChunk {
src?: string // Input file name
file: string // Output file name
css?: string[] // CSS files (JS chunks only)
assets?: string[] // Non-CSS assets (JS chunks only)
isEntry?: boolean // Is entry point
isDynamicEntry?: boolean // Is dynamic import
imports?: string[] // Static imports (manifest keys)
dynamicImports?: string[] // Dynamic imports (manifest keys)
}Processing Imports
Recursively collect all imported chunks:
function getImportedChunks(manifest, name) {
const seen = new Set()
const chunks = []
function collect(chunk) {
for (const file of chunk.imports ?? []) {
if (seen.has(file)) continue
seen.add(file)
const importee = manifest[file]
collect(importee)
chunks.push(importee)
}
}
collect(manifest[name])
return chunks
}Existing Integrations
Check Awesome Vite for:
- Laravel (laravel-vite)
- Rails
- Django
- Flask
- And more
<!-- Source references:
- https://vite.dev/guide/backend-integration.html
-->
Performance Optimization
Browser Setup
- Use dev-only browser profile without extensions
- Disable "Disable Cache" in DevTools when using Vite
- Extensions can interfere with requests and slow startup
Audit Plugin Performance
1. Lazy load large dependencies in plugins 2. Avoid long operations in buildStart, config, configResolved 3. Optimize transform hooks - check id extension before processing
Debug transform times:
vite --debug plugin-transformUse vite-plugin-inspect to inspect transforms.
Profiling
vite --profile
# Visit site, press 'p + enter' to record .cpuprofile
# Open in https://www.speedscope.appReduce Resolve Operations
Be explicit with extensions to avoid filesystem checks:
// Slow: checks .mjs, .js, .mts, .ts, .jsx, .tsx, .json
import Component from './Component'
// Fast: direct hit
import Component from './Component.tsx'Enable TypeScript path resolution for explicit imports:
{
"compilerOptions": {
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
}
}Avoid Barrel Files
Barrel files (index.js re-exporting everything) cause all files to load:
// Slow: loads all utils
import { slash } from './utils'
// Fast: loads only slash.js
import { slash } from './utils/slash.js'Warm Up Frequently Used Files
Pre-transform files that are always loaded:
export default defineConfig({
server: {
warmup: {
clientFiles: [
'./src/components/BigComponent.vue',
'./src/utils/big-utils.js'
],
ssrFiles: ['./src/server/modules/*.js']
}
}
})Find files to warm up:
vite --debug transformUse Native/Less Tooling
Do less work:
- CSS instead of Sass/Less (PostCSS has nesting)
- Don't transform SVGs to components - import as strings/URLs
- Skip Babel in
@vitejs/plugin-reactif not needed
Use native tools:
- Try Lightning CSS for faster CSS processing
export default defineConfig({
css: {
transformer: 'lightningcss'
}
})Server Options
Open Browser Automatically
Triggers warmup of entry point:
export default defineConfig({
server: {
open: true
}
})Limit File Watching
export default defineConfig({
server: {
watch: {
ignored: ['**/large-folder/**']
}
}
})Build Performance
Disable Reporting
Skip gzip size calculation for large projects:
export default defineConfig({
build: {
reportCompressedSize: false
}
})Sourcemaps
Disable if not needed:
export default defineConfig({
build: {
sourcemap: false
}
})<!-- Source references:
- https://vite.dev/guide/performance.html
-->
Plugin API
Vite plugins extend Rolldown's plugin interface with Vite-specific hooks.
Basic Plugin Structure
export default function myPlugin(options = {}) {
return {
name: 'vite-plugin-my-plugin',
// Hooks...
}
}Naming Conventions
- Vite-only plugins:
vite-plugin-* - Rollup-compatible:
rollup-plugin-* - Framework-specific:
vite-plugin-vue-*,vite-plugin-react-*
Universal Hooks (from Rolldown)
Called on server start:
options- Modify Rolldown optionsbuildStart- Build starting
Called per module request:
resolveId- Resolve import pathsload- Load module contenttransform- Transform module code
Called on server close:
buildEndcloseBundle
Vite-Specific Hooks
config
Modify config before resolution:
{
name: 'modify-config',
config(config, { command, mode }) {
if (command === 'build') {
return {
resolve: {
alias: { foo: 'bar' }
}
}
}
}
}configResolved
Access final resolved config:
{
name: 'read-config',
configResolved(config) {
this.config = config
}
}configureServer
Add dev server middleware:
{
name: 'configure-server',
configureServer(server) {
// Before Vite's middlewares
server.middlewares.use((req, res, next) => {
// Handle request
next()
})
// Return function to run after Vite's middlewares
return () => {
server.middlewares.use((req, res, next) => {
// Post middleware
})
}
}
}transformIndexHtml
Transform HTML files:
{
name: 'html-transform',
transformIndexHtml(html) {
return html.replace(/<title>(.*?)<\/title>/, '<title>New Title</title>')
}
}Inject tags:
{
name: 'html-inject',
transformIndexHtml() {
return {
tags: [
{
tag: 'script',
attrs: { src: '/inject.js' },
injectTo: 'body' // 'head' | 'body' | 'head-prepend' | 'body-prepend'
}
]
}
}
}handleHotUpdate
Custom HMR handling:
{
name: 'custom-hmr',
handleHotUpdate({ file, server, modules }) {
if (file.endsWith('.custom')) {
server.ws.send({
type: 'custom',
event: 'custom-update',
data: { file }
})
return [] // Prevent default HMR
}
}
}Virtual Modules
Provide build-time information to source code:
export default function myPlugin() {
const virtualModuleId = 'virtual:my-module'
const resolvedId = '\0' + virtualModuleId
return {
name: 'virtual-module',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedId
}
},
load(id) {
if (id === resolvedId) {
return `export const msg = "from virtual module"`
}
}
}
}Usage:
import { msg } from 'virtual:my-module'Client-Server Communication
Server to Client
{
configureServer(server) {
server.ws.on('connection', () => {
server.ws.send('my:greetings', { msg: 'hello' })
})
}
}Client receives:
if (import.meta.hot) {
import.meta.hot.on('my:greetings', (data) => {
console.log(data.msg)
})
}Client to Server
// Client
if (import.meta.hot) {
import.meta.hot.send('my:from-client', { msg: 'Hey!' })
}
// Server (in plugin)
{
configureServer(server) {
server.ws.on('my:from-client', (data, client) => {
console.log(data.msg)
client.send('my:reply', { msg: 'Got it!' })
})
}
}Transform with Filtering
{
name: 'transform-js',
transform: {
filter: {
id: /\.js$/ // Only .js files
},
handler(code, id) {
return transformCode(code)
}
}
}Path Normalization
Use POSIX separators for cross-platform compatibility:
import { normalizePath } from 'vite'
normalizePath('foo\\bar') // 'foo/bar'<!-- Source references:
- https://vite.dev/guide/api-plugin.html
-->
Library Mode
Build browser-oriented libraries for distribution.
Basic Configuration
import { resolve } from 'path'
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'lib/main.js'),
name: 'MyLib', // Global variable name for UMD
fileName: 'my-lib' // Output filename (without extension)
},
rolldownOptions: {
external: ['vue'], // Don't bundle these
output: {
globals: {
vue: 'Vue' // Global var for externals in UMD
}
}
}
}
})Multiple Entry Points
export default defineConfig({
build: {
lib: {
entry: {
'my-lib': resolve(__dirname, 'lib/main.js'),
'secondary': resolve(__dirname, 'lib/secondary.js')
},
name: 'MyLib'
}
}
})Output Formats
Single entry defaults: ['es', 'umd'] Multiple entries defaults: ['es', 'cjs']
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'lib/main.js'),
formats: ['es', 'cjs', 'umd', 'iife']
}
}
})Custom File Names
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'lib/main.js'),
fileName: (format, entryName) => `${entryName}.${format}.js`,
cssFileName: 'styles' // For bundled CSS
}
}
})Package.json Configuration
Single Entry
{
"name": "my-lib",
"type": "module",
"files": ["dist"],
"main": "./dist/my-lib.umd.cjs",
"module": "./dist/my-lib.js",
"exports": {
".": {
"import": "./dist/my-lib.js",
"require": "./dist/my-lib.umd.cjs"
}
}
}Multiple Entries
{
"name": "my-lib",
"type": "module",
"files": ["dist"],
"main": "./dist/my-lib.cjs",
"module": "./dist/my-lib.js",
"exports": {
".": {
"import": "./dist/my-lib.js",
"require": "./dist/my-lib.cjs"
},
"./secondary": {
"import": "./dist/secondary.js",
"require": "./dist/secondary.cjs"
}
}
}With CSS
{
"exports": {
".": {
"import": "./dist/my-lib.js",
"require": "./dist/my-lib.umd.cjs"
},
"./style.css": "./dist/my-lib.css"
}
}Library Entry File
// lib/main.js
import Foo from './Foo.vue'
import Bar from './Bar.vue'
export { Foo, Bar }Environment Variables
In library mode:
import.meta.env.*is statically replacedprocess.env.*is NOT replaced (consumers can change it)
To replace process.env:
export default defineConfig({
define: {
'process.env.NODE_ENV': '"production"'
}
})Notes
assetsInlineLimitis ignored - assets always inlinedcssCodeSplitdefaults tofalse- For non-browser libraries, consider using tsdown or Rolldown directly
<!-- Source references:
- https://vite.dev/guide/build.html#library-mode
-->
Building for Production
Basic Build
vite buildUses <root>/index.html as entry point, outputs to dist/.
Browser Compatibility
Default target: Baseline Widely Available browsers (Chrome 111+, Edge 111+, Firefox 114+, Safari 16.4+).
export default defineConfig({
build: {
target: 'es2020', // Or specific browsers
// target: ['chrome64', 'firefox78', 'safari12']
}
})For legacy browsers:
npm add -D @vitejs/plugin-legacyimport legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11']
})
]
})Output Configuration
export default defineConfig({
build: {
outDir: 'dist', // Output directory
assetsDir: 'assets', // Assets subdirectory
emptyOutDir: true, // Clear outDir before build
sourcemap: true, // Generate sourcemaps
// sourcemap: 'inline' | 'hidden'
}
})Public Base Path
For deploying under a subpath:
export default defineConfig({
base: '/my-app/'
})Relative base (works anywhere):
export default defineConfig({
base: './'
})Access in code:
const base = import.meta.env.BASE_URLMulti-Page App
import { resolve } from 'path'
export default defineConfig({
build: {
rolldownOptions: {
input: {
main: resolve(__dirname, 'index.html'),
nested: resolve(__dirname, 'nested/index.html')
}
}
}
})Minification
export default defineConfig({
build: {
minify: 'oxc', // Default, fastest
// minify: 'terser', // More options, slower
// minify: false, // Disable
terserOptions: { // If using terser
compress: {
drop_console: true
}
}
}
})Chunk Strategy
export default defineConfig({
build: {
rolldownOptions: {
output: {
codeSplitting: {
// Manual chunks configuration
}
}
},
chunkSizeWarningLimit: 500 // KB
}
})CSS Options
export default defineConfig({
build: {
cssCodeSplit: true, // CSS per async chunk
cssMinify: 'lightningcss', // or 'esbuild'
cssTarget: 'chrome61' // Different from JS target
}
})Asset Handling
export default defineConfig({
build: {
assetsInlineLimit: 4096, // Inline assets < 4KB as base64
copyPublicDir: true // Copy public/ to outDir
}
})Manifest
Generate manifest for backend integration:
export default defineConfig({
build: {
manifest: true // .vite/manifest.json
}
})Watch Mode
Rebuild on file changes:
vite build --watchexport default defineConfig({
build: {
watch: {} // Enable programmatically
}
})Load Error Handling
Handle dynamic import failures (e.g., after deployment):
window.addEventListener('vite:preloadError', (event) => {
window.location.reload()
})Build Optimizations (Automatic)
- CSS code splitting - CSS per async chunk
- Preload directives -
<link rel="modulepreload"> - Async chunk optimization - Parallel fetching of dependencies
License Generation
Generate license file for dependencies:
export default defineConfig({
build: {
license: true // .vite/license.md
}
})<!-- Source references:
- https://vite.dev/guide/build.html
- https://vite.dev/config/build-options.html
-->
Server-Side Rendering (SSR)
Low-level API for framework authors. For applications, use higher-level tools from Awesome Vite SSR.
Project Structure
├── index.html
├── server.js # Express/Node server
└── src/
├── main.js # Universal app code
├── entry-client.js # Mounts app to DOM
└── entry-server.js # Renders app with SSR APIindex.html
<!DOCTYPE html>
<html>
<body>
<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>
</body>
</html>Development Server
// server.js
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('*all', async (req, res, next) => {
const url = req.originalUrl
try {
// 1. Read index.html
let template = fs.readFileSync(
path.resolve(__dirname, 'index.html'),
'utf-8'
)
// 2. Apply Vite transforms
template = await vite.transformIndexHtml(url, template)
// 3. Load server entry
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
// 4. Render app HTML
const appHtml = await render(url)
// 5. Inject into template
const html = template.replace('<!--ssr-outlet-->', appHtml)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite.ssrFixStacktrace(e)
next(e)
}
})
app.listen(5173)
}
createServer()Conditional Logic
if (import.meta.env.SSR) {
// Server-only code (tree-shaken on client)
}Production Build
{
"scripts": {
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/entry-server.js"
}
}Production Server
// Differences from dev:
// 1. Use dist/client/index.html as template
// 2. Use import('./dist/server/entry-server.js') instead of ssrLoadModule
// 3. Serve static files from dist/clientSSR Manifest
For preload directives:
vite build --outDir dist/client --ssrManifestGenerates dist/client/.vite/ssr-manifest.json with module-to-chunk mappings.
SSR Externals
Dependencies are externalized by default. To transform with Vite:
export default defineConfig({
ssr: {
noExternal: ['package-that-needs-transform'],
external: ['package-to-externalize']
}
})SSR-specific Plugin Logic
export function mySSRPlugin() {
return {
name: 'my-ssr',
transform(code, id, options) {
if (options?.ssr) {
// SSR-specific transform
}
}
}
}SSR Target
export default defineConfig({
ssr: {
target: 'node', // Default
// target: 'webworker' // For edge runtimes
}
})SSR Bundle
Bundle all dependencies (for workers):
export default defineConfig({
ssr: {
noExternal: true // Bundle everything
}
})Resolve Conditions
export default defineConfig({
ssr: {
resolve: {
conditions: ['node'],
externalConditions: ['node']
}
}
})Pre-Rendering / SSG
Pre-render routes with known data into static HTML at build time.
<!-- Source references:
- https://vite.dev/guide/ssr.html
-->
Vite CLI
Dev Server
Start the development server:
vite [root]
vite dev [root] # alias
vite serve [root] # aliasDev Server Options
| Option | Description |
|---|---|
--host [host] | Specify hostname (use 0.0.0.0 for LAN access) |
--port <port> | Specify port (default: 5173) |
--open [path] | Open browser on startup |
--cors | Enable CORS |
--strictPort | Exit if port is in use |
--force | Force optimizer to re-bundle dependencies |
-c, --config <file> | Use specified config file |
--base <path> | Public base path |
-m, --mode <mode> | Set env mode |
-l, --logLevel <level> | info \ |
--clearScreen | Allow/disable clear screen when logging |
Build
Build for production:
vite build [root]Build Options
| Option | Description |
|---|---|
--target <target> | Transpile target (default: "modules") |
--outDir <dir> | Output directory (default: dist) |
--assetsDir <dir> | Assets directory under outDir (default: "assets") |
--assetsInlineLimit <number> | Inline threshold in bytes (default: 4096) |
--ssr [entry] | Build for SSR |
--sourcemap [output] | Generate source maps (`boolean \ |
--minify [minifier] | Minifier (`boolean \ |
--manifest [name] | Generate build manifest JSON |
--ssrManifest [name] | Generate SSR manifest JSON |
--emptyOutDir | Force empty outDir |
-w, --watch | Watch mode for rebuilding |
Preview
Locally preview the production build:
vite preview [root]Preview Options
| Option | Description |
|---|---|
--host [host] | Specify hostname |
--port <port> | Specify port |
--strictPort | Exit if port is in use |
--open [path] | Open browser on startup |
--outDir <dir> | Output directory (default: dist) |
Package Scripts
Typical package.json scripts:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}Running Vite
# With npm
npx vite
# With pnpm
pnpm vite
# With yarn
yarn vite
# With bun
bunx viteScaffolding New Project
# Interactive prompts
npm create vite@latest
# With project name and template
npm create vite@latest my-app -- --template vue-ts
# Available templates: vanilla, vanilla-ts, vue, vue-ts, react, react-ts,
# react-swc, react-swc-ts, preact, preact-ts, lit, lit-ts, svelte, svelte-ts,
# solid, solid-ts, qwik, qwik-tsDebugging
# Debug plugin transforms
vite --debug plugin-transform
# Debug with profiling
vite --profile
# Then press 'p + enter' to record .cpuprofile
# Filter debug logs
vite --debug -f plugin-transform<!-- Source references:
- https://vite.dev/guide/cli.html
-->
Vite Configuration
Vite automatically resolves a config file named vite.config.* in the project root.
Basic Configuration
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
// config options
})Use defineConfig for TypeScript intellisense. Alternatively, use JSDoc annotations:
/** @type {import('vite').UserConfig} */
export default {
// config options
}Conditional Config
Export a function to conditionally determine options based on command, mode, or build type:
import { defineConfig } from 'vite'
export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => {
if (command === 'serve') {
// dev specific config
return {
define: {
__DEV__: true
}
}
} else {
// build specific config
return {
define: {
__DEV__: false
}
}
}
})commandis'serve'during dev (vite,vite dev,vite serve) and'build'for productionmodedefaults to'development'for serve,'production'for build
Async Config
import { defineConfig } from 'vite'
export default defineConfig(async ({ command, mode }) => {
const data = await fetchRemoteConfig()
return {
// config using fetched data
}
})Key Configuration Options
Root and Base
export default defineConfig({
root: './src', // Project root directory (where index.html is)
base: '/my-app/', // Public base path for assets
publicDir: 'public', // Static assets directory
cacheDir: 'node_modules/.vite' // Cache directory
})Resolve Aliases
import { resolve } from 'path'
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'~': resolve(__dirname, 'src/components')
},
// File extensions to try for imports without extension
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']
}
})Define Global Constants
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify('1.0.0'),
__API_URL__: JSON.stringify('https://api.example.com')
}
})Values must be JSON-serializable or a single identifier. Add TypeScript declarations:
// vite-env.d.ts
declare const __APP_VERSION__: string
declare const __API_URL__: stringJSON Handling
export default defineConfig({
json: {
namedExports: true, // Support named imports from JSON
stringify: 'auto' // Stringify large JSON for performance
}
})Using Environment Variables in Config
Variables from .env files are NOT automatically available in config. Use loadEnv:
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// Load env vars from .env files
const env = loadEnv(mode, process.cwd(), '')
return {
define: {
__APP_ENV__: JSON.stringify(env.APP_ENV)
},
server: {
port: env.APP_PORT ? Number(env.APP_PORT) : 5173
}
}
})Specifying Config File
vite --config my-config.tsConfig Loading Methods
# Default: bundle with Rolldown (may have issues in monorepos)
vite
# Use module runner (no temp file, transforms on the fly)
vite --configLoader runner
# Use native runtime (requires Node.js with TypeScript support)
vite --configLoader native<!-- Source references:
- https://vite.dev/config/
-->
Core Features
TypeScript
Vite supports .ts files out of the box with transpilation via Oxc Transformer (20-30x faster than tsc).
Important: Transpile Only
Vite does NOT perform type checking. Run type checking separately:
# Production build
tsc --noEmit && vite build
# During development (separate process)
tsc --noEmit --watch
# Or use vite-plugin-checker for browser error reportingTypeScript Configuration
Required tsconfig.json settings:
{
"compilerOptions": {
"isolatedModules": true,
"useDefineForClassFields": true,
"skipLibCheck": true
}
}Client Types
Add Vite's client types for import.meta.env and asset imports:
{
"compilerOptions": {
"types": ["vite/client"]
}
}This provides types for:
- Asset imports (
.svg,.png, etc.) import.meta.envconstantsimport.meta.hotHMR API
Custom Type Overrides
Override default asset import types:
// vite-env-override.d.ts
declare module '*.svg' {
const content: React.FC<React.SVGProps<SVGElement>>
export default content
}Path Aliases with tsconfig
Enable tsconfig paths resolution:
// vite.config.ts
export default defineConfig({
resolve: {
tsconfigPaths: true
}
})JSX
.jsx and .tsx files are supported out of the box. Custom JSX configuration:
export default defineConfig({
oxc: {
jsx: {
runtime: 'classic', // or 'automatic'
pragma: 'h',
pragmaFrag: 'Fragment'
},
// Auto-inject JSX helpers
jsxInject: `import React from 'react'`
}
})HTML
index.html is the entry point, not tucked away in public/. Vite processes it as part of the module graph.
Supported Elements
Vite processes these HTML element attributes:
<script type="module" src><link href>(stylesheets)<img src>,<img srcset><video src>,<video poster><audio src><source src>,<source srcset><meta content>(for og:image, twitter:image, etc.)
Opt-out of Processing
<script vite-ignore type="module" src="https://cdn.example.com/lib.js"></script>Multi-Page Apps
Access any HTML file by its path:
<root>/index.html→http://localhost:5173/<root>/about.html→http://localhost:5173/about.html<root>/blog/index.html→http://localhost:5173/blog/index.html
JSON
Direct import with named exports support:
// Import entire object
import json from './data.json'
// Named imports (tree-shakeable)
import { field } from './data.json'Framework Support
Official framework plugins:
| Framework | Plugin |
|---|---|
| Vue 3 | @vitejs/plugin-vue |
| Vue 3 JSX | @vitejs/plugin-vue-jsx |
| React | @vitejs/plugin-react |
| React (SWC) | @vitejs/plugin-react-swc |
| React Server Components | @vitejs/plugin-rsc |
| Legacy browsers | @vitejs/plugin-legacy |
Content Security Policy
Configure nonce for CSP:
export default defineConfig({
html: {
cspNonce: 'PLACEHOLDER' // Replace per-request
}
})<!-- Source references:
- https://vite.dev/guide/features.html
-->
Using Plugins
Vite extends Rolldown's plugin interface with extra Vite-specific options.
Adding Plugins
Install and add to config:
npm add -D @vitejs/plugin-vue// vite.config.ts
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()]
})Plugin Arrays
Plugins can return arrays (for complex features):
// Framework plugin returning multiple plugins
export default function framework(config) {
return [
frameworkRefresh(config),
frameworkDevtools(config)
]
}Conditional Plugins
Falsy values are ignored:
export default defineConfig({
plugins: [
vue(),
process.env.ANALYZE && visualizer() // Only if ANALYZE is set
]
})Enforcing Plugin Order
Control when plugin runs relative to Vite core:
export default defineConfig({
plugins: [
{
...somePlugin(),
enforce: 'pre' // Before Vite core plugins
},
{
...anotherPlugin(),
enforce: 'post' // After Vite build plugins
}
]
})Order: 1. Alias 2. Plugins with enforce: 'pre' 3. Vite core plugins 4. Plugins without enforce 5. Vite build plugins 6. Plugins with enforce: 'post' 7. Vite post-build plugins (minify, manifest)
Conditional Application
Apply only during serve or build:
export default defineConfig({
plugins: [
{
...typescript2(),
apply: 'build' // Only during build
},
{
...devOnlyPlugin(),
apply: 'serve' // Only during dev
}
]
})Function form for more control:
{
...myPlugin(),
apply(config, { command }) {
// Apply only on build but not for SSR
return command === 'build' && !config.build.ssr
}
}Finding Plugins
1. Check Vite Features Guide - many use cases are built-in 2. Official plugins in Vite Plugins 3. Community plugins in awesome-vite 4. Search npm for vite-plugin-* or rollup-plugin-*
Official Plugins
| Plugin | Purpose |
|---|---|
@vitejs/plugin-vue | Vue 3 SFC support |
@vitejs/plugin-vue-jsx | Vue 3 JSX support |
@vitejs/plugin-react | React with Babel/Oxc |
@vitejs/plugin-react-swc | React with SWC |
@vitejs/plugin-rsc | React Server Components |
@vitejs/plugin-legacy | Legacy browser support |
Rollup/Rolldown Plugin Compatibility
Many Rollup plugins work directly with Vite if they:
- Don't use
moduleParsedhook - Don't rely on Rolldown-specific options
- Don't have strong coupling between bundle and output phases
For build-only Rollup plugins:
export default defineConfig({
build: {
rolldownOptions: {
plugins: [rollupPluginForBuildOnly()]
}
}
})<!-- Source references:
- https://vite.dev/guide/using-plugins.html
-->
Static Asset Handling
Importing Assets as URL
import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
// Dev: /src/img.png
// Build: /assets/img.2d8efhg.pngCommon image, media, and font types are detected automatically.
Import Queries
Explicit URL Import
import workletURL from './worklet.js?url'
CSS.paintWorklet.addModule(workletURL)Import as String (Raw)
import shaderString from './shader.glsl?raw'Control Inlining
import imgUrl1 from './img.svg?no-inline' // Never inline
import imgUrl2 from './img.png?inline' // Always inline as base64Asset Inlining
Assets smaller than assetsInlineLimit (default 4KB) are inlined as base64:
export default defineConfig({
build: {
assetsInlineLimit: 4096, // 4KB
// Or use callback for fine control
assetsInlineLimit: (filePath) => {
return !filePath.endsWith('.svg')
}
}
})The public Directory
Files in public/ are:
- Served at root path
/during dev - Copied as-is to
dist/root during build - Not processed or hashed
public/
favicon.ico → /favicon.ico
robots.txt → /robots.txtReference with absolute paths in source:
<img src="/icon.png" />Configure directory:
export default defineConfig({
publicDir: 'static' // or false to disable
})Extending Asset Types
export default defineConfig({
assetsInclude: ['**/*.gltf', '**/*.hdr']
})Dynamic URLs with import.meta.url
// Works natively in modern browsers
const imgUrl = new URL('./img.png', import.meta.url).href
// Dynamic pattern (limited)
function getImageUrl(name) {
return new URL(`./dir/${name}.png`, import.meta.url).href
}Limitations:
- URL string must be static for build analysis
- Does not work with SSR (different semantics in Node.js vs browser)
TypeScript Support
Add vite/client to types for asset import recognition:
{
"compilerOptions": {
"types": ["vite/client"]
}
}URL Handling in CSS
.hero {
background: url('./img.png'); /* Processed and rebased */
}For dynamically constructed SVG URLs:
import imgUrl from './img.svg'
element.style.background = `url("${imgUrl}")` // Note double quotes<!-- Source references:
- https://vite.dev/guide/assets.html
-->
CSS Handling
Vite provides rich CSS support with HMR, @import inlining, and automatic URL rebasing.
Basic CSS Import
import './styles.css' // Injected into page with HMR supportCSS Modules
Files ending with .module.css are treated as CSS modules:
/* example.module.css */
.red {
color: red;
}import classes from './example.module.css'
element.className = classes.redNamed Imports with camelCase
// vite.config.ts
export default defineConfig({
css: {
modules: {
localsConvention: 'camelCaseOnly'
}
}
})// .apply-color -> applyColor
import { applyColor } from './example.module.css'CSS Pre-processors
Install the pre-processor, no Vite plugin needed:
# Sass (sass-embedded recommended for performance)
npm add -D sass-embedded
# Less
npm add -D less
# Stylus
npm add -D stylusUse by file extension:
import './styles.scss'
import './styles.less'
import './styles.styl'Pre-processor Options
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `$injectedColor: orange;`,
importers: [/* ... */]
},
less: {
math: 'parens-division'
}
},
preprocessorMaxWorkers: true // Use multiple threads
}
})Combined with CSS Modules
import styles from './component.module.scss'PostCSS
Automatically applied if postcss.config.js exists:
// postcss.config.js
export default {
plugins: [
require('postcss-nesting'),
require('autoprefixer')
]
}Or configure inline:
export default defineConfig({
css: {
postcss: {
plugins: [
postcssNesting(),
autoprefixer()
]
}
}
})Lightning CSS
Experimental faster CSS processing:
npm add -D lightningcssexport default defineConfig({
css: {
transformer: 'lightningcss',
lightningcss: {
targets: {
chrome: 111
},
cssModules: {
// Lightning CSS modules config
}
}
}
})Use Lightning CSS for minification only:
export default defineConfig({
build: {
cssMinify: 'lightningcss'
}
})Disable CSS Injection
Import CSS as string without injecting:
import styles from './styles.css?inline' // Returns CSS string, not injectedSource Maps
Enable CSS source maps in development:
export default defineConfig({
css: {
devSourcemap: true
}
})CSS Code Splitting
By default, CSS is extracted per async chunk. Disable to get single CSS file:
export default defineConfig({
build: {
cssCodeSplit: false // Single CSS file for entire app
}
})CSS Target
Set different browser target for CSS:
export default defineConfig({
build: {
cssTarget: 'chrome61' // For Android WeChat WebView
}
})@import and URL Handling
@importstatements are inlined automatically- Vite aliases work in
@import url()references are rebased for correctness- Works across Sass/Less files in different directories
<!-- Source references:
- https://vite.dev/guide/features.html#css
-->
Dependency Pre-Bundling
Vite pre-bundles dependencies on first run for faster dev server startup.
Why Pre-Bundling
1. CommonJS/UMD to ESM - Convert non-ESM dependencies 2. Performance - Bundle many internal modules into single file (e.g., lodash-es has 600+ modules)
// Works thanks to smart import analysis
import React, { useState } from 'react'Automatic Discovery
Vite crawls source code to find bare imports and pre-bundles them with Rolldown.
New dependencies discovered after server start trigger re-bundling.
Including Dependencies
Force pre-bundling for dependencies not auto-discovered:
export default defineConfig({
optimizeDeps: {
include: [
'some-package',
'another-package/nested' // Deep imports
]
}
})When to include:
- Dynamically imported (via plugin transform)
- Large dependencies with many internal modules
- CommonJS dependencies
Excluding Dependencies
Skip pre-bundling for small ESM-only dependencies:
export default defineConfig({
optimizeDeps: {
exclude: ['small-esm-dep']
}
})Monorepo Linked Dependencies
Linked packages are treated as source code by default. If not ESM:
export default defineConfig({
optimizeDeps: {
include: ['linked-dep']
}
})Restart with --force after making changes to linked deps.
Custom Rolldown Options
export default defineConfig({
optimizeDeps: {
rolldownOptions: {
plugins: [/* Rolldown plugins */],
// Other Rolldown options
}
}
})Caching
File System Cache
Located in node_modules/.vite. Re-runs when:
- Package lockfile changes (
package-lock.json,pnpm-lock.yaml, etc.) - Patches folder modified
vite.config.jschangesNODE_ENVchanges
Force re-bundle:
vite --force
# Or delete node_modules/.viteBrowser Cache
Pre-bundled deps are cached with max-age=31536000,immutable.
To debug dependencies with local edits:
1. Disable cache in browser DevTools Network tab 2. Restart Vite with --force 3. Reload page
Entries
Specify custom entry points for discovery:
export default defineConfig({
optimizeDeps: {
entries: [
'src/main.ts',
'src/other-entry.ts'
]
}
})By default, all HTML files are used as entries.
esbuildOptions (Deprecated)
Use rolldownOptions instead:
export default defineConfig({
optimizeDeps: {
// Deprecated
esbuildOptions: {},
// Use instead
rolldownOptions: {}
}
})<!-- Source references:
- https://vite.dev/guide/dep-pre-bundling.html
-->
Environment Variables and Modes
Built-in Constants
Available via import.meta.env:
| Constant | Description |
|---|---|
import.meta.env.MODE | App mode ('development' or 'production') |
import.meta.env.BASE_URL | Base URL from base config |
import.meta.env.PROD | true in production |
import.meta.env.DEV | true in development |
import.meta.env.SSR | true in server-side rendering |
if (import.meta.env.DEV) {
console.log('Development mode')
// Tree-shaken in production
}Custom Environment Variables
Only variables prefixed with VITE_ are exposed to client code:
# .env
VITE_API_URL=https://api.example.com
DB_PASSWORD=secret # NOT exposed to clientconsole.log(import.meta.env.VITE_API_URL) // "https://api.example.com"
console.log(import.meta.env.DB_PASSWORD) // undefinedCustom Prefix
export default defineConfig({
envPrefix: ['VITE_', 'APP_'] // Expose VITE_* and APP_*
}).env Files
Load order (later has higher priority):
.env # Always loaded
.env.local # Always loaded, gitignored
.env.[mode] # Only in specified mode
.env.[mode].local # Only in specified mode, gitignoredVariable Expansion
# .env
KEY=123
EXPANDED=test$KEY # test123
ESCAPED=test\$foo # test$foo (escaped)Modes
# Development mode (default for dev)
vite
# Production mode (default for build)
vite build
# Custom mode
vite build --mode stagingCreate mode-specific env file:
# .env.staging
VITE_APP_TITLE=My App (staging)
NODE_ENV=production # Still production buildNODE_ENV vs Mode
| Command | NODE_ENV | Mode |
|---|---|---|
vite build | production | production |
vite build --mode development | production | development |
NODE_ENV=development vite build | development | production |
TypeScript IntelliSense
Create type declarations for custom env variables:
// vite-env.d.ts
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_API_URL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}For strict typing (disallow unknown keys):
interface ViteTypeOptions {
strictImportMetaEnv: unknown
}HTML Replacement
Use %VARIABLE% syntax in HTML:
<title>%VITE_APP_TITLE%</title>
<p>Mode: %MODE%</p>Non-existent variables are left as-is (not replaced with undefined).
Loading Env in Config
Env vars are NOT available in vite.config.ts automatically:
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '') // '' loads all vars
return {
define: {
__APP_ENV__: JSON.stringify(env.APP_ENV)
}
}
})Security Notes
- Add
*.localto.gitignore VITE_*variables end up in client bundle - no secrets- Never set
envPrefixto''(exposes everything)
<!-- Source references:
- https://vite.dev/guide/env-and-mode.html
-->
Glob Import
Basic Usage
Import multiple modules using glob patterns:
const modules = import.meta.glob('./dir/*.js')
// Transformed to:
// {
// './dir/foo.js': () => import('./dir/foo.js'),
// './dir/bar.js': () => import('./dir/bar.js'),
// }Iterate and load:
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod)
})
}Eager Loading
Load all modules immediately (no dynamic import):
const modules = import.meta.glob('./dir/*.js', { eager: true })
// Transformed to:
// import * as __glob_0 from './dir/foo.js'
// import * as __glob_1 from './dir/bar.js'
// const modules = {
// './dir/foo.js': __glob_0,
// './dir/bar.js': __glob_1,
// }Multiple Patterns
const modules = import.meta.glob([
'./dir/*.js',
'./another/*.js'
])Negative Patterns
Exclude files with ! prefix:
const modules = import.meta.glob([
'./dir/*.js',
'!**/bar.js' // Exclude bar.js
])Named Imports
Import specific exports for tree-shaking:
const modules = import.meta.glob('./dir/*.js', {
import: 'setup'
})
// './dir/foo.js': () => import('./dir/foo.js').then(m => m.setup)Import default export:
const modules = import.meta.glob('./dir/*.js', {
import: 'default',
eager: true
})Custom Queries
Import as raw strings or URLs:
const moduleStrings = import.meta.glob('./dir/*.svg', {
query: '?raw',
import: 'default'
})
const moduleUrls = import.meta.glob('./dir/*.svg', {
query: '?url',
import: 'default'
})Custom queries for plugins:
const modules = import.meta.glob('./dir/*.js', {
query: { foo: 'bar', bar: true }
})Base Path
Change the base path for imports:
const modules = import.meta.glob('./**/*.js', {
base: './base'
})
// Keys: './dir/foo.js'
// Imports: './base/dir/foo.js'Important Caveats
1. Vite-only feature - Not a web standard 2. Patterns must be literals - Cannot use variables 3. Relative or absolute - Must start with ./, /, or use an alias 4. Glob matching - Uses tinyglobby
Dynamic Import with Variables
Limited dynamic import support:
const module = await import(`./dir/${file}.js`)Rules:
- Must start with
./or../ - Must end with file extension
- Variable represents only one level (no
foo/bar) - Own directory needs filename pattern:
./prefix-${foo}.jsnot./${foo}.js
Practical Example: Loading Route Components
// Lazy load all page components
const pages = import.meta.glob('./pages/*.vue')
const routes = Object.keys(pages).map((path) => {
const name = path.match(/\.\/pages\/(.*)\.vue$/)[1]
return {
path: `/${name.toLowerCase()}`,
component: pages[path] // Lazy loaded
}
})<!-- Source references:
- https://vite.dev/guide/features.html#glob-import
-->
HMR API
The HMR API is exposed via import.meta.hot. Primarily for framework and tooling authors.
Conditional Guard
Always guard HMR code for tree-shaking in production:
if (import.meta.hot) {
// HMR code
}TypeScript Support
Add to tsconfig.json:
{
"compilerOptions": {
"types": ["vite/client"]
}
}Self-Accepting Module
Module handles its own updates:
export const count = 1
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
if (newModule) {
console.log('updated: count is now', newModule.count)
}
})
}Accept Dependency Updates
React to changes in dependencies without self-reload:
import { foo } from './foo.js'
foo()
if (import.meta.hot) {
// Single dependency
import.meta.hot.accept('./foo.js', (newFoo) => {
newFoo?.foo()
})
// Multiple dependencies
import.meta.hot.accept(
['./foo.js', './bar.js'],
([newFooModule, newBarModule]) => {
// Handle updates
}
)
}Cleanup on Update
Clean up side effects before module is replaced:
function setupSideEffect() {
const interval = setInterval(() => {}, 1000)
return interval
}
const interval = setupSideEffect()
if (import.meta.hot) {
import.meta.hot.dispose((data) => {
clearInterval(interval)
})
}Prune Callback
Called when module is no longer imported:
if (import.meta.hot) {
import.meta.hot.prune((data) => {
// Cleanup when module is removed from page
})
}Persistent Data
Pass data between module instances:
if (import.meta.hot) {
// Mutate properties, don't reassign data itself
import.meta.hot.data.count = (import.meta.hot.data.count || 0) + 1
}Invalidate
Force propagation to importers:
if (import.meta.hot) {
import.meta.hot.accept((module) => {
if (cannotHandleUpdate(module)) {
import.meta.hot.invalidate() // Propagate to importers
}
})
}HMR Events
Listen to built-in events:
if (import.meta.hot) {
import.meta.hot.on('vite:beforeUpdate', (payload) => {
console.log('Update incoming')
})
import.meta.hot.on('vite:afterUpdate', (payload) => {
console.log('Update applied')
})
import.meta.hot.on('vite:beforeFullReload', () => {
console.log('Full reload')
})
import.meta.hot.on('vite:error', (error) => {
console.error('HMR error', error)
})
import.meta.hot.on('vite:ws:connect', () => {
console.log('WebSocket connected')
})
import.meta.hot.on('vite:ws:disconnect', () => {
console.log('WebSocket disconnected')
})
}Custom Events
Send events to server:
// Client
if (import.meta.hot) {
import.meta.hot.send('my:event', { msg: 'Hello from client' })
}Receive from server:
// Client
if (import.meta.hot) {
import.meta.hot.on('my:response', (data) => {
console.log(data.msg)
})
}TypeScript for Custom Events
// events.d.ts
import 'vite/types/customEvent.d.ts'
declare module 'vite/types/customEvent.d.ts' {
interface CustomEventMap {
'my:event': { msg: string }
'my:response': { msg: string }
}
}<!-- Source references:
- https://vite.dev/guide/api-hmr.html
-->
Web Workers
Constructor Syntax (Recommended)
Standard Web Worker creation:
const worker = new Worker(new URL('./worker.js', import.meta.url))Module worker:
const worker = new Worker(new URL('./worker.js', import.meta.url), {
type: 'module'
})Shared Worker:
const sharedWorker = new SharedWorker(
new URL('./shared-worker.js', import.meta.url)
)Note: The new URL() must be used directly inside new Worker() for detection.
Query Suffix Syntax
Import with ?worker suffix:
import MyWorker from './worker?worker'
const worker = new MyWorker()Shared worker:
import MySharedWorker from './worker?sharedworker'
const worker = new MySharedWorker()Inline Worker
Inline as base64 string (no separate chunk):
import MyWorker from './worker?worker&inline'
const worker = new MyWorker()Worker URL Only
Get URL instead of constructor:
import workerUrl from './worker?worker&url'Worker Script
Workers can use ESM import statements:
// worker.js
import { heavyComputation } from './utils'
self.onmessage = (e) => {
const result = heavyComputation(e.data)
self.postMessage(result)
}Worker Options
Configure worker bundling:
// vite.config.ts
export default defineConfig({
worker: {
format: 'es', // or 'iife'
plugins: () => [/* worker-specific plugins */],
rollupOptions: {
// Rollup options for worker bundle
}
}
})WebAssembly in Workers
// worker.js
import init from './module.wasm?init'
init().then((instance) => {
instance.exports.compute()
})<!-- Source references:
- https://vite.dev/guide/features.html#web-workers
-->
Sync Info
- Source:
vendor/antfu/skills/vite - Git SHA:
4086f59fa12d308fb05592ca8ce1704fbb940aa3 - Synced: 2026-01-30
Related skills
How it compares
Pick vite for Vite-specific config and HMR; pick a framework skill when the task is React or Vue component architecture rather than bundler setup.
FAQ
Which Vite version does the vite skill target?
The vite skill is based on Vite 6.x and carries metadata version 2026.1.28, generated from the official vitejs/vite repository and antfu/skills scripts.
What frontend features does the vite skill cover?
The vite skill covers dev-server startup, HMR, TypeScript and JSX support, CSS pre-processors, plugin configuration, and production builds using Rolldown and Rollup.