Understanding Webpack → Vite migration
A loader stack becomes a plugin array.
The conceptual mapping between Webpack and Vite, the patterns that translate directly, and the ones that need a rewrite.
Why migrate.
A reasonable Webpack project dev-rebuilds in 3-15 seconds; Vite does the same in 200-500ms. The cold-start is even more dramatic: Webpack rebuilds the dependency graph from scratch; Vite serves modules via native ESM, one network round-trip per request, no pre-bundling needed. For developer experience on any project with more than a hundred files, the migration payoff is real and felt daily.
The conceptual mapping.
Webpack loaders → Vite plugins (mostly). Webpack entry → Vite build.rollupOptions.input. Webpack output → Vite build.outDir and build.assetsDir. Webpack resolve.alias → Vite resolve.alias (identical shape). Webpack devServer.proxy → Vite server.proxy (also identical shape). The configs look different on the surface; the concepts are the same.
The things that don't migrate cleanly.
Webpack-specific features that don't have Vite equivalents: require.context (Vite uses import.meta.glob), Module Federation (Vite has a community plugin, less mature), html-webpack-plugin's elaborate template options (Vite's HTML support is simpler), pre-rendering via static-site-generator-webpack-plugin (Vite ecosystem has different tools). For each, there's either a Vite plugin or a small rewrite; the cost is project-specific.
The migration checklist.
1. Move index.html to project root (Vite's entry point — must link to /src/main.tsx or similar). 2. Install Vite + framework plugin. 3. Write minimal vite.config.ts. 4. Update environment variables: process.env.X → import.meta.env.VITE_X (must be prefixed VITE_ to be exposed to the client). 5. Update dynamic imports: require.context → import.meta.glob. 6. Test dev server, then production build, then verify the deployed bundle works.
A worked translation.
Webpack: module.exports = {
entry: "./src/index.tsx",
output: { path: "dist", filename: "[name].[contenthash].js" },
resolve: { alias: { "@": path.resolve(__dirname, "src") } },
module: { rules: [{ test: /\.tsx?$/, use: "ts-loader" }] },
}; Vite: export default defineConfig({
plugins: [react()],
resolve: { alias: { "@": path.resolve(__dirname, "src") } },
build: { outDir: "dist" },
}); Half the lines. ts-loader is replaced by Vite's built-in esbuild transpilation; content-hashed filenames are the Vite default; React JSX is the framework plugin's job.
Same project, two configs
loaders → plugins ; entry → input ; output → build
Vite assumes more defaults; the config gets shorter.
20-line webpack.config → 8-line vite.config
= Less to maintain
When not to migrate.
Massive monorepos with deep Webpack-specific plugin chains where the migration would touch every package. Projects using Module Federation aggressively. Apps that ship to Internet Explorer (Vite needs evergreen browsers for dev; build target can still be ES5 but dev is harder). For anything else, the migration usually takes a day for small projects, a week for medium, longer only for the very-customised end. The DX dividend pays back fast.