DEV Community

Andrew Welch
Andrew Welch

Posted on • Originally published at nystudio107.com on

1 1

A taste of Vue.js 3: API Changes, Async Components, and Plugins

A taste of Vue.js 3: API Changes, Async Components, and Plugins

This arti­cle takes you through changes that have to be made when mov­ing to Vue.js 3, cov­er­ing API changes, async com­po­nents, and adapt­ing exist­ing plugins

Andrew Welch / nystudio107

Vue js 3

We’re cur­rent­ly in the plan­ning phase for a project, and are choos­ing the tech­nolo­gies that we’ll be using as the basis for it.

Vue.js will be amongst those tech­nolo­gies, but should we go with Vue 2 or Vue 3, which is cur­rent­ly still a beta?

                            It’s at that awk­ward stage where it could go either way
Enter fullscreen mode Exit fullscreen mode

At the time of this writ­ing, Vue.js 3 is at ver­sion 3.0.0-beta 14, and is slat­ed for release Q2 2020. For now, it can be found at the vue­js/vue-next GitHub repo.

What we decid­ed to do was attempt to con­vert over the scaf­fold­ing we use in the nystudio107/​craft repo and detailed in the An Anno­tat­ed web­pack 4 Con­fig for Fron­tend Web Devel­op­ment article.

If every­thing went smooth­ly, then away we go… Vue.js 3 it is. If not, then we stick with Vue.js 2.

We were pri­mar­i­ly inter­est­ed in using the new Com­po­si­tion API, bet­ter Type­Script sup­port, and some oth­er key improve­ments in Vue.js 3.

                            But most­ly, the ver­sion of Vue we picked would be in use for some time
Enter fullscreen mode Exit fullscreen mode

This arti­cle dis­cuss­es the changes we need­ed to make to con­vert the scaf­fold­ing over. It shows some real-world sit­u­a­tions and anno­tates the changes we had to make to get the code up and run­ning on Vue.js 3.

This arti­cle does­n’t detail every change in Vue.js 3, for that check out the Vue 3 Tuto­r­i­al (for Vue 2 users) arti­cle and the New awe­some­ness com­ing in Vue.js 3.0 podcast.

Spoil­er alert: it went well, we’re using Vue.js 3 for this project!

Overview of the Changes

The changes we’re going to be mak­ing here are real­ly rel­a­tive­ly triv­ial. We have a sin­gle JavaScript entry point app.js file, and a Vue­Con­fet­ti component.

This skele­ton code is what I use for my scaf­fold­ing, because it’s nice to see some con­fet­ti to indi­cate that your code is work­ing as intended.

The app.js is just a shell that does­n’t do much of any­thing oth­er than load the Vue­Con­fet­ti com­po­nent, but the project does demon­strate some inter­est­ing things:

  • Changes need­ed to your package.json file
  • Changes need­ed to your web­pack config
  • The changes need­ed to instan­ti­ate a new Vue app
  • How to do web­pack dynam­ic imports of Vue 3 APIs
  • How to use async com­po­nents in Vue 3 using new Async Com­po­nent API
  • How we can adapt a Vue plu­g­in that assumes being able to glob­al­ly inject instance prop­er­ties via Vue.prototype

If you’re using vue-cli, there’s a vue-cli-plu­g­in-vue-next plu­g­in that will auto­mate some of the project con­ver­sion for you, but I want­ed to get my hands dirty.

If you’re inter­est­ed in see­ing all of the major changes in Vue.js 3, check out the Vue.js merged RFCs.

And now, with­out fur­ther ado… let’s get on with the show!

Package.json Changes

The first thing we need to do is con­vert over the package.json pack­ages to ver­sions that work with Vue.js 3.

Here are just the additions/​changes need­ed (not the com­plete package.json):


{
    "devDependencies": {
        "@vue/compiler-sfc": "^3.0.0-beta.2",
        "css-loader": "^3.4.2",
        "file-loader": "^6.0.0",
        "mini-css-extract-plugin": "^0.9.0",
        "vue-loader": "^16.0.0-alpha.3"
    },
    "dependencies": {
        "vue": "^3.0.0-beta.14"
    }
}

Enter fullscreen mode Exit fullscreen mode

web­pack con­fig changes

Next up we need to make some very minor changes to the web­pack con­fig detailed in the An Anno­tat­ed web­pack 4 Con­fig for Fron­tend Web Devel­op­ment article.

We just need to make two changes in the webpack.common.js file, and we’re done.

First, we need to change how we import the Vue­Load­er­Plu­g­in:


const VueLoaderPlugin = require('vue-loader/lib/plugin');

Enter fullscreen mode Exit fullscreen mode

To look like this:


const { VueLoaderPlugin } = require('vue-loader');

Enter fullscreen mode Exit fullscreen mode

Next up we need to change what file we alias vue to, by changing:


        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        },

Enter fullscreen mode Exit fullscreen mode

