Home / Blog / How to Migrate from Vue CLI to Vite
How to Migrate from Vue CLI to Vite

How to Migrate from Vue CLI to Vite

Daniel Kelly
Daniel Kelly
November 11th 2021

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 add the environment es2022 as the src of the project now lives completely in ES Modules land. You can leave the node env in place as well, as some configuration files still run in the node environment.

// .eslintrc
env: {
    node: true,
    es2022: true, // 👈 add this
}

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

$ npm install eslint@8 eslint-plugin-vue@8

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 vite@latest.

// 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.

Related Courses

Start learning Vue.js for free

Daniel Kelly
Daniel Kelly
Daniel is the lead instructor at Vue School and enjoys helping other developers reach their full potential. He has 10+ years of developer experience using technologies including Vue.js, Nuxt.js, and Laravel.

Comments

Latest Vue School Articles

Enhance Your Vue.js UI Skills: Free Learning Opportunity

Enhance Your Vue.js UI Skills: Free Learning Opportunity

Explore practical Vue.js UI tools and techniques during Vue School's Free Weekend. Learn how to efficiently use Tailwind CSS, Vuetify, and animations to enhance your applications. Access 1300+ lessons to improve your development skills.
Maria Panagiotidou
Maria Panagiotidou
How to Access Vue Refs Defined in Script Setup within Unit Tests

How to Access Vue Refs Defined in Script Setup within Unit Tests

Need to access a component’s data defined within script setup? In this article we’ll teach you how! But be warned you probably want to approach your test a little differently.
Daniel Kelly
Daniel Kelly

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.