DEV Community

Cover image for Migrating a Legacy Vue 2 Application from Webpack 2 to Vite: A Practical Step-by-Step Guide
Camila Rody
Camila Rody

Posted on

Migrating a Legacy Vue 2 Application from Webpack 2 to Vite: A Practical Step-by-Step Guide

Most migration guides assume a relatively modern codebase. They start with Vue CLI 5, recent Node versions, maintained dependencies, and a straightforward path toward Vite.

That was not my reality.

The project I inherited was a legacy Vue 2 application built on Webpack 2. The ecosystem contained deprecated loaders, abandoned plugins, outdated Babel configurations, CommonJS packages, custom aliases, and years of accumulated technical debt. Rewriting the application was not an option, and upgrading every dependency simultaneously would have introduced an unacceptable amount of risk.

The objective was simple: replace Webpack 2 with Vite while preserving the existing application architecture and maintaining compatibility with legacy dependencies.

This article focuses on the technical process itself. Rather than discussing modernization from a high level, I will walk through the exact migration strategy, the compatibility issues encountered, and the solutions that allowed the project to continue operating without a complete rewrite.

Understanding What Webpack Is Actually Doing

Before installing Vite, it is essential to understand that Webpack is not merely bundling JavaScript. In most Vue 2 applications that have existed for several years, Webpack is responsible for resolving aliases, transpiling code, injecting environment variables, loading assets, compiling stylesheets, handling code splitting, and often compensating for browser incompatibilities.

A common mistake is attempting to install Vite and immediately delete the existing Webpack configuration.

Instead, start by mapping every responsibility currently performed by Webpack.

Analyze files such as:

webpack.config.js
webpack.prod.js
webpack.dev.js
.babelrc
package.json
Enter fullscreen mode Exit fullscreen mode

Document:

  • Aliases
  • Loaders
  • Plugins
  • Environment variables
  • Babel presets
  • Polyfills
  • Dynamic imports
  • Asset handling
  • Sass/Less configurations

Only after understanding these responsibilities should the migration begin.

Establishing a Stable Node Environment

Legacy Vue 2 projects often depend on old Node versions.

Before introducing Vite, identify the highest Node version capable of running the project without breaking dependencies.

A typical scenario looks like this:

{
  "engines": {
    "node": "8.x"
  }
}
Enter fullscreen mode Exit fullscreen mode

Attempting to jump directly from Node 8 to Node 22 while simultaneously replacing Webpack introduces too many variables.

Instead:

  1. Upgrade Node incrementally.
  2. Verify build stability.
  3. Resolve dependency incompatibilities.
  4. Only then introduce Vite.

Many failures blamed on Vite are actually caused by outdated dependencies that cannot execute in modern Node environments.

Installing Vite Without Removing Webpack

The safest migration strategy is running both build systems simultaneously.

Install Vite and Vue 2 support:

npm install vite vite-plugin-vue2 --save-dev
Enter fullscreen mode Exit fullscreen mode

Create a dedicated Vite configuration:

import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'

export default defineConfig({
  plugins: [
    createVuePlugin()
  ]
})
Enter fullscreen mode Exit fullscreen mode

At this stage, Webpack remains untouched.

The objective is creating a parallel build environment rather than replacing the existing one immediately.

Your scripts may temporarily look like:

{
  "scripts": {
    "dev": "webpack-dev-server",
    "dev:vite": "vite",
    "build": "webpack",
    "build:vite": "vite build"
  }
}
Enter fullscreen mode Exit fullscreen mode

This allows side-by-side validation throughout the migration process.

Migrating Aliases

Most large Vue 2 applications rely heavily on aliases.

A typical Webpack configuration might contain:

resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src'),
    '@components': path.resolve(__dirname, 'src/components'),
    '@services': path.resolve(__dirname, 'src/services')
  }
}
Enter fullscreen mode Exit fullscreen mode

These aliases must be replicated within Vite:

import path from 'path'

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@services': path.resolve(__dirname, './src/services')
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Failure to mirror aliases correctly usually produces hundreds of import resolution errors.

This should be one of the first migration tasks completed.

Replacing Webpack-Specific Environment Variables

Webpack projects frequently depend on:

process.env.API_URL
Enter fullscreen mode Exit fullscreen mode

Vite uses a different model based on native ESM.

Before:

const apiUrl = process.env.API_URL
Enter fullscreen mode Exit fullscreen mode

After:

const apiUrl = import.meta.env.VITE_API_URL
Enter fullscreen mode Exit fullscreen mode

Environment files must also be updated:

VITE_API_URL=https://api.company.com
Enter fullscreen mode Exit fullscreen mode

One important detail is that Vite only exposes variables prefixed with:

VITE_
Enter fullscreen mode Exit fullscreen mode

Variables without this prefix will not be available in client-side code.

Refactoring Dynamic Require Statements