To look like this:


        alias: {
            'vue$': 'vue/dist/vue.esm-bundler.js'
        },

Enter fullscreen mode Exit fullscreen mode

app.js Changes

House­keep­ing out of the way, now we can get into the actu­al changes in the JavaScript & Vue components.

Here’s what the skele­ton app.js looked like for Vue.js 2:


// Import our CSS
import styles from '../css/app.pcss';

// App main
const main = async () => {
    // Async load the vue module
    const { default: Vue } = await import(/* webpackChunkName: "vue" */ 'vue');
    // Create our vue instance
    return new Vue({
        el: "#page-container",
        components: {
            'confetti': () => import(/* webpackChunkName: "confetti" */ '../vue/Confetti.vue'),
        },
        data: {
        },
    });
};

// Execute async function
main().then( (vm) => {
});

// Accept HMR as per: https://webpack.js.org/api/hot-module-replacement#accept
if (module.hot) {
    module.hot.accept();
}

Enter fullscreen mode Exit fullscreen mode

We have an async func­tion main() that awaits the promise returned by the web­pack dynam­ic import of the Vue con­struc­tor.

This pat­tern allows the main thread to con­tin­ue exe­cut­ing while web­pack han­dles dynam­i­cal­ly load­ing the vue chunk.

While this is some­what point­less in the skele­ton code, this type of dynam­ic import­ing allows for code split­ting that becomes ben­e­fi­cial from a per­for­mance point of view as our appli­ca­tion gets fleshed out.

Then we cre­ate a new View­Mod­el, adding in our async com­po­nent Confetti.vue (we’ll get to the com­po­nent in a bit).

Let’s have a look at the changes we need to make to this code to get it work­ing on Vue.js 3:


// Import our CSS
import styles from '../css/app.pcss';

// App main
const main = async () => {
    // Async load the Vue 3 APIs we need from the Vue ESM
    const { createApp, defineAsyncComponent } = await import(/* webpackChunkName: "vue" */ 'vue');
    // Create our root vue instance
    return createApp({
        components: {
            'confetti': defineAsyncComponent(() => import(/* webpackChunkName: "confetti" */ '../vue/Confetti.vue')),
        },
        data: () => ({
        }),
    }).mount("#page-container");
};

// Execute async function
main().then( (root) => {
});

// Accept HMR as per: https://webpack.js.org/api/hot-module-replacement#accept
if (module.hot) {
    module.hot.accept();
}

Enter fullscreen mode Exit fullscreen mode

The glob­al Vue con­struc­tor is gone in Vue.js 3, and instead we need to explic­it­ly import the func­tions from the Vue.js 3 API that we need.

In this case, we’re going to need createApp() to cre­ate our app instance, and we’ll need defineAsyncComponent() to uti­lize the new Async Com­po­nent API for using async components.

createApp() returns an app instance, which has an app con­text that is avail­able to all com­po­nents in the com­po­nent tree.

Unlike Vue.js 2, this app does­n’t auto­mat­i­cal­ly mount, so we call .mount("#page-container"), which returns the root com­po­nent instance, which mounts on the DOM ele­ment with the id page-container.

To get our async com­po­nent Confetti.vue work­ing, all we need to do is wrap the func­tion we used in Vue.js 2 with defineAsyncComponent().

Also of note is that data can no longer be an object, but rather needs to be a fac­to­ry func­tion that returns a data object. While you’d often do this in Vue.js 2 already, it’s now manda­to­ry in Vue.js 3.

If you want to learn more about some of these glob­al API changes, check out the Glob­al API Change RFC.

Confetti.vue changes

Now onto the all impor­tant Confetti.vue component! 🎉

The exist­ing code for the Confetti.vue com­po­nent looks like this, and is rough­ly a copy & paste of the exam­ple on the vue-con­fet­ti GitHub repo:


<template>
    <main>
    </main>
</template>

<script>
    import Vue from 'vue'
    import VueConfetti from 'vue-confetti'

    Vue.use(VueConfetti);

    export default {
        mounted: function() {
            this.$confetti.start({
                shape: 'heart',
                colors: ['DodgerBlue', 'OliveDrab', 'Gold', 'pink', 'SlateBlue', 'lightblue', 'Violet', 'PaleGreen', 'SteelBlue', 'SandyBrown', 'Chocolate', 'Crimson'],
            });
            setTimeout(() => {
                this.$confetti.stop();
            }, 5000);
        },
        methods: {}
    }
</script>

Enter fullscreen mode Exit fullscreen mode

Unfor­tu­nate­ly, this did­n’t work out of the box on Vue.js 3, giv­ing us the error:

Uncaught TypeError: Cannot read property '$confetti' of undefined

So to fig­ure out what was wrong here, I had a look at the Vue plu­g­in VueConfetti we’re import­ing, which looks like this:


import Confetti from './confetti';

export { Confetti };

