How to Migrate from Webpack to Vite

Type: Software Reference Confidence: 0.93 Sources: 9 Verified: 2026-02-23 Freshness: monthly

TL;DR

Constraints

Quick Reference

Webpack Config/FeatureVite EquivalentExample
entry (string)build.rollupOptions.inputinput: { main: 'src/main.js' }
output.pathbuild.outDirbuild: { outDir: 'dist' }
output.publicPathbasebase: '/my-app/'
output.filenamebuild.rollupOptions.output.entryFileNamesentryFileNames: 'assets/[name]-[hash].js'
output.chunkFilenamebuild.rollupOptions.output.chunkFileNameschunkFileNames: 'assets/[name]-[hash].js'
devServer.portserver.portserver: { port: 3000 }
devServer.proxyserver.proxyserver: { proxy: { '/api': 'http://localhost:8080' } }
devServer.httpsserver.httpsserver: { https: true } (or @vitejs/plugin-basic-ssl)
resolve.aliasresolve.aliasresolve: { alias: { '@': '/src' } }
resolve.extensionsresolve.extensionsresolve: { extensions: ['.js', '.ts', '.jsx'] }
DefinePlugindefinedefine: { __APP_VERSION__: JSON.stringify('1.0') }
process.env.NODE_ENVimport.meta.env.MODEAutomatic in Vite (development/production)
process.env.REACT_APP_*import.meta.env.VITE_*Prefix env vars with VITE_ in .env files
copy-webpack-pluginpublicDir (default: public/)Static files in public/ are copied to dist/ automatically
css-loader + style-loaderBuilt-in CSS supportimport './style.css' works out of the box
sass-loaderBuilt-in (install sass)npm install sass -D — no loader config needed
postcss-loaderBuilt-in (auto-detects postcss.config.js)Just keep your PostCSS config file
file-loader / url-loaderBuilt-in asset handlingimport logo from './logo.png' returns URL
html-webpack-pluginindex.html at project rootMove index.html to root, add <script type="module" src="/src/main.js">
MiniCssExtractPluginBuilt-in CSS code splittingAutomatic in production builds
HotModuleReplacementPluginBuilt-in HMRAutomatic with framework plugins
require.context()import.meta.glob()const modules = import.meta.glob('./modules/*.js')
require() (dynamic)new URL('./path', import.meta.url).hrefFor asset URLs in ESM context
babel-loaderBuilt-in esbuild (or @vitejs/plugin-react)esbuild handles JSX/TSX transformation
ts-loaderBuilt-in esbuild TypeScriptNo loader needed; esbuild transpiles TS natively
webpack-bundle-analyzerrollup-plugin-visualizerimport { visualizer } from 'rollup-plugin-visualizer'
ProvidePluginvite-plugin-inject or manual importsReplace global shimming with explicit imports
splitVendorChunkPlugin (deprecated Vite 7)build.rollupOptions.output.manualChunksDefine manual chunk splitting in Rollup options

Decision Tree

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

Step-by-Step Guide

1. Audit your Webpack configuration

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.

2. Install Vite and framework plugin

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.

3. Create vite.config.js from your webpack.config.js

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.

4. Move index.html to project root and update it

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.

5. Convert environment variables

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.

6. Convert CommonJS require() to ESM imports

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.

7. Rename .jsx/.tsx files if needed

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.

8. Update package.json scripts and remove Webpack

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.

Code Examples

JavaScript/React: Complete vite.config.js replacing a typical webpack.config.js

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'],
          },
        },
      },
    },
  };
});

TypeScript: Migrating Webpack require.context to Vite import.meta.glob

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 };
  },
}));

Node.js: Migration script to automate environment variable renaming

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');

Anti-Patterns

Wrong: Keeping process.env references and shimming with define

// ❌ 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!
  },
});

Correct: Use Vite's import.meta.env with VITE_ prefix

// ✅ 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;

Wrong: Copying webpack.config.js structure into vite.config.js

// ❌ 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
  ],
});

Correct: Minimal Vite config — let defaults work

// ✅ 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
});

Wrong: Using require() in source code with Vite

// ❌ 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$/);

Correct: Use ESM imports and import.meta.glob

// ✅ 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 });

Wrong: Defining global as empty object to fix Node.js polyfill errors

// ❌ BAD — Breaks libraries that actually use global
export default defineConfig({
  define: {
    global: {},  // Causes "global is not an object" errors at runtime
  },
});

Correct: Properly polyfill global for browser context

// ✅ GOOD — Define global as globalThis for browser compatibility
export default defineConfig({
  define: {
    global: 'globalThis',  // globalThis works in both Node and browser
  },
});

Wrong: Using the deprecated Sass legacy API with Vite 7

// ❌ 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
      },
    },
  },
});

Correct: Use the modern Sass API (Vite 7 default)

// ✅ 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
      },
    },
  },
});

Common Pitfalls

Diagnostic Commands

# 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 History & Compatibility

VersionStatusKey FeaturesMigration Notes
Vite 8.x (2026 beta)BetaRolldown bundler (10-30x faster builds), unified toolchain, built-in tsconfig pathsEarly adopters only; Rollup/esbuild options may need adjustment
Vite 7.x (2025)CurrentNode 20.19+ required, baseline-widely-available target, buildApp hook, Sass legacy API removedDrop Node 18; remove splitVendorChunkPlugin; use order/handler in hooks
Vite 6.x (2024)LTSEnvironment API (experimental), Rolldown previewVite 5 configs work with minor updates; server.fs.allow tightened
Vite 5.x (2023)MaintenanceRollup 4, Node 18+ requiredDrop Node 14/16 support; update Rollup plugins to v4-compatible
Vite 4.x (2022)EOLSWC support via pluginFirst version with stable React SWC plugin
Vite 3.x (2022)EOLESM-only config, import.meta.glob changesglob returns lazy imports by default (add { eager: true } for sync)
Vite 2.x (2021)EOLFirst stable release, Rollup-basedOriginal migration target from Webpack; most guides reference this

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Starting a new project or major refactorWebpack config is simple and build times are acceptableKeep Webpack
Dev server HMR is painfully slow (>2s)Project relies on Webpack Module FederationWebpack 5 with Module Federation
Want to reduce build configuration complexityTeam has deep Webpack plugin expertiseMaintain existing Webpack setup
Using React, Vue, Svelte, or vanilla JS/TSBuilding a complex micro-frontend architectureWebpack 5 + Module Federation or Rspack
Need native ESM and modern browser targetingMust support IE11 or very old browsersWebpack 5 with Babel
Project size is small to medium (<500 modules)Very large monorepo with 10K+ modulesTurbopack or Rspack
Want faster builds without changing bundler APINeed Webpack API compatibility with better perfRspack (drop-in Webpack replacement)

Important Caveats

Related Units