Intro

At CodersRank we build our front-end with Vue.js. And with the recent release of Vue.js version 3 we decided to upgrade. Here, we’ll cover what needs to be changed to migrate to the all new Vue.js based on our experience.

We have 2 main projects. One project is the large developers profile website and the other one is a smaller admin section. To start, we decided to migrate the smaller admin section to see how hard it is to migrate (spoiler: not hard).

So, let’s start.

Init Vue App

Let’s start at the main app entrypoint script. Vue.js version 3 uses new Global API and requires a different approach to initialize the app:

In Vue 2 we had:

// Import Vue
import Vue from "vue";

// Import main App component
import App from "./App.vue";

// Init Vue app
const app = new Vue({
  // element where to mount the app
  el: "#app",
  // render main component
  render: (h) => h(App),
});

And in Vue 3 it is now:

// Import createApp function
import { createApp } from 'vue';

// Import main App component
import App from './App.vue';

// Init Vue app and pass main app component
const app = createApp(App);

// Mount app
app.mount('#app');

Vuex Store

If you use Vuex state management library, then it also needs to be updated to the latest version for Vue 3. And Vuex v4 also has new global API changes.

Let’s look at how we use that and the init Vuex store in Vue 2:

import Vue from "vue";

// Import Vuex
import Vuex from "vuex";

import App from "./App.vue";

// Tell Vue.js to use Vuex plugin
Vue.use(Vuex);

// Create store instance
const store = new Vuex.Store({
  state: {
    /* ... */
  },
  getters: {
    /* ... */
  },
  mutations: {
    /* ... */
  },
  actions: {
    /* ... */
  },
});

const app = new Vue({
  el: "#app",
  render: (h) => h(App),
  // pass store instance
  store: store,
});

The same but in Vue 3 should be the following:

import { createApp } from 'vue';

// Import createStore function
import { createStore } from 'vuex';

import App from './App.vue';

const app = createApp(App);

// Create store instance
const store = createStore({
  state: { /* ... */ },
  getters: { /* ... */ },
  mutations: { /* ... */ },
  actions: { /* ... */ },
})

// Tell app to use store
app.use(store);

// Mount app
app.mount('#app');

Slots

In our Vue 2 app we were still using legacy slots API:

<some-component>
  <h1 slot="header">Title</h1>
  <p slot="content">Content</p>
</some-component>

It is required to be changed to a new one using <template> tags:

<some-component>
  <template #header>
    <h1>Title</h1>
  </template>
  <template #content>
    <p>Content</p>
  </template>
</some-component>

v-model

v-model also has new syntax in Vue 3. For example, if in Vue 2 we had the following component:

<custom-input v-model="inputValue" />
<template>
  <input :value="value" @input="onInput" />
</template>
<script>
  export default {
    model: {
      // specify prop that will be modified by v-model
      props: 'value',
      // specify event that should be received by v-model with the new value
      event: 'input',
    },
    props: {
      value: String,
    },
    methods: {
      onInput(e) {
        this.$emit('input', e.target.value),
      },
    },
  }
</script>

In Vue 3, by default, it expects that v-model should be bound to modelValue prop of the component and emit update:modelValue event in order to update model value. So we have to change the component to the following:

<script>
  export default {
    props: {
      // change "value" prop to "modelValue"
      modelValue: String,
    },
    methods: {
      onInput(e) {
        // emit "update:modelValue" prop with new value
        this.$emit('update:modelValue', e.target.value),
      },
    },
  }
</script>

But also Vue 3 provides more control over it and we still keep the prop named value. In this case we need to emit update:value event:

<script>
  export default {
    props: {
      // keep name as value
      value: String,
    },
    methods: {
      onInput(e) {
        // emit "update:value" prop with new value
        this.$emit('update:value', e.target.value),
      },
    },
  }
</script>

And to let Vue know that we need model to be bound to the value prop instead of the default modelValue, we should use v-model like this:

<custom-input v-model:value="inputValue" />

The best thing about it is that now components can have multiple v-models:

<some-component v-model:title="titleValue" v-model:content="contentValue" />

Composition API

Vue 3 comes with a new Composition API.

It is not necessary to change all your components to the new Composition API as Vue 3 still works perfectly with the current Options API. That is why we decided to keep it at the moment.

Custom Elements (Web Components)

At CodersRank we have a nice set of web components for developers to integrate on their personal websites. We also use them on our website:

It was not so straightforward to make Vue 3 understand them properly and not to think these are not Vue components.

In Vue 2 to specify custom elements, we used Vue.config.ignoredElements:

import Vue from "vue";
Vue.config.ignoredElements = ["codersrank-activity", "codersrank-skills-chart"];

In Vue 3 it is decided whether it is a custom element or not during template compilation phase, so it should be specified in webpack config Vue loader options:

{
  test: /\.vue$/,
  use: {
    loader: 'vue-loader',
    options: {
      compilerOptions: {
        // ignore elements that starts with codersrank-
        isCustomElement: (tag) => tag.indexOf('codersrank-') === 0,
      },
    },
  },
},

TypeScript

Vue 3 has much better TypeScript support. And during the migration to Vue 3 all we needed to change was the component declaration in single-file components:

In Vue 2 we used Vue.extend to define the Vue component:

<template>
  <!-- ... -->
</template>
<script>
  import Vue from "vue";

  export default Vue.extend({
    props: {
      // ...
    },
    data() {
      // ...
    },
    // ...
  });
</script>

And in Vue 3 we need to use the new defineComponent function:

<template>
  <!-- ... -->
</template>
<script>
  import { defineComponent } from "vue";

  export default defineComponent({
    props: {
      // ...
    },
    data() {
      // ...
    },
    // ...
  });
</script>

Post Scriptum

In this article we have covered just the basics that we faced ourselves in our own project during migration from Vue.js 2 to Vue.js 3. Of course there are many things to pay attention to if you use other APIs, Vue features and plugins. Worth to mention:

About CodersRank

Interested in learning more about how we help developers? Check out the details here & create your own profile!

Author

CTO & founder of @CodersRank, the very same website you are looking at. Back-end developer, father, sports man, author of an e-book and audiobook, creator of an online interview platform. Avid gamer when time allows. | CodersRank: Our goal is supporting CODERS growth by their always up to date, professional CodersRank profile.

Write A Comment