export default {
  install(Vue, options) {
    if (this.installed) {
      return;
    }
    this.installed = true;
    Vue.prototype.$confetti = new Confetti(options); // eslint-disable-line no-param-reassign
  },
};

Enter fullscreen mode Exit fullscreen mode

The way plu­g­ins work is that they define an install() func­tion that is called to do what­ev­er they need to do to install them­selves, when Vue.use() is called.

In Vue.js 2, the glob­al Vue con­struc­tor is passed in as the first para­me­ter, but in Vue.js 3 we’d actu­al­ly be call­ing app.use(), and the first para­me­ter then becomes the app con­text, which is not a con­struc­tor, and thus has no .prototype.

Indeed, if we console.log() the first para­me­ter passed in via Vue.js 2, we’ll see the Vue constructor:


ƒ Vue (options) {
  if ( true &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options);
}

Enter fullscreen mode Exit fullscreen mode

But a console.log() the first para­me­ter passed in via Vue.js 3, we’ll see the app context:


{_component: {…}, _props: null, _container: null, _context: {…}, …}
component: ƒ component(name, component)
config: (...)
directive: ƒ directive(name, directive)
mixin: ƒ mixin(mixin)
mount: (containerOrSelector) => {…}
provide: ƒ provide(key, value)
unmount: ƒ unmount()
use: ƒ use(plugin, ...options)
_component: {components: {…}, data: ƒ}
_container: null
_context: {config: {…}, mixins: Array(0), components: {…}, directives: {…}, provides: {…}}
_props: null
get config: ƒ config()
set config: ƒ config(v)
__proto__ : Object

Enter fullscreen mode Exit fullscreen mode

So okay, how can we fix this? The prob­lem is that Vue­Con­fet­ti is try­ing to inject a glob­al­ly shared instance prop­er­ty $confetti via Vue.prototype.$confetti, but don’t have a glob­al con­struc­tor in Vue.js 3, so .prototype isn’t a thing here.

One way would be to change the vue-confetti/index.js code to use the new app instance’s config.globalProperties to accom­plish the same thing, some­thing like:

app.config.globalProperties.$confetti = new Confetti(options);

c.f.: Attach­ing Glob­al­ly Shared Instance Properties

But this would require chang­ing the Vue­Con­fet­ti code via a fork/​pull request. While I’m not against doing this, I real­ized there was an eas­i­er way to accom­plish the same thing:


<template>
</template>

<script>
    import Confetti from 'vue-confetti/src/confetti.js';
    export default {
        data: () => ({
            confetti: new Confetti(),
        }),
        mounted: function() {
            this.confetti.start({
                shape: 'heart',
                colors: ['DodgerBlue', 'OliveDrab', 'Gold', 'pink', 'SlateBlue', 'lightblue', 'Violet', 'PaleGreen', 'SteelBlue', 'SandyBrown', 'Chocolate', 'Crimson'],
            });
            setTimeout(() => {
                this.confetti.stop();
            }, 5000);
        },
        methods: {}
    }
</script>

Enter fullscreen mode Exit fullscreen mode

Here we change the Confetti.vue com­po­nent to direct­ly import 'vue-confetti/src/confetti.js' and assign the new Confetti() object to our local data state object, rather than hav­ing it be glob­al­ly available.

This feels a lit­tle nicer to me in gen­er­al, because there’s prob­a­bly no great rea­son for the $confetti object to be glob­al­ly avail­able, if we’re cre­at­ing a Confetti.vue com­po­nent that can nice­ly encap­su­late it.

Should you use Vue.js 3 now?

We decid­ed to use Vue.js 3 now, but should you?

I think much depends on how heav­i­ly you lean on third par­ty com­po­nents, plu­g­ins, and mixins.

                            The more code you’ll be writ­ing your­self, the safer it is to use Vue.js <span>3</span> now
Enter fullscreen mode Exit fullscreen mode

While all soft­ware always has issues, Vue.js 3 itself seems quite sol­id, and the first-par­ty pack­ages like Vuex and Vue-Router are com­ing along great.

There will like­ly be some lag in third par­ty pack­ages get­ting updat­ed for Vue.js 3, and some may nev­er be.

Thus whether to go with Vue.js 3 now real­ly depends on how much you rely on said third par­ty packages.

For us, the ben­e­fits are worth­while enough for us to begin learn­ing and using Vue.js 3 now.

Wrap­ping up

Hope­ful­ly this small dive into what it looks like updat­ing your code for Vue.js 3 is help­ful to you. While it is rel­a­tive­ly nar­row in scope, it does touch on some top­ics I had­n’t seen cov­ered else­where, at least not wrapped up in a neat package.

Wrapped up in a bow

I’m excit­ed to explore Vue.js 3 fur­ther, and will very like­ly doc­u­ment more of my jour­ney learn­ing the new hot­ness in Vue.js 3.

Hap­py coding!

Further Reading

If you want to be notified about new articles, follow nystudio107 on Twitter.

Copyright ©2020 nystudio107. Designed by nystudio107

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay