How to Migrate from Vue CLI to Vite

Written by Daniel Kelly

If you've been developing with Vue prior to 2021 there's a good chance that your build tool of choice was the Vue CLI. It's been the de-facto standard for scaffolding Vue.js projects for a while. Now though, Evan You's next generation build tool Vite, has been garnering a lot of attention and is a great alternative to the Vue CLI.

Why migrate from Vue CLI to Vite?

The primary reason for making the move is all about speed. Vite's development server is fast. Because it uses native browser support for JavaScript modules, the server start time is instant. The approach also means that Hot Module Replacement remains fast no matter the size of the application since the entire bundle doesn't have to be reconstructed.

To demonstrate, here are the benchmarks of a brand new Vue CLI project compared to the exact same project using Vite.

  • Start time:
    • Vue CLI - 2591ms (over 2 seconds)
    • Vite - 259ms (well under half a second - 10x faster) ⚡️
  • Hot Module Replacement (HMR):
    • Vue CLI - 171ms
    • Vite - Not printed to console by Vite but from observation... essentially instant

And remember this is just the scaffolded boilerplate. As your project grows, the Vue CLI version would just get incrementally slower, while Vite promises to perform at the same levels no matter the size of your project.

How to migrate from Vue CLI to Vite

So you're convinced that Vite is right for you, how do you go about migrating your project from using Vue CLI to Vite?

In order to answer that question, I built a brand new project with Vue CLI and I'm going to go through the steps with you to convert it to Vite. Of course, you wouldn't typically be starting with a brand new Vue CLI project, but many of these steps will be the same for your established projects.

Also, I've chosen to work from a Vue 2 codebase, as most of you probably have your well established codebases still running on Vue 2. However, I've also made notes in the process below where things would differ for Vue 3.

Finally, if you don't want to walk through the whole process together with me, you can see a diff of the changes in this example repo.

Step #1: Updating Dependencies

The first step to migrating to Vite is to update the dependencies in package.json. We'll need to remove the Vue CLI related dependencies.

// package.json
"@vue/cli-plugin-babel": "~4.5.0", // remove
"@vue/cli-plugin-eslint": "~4.5.0", // remove
"@vue/cli-plugin-router": "~4.5.0", // remove
"@vue/cli-plugin-vuex": "~4.5.0", // remove
"@vue/cli-service": "~4.5.0", // remove

We can also remove the sass-loader as Vite provides built in support for the most common pre-processors out of the box. This will allow us to continue to use our CSS pre-processor of choice. Do note though that Vite recommends to use native CSS variables with PostCSS plugins that implement CSSWG drafts and author plain, future-standards-compliant CSS.

// package.json
"sass-loader": "^8.0.2" // remove

Lastly, we'll add Vite as a dependency, as well as the Vue plugin component for Vite in order to support Single File Components.

// package.json
"@vitejs/plugin-vue": "^1.6.1",
"vite": "^2.5.4",

Also, since we're migrating a Vue 2 project we also need to include the community maintained Vite plugin for Vue 2, in addition to the official Vue plugin. If we were working with Vue 3, this wouldn't be necessary.

// package.json
"vite-plugin-vue2" : "1.9.0" // add for Vue 2

With the Vite plugins installed, we can now also remove the vue template compiler as that's handled by the Vite Vue plugins.

// package.json
"vue-template-compiler": "^2.6.11" //remove (SFC support provided by vite vue plugin)

Step #2: Providing Support only For Modern Browsers

Since Vite is a next generation build tool, let's proceed optimistically by only supporting the most modern browsers. This will keep our builds as lean and as fast as possible.

Practically speaking, this means that we can remove Babel completely from our dependencies as most mobile and desktop evergreen browsers have near full support for all ES6 features. If you still need to support older browsers like Internet Explorer 11, Vite does provide an official plugin for that.

So, to remove Babel, first we'll delete the babel.config.js file.

Next, since we already removed the @vue/cli-plugin-babel dependency that requires babel itself, we just need to remove a couple other babel related dependencies from package.json.

// package.json
"babel-eslint": "^10.1.0", // remove
"core-js": "^3.6.5", // remove

With babel-eslint now removed we need to remove it as a parser from our .eslintrc file.

// .eslintrc
// remove
parserOptions: {
    parser: "babel-eslint",
},

Note: If you don't have linting/formatting setup in your project you can skip to the next step but I highly encourage you to add it in if you don't have it already. Here's a great tutorial for getting it setup on your Vite powered Vue projects.

Lastly, while we're in .eslintrc we need to update the env from node to es2021 since we're only supporting those evergreen browsers.

// .eslintrc
env: {
    node: true, // remove
    es2021: true,
}

This change will also force us to update eslint itself, as well as the eslint-plugin-vue to support the es2021 environment.

