TL;DR
- Vite uses the browser’s native ES modules for dev and esbuild for lightning‑fast pre‑bundling → near‑instant cold starts and snappy HMR even in large apps.
- For production, Vite builds with Rollup, yielding modern, tree‑shaken bundles (code‑splitting, dynamic imports).
- Webpack still shines for heavy customization, legacy browser support, and ecosystems built around its loader/plugin model—but it pays a cost in startup time and HMR latency because it bundles everything up front in dev.
- Migrating is usually straightforward: map env vars, replace dev server/alias config, validate plugins, and keep Webpack only where you truly need its deep customization.
Why the ecosystem is shifting
Old model (Webpack dev): bundle entire app on startup → serve from an in‑memory bundle → HMR patches the bundle. Great power, but slow as apps grow.
Vite model:
- Dev server serves source files over ESM.
- First request compiles only what’s imported on that page.
- Uses esbuild (Go) for fast TS/JS transform + dependency pre‑bundle and Rollup for prod.
Result: fast cold starts and on‑demand compilation; edits reflect quickly.
Mental model (60 seconds)
Webpack (dev)
source → loader/plug-in graph → big in-memory bundle → serve → HMR patches
Vite (dev)
source served as ESM → compile on demand (esbuild) → cache per module → HMR per module
Developer experience: what you feel day‑to‑day
- Cold start: Vite often starts in sub‑second to a few seconds, even in large repos, because it doesn’t bundle everything at boot.
- HMR: Module‑granular updates without rebundling the world → faster feedback and fewer full reloads.
- TypeScript/JSX: esbuild handles transpile; type‑checking can run in a separate process (Volar/tsc/vite plugins).
- Framework support: First‑class templates for React, Vue, Svelte, Preact, and SSR adapters.
- DX niceties: HTML is first‑class (index.html as entry), zero‑config CSS modules, PostCSS, environment variables (
import.meta.env).
Production build story
- Vite → Rollup for production: tree‑shaking, code‑splitting, dynamic imports, asset inlining, and chunk hashing.
- Legacy browsers: add the legacy plugin (transpile + polyfill) when you must support non‑ESM/old browsers.
- Library mode: produce ESM/CJS bundles easily with Rollup under the hood.
Configuration & plugins
- Webpack: deep customization via loaders and plugins; can do almost anything—at the cost of complexity/perf.
- Vite: leaner config; many needs handled by default or via small plugins. You can still reach into Rollup options when needed.
- Monorepos: Vite + workspaces is straightforward; shared packages are served as ESM and pre‑bundled when necessary.
Practical comparison
| Topic | Webpack | Vite | |---|---|---| | Dev startup | Bundles whole app first → slower | ESM on demand + esbuild → fast | | HMR | Works but can lag on big graphs | Fast, fine‑grained updates | | Prod build | Webpack/Terser/SWC possible | Rollup + plugins | | Config surface | Huge power/flexibility | Smaller, opinionated defaults | | Legacy browser support | Strong via Babel/Polyfills | Via legacy plugin when needed | | Ecosystem/plugins | Vast, mature | Growing fast; covers common cases | | Non‑standard assets | Loaders can transform anything | Many built‑ins; Rollup plugins fill gaps |
Migration sketch (Webpack → Vite)
- Scaffold:
npm create vite@latest(orpnpm dlx,yarn create). Pick your framework template. - Aliases & env: Translate
resolve.aliasand replaceprocess.env.Xwithimport.meta.env.VITE_X(or use a shim plugin). - CSS/Assets: Most PostCSS/CSS Modules work out of the box. Replace custom file loaders with Vite’s asset handling or a plugin.
- Dev‑only loaders: If you depended on a Webpack loader for dev transforms, find the Vite plugin or a Rollup plugin.
- Type checking: keep
tsc --noEmitor use a Vite plugin for type checks in parallel. - Legacy targets: add the legacy plugin if you support IE/old Chromium.
- Validate build: Compare output size & route‑level code‑splitting. Tweak Rollup chunking if needed.
Example configs (short)
vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
resolve: {
alias: { "@": "/src" }
},
build: {
sourcemap: true,
rollupOptions: {
output: { manualChunks: { vendor: ["react", "react-dom"] } }
}
}
});
webpack.config.js (roughly equivalent intent)
module.exports = {
entry: "./src/main.tsx",
resolve: { alias: { "@": path.resolve(__dirname, "src") } },
module: { rules: [/* ts/tsx/css loaders, file-loader, etc. */] },
plugins: [/* HtmlWebpackPlugin, DefinePlugin, ReactRefresh, ... */],
devServer: { hot: true },
devtool: "source-map",
optimization: { splitChunks: { chunks: "all" } }
};
When Webpack is still the right call
- You need extreme customization or niche loaders not available in Vite/Rollup.
- Complex module federation, or legacy apps with heavy non‑ESM ecosystems.
- Strict IE/very old browser support without additional plugins.
- Enterprise setups deeply invested in Webpack plugins and internal tooling.
Pitfalls & fixes
| Problem | Cause | Fix |
|---|---|---|
| process is not defined in browser | Vite doesn’t auto‑polyfill Node globals | Add polyfills or avoid Node APIs in the browser |
| Third‑party expects require() | ESM vs CJS mismatch | Use optimizeDeps / build commonjs plugin, or alias ESM build |
| HMR full reloads often | Non‑deterministic transform or plugin | Check plugin order; enable sourcemap; watch plugin logs |
| Legacy browser complaints | Missing transpile/polyfills | Use @vitejs/plugin-legacy with proper targets |
| Monorepo linked pkg not pre‑bundled | Excluded from deps optimization | Add to optimizeDeps.include or mark as external when building a lib |
Quick checklist
- [ ] Try Vite on a feature branch; compare dev startup and HMR latency.
- [ ] Translate aliases and env vars; swap Node globals with browser‑safe APIs.
- [ ] Keep TS type‑checking in a parallel process.
- [ ] Verify prod build and chunking; add legacy plugin if needed.
- [ ] Remove unused Webpack/Babel config after migration.
One‑minute adoption plan
- Spin up a Vite prototype (
create vite). - Move one route/page and its deps; measure HMR and cold start.
- Port aliases/env; add must‑have plugins (React/Vue/Svelte).
- Validate prod bundle with Rollup options; enable the legacy plugin if required.
- Roll out gradually; retire Webpack config once parity is reached.