A Comprehensive Vue 2 to Vue 3 Migration Guide

Milan Dadhaniya
Simform Engineering
9 min readJul 24, 2023

Embracing Vue3’s new era

Welcome to the cutting-edge world of Vue.js!

The arrival of Vue 3 has ushered in a new era of enhancements and groundbreaking features. This evolutionary step from Vue 2 to Vue 3 promises developers a realm of opportunities to stay at the forefront of web development trends and harness the true potential of this remarkable framework.

In this comprehensive article, we'll take you through the seamless transition from Vue 2 to Vue 3, unveiling the magnificent advancements Vue 3 brings to the table. We’ll also cover how to handle the deprecated and eliminated parts during the migration process.

📄 Table of content

  1. What’s New in Vue 3
  2. Deprecated and Removed Items in Vue 3
  3. Step-by-Step Migration Guide: Transitioning Your Vue 2 App to Vue 3
    1. Set the Foundation: Establish Vue 2 Project
    2. Install Vue’s Migration Build
    3. Fix Migration Build Errors
    4. Resolve Package Compatibilities
    5. Resolve Migration Build Warnings
    6. Migrate to Vue 3’s Composition API
    7. Test and Assure Your App’s Performance and Stability
    8. Uninstall the Migration Build

What’s New in Vue 3

Composition API

Composition API provides a flexible way to organize component logic. This makes it easier to reuse code across components with better type checking. The Composition API is easier to maintain than the Options API.

While the Composition API was already available in Vue 2.7 through the officially maintained @vue/composition-api plugin. In Vue 3, it is primarily used in conjunction with the <script setup>.

Better Type Support

Vue 3’s codebase is entirely written in TypeScript with auto-generated type definitions. It has better type inference and support for TypeScript’s optional chaining and null coalescing operators.

Improved Virtual DOM Algorithm

Virtual DOM has been completely redesigned in Vue 3. It utilizes a new diffing algorithm with compiler-based optimizations to speed up rendering.

Fragments, AKA Multiple Roots

In Vue 3, we can create multiple roots for a single component which was not feasible in Vue 2.

Vue 2 Syntax

<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>

Vue 3 Syntax: Components now can have multiple root nodes! However, this does require developers to explicitly define where attributes should be distributed.

<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>

Teleport

<Teleport> is a built-in component that allows us to "teleport" a part of a component's template into a DOM node that exists outside the DOM hierarchy of that component.

<demo-component>
<teleport to="#teleport-area">
<pop-up />
</teleport>
</demo-component>
<div id="teleport-area"></div>

The to target of <Teleport> expects a CSS selector string or an actual DOM node. The teleport to the target must be already in the DOM when the <Teleport> component is mounted.

SFC State-driven CSS Variables (v-bind in <style>)

In Vue 3, style tags support linking CSS values to dynamic component state using the v-bind CSS function.

<template>
<div class="text">hello</div>
</template>

<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>

<style>
.text {
color: v-bind(color);
}
</style>

Deprecated and Removed Items in Vue 3 ❌

Filters

In Vue 3, filters are removed. Instead, we can use methods or computed properties. You can refer below example.

In Vue 2:

<template>
<div>{{ name | uppercase }}</div>
</template>

<script>
export default {
data: function () {
return {
name: "milan",
};
},
filters: {
uppercase: function(value) {
return value.toUpperCase();
}
}
};
</script>

In Vue 3: Replaced filter with a computed property

<template>
<div>{{ uppercaseName }}</div>
</template>

<script>
export default {
data: function () {
return {
name: "milan",
};
},
computed: {
uppercaseName: function() {
return this.name.toUpperCase();
}
}
};
</script>

v-model

v-model prop and event default names are changed:

  • PROP: valuemodelValue
  • EVENT: inputupdate:modelValue
  • BREAKING: v-bind's .sync modifier and component model option is removed and replaced with an argument on v-model
  • NEW: Multiple v-model bindings on the same component are possible now
  • NEW: Added the ability to create custom v-model modifiers.

v-model in Vue 2

<template>
<ChildComponent v-model="pageTitle" />
<!-- OR -->
<ChildComponent :value="pageTitle" @input="pageTitle=$event" />
</template>

v-model in Vue 3

<template>
<!-- Single v-model -->
<ChildComponent v-model="pageTitle" />
<!-- OR -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />


<!-- Multiple v-model -->
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- OR -->
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
</template>

Event in Vue 2

<script lang="ts">
export default {
methods: {
updateTitle() {
this.$emit('input')
}
}
}
</script>

Event in Vue 3

<script lang="ts">
export default {
props: {
modelValue: String
},
emits: ['update:pageTitle'],
methods: {
changeTitle(title) {
this.$emit('update:pageTitle', title)
}
}
}
</script>

Inline templates

In Vue 2, the inline-template attribute allowed child components to use their inner content as a template instead of distributing it.

However, Vue 3 no longer supports this feature. If you were using inline-template in Vue 2, you can refactor the component using the default slot instead. This approach provides explicit data scoping while still allowing you to write child content inline for added convenience.

Named and Scoped Slots‍

In Vue 3, named and scoped slots have different syntaxes. To identify the slot name, use the v-slot directive or its # shorthand.

Slot in Vue 2


<template>
<div>
<template slot="heading" slot-scope="slotProps">
<h1>Slot ({{ slotProps.items.length }})</h1>
</template>
</div>
</template>

Slot in Vue 3

