How to migrate your library from Vue2 to Vue3
Tags: vue, library, tutorial, webdev
Published: 2020-09-01 15:33:21


Vue 3 is just around the corner. The hype is real so you might be tempted to start migrating all your existing projects to the new version. Before doing that I will save you some pain with this simple question:

Does your app heavily depends on third party libraries like (BootstrapVue, Vuetify, etc)?

If the answer is yes, you might want to stop the process for a moment.

Why?

Most of Vue plugins and third-party libraries will not work on Vue3 (yet) due to the breaking changes on the **Global API** [see reference](https://v3.vuejs.org/guide/migration/global-api.html).

Vue contributor [@posva](https://twitter.com/posva) stated in this [github thread](https://github.com/vuejs/vue-next/issues/1267):
![@posva github comment](https://res.cloudinary.com/alvarosaburido/image/upload/v1598945483/blog/How%20to%20migrate%20your%20library%20from%20Vue2%20to%20Vue3/posva_github_comment_wubsbz.png)

In this article, I will show you how to migrate a Vue 2.x library to Vue 3.x plugin, so if you are the owner of a library or just a user that wants to contribute to your favorite carousel plugin to migrate to Vue3 this tutorial is for you.

## The new global API

One of the major breaking changes introduced on Vue 3.x is how the app is created:

In `2.x` global APIs and configurations globally mutate Vue's behavior

```javascript
// main.js
import Vue from 'vue';
import App from './App.vue';

new Vue({
  render: h => h(App),
}).$mount('#app');
```

For instance, you want to add `vue-toasted` library into your project, you would use `Vue.use` and pass the library object with the options:

```javascript
// main.js
import Vue from 'vue';
import VueToasted from 'vue-toasted';

Vue.use(VueToasted, { ...options });
```

Under the hood, what `VueToasted` does, extends the Vue instance and declare some components (Vue.component) and global objects into it (Vue.prototype):

```javascript
// vue-toasted/src/index.js

const Toasted = {
  install(Vue, options) {
    if (!options) {
      options = {};
    }

    const Toast = new T(options);
    Vue.component('toasted', ToastComponent);
    Vue.toasted = Vue.prototype.$toasted = Toast;
  },
};
```

In 3.x the app instance is created trough `createApp`:

```javascript
// main.js

import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);
```

An app instance exposes a subset of the current global APIs. The rule of thumb is any APIs that globally mutate Vue's behavior are now moved to the app instance like this

```javascript
const app = createApp(App);

app.component('button-counter', {
  data: () => ({
    count: 0,
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>',
});

app.directive('blur', {
  mounted: el => el.blur(),
});
```

So you might be tempted to do:

```javascript
const app = createApp(App);

app.use(VueToasted, { ...options });
```

```
Uncaught TypeError: Cannot set property '\$toasted' of undefined
```

Why? Because in the `vue-toasted` library the property is added to 'Vue': `Vue.toasted = Vue.prototype.$toasted = Toast;`

## The Solution

Actually, is pretty simple, let's remove the old `plugin/index.js` and create a plugin object:

```javascript
const VueToastedPlugin = {
  install(app, options) {
    if (!options) {
      options = {};
    }
    const Toast = new T(options);
    app.component('toasted', ToastComponent);
    app.config.globalProperties.$toasted = Toast;
  },
};
export default VueToastedPlugin;
```

You may notice two subtle changes:

1. app instance is passed as a parameter of the install method, so now instead of doing `Vue.component` we do `app.component`
2. To add a global property, `Vue.prototype` becomes `app.config.globalProperties`

Now, you will be able to use `app.use(VueToasted, {...options});`. In the specific case of `vue-toasted` library you will normally create a new toasted message accesing the `$toasted` on `this`:

```javascript
methods: {
   showToast() {
      this.$toasted.show('How you doing?');
   }
}

```

## With Composition API

So we manage to take a random Vue library without Vue 3.x support into the new standard. The previous code will work perfectly with the options API but what about using it along of one of the most interesting and new features of Vue3, the composition API?

Yes, `this` is not accesible in the `setup()` method, a lot of libraries today inject properties onto `this`. Let's take another example, [Vue Router](https://github.com/vuejs/vue-router/blob/dev/src/install.js) injects `this.$route` and `this.$router`, and [Vuex](https://github.com/vuejs/vuex/tree/v3.5.1) injects `this.$store`.

When using the Composition API, since there is no `this`. Plugins will leverage `provide` and `inject` internally and expose a composition function. Let's continue using `vue-toasted` as an example:

```javascript
// useApi.js
import { inject } from 'vue';

export const VueToastedSymbol = Symbol();

export function useToasted() {
  const VueToasted = inject(VueToastedSymbol);
  if (!VueToasted) throw new Error('No VueToasted provided!!!');
  return VueToasted;
}
```

then we provide into the app instance --> `app.provide(VueToastedSymbol, Toast);`

```javascript
import { Toasted as T } from './js/toast';
import ToastComponent from './toast.vue';
import { VueToastedSymbol } from './useApi';
export * from './useApi';

const VueToastedPlugin = {
  install(app, options) {
    if (!options) {
      options = {};
    }
    const Toast = new T(options);
    app.component('toasted', ToastComponent);
    app.config.globalProperties.$toasted = Toast;
    app.provide(VueToastedSymbol, Toast);
  },
};

export default VueToastedPlugin;
```

So now, in any setup method or composition function we can do:

```javascript

import { useToasted }  from 'vue-toasted`;

const Component = {
  setup() {
    const toasted = useToasted();

    toasted.success('Composition API BABYYY!', {
      position: 'bottom-right',
      duration: 5000,
    });
  },
};
```

## Conclusion

You might be thinking, why the plugin authors are not doing this already 🤔? Most of the core libraries like `Vue-router` and `Vuex` already have a `/next` branch and beta releases with the support for vue3 and even with Typescript as default but the rest of third party libraries are open source, and believe, is hard to keep your library updated by your own(we have limited hours per day) without contribution for other developers.

So, did you find out the awesome library you were working with for your toast message isn't working for vue3? Make a PR like I did [here](https://github.com/shakee93/vue-toasted/pull/180) to the library with the stuff you learn today. Not only it will be highly appreciated by the authors of the plugin but also will give you a higher level of knowledge in Vue. You will contribute to the community 😊.

{% github https://github.com/shakee93/vue-toasted/pull/180 %}

That's all folks, keep it rockin'.

![That's all folks](https://media.giphy.com/media/upg0i1m4DLe5q/giphy.gif)