One of the largest migration blockers in legacy Vue applications is the extensive use of runtime require statements.

Webpack tolerated patterns like:

const page = require('./pages/' + pageName)
Enter fullscreen mode Exit fullscreen mode

or

const component = require(path)
Enter fullscreen mode Exit fullscreen mode

Vite cannot statically analyze these imports.

The modern replacement is:

const pages = import.meta.glob('./pages/*.vue')
Enter fullscreen mode Exit fullscreen mode

Usage:

const page = pages[`./pages/${pageName}.vue`]
Enter fullscreen mode Exit fullscreen mode

This change is often unavoidable.

Projects with extensive dynamic loading mechanisms usually require dedicated refactoring during migration.

Migrating Asset Imports

Webpack loaders often hide complexity.

Examples include:

import logo from './logo.png'
import icon from './icon.svg'
Enter fullscreen mode Exit fullscreen mode

Fortunately, most static assets work immediately in Vite.

However, custom loaders require investigation.

Common examples include:

file-loader
url-loader
svg-inline-loader
raw-loader
Enter fullscreen mode Exit fullscreen mode

Many become unnecessary because Vite provides native handling.

When custom behavior exists, identify whether it can be replaced through:

  • Vite plugins
  • Native imports
  • Asset URL transformations

Never assume loader behavior automatically transfers to Vite.

Handling CommonJS Dependencies

Legacy Vue 2 projects often depend on libraries published years before ES Modules became standard.

Examples:

module.exports = library
Enter fullscreen mode Exit fullscreen mode

or

exports.default = library
Enter fullscreen mode Exit fullscreen mode

Vite can optimize many CommonJS packages automatically.

When issues occur:

export default defineConfig({
  optimizeDeps: {
    include: [
      'legacy-library',
      'old-plugin'
    ]
  }
})
Enter fullscreen mode Exit fullscreen mode

For problematic packages:

import library from 'legacy-library'

export default library
Enter fullscreen mode Exit fullscreen mode

Creating compatibility wrappers often avoids widespread refactoring.

Fixing Global Variables and Polyfills

Webpack silently injected several browser polyfills.

Vite does not.

Typical migration errors include:

process is not defined
Enter fullscreen mode Exit fullscreen mode
Buffer is not defined
Enter fullscreen mode Exit fullscreen mode
global is not defined
Enter fullscreen mode Exit fullscreen mode

Solutions vary depending on usage.

For Buffer:

import { Buffer } from 'buffer'

window.Buffer = Buffer
Enter fullscreen mode Exit fullscreen mode

For process:

npm install process
Enter fullscreen mode Exit fullscreen mode
import process from 'process'

window.process = process
Enter fullscreen mode Exit fullscreen mode

Polyfills should be added intentionally rather than globally whenever possible.

Reviewing Babel Requirements

Many Vue 2 applications contain complex Babel configurations inherited from older browser support requirements.

Example:

{
  "presets": [
    ["env", {
      "modules": false
    }]
  ]
}
Enter fullscreen mode Exit fullscreen mode

Before migrating, determine whether Babel is still necessary.

In many cases:

  • Legacy IE support has been dropped.
  • Modern browsers are sufficient.
  • Several transformations become unnecessary.

Reducing Babel complexity often simplifies the migration significantly.

Converting Development Workflows

One of the most visible differences after migration is the development experience.

Webpack:

npm run dev
Enter fullscreen mode Exit fullscreen mode

may require 20–60 seconds before changes appear.

Vite:

npm run dev:vite
Enter fullscreen mode Exit fullscreen mode

typically starts almost instantly because dependencies are pre-bundled and source files are served as native ES modules.

The perceived performance improvement is often the first confirmation that the migration is succeeding.

Production Validation

A successful development server means very little.

The final phase should focus on production validation.

Review:

  • Routing
  • Authentication
  • API communication
  • File uploads
  • Lazy loading
  • Asset generation
  • Source maps
  • Environment variables

Compare generated bundles.

Analyze network requests.

Verify route-level code splitting.

Inspect console warnings.

Most migration issues appear during production builds rather than development execution.

Final Thoughts

The hardest part of migrating a Vue 2 application from Webpack 2 to Vite is not configuring Vite itself. The real challenge lies in uncovering years of implicit assumptions hidden within a mature codebase.

Webpack frequently compensates for architectural decisions that developers no longer remember making. Dynamic imports, CommonJS dependencies, deprecated loaders, implicit polyfills, and custom build behavior all become visible once the abstraction layer changes.

A successful migration is rarely a matter of replacing one bundler with another. It is an exercise in understanding the architecture deeply enough to preserve existing behavior while modernizing the tooling around it.

If I had to summarize the process into a single recommendation, it would be this: run Webpack and Vite side by side for as long as necessary. The ability to compare outputs, isolate regressions, and maintain a rollback path is often the difference between a smooth migration and weeks of production issues.

Top comments (0)