<template>
<div>
<template #heading="slotProps">
<h1>Slot ({{ slotProps.items.length }})</h1>
</template>
</div>
</template>

Global API Application Instance

Technically, Vue 2 doesn’t have a concept of an “app”. What we define as an app is simply a root Vue instance created via new Vue(). Every root instance created from the same Vue constructor shares the same global configuration.

This global configuration poses challenges when using the same Vue instance for multiple “apps” on the same page with different global configurations.

// this affects both root instances
Vue.mixin({
/* ... */
})

const app1 = new Vue({ el: '#app-1' });
const app2 = new Vue({ el: '#app-2' });

To avoid these problems, Vue 3 introduces a new way to create Vue app — createApp.

const app = createApp(App);
// This will effect only 1 instance
app.mixin(/* ... */);
app.mount("#app");

Key Attribute

NEW: key is no longer necessary on v-if/v-else/v-else-if branches since Vue now automatically generates unique key.
BREAKING: If you manually provide key, each branch must use a unique key.
BREAKING: <template v-for> key should be placed on the <template> tag (rather than on its children).

v-if vs. v-for Precedence

In Vue 2: When using v-if and v-for on the same element, v-for would take precedence.

In Vue 3: v-if will always have higher precedence than v-for

There are few more minor changes in Vue3. Refer to this link for detailed info.

Step-by-Step Migration Guide: Transitioning Your Vue 2 App to Vue 3

Step 1: Set the Foundation: Establish Vue 2 Project

We’ll use one sample Vue 2 app to illustrate the migration procedure. You may clone this repository if you would like to practice with it as well.

Example: To do app with Vue2 — Vue 2 to Vue 3 migration

Step 2: Install Vue’s Migration Build

Before installing the migration build, you need to check a few things:

1. Update any deprecated named/scoped slot syntax to the latest version. Click here for the ref link.
2. If you are using a custom webpack, update the vue-loader to the latest version.
3. For vue-cli users, upgrade @vue/cli-service to the latest version.

Now, follow these steps:

  • Update vue version to ^3.1.0
  • Install @vue/compat,also known as Vue’s migration build, with the same version as the updated one
  • Replace vue-template-compiler with @vue/compiler-sfc@^3.1.0

Example commit of package.json

"dependencies": {
- "vue": "^2.6.14",
+ "vue": "^3.1.0",
+ "@vue/compat": "^3.1.0"
...
},
"devDependencies": {
- "vue-template-compiler": "^2.6.14"
+ "@vue/compiler-sfc": "^3.1.0"
}

Then, configure the alias vue to @vue/compat and compiler options in *.config.jsas shown below.

For vue-cli:

// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.resolve.alias.set('vue', '@vue/compat')

config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
return {
...options,
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
})
}
}

For webpack:

// webpack.config.js
module.exports = {
resolve: {
alias: {
vue: '@vue/compat'
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
}
]
}
}

For Vite:

// vite.config.js
export default {
resolve: {
alias: {
vue: '@vue/compat'
}
},
plugins: [
vue({
template: {
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
})
]
}

After switching to the migration build, you’ll encounter runtime warnings (in the browser console) and compiler warnings (in the command line). It’s best to address compiler warnings first, as some may break the application, such as the use of filters.

Example Commit : Click here

Note : Keep the compatConfig option until the full migration is complete.

Step 3: Fix Migration Build Errors

The Migration Build may not be fully compatible with Vue 3, so starting your application might not work. First, fix the deprecations.

Example Migration Build’s Errors
Solution

Example Commit : Click here
Note:
Refer to the Section 2: Deprecated and Removed Items in Vue 3 for more details on how to solve deprications.

Step 4: Resolve Package Compatibility

Many packages are now compatible with Vue 3. Update them to their latest versions. If you can’t find a Vue 3 compatible version for some packages, find suitable replacements.

Example Commit : Click here

Step 5: Resolve Migration Build Warnings

You may be able to execute your app now, but hold off on celebrating just yet. There’s still work to be done. When you run your application, you’ll encounter console warnings like the ones below.

Example Migration build’s Warnings

These warnings need to be fixed.

Each warning has an identifier (e.g., GLOBAL_MOUNT, OPTIONS_DATA_FN, etc.). You can find a complete list of all the different warnings here.

Example Commit : Click here

Step 6: Migrate to Vue 3’s Composition API

This step is not necessary but recommended.

Example Commit : Click here

Step 7: Test and Assure Your App Performance and Stability

Thoroughly test your migrated components to ensure their functionality in the Vue 3 environment. Leverage the Vue Devtools extension to debug and inspect your application during migration.

Step 8: Uninstall the Migration Build

Once testing is complete and your application runs perfectly in Vue 3, it’s time to wrap things up!

  • Uninstall @vue/compat package
  • Remove the changes we made at the beginning of the article to vue.config.js.

Example Commit : Click here for link

📢 TADA!! Migration is completed. 🎉

Full Migration Example:

Vue 2 Todo App: main branch
Post Migration : vue3 branch

Adding to this,

Vue 2 support will end on December 31st, 2023.

To fully embrace Vue.js’ capabilities and the future of web development, developers like you must switch to Vue 3. This article covers Vue 3’s new features, removed and deprecated features, and step-by-step instructions for updating a sample Vue 2 application to Vue 3. Accept Vue 3 for a better development experience, cutting-edge web apps, and higher performance.

For more updates on the latest tools and technologies, follow the Simform Engineering blog.

Follow us: Twitter | LinkedIn

--

--