Today I upgraded from Vue 2 to Vue 3 and it was pretty painless, so I thought I’d share the process.
The use case
I run a completely free planning poker application at planfree.dev and it uses Vue 2.6.11. I’ve been meaning to learn Vue 3 for a while now as I love the simplicity of the Composition API — this seemed like the perfect excuse to jump in with both feet.
The application has a few dependencies I need to consider:
Socket.io
Socket.io is used extensively for socket communication between the client and the server, this allows me to display votes as they’re coming in to all clients with ease.
Vuex
Vuex is a state management library for Vue.js, the default for Vue 3 is Pinia, but I want to try avoid changing this if possible.
Vue Class Component
Vue Class Component allows you to write Vue applications using class-style syntax. This is the only way I’ve ever really written Vue code as my object-oriented brain can parse it much faster. Here’s an example of the Vue Class Component syntax:
<script>
import Vue from 'vue'
import Component from 'vue-class-component'
@Component
export default class Counter extends Vue {
private count = 0
public increment() {
this.count++
}
public decrement() {
this.count--
}
}
</script>
Vue Property Decorator
Dependent upon your application using the Vue Class Component, Vue Property Decorator allows for quickly adding properties, watches, emits, etc by using property decorators, here’s an example of how I’d use it:
@Component
export default class YourComponent extends Vue {
@Prop() age!: number
@Watch('child')
public onChildChanged(val: string, oldVal: string) {}
}
The plan
To tackle this I made use of a couple resources, first and foremost the Vue 3 Migration Guide. This guide takes you through migrating an example application and even shows you the commits they made. I also created a new application using the vue-cli
which was as similar to my application as possible, i.e included Typescript, Lint, Router, etc. This allowed me to quickly compare how things should be done now in Vue 3.
The Migration
First step was to update the Vue version from v2.6.11 to ^3.2.36, and also adding the @vue/compat dependency:
@vue/compat
(aka “the migration build”) is a build of Vue 3 that provides configurable Vue 2 compatible behavior.
"vue": "^3.2.36",
"@vue/compat": "^3.2.36",
And change vue-template-compiler
to @vue/compiler-sfc
"devDependencies": {
"@vue/compiler-sfc": "^3.2.36"
}
The default export for typescript is no longer available in vue 3, so I had to update my shims with the following code:
declare module '*.vue' {
import { CompatVue } from '@vue/runtime-dom'
const Vue: CompatVue
export default Vue
export * from '@vue/runtime-dom'
const { configureCompat } = Vue
export { configureCompat }
}
Next, it was a case of reviewing the warnings and updating the code to use the composition API, here’s an example of a before and after:
Before:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class Modal extends Vue {
@Prop() private title!: string;
public name = '';
public mounted() {
const input = document.getElementById('selectNameInput');
if (input) {
input.focus();
}
}
public completed() {
this.$emit('completed', this.name);
}
}
</script>
After:
<script setup lang="ts">
import {ref, onMounted} from 'vue';
const props = defineProps<{
title: string
}>()
const emit = defineEmits(['completed'])
const name = ref('');
onMounted(() => {
const input = document.getElementById('selectNameInput');
if (input) {
input.focus();
}
})
function completed() {
emit('completed', name.value);
}
</script>
With the new setup, I no longer require the two dependencies vue-property-decorate
and vue-class-component
, I removed them.
Vue-router
Vue-router was a relatively simple fix, I upgraded from "vue-router": "^3.2.0"
to "vue-router": "^4.0.14"
. And then made the following changes:
Before:
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
import Home from '../views/Home.vue'
import Game from '../views/Game.vue'
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: Home,
meta: {
auth: false,
title: 'Planning Poker'
}
},
{
path: '/game/:id',
name: 'Game',
component: Game,
meta: {
auth: false,
title: 'Planning Poker'
}
}
]
const router = new VueRouter({
routes
})
export default router
After:
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Game from '../views/Game.vue'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'Home',
component: Home,
meta: {
auth: false,
title: 'Planning Poker'
}
},
{
path: '/game/:id',
name: 'Game',
component: Game,
meta: {
auth: false,
title: 'Planning Poker'
}
}
]
});
export default router
Which then led me on to a warning in the main.ts — the way you mount the application is a little different:
Before:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')
After:
import App from './App.vue'
import router from './router'
import store from './store'
import { createApp } from 'vue'
const app = createApp(App)
app.use(router)
app.use(store);
app.mount('#app');
Vuex gave me no troubles at all, I just updated to the latest version (4.0.2 at the time of writing this) and it worked without having to make any code changes, brilliant.
Conclusion
Moving from Vue 2 to Vue 3 was a pretty painless experience. My application at planfree.dev is relatively simple, with most of the logic — rightly or wrongly — bunged into one component. I imagine this would be far more laborious if the application was of a significant size.
There’s still some work I can do with the migration like removing some of the added dependencies or introducing more Vue 3 features like fragments — which I particularly like as I never quite understood why multi-root components weren’t supported. But, getting it to the point where I can add new components using the Composition API was a doddle.
Let me know how your migration goes!
If you liked this blog then please sign up for my newsletter and join an awesome community!