webpack.config.js with vite.config.js, swap Webpack loaders for Vite plugins or built-in features, convert process.env to import.meta.env, move your index.html to the project root, and convert any CommonJS require() calls to ESM import statements.npm install vite @vitejs/plugin-react --save-dev && npx viterequire() calls and dynamic require.context() patterns that Vite cannot handle natively -- these must be converted to import.meta.glob() or ESM imports.import/export) — CommonJS require() is not supported in Vite's module graph. Convert before migrating. [src3, src6]VITE_ prefix — never shim process.env globally as it leaks all server env vars to the browser bundle. [src2, src4]index.html must be at the project root (not in public/ or src/) — Vite uses it as the entry point instead of html-webpack-plugin. [src4, src5]vite build && vite preview before deploying, as behavior can differ. [src4]vite-plugin-federation or keep Webpack for that entry point. [src7]| Webpack Config/Feature | Vite Equivalent | Example |
|---|---|---|
entry (string) | build.rollupOptions.input | input: { main: 'src/main.js' } |
output.path | build.outDir | build: { outDir: 'dist' } |
output.publicPath | base | base: '/my-app/' |
output.filename | build.rollupOptions.output.entryFileNames | entryFileNames: 'assets/[name]-[hash].js' |
output.chunkFilename | build.rollupOptions.output.chunkFileNames | chunkFileNames: 'assets/[name]-[hash].js' |
devServer.port | server.port | server: { port: 3000 } |
devServer.proxy | server.proxy | server: { proxy: { '/api': 'http://localhost:8080' } } |
devServer.https | server.https | server: { https: true } (or @vitejs/plugin-basic-ssl) |
resolve.alias | resolve.alias | resolve: { alias: { '@': '/src' } } |
resolve.extensions | resolve.extensions | resolve: { extensions: ['.js', '.ts', '.jsx'] } |
DefinePlugin | define | define: { __APP_VERSION__: JSON.stringify('1.0') } |
process.env.NODE_ENV | import.meta.env.MODE | Automatic in Vite (development/production) |
process.env.REACT_APP_* | import.meta.env.VITE_* | Prefix env vars with VITE_ in .env files |
copy-webpack-plugin | publicDir (default: public/) | Static files in public/ are copied to dist/ automatically |
css-loader + style-loader | Built-in CSS support | import './style.css' works out of the box |
sass-loader | Built-in (install sass) | npm install sass -D — no loader config needed |
postcss-loader | Built-in (auto-detects postcss.config.js) | Just keep your PostCSS config file |
file-loader / url-loader | Built-in asset handling | import logo from './logo.png' returns URL |
html-webpack-plugin | index.html at project root | Move index.html to root, add <script type="module" src="/src/main.js"> |
MiniCssExtractPlugin | Built-in CSS code splitting | Automatic in production builds |
HotModuleReplacementPlugin | Built-in HMR | Automatic with framework plugins |
require.context() | import.meta.glob() | const modules = import.meta.glob('./modules/*.js') |
require() (dynamic) | new URL('./path', import.meta.url).href | For asset URLs in ESM context |
babel-loader | Built-in esbuild (or @vitejs/plugin-react) | esbuild handles JSX/TSX transformation |
ts-loader | Built-in esbuild TypeScript | No loader needed; esbuild transpiles TS natively |
webpack-bundle-analyzer | rollup-plugin-visualizer | import { visualizer } from 'rollup-plugin-visualizer' |
ProvidePlugin | vite-plugin-inject or manual imports | Replace global shimming with explicit imports |
splitVendorChunkPlugin (deprecated Vite 7) | build.rollupOptions.output.manualChunks | Define manual chunk splitting in Rollup options |
START
├── Is the project using CommonJS (require/module.exports) extensively?
│ ├── YES → First convert to ESM imports/exports, then proceed with migration
│ └── NO ↓
├── Is the project using Vue CLI (vue-cli-service)?
│ ├── YES → Use `npx @originjs/webpack-to-vite` automated tool, then fix remaining issues
│ └── NO ↓
├── Is the project using Create React App (react-scripts)?
│ ├── YES → See CRA to Vite migration (simpler path with fewer config decisions)
│ └── NO ↓
├── Does the project rely on Webpack-specific APIs (require.context, module.hot)?
│ ├── YES → Replace require.context with import.meta.glob, remove module.hot (Vite HMR is automatic)
│ └── NO ↓
├── Does the project use Webpack Module Federation?
│ ├── YES → Evaluate vite-plugin-federation or keep Webpack for federated entry points
│ └── NO ↓
├── Does the project have complex Webpack plugins with no Vite equivalent?
│ ├── YES → Check for Rollup-compatible plugin or write a custom Vite plugin (Rollup plugin API)
│ └── NO ↓
├── Is this a React project?
│ ├── YES → Install @vitejs/plugin-react, create vite.config.js, update scripts
│ └── NO ↓
├── Is this a Vue project?
│ ├── YES → Install @vitejs/plugin-vue, create vite.config.js, update scripts
│ └── NO ↓
└── DEFAULT → Install vite, create minimal vite.config.js, move index.html to root, update scripts
Catalog all loaders, plugins, and custom configuration in your webpack.config.js. Identify which ones have built-in Vite equivalents (most CSS/asset loaders), which need Vite plugins, and which require manual conversion. [src3, src7]
# List all Webpack dependencies in your project
npm ls | grep -i webpack
npm ls | grep -i loader
npm ls | grep -i plugin
# Count configuration complexity
cat webpack.config.js | wc -l
Verify: You have a complete list of every loader and plugin that needs a Vite replacement or removal.
Install Vite and the official plugin for your framework. Keep Webpack installed during migration so you can compare builds. [src1, src5]
# For React projects
npm install vite @vitejs/plugin-react --save-dev
# For Vue 3 projects
npm install vite @vitejs/plugin-vue --save-dev
# For Vue 2 projects
npm install vite vite-plugin-vue2 --save-dev
# For Svelte projects
npm install vite @sveltejs/vite-plugin-svelte --save-dev
Verify: npx vite --version prints the installed Vite version without errors.
Translate your Webpack configuration to the Vite equivalent using the Quick Reference table above. [src1, src3, src4]
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
},
});
Verify: npx vite build --config vite.config.js does not throw configuration errors.
Vite uses index.html at the project root as the entry point (unlike Webpack's html-webpack-plugin). Add a <script type="module"> tag pointing to your entry file. [src4, src5]
<!-- Move from public/index.html to project root -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Verify: The file is at the project root (same level as package.json), and the src attribute points to your actual entry file.
Rename all REACT_APP_* or custom process.env.* variables to VITE_* prefix. Replace all process.env.VARIABLE references with import.meta.env.VITE_VARIABLE. [src2, src4]
// BEFORE (Webpack)
const apiUrl = process.env.REACT_APP_API_URL;
const isProd = process.env.NODE_ENV === 'production';
// AFTER (Vite)
const apiUrl = import.meta.env.VITE_API_URL;
const isProd = import.meta.env.PROD; // Built-in boolean
Verify: grep -rn "process\.env\." src/ returns zero results.
Vite requires ESM syntax. Replace all require() calls with import statements and module.exports with export. [src3, src6]
// BEFORE (CommonJS)
const React = require('react');
const logo = require('./logo.png');
const modules = require.context('./modules', true, /\.js$/);
// AFTER (ESM)
import React from 'react';
import logo from './logo.png';
const modules = import.meta.glob('./modules/**/*.js');
Verify: grep -rn "require(" src/ returns zero results.
Vite is stricter than Webpack about file extensions. Any file that contains JSX syntax must have a .jsx or .tsx extension -- .js files with JSX will fail. [src2, src7]
# Find .js files containing JSX syntax
grep -rln "<[A-Z][a-zA-Z]*" --include='*.js' src/ | head -20
# Rename them
git mv src/components/App.js src/components/App.jsx
Verify: npx vite build does not throw JSX-related parse errors.
Replace Webpack scripts with Vite commands and uninstall all Webpack dependencies. [src5, src7]
# Remove Webpack and all related packages
npm uninstall webpack webpack-cli webpack-dev-server \
html-webpack-plugin mini-css-extract-plugin css-loader \
style-loader file-loader url-loader babel-loader ts-loader \
sass-loader postcss-loader webpack-bundle-analyzer \
copy-webpack-plugin
# Remove config files
rm webpack.config.js webpack.dev.js webpack.prod.js
Verify: npm run dev starts the Vite dev server; npm run build produces output in dist/. Application loads without console errors.
Full script: javascript-react-complete-vite-config-js-replacing.js (67 lines)
// Input: A React project with Webpack using aliases, proxy, env vars, SCSS, and SVG
// Output: Equivalent Vite configuration
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr';
import path from 'path';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
react(), // Replaces babel-loader + @babel/preset-react
svgr(), // Replaces @svgr/webpack
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: env.VITE_API_URL || 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/variables" as *;`,
},
},
modules: {
localsConvention: 'camelCaseOnly',
},
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
},
},
},
},
};
});
Full script: typescript-migrating-webpack-require-context-to-vi.ts (37 lines)
// Input: Webpack project using require.context for dynamic module loading
// Output: Equivalent Vite pattern using import.meta.glob
// BEFORE: Webpack require.context (auto-import all route modules)
// const routeModules = require.context('./routes', true, /\.tsx$/);
// AFTER: Vite import.meta.glob (eager loading equivalent)
const routeModules = import.meta.glob<{ default: React.ComponentType }>(
'./routes/**/*.tsx',
{ eager: true }
);
const routes = Object.entries(routeModules).map(([path, module]) => ({
path: path
.replace('./routes', '')
.replace('.tsx', '')
.replace('/index', '/'),
component: module.default,
}));
// AFTER: Vite import.meta.glob (lazy loading for code splitting)
const lazyRouteModules = import.meta.glob<{ default: React.ComponentType }>(
'./routes/**/*.tsx'
);
const lazyRoutes = Object.entries(lazyRouteModules).map(([path, loader]) => ({
path: path.replace('./routes', '').replace('.tsx', '').replace('/index', '/'),
lazy: async () => {
const module = await loader();
return { Component: module.default };
},
}));
Full script: node-js-migration-script-to-automate-environment-v.js (52 lines)
// Input: A project with REACT_APP_* env vars in .env files and process.env.* in source
// Output: Converted VITE_* env vars and import.meta.env.* references
import { readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
import { join, extname } from 'path';
// Step 1: Rename env vars in .env files
function migrateEnvFiles(rootDir) {
const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
for (const file of envFiles) {
const filePath = join(rootDir, file);
try {
let content = readFileSync(filePath, 'utf-8');
content = content.replace(/^REACT_APP_/gm, 'VITE_');
writeFileSync(filePath, content);
console.log(`Migrated: ${file}`);
} catch { /* file doesn't exist, skip */ }
}
}
// Step 2: Replace process.env references in source files
function migrateSourceFiles(dir) {
for (const entry of readdirSync(dir)) {
const fullPath = join(dir, entry);
if (entry === 'node_modules' || entry === 'dist') continue;
if (statSync(fullPath).isDirectory()) {
migrateSourceFiles(fullPath);
continue;
}
if (!['.js', '.jsx', '.ts', '.tsx'].includes(extname(fullPath))) continue;
let content = readFileSync(fullPath, 'utf-8');
let modified = false;
content = content.replace(/process\.env\.REACT_APP_(\w+)/g, (_, name) => {
modified = true;
return `import.meta.env.VITE_${name}`;
});
content = content.replace(
/process\.env\.NODE_ENV\s*===?\s*['"]production['"]/g,
() => { modified = true; return 'import.meta.env.PROD'; }
);
if (modified) {
writeFileSync(fullPath, content);
console.log(`Updated: ${fullPath}`);
}
}
}
migrateEnvFiles('.');
migrateSourceFiles('./src');
// ❌ BAD — Shimming process.env globally to avoid refactoring
// vite.config.js
export default defineConfig({
define: {
'process.env': process.env, // Leaks ALL env vars to client bundle!
},
});
// ✅ GOOD — Only VITE_-prefixed vars are exposed to client code
// .env
// VITE_API_URL=https://api.example.com
// SECRET_KEY=abc123 ← NOT exposed to client (no VITE_ prefix)
// src/config.js
const apiUrl = import.meta.env.VITE_API_URL;
const mode = import.meta.env.MODE;
// ❌ BAD — Manually configuring things Vite handles automatically
export default defineConfig({
plugins: [
react(),
cssPlugin(), // Vite has built-in CSS support
assetPlugin(), // Vite has built-in asset handling
htmlPlugin(), // Vite uses index.html at root
hotReloadPlugin(), // Vite has built-in HMR
],
});
// ✅ GOOD — Vite needs very little configuration for most projects
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
// CSS, assets, HMR, HTML all work out of the box
});
// ❌ BAD — CommonJS require doesn't work in Vite's ESM-first architecture
const logo = require('./assets/logo.png');
const config = require('./config.json');
const modules = require.context('./plugins', true, /\.js$/);
// ✅ GOOD — ESM imports for static assets and modules
import logo from './assets/logo.png';
import config from './config.json';
const modules = import.meta.glob('./plugins/**/*.js', { eager: true });
// ❌ BAD — Breaks libraries that actually use global
export default defineConfig({
define: {
global: {}, // Causes "global is not an object" errors at runtime
},
});
// ✅ GOOD — Define global as globalThis for browser compatibility
export default defineConfig({
define: {
global: 'globalThis', // globalThis works in both Node and browser
},
});
// ❌ BAD — Sass legacy API was removed in Vite 7
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
api: 'legacy', // This option no longer exists in Vite 7
},
},
},
});
// ✅ GOOD — Modern Sass API is the only option in Vite 7
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/variables" as *;`,
// No 'api' option needed — modern API is the default
},
},
},
});
undefined imports, runtime errors only in dev. Fix: Run npx madge src/index.tsx --circular to find cycles, then extract shared code into separate modules. [src6].vue extensions and may require other extensions depending on configuration. Fix: Add extensions to all imports or configure resolve.extensions, but prefer explicit extensions. [src3]global is not defined errors: Libraries that reference Node.js global object fail in Vite's browser ESM context. Fix: Add define: { global: 'globalThis' } to vite.config.js. [src4].module.css naming: Webpack's css-loader with modules: true works on any .css file, but Vite requires the .module.css suffix convention. Fix: Rename style.css to style.module.css. [src3]module.exports = X which Vite handles differently. Symptoms: X is not a function. Fix: Use const Lib = (X as any).default || X as a fallback. [src6]process.env not defined at runtime: Vite does not inject process.env by default unlike Webpack. Fix: Replace all process.env.* with import.meta.env.* using VITE_ prefix. [src2]vite build && vite preview before deploying. [src4]this references in legacy code: Code that uses this at module top level fails in ESM where this is undefined. Fix: Replace this with globalThis or configure esbuild: { define: { this: 'window' } }. [src4].jsx/.tsx files by default. Fix: Rename files containing JSX to .jsx or .tsx extensions. [src2, src7]optimizeDeps.entries treated as globs in Vite 7: All values are treated as glob patterns rather than literal file paths. Fix: Escape special characters if you have literal paths. [src8]# Check for remaining Webpack references in the project
grep -rn "webpack\|require(\|module\.exports" --include='*.js' --include='*.ts' --include='*.jsx' --include='*.tsx' src/ | grep -v node_modules
# Find remaining process.env references
grep -rn "process\.env\." --include='*.js' --include='*.ts' --include='*.jsx' --include='*.tsx' src/
# Find .js files containing JSX that need renaming
grep -rln "<[A-Z][a-zA-Z]*" --include='*.js' src/
# Detect circular dependencies
npx madge src/main.tsx --circular --extensions ts,tsx,js,jsx
# Verify Vite dev server starts
npx vite --debug
# Verify production build completes
npx vite build 2>&1 | tail -20
# Compare bundle sizes (install rollup-plugin-visualizer)
npx vite build && open dist/stats.html
# Check that all env vars are prefixed correctly
grep -rn "^[A-Z]" .env* | grep -v "^.*:VITE_\|^.*:#\|^.*:NODE_ENV"
# List all dependencies that might need Vite-specific plugins
npm ls --depth=0 | grep -i "webpack\|loader\|babel"
# Verify Node.js version meets Vite 7 requirements
node -v # Must be 20.19+ or 22.12+
| Version | Status | Key Features | Migration Notes |
|---|---|---|---|
| Vite 8.x (2026 beta) | Beta | Rolldown bundler (10-30x faster builds), unified toolchain, built-in tsconfig paths | Early adopters only; Rollup/esbuild options may need adjustment |
| Vite 7.x (2025) | Current | Node 20.19+ required, baseline-widely-available target, buildApp hook, Sass legacy API removed | Drop Node 18; remove splitVendorChunkPlugin; use order/handler in hooks |
| Vite 6.x (2024) | LTS | Environment API (experimental), Rolldown preview | Vite 5 configs work with minor updates; server.fs.allow tightened |
| Vite 5.x (2023) | Maintenance | Rollup 4, Node 18+ required | Drop Node 14/16 support; update Rollup plugins to v4-compatible |
| Vite 4.x (2022) | EOL | SWC support via plugin | First version with stable React SWC plugin |
| Vite 3.x (2022) | EOL | ESM-only config, import.meta.glob changes | glob returns lazy imports by default (add { eager: true } for sync) |
| Vite 2.x (2021) | EOL | First stable release, Rollup-based | Original migration target from Webpack; most guides reference this |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Starting a new project or major refactor | Webpack config is simple and build times are acceptable | Keep Webpack |
| Dev server HMR is painfully slow (>2s) | Project relies on Webpack Module Federation | Webpack 5 with Module Federation |
| Want to reduce build configuration complexity | Team has deep Webpack plugin expertise | Maintain existing Webpack setup |
| Using React, Vue, Svelte, or vanilla JS/TS | Building a complex micro-frontend architecture | Webpack 5 + Module Federation or Rspack |
| Need native ESM and modern browser targeting | Must support IE11 or very old browsers | Webpack 5 with Babel |
| Project size is small to medium (<500 modules) | Very large monorepo with 10K+ modules | Turbopack or Rspack |
| Want faster builds without changing bundler API | Need Webpack API compatibility with better perf | Rspack (drop-in Webpack replacement) |
vite build && vite preview.path, fs, crypto, etc.). If your frontend code imports these, you need vite-plugin-node-polyfills or must refactor.optimizeDeps.include to pre-bundle heavy dependencies.@vitejs/plugin-react-swc plugin offers faster builds but may crash on syntax errors rather than displaying error overlays. Test stability before committing to SWC.rolldown-vite for large projects, but be aware it is still in beta.'modules' (Chrome 87, Firefox 78, Safari 14) to 'baseline-widely-available' (Chrome 107, Firefox 104, Safari 16). If you need older browser support, set build.target explicitly.