$ npm install [email protected] [email protected]

Step #3: Add Vite Config

In this step, let's configure Vite for our Vue.js project. Vite is configured via a vite.config.js file in the root of the project. This is what the default vite.config.js file looks like when generating a brand new Vite project for Vue using npm init [email protected].

// vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()]
})

We'll want to add 2 more things to it.

First, we'll import the Vue plugin from vite-plugin-vue2 instead of the official Vite Vue plugin.

// vite.config.js
import vue from '@vitejs/plugin-vue' // remove
import { createVuePlugin as vue } from "vite-plugin-vue2";

//...

If you're using Vue 3, of course, you don't have to do this.

Secondly, in order for the @ import alias to work as it did with Vue CLI, we'll need to add this bit.

// vite.config.js
//...
const path = require("path");
export default defineConfig({
  //...
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

Step #4: Moving the index.html

Contrary to the Vue CLI, Vite actually puts the index.html file that holds the Vue.js application in the root of the project instead of the public directory, so you'll need to move it.

Also inside of index.html you'll want to make a few changes.

First we'll change instances of the <%= htmlWebpackPlugin.options.title %> placeholder to hardcoded values.

// index.html

<!--remove-->
<title><%= htmlWebpackPlugin.options.title %></title> 
<!--add-->
<title>Hard Coded Title</title>

//...
<!--remove-->
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<!--add-->
<strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>

We'll also need to replace the <%= BASE_URL %> placeholder, with an absolute path.

// index.html

<!--remove-->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!--add-->
<link rel="icon" href="/favicon.ico">

Lastly and most importantly, the JavaScript application is no longer auto injected so we need to include it like this:

<script type="module" src="/src/main.js"></script>

Step #5: Update the Scripts

Back in package.json we'll also need to update the scripts. We'll change the old vue-cli-service commands to Vite specific commands.

// package.json
"serve": "vue-cli-service serve", // remove
"build": "vue-cli-service build", // remove
"dev": "vite",
"build": "vite build",
"serve": "vite preview",

Note that the command to spin up the development server is no longer serve. Vite uses dev instead and serve is used to preview the production build locally.

Also if you have linting enabled you should update the lint script to run eslint directly.

"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"

Step #6: Update Environment Variables

There is a lot of cross over between the way environment variables work in Vite and how they work in Vue CLI. For instance, your .env naming convention can remain the same.

.env                # loaded in all cases
.env.local          # loaded in all cases, ignored by git
.env.[mode]         # only loaded in specified mode
.env.[mode].local   # only loaded in specified mode, ignored by git

However, you can no longer access environment variables on a process variable. Instead they can be found on import.meta.env.

// router/index.js
base: process.env.BASE_URL, //remove
base: import.meta.env.BASE_URL,

Also, the VUE_APP_ prefix used to make declaring client exposed env variables more apparent is changed to VITE_, so if you have any such environment variables you'll have to update them accordingly.

Step #7: Add .vue extension to SFC imports

While our newly created Vue CLI project already does this, my bet is that your existing applications probably do not. So, you must ensure all imports of single file components end with the .vue extension.

// Home.vue
import HelloWorld from "@/components/HelloWorld.vue"; // .vue is required

If this process is too overwhelming due to the size of your codebase you can configure vite so that this isn't required. This is accomplished by adding .vue to the resolve.extensions config option in vite.config.js. Make sure you also manually include all the default extensions as well though as this option overrides the default.

// vite.config.js
//...
export default defineConfig({
  plugins: [vue()],
  resolve: {
    extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
    //...
  },
});

While this works, it should be avoided if at all possible. Why? Because according to the Vite docs: "Note it is NOT recommended to omit extensions for custom import types (e.g. .vue) since it can interfere with IDE and type support."

Step #8: Clean Up Magic Comments

Lastly, you can remove all the magic comments for naming your dynamic imports as these are webpack specific comments and don't mean anything to Vite.

// router/index.js
import(
    /* webpackChunkName: "about" */  // remove
    "../views/About.vue"
),

Instead Vite will automatically name your chunk based off of the original .vue file name combined with a cache busting hash, like this: About.37a9fa9f.js

Step #9: Enjoy a Faster and More Seamless Development Experience

After completing steps 1-8 above, your application should be ready to start running with Vite! Go ahead and start up your dev server with npm run dev and see how fast Vite is for yourself.

If you have any other errors pop up at this point, please comment below and share them with the community, as well as any solutions you might have for them!

Finally, remember that you can see all these changes as a diff in this example repo to help with your migration.

Learn Vue.js 3 With Vue School

Leave a Reply

Your email address will not be published. Required fields are marked *

Up Next:

VueUse Head and Netlify PreRendering for SEO and Social Friendly SPAs

VueUse Head and Netlify PreRendering for SEO and Social Friendly SPAs