Vite Deep Dive — Dev Server, HMR, Plugins, SSR & Build Optimization
In this tutorial, you'll learn about Vite Deep Dive. We cover key concepts, practical examples, and best practices.
Vite is a next-generation build tool that leverages native ES modules for a fast dev server and Rollup for optimized production builds. This deep dive covers how Vite works under the hood, its plugin API, SSR support, library mode, environment variables, CSS handling, and a practical comparison with Webpack.
What you'll learn: Vite's ESM-based dev server architecture, HMR protocol, Rollup build pipeline, plugin development, SSR setup, library mode for publishing packages, env variable patterns, CSS/PostCSS integration, and how Vite compares with Webpack. Why it matters: Vite is the default build tool for Vue, Svelte, Astro, and increasingly React/Next.js projects. Real-world use: DodaZIP's frontend dashboard uses Vite with React, TypeScript, and Tailwind — compiling 300+ components in under 2 seconds for HMR.
Learning Path
flowchart LR A["Build Tools
Webpack Basics"] --> B["Vite
This Tutorial"] B --> C["Vite Plugins
Custom Development"] C --> D["SSR & Library Mode
Advanced Vite"] B --> E["Production Build
Rollup Optimization"] style B fill:#f90,color:#fff,stroke-width:2px
How Vite's Dev Server Works
flowchart TD
subgraph "Vite Dev Server"
B["Native ESM
Server"]
C["HMR
WebSocket"]
D["Pre-bundling
esbuild"]
end
E["Browser"] -->|"import /src/App.vue"| B
B -->|"Transform on the fly"| E
C <-->|"Hot update"| E
D -->|"node_modules
dependencies"| B
Vite serves source files as native ES modules. The browser imports them directly — no bundling during development.
npx create-vite@latest my-app --template react
cd my-app
npm run dev
Expected output:
VITE v6.0.0 ready in 135ms
➜ Local: http://localhost:5173/
HMR in Detail
Vite's HMR uses a WebSocket connection between server and browser:
// vite.config.js — Adding custom HMR handling
export default {
server: {
hmr: {
protocol: 'ws',
host: 'localhost',
port: 24678,
overlay: true
}
}
}
Each framework integration implements handleHotUpdate:
if (import.meta.hot) {
import.meta.hot.accept('./module.js', (newModule) => {
console.log('Module updated:', newModule)
})
}
Rollup-Based Production Build
npm run build
Expected output:
vite v6.0.0 building for production...
✓ built in 3.2s
dist/index.html 0.48 kB
dist/assets/index-D1x2E3F4.js 142.30 kB
Configure Rollup options:
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash-es', 'date-fns']
}
}
},
target: 'es2020',
minify: 'esbuild', // or 'terser' for IE11 support
sourcemap: true
}
}
Plugins
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
react(),
legacy({
targets: ['defaults', 'not IE 11']
})
]
})
Custom Plugin Example
function myPlugin() {
return {
name: 'my-plugin',
enforce: 'pre',
transform(code, id) {
if (id.endsWith('.special.txt')) {
return {
code: `export default ${JSON.stringify(code)}`,
map: null
}
}
}
}
}
SSR (Server-Side Rendering)
// vite.config.js
export default defineConfig({
build: {
ssr: 'src/entry-server.jsx'
}
})
// server.js
import express from 'express'
import { createServer } from 'vite'
const app = express()
const vite = await createServer({
server: { middlewareMode: true },
appType: 'custom'
})
app.use(vite.middlewares)
app.use('*', async (req, res) => {
const { render } = await vite.ssrLoadModule('/src/entry-server.jsx')
const html = await render(req.url)
res.send(html)
})
Library Mode
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'MyLib',
formats: ['es', 'cjs', 'umd']
},
rollupOptions: {
external: ['react', 'react-dom']
}
}
})
Build creates dist/my-lib.mjs, dist/my-lib.cjs, and dist/my-lib.umd.js.
Environment Variables
// .env
VITE_API_URL=https://api.example.com
// .env.production
VITE_API_URL=https://api.prod.example.com
console.log(import.meta.env.VITE_API_URL)
Only VITE_ prefixed variables are exposed. Access import.meta.env.MODE for development or production.
CSS Handling
export default defineConfig({
css: {
modules: {
localsConvention: 'camelCaseOnly'
},
postcss: {
plugins: [require('autoprefixer'), require('tailwindcss')]
},
preprocessorOptions: {
scss: {
additionalData: `@import "./src/styles/variables";`
}
}
}
})
Vite vs Webpack
| Feature | Vite | Webpack |
|---|---|---|
| Dev server approach | Native ESM, no bundling | Bundle all modules |
| Cold start | ~100–300ms | ~5–30s |
| HMR | Instant (< 50ms) | 200ms–2s |
| Production bundler | Rollup | webpack |
| Configuration | Minimal, sensible defaults | Extensive, verbose |
| Plugin ecosystem | Growing, Rollup-compatible | Mature, massive |
| Best for | New projects (2024+) | Legacy projects, complex webpack configs |
Common Errors
Cannot find modulein production but works in dev — Dev server uses ESM resolution; production uses Rollup. If you use bare imports without installing the package, dev works but build fails. Always install dependencies.- HMR not working with symlinked packages — Vite's file watcher doesn't follow symlinks by default. Use
server.watch.ignored: falseor setresolve.symlinks: false. - Environment variables returning
undefined— OnlyVITE_prefixed variables are exposed. Non-prefixed variables are not available in client code. - SSR build errors with Node.js built-ins — Vite's SSR build runs in Node.js, not the browser. Use
build.rollupOptions.externalto exclude Node modules. - Legacy browser support issues — Vite targets modern browsers. For IE11 or older Chrome, use
@vitejs/plugin-legacywhich generates fallback chunks. - Asset paths wrong in production — Set
base: '/my-app/'in config if your app is served from a subdirectory. - TypeScript path aliases not resolved in build — Configure
resolve.aliasinvite.config.jsmatching yourtsconfig.jsonpaths. - CSS modules not applying — File must be named
*.module.css(or.scss,.less). Plain.cssfiles don't activate CSS modules.
Practice Questions
1. Why is Vite's dev server faster than Webpack's? Vite serves files as native ES modules without bundling. Only the requested file is transformed. Webpack must bundle and rebuild the entire dependency graph on every change.
2. How do you create a library with Vite?
Use build.lib configuration with entry point, name, and formats. The build produces ESM, CJS, and/or UMD outputs.
3. What's the difference between import.meta.env.MODE and import.meta.env.PROD?
MODE returns the string 'development' or 'production'. PROD returns a boolean (true in production mode).
4. How does Vite handle CSS imports?
Vite supports .css, .scss, .less, .styl, and CSS modules (.module.css) out of the box. PostCSS is applied automatically if postcss.config.js exists.
5. Challenge: Build a Vite plugin
Create a Vite plugin that: reads all .<a href="/apis/graphql/">graphql</a> files in the src/ directory, inlines them as JavaScript template literals, and adds a virtual module virtual:<a href="/apis/graphql/">graphql</a>-schema that exports all queries combined.
Mini Project: Multi-Page Vite App
export default defineConfig({
build: {
rollupOptions: {
input: {
main: '/index.html',
admin: '/admin.html'
}
}
}
})
npm run build
Produces dist/index.html, dist/admin.html, and shared assets.
FAQ
Related Tutorials
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro