10 Steps to Migrate from Vue 2 to Vue 3
Active support for Vue 2 ended on March 18, 2022, and it is scheduled to reach its end of life on December 31, 2023. This means that, starting from 2024, there will be no more bug fixes or security patches provided. If your application is still running on Vue 2, it is essential to start preparing for the migration to version 3.
The Vue team has been considerate of developers who are transitioning from Vue 2 to Vue 3 by providing a helpful tool called the “migration build.” This build, available as a JavaScript package named vue/compat , is specifically designed to assist teams in migrating their applications smoothly from version 2 to version 3.
With the migration build, you have the flexibility to migrate your application gradually, allowing you to ship updates to production without the need to complete the entire migration at once. It’s important to note that while using the migration build, there may be minor performance and bundle size trade-offs.
However, it’s worth considering that the Vue team has plans to discontinue publishing the migration build at some point in the future. Therefore, it is advisable to eventually move beyond the migration build to fully embrace the benefits of Vue 3.
Who is this post for?
In this post, we will guide you through the 10 steps required to successfully migrate from Vue 2 to Vue 3. However, it’s important to note that we are making a few assumptions along the way. These assumptions are based on common scenarios and best practices, but they may not cover every specific use case. It’s always recommended to thoroughly review and adapt the steps according to your application’s unique requirements.
If your application requires Internet Explorer 11 (IE11) support, it means you will need to continue using Vue 2. Vue 3 takes advantage of modern JavaScript features and optimizations that are not compatible with IE11’s older browser environment. Therefore, if IE11 compatibility is a critical requirement for your project, it is necessary to stick with Vue 2 for the time being. It’s important to evaluate your target audience and browser compatibility needs before making the decision to migrate to Vue 3.
Something to keep in mind is that the migration build (vue/compat
) only supports the publically
exposed Vue API. Migrating applications that rely on private APIs would require additional
effort and consideration. It is necessary to identify and update any code that utilizes private
APIs as an additional task before proceeding with the migration to Vue 3. Private APIs are
subject to change or removal in different Vue versions, so it’s essential to review your codebase
for any dependencies on these APIs and modify them accordingly to ensure compatibility with Vue 3.
Due to the intricacies involved in SSR, this article will not delve into the specific steps required to migrate the SSR portion of your application. SSR often involves additional dependencies, configurations, and specific implementation considerations, which may differ from one project to another.
We assume that the application you intend to upgrade is currently running on either Vue 2.6 or Vue 2.7, which already supports the new slot syntax. If your application is on an older version of Vue, it is recommended to first upgrade to Vue 2.7 or a compatible version that includes the new slot syntax updates. Once you have successfully updated the slot syntax, you can then proceed with the other steps outlined in the migration process.
Lastly, it’s important to consider that your application may have dependencies that are incompatible with Vue 3. As part of the migration process, you might need to replace or find Vue 3-compatible versions of those dependencies. It’s worth noting that this article will primarily focus on discussing the need to update two crucial dependencies: Vuex and Vue Router. However, please keep in mind that there may be other dependencies in your project that also require attention and updates to work seamlessly with Vue 3.
Strategy
For the upgrade strategy, we will begin by creating a new migration branch in our version control system.
Throughout the migration, it is important to stay up-to-date with the changes made to the production version of our application. To achieve this, we will frequently rebase our migration branch on top of the main branch. Rebasing (or merging) ensures that we incorporate the latest changes from the main branch into our migration branch, enabling us to work with the most recent version of the application while performing the migration.
As we proceed with the migration, we have the flexibility to decide whether to merge our migration branch into the main branch before completing the entire migration (after step 9) or only after making the complete switch to Vue 3 (after step 10). Both approaches have their merits and depend on the specific needs and circumstances of your project.
If we choose to merge the migration branch into the main branch before completing the entire migration, it allows for an incremental rollout of Vue 3 changes. This approach can help distribute the effort and mitigate the risks associated with a big-bang migration. However, it is crucial to conduct sufficient quality assurance testing during this process to ensure that the application functions as expected in the production environment. Testing should encompass both the existing features and any new features introduced during the migration.
Alternatively, we can decide to merge the migration branch into the main branch only once we have completed the entire migration to Vue 3. This approach ensures a cleaner separation between the Vue 2 and Vue 3 codebases, providing more clarity during the transition. Again, rigorous testing and quality assurance should be performed after the merge to verify the stability and functionality of the application.
In either case, adequate quality assurance testing is essential to ensure that the application works as expected in the production environment. This includes functional testing, regression testing, and potentially performance testing to identify and address any issues or regressions that may arise during the migration process.
At this stage, it is worth taking a look at the upgrade workflow from the Vue 3 migration guide .
Step 0: Update slot syntax
Before proceeding with the Vue 2 to Vue 3 migration, it’s essential to update the slot syntax if we are still using the deprecated named / scoped slot syntax in our application. The latest syntax, which is already supported in Vue 2.6 and later versions, should be adopted.
To demonstrate the difference, consider the following code excerpts that showcase the old slot syntax compared to the new syntax:
Old syntax (deprecated):
<template slot="greeting" slot-scope="slotProps">
{{ slotProps.text }} ({{ slotProps.count }})
</template>
New syntax:
<template #greeting="{ text, count}">
{{ text }} ({{ count }})
</template>
Sidenote: This is a preparation step, and in the spirit of indexes I decided to start my count at 0. This also means I can end with step number 10 (which is vital 🙂).
Step 1: Update Vue and add Vue-Compat
The first step in the migration process is to update Vue to the desired version and add the Vue-Compat package. As of now, the latest versions for both Vue and Vue-Compat are 3.3.4. It’s recommended to use the same version for both packages to ensure compatibility.
Additionally, you can remove the vue-template-compiler
package since, starting from Vue 3.2.13,
vue/compiler-sfc
is bundled within the main vue
package. This means there is no longer
a need to install vue/compiler-sfc
separately. (See @vue/compiler-sfc )
Here’s a diff to illustrate the necessary changes:
"dependencies": {
- "vue": "^2.7.14",
+ "vue": "^3.3.4",
+ "@vue/compat": "^3.3.4"
...
},
"devDependencies": {
- "vue-template-compiler": "^2.7.14"
}
It’s important to note that at this stage, your application may break and your tests may fail. This is expected since there are additional steps to be taken before the app will run smoothly again. We will address these issues in the upcoming steps.
Step 2: Alias vue
to @vue/compat
After installing @vue/compat
as the migration build, the next step is to update your Webpack
, Rollup, Vue-cli, or Vite configuration. The goal is to configure your build tool to alias
vue
to @vue/compat
, so that whenever vue
is referenced, it loads @vue/compat
instead.
Make sure to review the documentation specific to your build tool for detailed instructions
on how to configure the aliasing of vue
to @vue/compat
. Here are some specific notes for
applications that use Webpack, but similar steps would be taken for other build tools as well.
Since we are making use of Webpack we need to upgrade vue-loader
to version ^16.0.0
.
To update vue-loader with yarn you could run:
yarn upgrade vue-loader@^16.0.0
If we fail to update vue-loader
it may result in a compile-time error similar to the following:
Module build failed: Error: Cannot find module 'vue-template-compiler'
To update vue-loader with yarn you could run:
yarn upgrade vue-loader@^16.0.0
If we fail to update vue-loader
it may result in a compile-time error similar to the following:
Module build failed: Error: Cannot find module 'vue-template-compiler'
You can confirm that the update was successful by confirming that you see the following in your package.json file:
"vue-loader": "^16.0.0"
Keep in mind that vue-loader
required webpack 4.1 and above to work.
"webpack": "^4.1.0 || ^5.0.0-0"
If you have an outdated version of Webpack, it’s necessary to update Webpack before continuing with the Vue 3 upgrade process. Updating Webpack may sometimes trigger a series of dependencies updates, which shows the importance of keeping dependencies up to date to avoid such situations in the future.
In your Webpack configuration file, you may have a line similar to this:
'vue$': 'vue/dist/vue.esm.js',
To update it for Vue 3 migration, you will modify it as follows:
'vue$': '@vue/compat/dist/vue.esm-bundler.js',
The $
sign is a convention used by Webpack to indicate an exact match when using the vue
alias. This modification ensures that when vue
is used in import paths across different
modules, Webpack correctly resolves it to @vue/compat
. This avoids confusion and enables
the seamless usage of vue
throughout your application.
For more information, you can refer to the Webpack documentation on resolve.alias: https://webpack.js.org/configuration/resolve/#resolvealias
Step 3: Update transition class names
To ensure compatibility with Vue 3, it’s necessary to update the transition class names in
your application. This update involves adding the -from
suffix to the existing CSS class
names related to transitions.
To perform this update, you can do a project-wide search for CSS class names ending with -enter
and -leave
. For each transition class found, modify it by appending the -from
suffix. Typically,
these changes will be made in the CSS files or relevant sections of your application.
Here is an example showing how .fade-enter
is updated to .fade-enter-from
.
- .fade-enter, .fade-leave-active {
+ .fade-enter-from, .fade-leave-active {
By updating the transition class names, you ensure compatibility with Vue 3’s transition system.
The suffix -from
indicates the initial state of the transition.
Note that you will not see any compile- or run-time errors if you skip this step. However this is a vital step in the process to ensure that transitions are not affected by the migration.
For a comprehensive understanding of the six transition class names and their usage, you can refer to the Vue documentation . Additionally, you can explore the vue-hackernews example app for detailed information on how to update the class names.
Step 4: Fix compile-time issues
After completing the previous steps and attempting to start your application locally, you may encounter compile-time errors or warnings. It’s important to address these issues before proceeding further with the migration process.
Based on the Vue-Compat migration guidelines, there are six known incompatibilities between Vue 2 and Vue 3 that can lead to compile-time errors. We are required to fix these incompatibilities upfront to ensure a smooth migration. You can find more details about these incompatibilities in the Vue-Compat documentation .
Issue 1: Mounted application does not replace the element it’s mounted to.
Inspect the rendered DOM, and pay close attention to the ID of the mounted vue app as well as the parent container. If you notice that the ID is identical then you would need to update one of the two IDs as well as Javascript or CSS references to that DOM node.
Before: After:
Remember to thoroughly test your application after making these changes to verify that the Vue app is now mounted as expected and functions correctly.
Issue 2: Production devtools is now a build-time flag
You might see the following error message when you run the application.
Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ are not explicitly defined. You are
running the esm-bundler build of Vue, which expects these compile-time feature flags to be
globally injected via the bundler config in order to get better tree-shaking in the production
bundle.
For more details, see https://link.vuejs.org/feature-flags.
It is recommended that we properly configure the build-time flags in our build tool to ensure that our final build bundles efficiently. (Read more about build time flags. )
If you are using Webpack the change might look like this:
const webpack = require('webpack');
module.exports = {
plugins: [
// Define Bundler Build Feature Flags
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: true, // enable/disable Options API support
__VUE_PROD_DEVTOOLS__: false // enable/disable devtools support in production
}),
],
};
Issue 3: v-if
and v-for
precedence when used on the same element has changed
In Vue 3, there has been a change in the precedence of v-if
and v-for
directives when
used on the same element. To address this breaking change, a simple solution is to wrap the
v-for
element inside a <template>
element. Then, you can move the v-if
directive to
the new <template>
element.
Here’s how you can adapt your code to accommodate this change: Before:
<div v-if="condition" v-for="item in items">
<!-- Content here -->
</div>
After:
<template v-for="item in items">
<div v-if="condition">
<!-- Content here -->
</div>
</template>
For more detailed information and additional insights into this breaking change, you can refer to the v-if and v-for precedence section in the Vue 3 migration guide.
Issue 4: v-if
branches can no longer have the same key
In Vue 3, it is no longer allowed for v-if
branches to have the same key. This change ensures
better rendering and behavior in the component tree. When encountering this issue, there are
a few options available depending on your specific use case.
- Remove the keys: In some cases, you can completely remove the
key
attribute from thev-if
branches. Vue will automatically assign keys based on its internal algorithm. This approach is suitable when the order and presence of the branches are stable and don’t rely on specific key values. Removing the keys simplifies the code and lets Vue handle the key assignment for you. - Assign unique keys: If the
v-if
branches require explicit keys due to dynamic changes or specific requirements, you need to ensure that each branch has a unique key value. This approach guarantees that Vue can differentiate between the branches and perform accurate updates.
The migration guide provides more detailed information on the
usage changes of the key
attribute
and offers additional insights and examples.
Issue 5: <template v-for>
key should now be placed on <template>
In Vue 3, there has been a change in the placement of the key
attribute when using <template>
with v-for
. Unlike in Vue 2, where the key
attribute was placed on the inner element within
the <template>
, in Vue 3, we can and should add the key
directly to the <template>
element
itself.
Vue 2:
<template v-for="(item, index) in items">
<div :key="index">
<!-- Content here -->
</div>
</template>
Vue 3:
<template v-for="(item, index) in items" :key="index">
<div>
<!-- Content here -->
</div>
</template>
For more detailed information and additional insights into this breaking change, you can refer to the Vue 3 migration guide .
Issue 6: <template functional>
is no longer supported in single-file-components
In Vue 2, functional components provided a way to return multiple root nodes and were known for their performance advantages over stateful components. However, in Vue 3, the Vue team made the decision to remove the syntax for functional components.
To update your codebase, you need to remove the functional
attribute from single-file components
or the { functional: true }
option if you used functional components with render functions
. Simply removing this attribute or option will align your code with the Vue 3 syntax and behavior.
For more detailed information and further insights into this breaking change, I recommend referring to the Vue 3 migration guide . It provides additional context and examples to help you transition smoothly from functional components in Vue 2 to the updated syntax in Vue 3.
Step 6: Fix run-time warnings
After addressing the compile-time errors and successfully running your application, it’s common to encounter a significant number of warnings during the migration process. These warnings may appear in both the command line output and the browser console.
To tackle these warnings effectively, it’s recommended to filter and prioritize them, focusing on one item at a time. This approach allows for a systematic resolution of warnings, starting with those originating from your own source code.
Global API Changes
To address the global API changes in Vue 3, specifically the introduction of createApp
and
the removal of the vm.$mount()
method and the “el” option, you need to update your application
code.
Here’s an example that demonstrates the necessary changes:
Vue 2 code:
import Vue from 'vue'
import VueRouter from 'vue-router'
import store from './store'
import App from './App'
Vue.use(VueRouter)
const router = new VueRouter({
routes // short for routes: routes
})
new Vue({
router,
store
}).$mount('#app')
Vue 3 code:
import Vue, { createApp } from 'vue'
import VueRouter from 'vue-router'
import store from './store'
import App from './App'
const router = new VueRouter({
routes // short for routes: routes
})
const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
In the Vue 3 code, we import the createApp
function from the vue
package and use it to
create the Vue app instance. We then use the mount()
method on the app instance to mount
the app to the DOM element with the ID of “app”.
By making this change, you’ll eliminate the warning message regarding the global app bootstrapping API.
[Vue warn]: (deprecation GLOBAL_MOUNT) The global app bootstrapping API has changed: vm.$mount
() and the "el" option have been removed. Use createApp(RootComponent).mount() instead.
Be sure to update all relevant parts of your application that use the deprecated global instance
API to follow the new createApp
approach.
It’s also worth mentioning that the migration guide provides additional information on optimizations and other global API changes. . It’s recommended to review the migration guide thoroughly to ensure that you address all necessary updates and improvements during the migration process.
In Vue 2 the configuration changes we made would modify the base Vue instance. This is clear
when we consider the use of Vue.use
and Vue.directive
. The effects of these base instance
modifications were more pronounced whenever we wrote tests and we were required to restore
global configuration after each test.
Keep in mind that updating the global API usage may require adjustments in your tests and testing frameworks as well. Be sure to update your testing code accordingly to align with the new Vue 3 API and configuration changes.
Address removed APIs
When addressing the removed APIs in Vue 3, there are several changes that need to be considered. Here’s a summary of the removed APIs and their alternatives:
Key code event modifiers are no longer supported in Vue 3. If your application uses key code event modifiers, you’ll need to convert them to their named modifier equivalents. You can refer to the migration guide for more details on how to handle this change.
$on
, $off
, and $once
instance methods have been removed in Vue 3. If you used them
to implement a global event bus, you’ll need to find an alternative approach. One option is
to use third-party libraries like mitt or
tiny-emitter to implement an event bus.
Here’s a simple example demonstrating how to replace the Vue 2 component event behavior with
the use of tiny-emitter
:
// eventBus.js
import emitter from 'tiny-emitter/instance'
export default {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args)
}
// main.js
import eventBus from './eventBus.js'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// This makes `$bus` available inside any component template in the application,
// and also on `this` of any component instance:
app.config.globalProperties.$bus = eventBus
app.mount('#app')
In the Vue 3 code above, we created an event bus. Then, we use createApp
to create the Vue
app instance and assign the emitter instance to a global property we calld $bus
. This allows
components to access and use the emitter for event communication. Read more about global properties
in the Vue API guides .
export default {
mounted() {
this.$bus.$emit('some-event')
}
}
Filters have been removed in Vue 3 . To achieve similar functionality, you can use computed properties or methods in your components. Using computed props instead of globally defined filters could lead to unnecessary code duplication. To combat code duplication we can use mixins or the composition API .
Defining inline templates, as described in the Vue 2 documentation , has been removed in Vue 3. You’ll need to restructure your code to use separate template files or the render function for component templates.
The $children
instance property, which provided direct access to a component’s child components,
has been removed in Vue 3. If your code relies on this property, you’ll need to refactor
it to use other techniques, such as component references or scoped slots.
The propsData
option, which allowed passing props to a root component during instantiation,
has been removed in Vue 3. Instead, you should use the props
option when creating the root
component using createApp
.
Breaking change to render function API
If your application utilizes the render function API, whether through custom render functions or renderless components, it’s important to be aware of the changes made to the render function API in Vue 3.
Notable changes to the render function API include:
h
is now globally imported, read more about the other render function API changes here .- Accessing slots in render functions has undergone various changes. For more details on the slots unification changes, refer to the Vue 3 migration guide .
- The
$attrs
object now includes properties such asclass
,style
, and$listeners
. This change has implications for projects that heavily rely on$attrs
and$listeners
. Make sure to review the breaking changes related to$attrs
and$listeners
in the migration guide. To summarize remove all usages of$listeners
and review components that useinheritAttrs: false
.
To see a practical example of updating a render function, you can refer to the Vue hacker news example app. The provided commit in the Vue hacker news repository demonstrates how to update a render function effectively.
Be sure to thoroughly review the Vue 3 migration guide for comprehensive information on the render function API changes, enabling a smooth transition and addressing any specific breaking changes in your application.
Many other changes
We understand that there may be numerous warnings generated during the migration process. Rather than listing every possible warning, we will outline a general approach for removing these warnings:
- Start by identifying the specific warning message you encounter. The warning message can often provide hints about the necessary changes.
- Refer to the feature reference list provided in the Vue Compatibility Build documentation . Each item on the list describes the required changes or provides a link to the relevant section of the Vue 3 migration guide .
- Follow the suggestions and instructions provided in the feature reference list and the migration guide to address the specific warnings. Apply the recommended changes to your codebase accordingly.
- It’s worth noting that some warnings might be generated by outdated dependencies, such as Vuex or Vue Router. To resolve these warnings, make sure to update your Vuex and Vue Router dependencies to their compatible versions for Vue 3.
By following these steps and carefully addressing the warnings one by one, you can effectively mitigate the issues and ensure a successful migration to Vue 3.
Step 7: Upgrade Vuex
When migrating to Vue 3, it’s important to upgrade Vuex from version 3 to version 4, as Vuex 3 is not compatible with Vue 3. Fortunately, the process of upgrading from Vuex 3 to Vuex 4 is relatively straightforward. Most of the APIs remain unchanged, but there are a few breaking changes that need to be addressed before proceeding.
One significant change is that we no longer pass Vuex to Vue during the installation step. Instead
, we now pass our store directly to our app using the app.use()
method:
// Example main.js
import { store } from './store'
import App from './App.vue'
const app = createApp(App)
app.use(store)
Vuex 4 also provides improved TypeScript support, which you can learn more about in the TypeScript Support section of the Vuex documentation.
To understand the full Vuex migration process and explore the new features introduced in Vuex 4, refer to the official Vuex migration guide: Migrating to 4.0 from 3.x .
For a real-world example of upgrading to Vuex 4, you can examine the vue-hackernews
example
app and review the commit that demonstrates the migration:
Vue HackerNews Example - Vuex 4 Upgrade .
Step 8: Upgrade Vue-Router
As part of the Vue 3 migration, it is necessary to upgrade to Vue-Router v4. This new version of Vue-Router introduces several breaking changes that we need to be aware of in order to successfully complete the Vue 3 migration.
One significant change is the introduction of the createRouter
function, which replaces
the previous usage of new Router()
. The Vue-Router migration guide
provides detailed information on how to import and use the createRouter
function in Vue 3.
Vue-Router 4 introduces a number of changes that may impact your application, including the removal and replacement of certain features as well as TypeScript changes. It is important to thoroughly review the Vue-Router migration guide to ensure a smooth migration process.
To see practical examples of the changes involved in upgrading Vue-Router, you can refer to the vue-hackernews example commit provided by the Vue team. This commit demonstrates the specific modifications typically required when upgrading Vue-Router.
Step 9: Update Typescript configuration
With the migration to Vue 3, it is important to review and update the TypeScript configuration in your project to ensure compatibility and take advantage of new features.
Vue 3 introduces the defineComponent
wrapper function, which enhances component creation
with improved type inference support. The Vue 3 TypeScript guide
provides comprehensive information on utilizing TypeScript in Vue applications, including
the usage of defineComponent
.
To ensure a smooth migration, it is recommended to review your TypeScript configuration and update it to align with the Vue 3 requirements and best practices. This may involve adjusting compiler options, updating type definitions, or making changes to typings within your project.
Step 10: Switch to Vue 3
Up to now we methodically addressed the conflicting behavior between Vue 2 and Vue 3. We also addressed changes due to compile-time errors, removed APIs and new features added in Vue 3.
Once all warnings are fixed and your codebase is ready, you can remove the migration build and switch to using Vue 3 directly. However, it’s important to note that if your project has dependencies that still rely on Vue 2 behavior, you may not be able to switch to Vue 3 immediately.
To switch from the “migration build” to the official Vue 3 build we would need to update the
vue
alias in our build tool. We can update the vue
alias by adding the following line
to our Webpack configuration:
'vue$': 'vue/dist/vue.esm-bundler.js',
Essentially we are updating the same file we changed in step 1 of this article.
We can switch our application over one component or feature at a time by making use of the
configureCompat
function provided by the vue-compat
.
import { createApp, configureCompat } from 'vue'
configureCompat({ })
Conclusion
Upgrading to Vue 3 is not just about keeping up with the latest version of the framework; it’s about embracing a more efficient and powerful development experience. With Vue 3, you gain access to new features like the composition API, improved performance optimizations, and enhanced TypeScript support.
Upgrading from Vue 2 to Vue 3 can seem like a daunting task with numerous breaking changes and migration steps. However, with careful planning and following the migration guide provided by the Vue team, the process can be manageable and rewarding.
Please get in touch with us if you need support or advice with your next Vue migration.