Migrating a Vue 2 Project from Vue CLI to Vite: Step‑by‑Step Guide

This article provides a comprehensive step‑by‑step guide for migrating a Vue 2 project built with Vue CLI to Vite, covering project structure analysis, removal of Vue CLI dependencies, configuration changes, handling of environment variables, alias setup, JSX support, asset handling, and additional build optimizations.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Migrating a Vue 2 Project from Vue CLI to Vite: Step‑by‑Step Guide

Why migrate?

The main motivation is faster build speed: Vite uses esbuild for pre‑bundling, which is significantly quicker than Vue CLI’s Webpack‑based builds, improving developer experience.

Preparation – original project structure

The original Vue CLI project contains a typical layout with

.browserslistrc, .env files, vue.config.js, src/, public/, tests/

and many configuration files.

├── .browserslistrc
├── .editorconfig
├── .env.development
├── .env.production
├── .eslintrc.js
├── .gitignore
├── package.json
├── vue.config.js
└── src/

Remove Vue CLI dependencies

Search for the vue-cli keyword in package.json and delete related dependencies.

Replace the original npm scripts that use vue-cli-service with Vite equivalents:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  }
}

Adjust entry point

Vue CLI’s default entry is src/main.js; Vite expects an index.html at the project root that includes a <script type="module" src="/src/main.js"> tag.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

Environment variables

Vue CLI exposes NODE_ENV, BASE_URL and variables prefixed with VUE_APP_. Vite uses MODE, BASE_URL, VITE_ ‑prefixed variables and accesses them via import.meta.env. Replace all VUE_APP_ variables with VITE_ or configure envPrefix in vite.config.js to keep the old prefix.

// package.json script example
"scripts": {
  "dev": "vite",
  "build": "vite build"
}

// vite.config.js snippet for envPrefix
export default defineConfig({
  envPrefix: ['VUE_APP_', 'VITE_']
});

Import extensions

Vue CLI allowed omitting the .vue extension; Vite recommends always specifying it for better IDE support.

// Vue CLI style (allowed but not recommended)
import App from './App'

// Vite style (recommended)
import App from './App.vue'

Enable JSX support

Install vite-plugin-vue2 and enable JSX by passing { jsx: true } to the plugin.

import { defineConfig } from 'vite';
const { createVuePlugin } = require('vite-plugin-vue2');

export default defineConfig(() => ({
  plugins: [createVuePlugin({ jsx: true })]
}));

In a component file, use <script lang="jsx"> and export a render function.

<script lang="jsx">
export default {
  render() { return <div>JSX Render</div>; }
}
</script>

Alias configuration

Copy the resolve.alias object from vue.config.js into vite.config.js. The same alias keys (e.g., @, @api, @components) work with Vite.

resolve: {
  alias: {
    '@': resolve('src'),
    '@api': resolve('src/api'),
    '@components': resolve('src/components'),
    // ...other aliases
  }
}

Node built‑in modules in client code

Modules like path are not available in the browser. Replace them with browser‑compatible equivalents such as path-browserify.

// Before (Vue CLI)
import path from 'path';
export function genPath(...paths) { return path.join(...paths); }

// After (Vite)
import path from 'path-browserify';
export function genPath(...paths) { return path.join(...paths); }

Global CSS variables

Vue CLI used sass-resources-loader to inject global SCSS. In Vite, use css.preprocessorOptions.scss.additionalData. Note that importing the same SCSS file twice can cause a “file is already being loaded” error.

css: {
  preprocessorOptions: {
    scss: {
      additionalData: `@import './node_modules/web-lib/packages/web-lib-styles/src/variables/index.scss';`
    }
  }
}

Dynamic asset imports

Webpack’s require for dynamic images is replaced by Vite’s import.meta.globEager (eager loading) or new URL(..., import.meta.url) (lazy loading).

// Eager glob import
const images = import.meta.globEager('./images/*.png');
const iconSrc = images[`./images/${iconName}.png`].default;

// URL constructor
const iconSrc = new URL(`./images/${iconName}.png`, import.meta.url);

SVG sprite handling

Replace svg-sprite-loader with vite-plugin-svg-icons. The plugin automatically registers SVG symbols.

import viteSvgIcons from 'vite-plugin-svg-icons';

export default defineConfig(() => ({
  plugins: [
    viteSvgIcons({
      iconDirs: [resolve('src/icons/svg')],
      symbolId: 'icon-[name]'
    })
  ]
}));

// In main.js
import 'virtual:svg-icons-register';

Other adjustments

Hot‑module‑replacement code in the store can be removed because Vite handles HMR automatically. When using environment variables inside vite.config.js, load them with dotenv instead of import.meta.env because the config file is evaluated before Vite processes .env files.

const { PORT } = require('dotenv').config({ path: './.env' }).parsed || {};
export default defineConfig({
  server: { port: PORT, open: true }
});

Final project structure and Vite configuration

The migrated project ends up with a simplified tree and a comprehensive vite.config.js that includes base path, plugins, alias, CSS pre‑processor options, server proxy settings, and build target.

├── .browserslistrc
├── .editorconfig
├── .env
├── .env.development
├── .env.production
├── .eslintrc.js
├── .gitignore
├── .husky/
├── package.json
├── public/
├── src/
├── tests/
├── vite.config.js
└── yarn.lock
import path from 'path';
import { defineConfig } from 'vite';
import viteSvgIcons from 'vite-plugin-svg-icons';
const { createVuePlugin } = require('vite-plugin-vue2');

function resolve(dir) { return path.join(__dirname, dir); }

export default defineConfig(({ mode }) => {
  const { PORT } = require('dotenv').config({ path: './.env' }).parsed || {};
  const { VITE_URI_BUSINESS_SERVICE_BASE, VITE_URI_FILE_SERVICE_BASE, VITE_USER_SOCTET_BASE } =
    require('dotenv').config({ path: `./.env.${mode}` }).parsed || {};
  return {
    base: './',
    clearScreen: false,
    plugins: [
      createVuePlugin({ jsx: true }),
      viteSvgIcons({ iconDirs: [resolve('src/icons/svg')], symbolId: 'icon-[name]' })
    ],
    resolve: {
      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
      alias: {
        '@': resolve('src'),
        '@api': resolve('src/api'),
        '@components': resolve('src/components'),
        // ...other aliases
      }
    },
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: `@import './node_modules/web-lib/packages/web-lib-styles/src/variables/index.scss';`
        }
      }
    },
    server: {
      port: PORT,
      open: true,
      proxy: {
        '/user': { target: VITE_URI_BUSINESS_SERVICE_BASE, changeOrigin: true, secure: false },
        '/file': { target: VITE_URI_BUSINESS_SERVICE_BASE, changeOrigin: true, secure: false },
        '^/group1': { target: VITE_URI_FILE_SERVICE_BASE, changeOrigin: true, secure: false },
        '^/wsUser': {
          target: VITE_USER_SOCTET_BASE,
          ws: true,
          changeOrigin: true,
          secure: false,
          pathRewrite: { '^/wsUser': '' },
          onProxyReqWs(proxyReq, req, socket) {
            socket.on('error', err => console.error(err));
          }
        }
      }
    },
    build: { target: 'es2015' }
  };
});
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

migrationJavaScriptbuild toolsVite
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.