Faster Web Applications with Vue 3

Written by Filip Rakowski

We tend to hear more and more about the upcoming 3rd major release of Vue.js. While not everything is certain yet by following discussions, we can safely assume that it’s going to be a huge improvement over the current version (that is already outstanding). Vue team is doing an amazing job of improving framework APIs. Evan You described goals for Vue 3 as:

  • Make it faster
  • Make it smaller
  • Make it more maintainable
  • Make it easier to target native
  • Make your life easier

And I am sure by looking at RFCs and talks that all of the above goals will be achieved without a problem. In this article, I would like to guide you through some of the changes that seem most interesting to me in terms of their impact and possibilities.

Performance optimization

Before digging into certain APIs, as a performance freak I would like to talk about the performance of Vue 3. And there is a lot to talk about! We can find significant improvements in almost every surface!

Let’s start with the bundle size of Vue 3.

Currently minified and compressed Vue runtime weights around 20kB (22.8kB for current 2.6.10 version). Vue 3 bundle is estimated to weigh around half of it, so only ~10kB!

Global API tree-shaking

Above many other optimizations like better modularization Vue 3 source code will be tree-shakeable. It means that if you don’t use some of its features (like <keep-alive> component or v-show directive) they won’t be included in your production bundle. Currently, no matter what features we use from Vue core they are ending up in our production code because Vue instance is exported as a single object and bundlers can’t detect which properties of this object were used in the code.

 // Vue 2.x - whole `Vue` object is bundled for production 
import Vue from 'vue'

Vue.nextTick(() => {})
const obj =  Vue.observable({})

To make global API tree-shakeable Vue team decided to import the majority of them through named exports so bundlers can detect and remove unused code:

 // Vue 3.x - only imported properties are bundled
import { nextTick, observable } from 'vue'

nextTick(() => {})
const obj = observable({})

This is a breaking change as previously global APIs will now be available only through named exports. This change affects:

  • Vue.nextTick
  • Vue.observable
  • Vue.version
  • Vue.compile (only in full builds)
  • Vue.set (only in 2.x compatibility builds, you’ll find out soon why)
  • Vue.delete (same as above)

It will take some time until we can fully benefit from this feature because It needs to be adopted in the ecosystem. Vue team will release compatibility builds so we should be able to use plugins that use old APIs as well but with a cost of performance impact.

There is more than JavaScript APIs to be tree-shakeable. Under the hood Vue compiler (the tool that is transforming Vue template to render function) will detect directives used in templates and tree-shake them as well. For example below template:

<transition>
  <div v-show="ok">hello</div>
</transition>

after being processed by Vue compiler will look more or less like this:

import { h, Transition, applyDirectives, vShow } from 'vue'

export function render() {
  return h(Transition, [
    applyDirectives(h('div', 'hello'), this, [vShow, this.ok])
  ])
}

Everyone will benefit from global API tree-shaking (especially our users), but I think people that make small, lightweight websites and use just a subset of Vue functionalities for interactivity as a replacement for libraries like jQuery will value it the most.

Proxy-based reactivity

Bundle size can significantly impact your app loading time, but after being downloaded, it also should render fast and perform smooth.

Vue core team is very aware of that, and this is why we have great improvements in runtime performance as well.

Let’s start with one of the most impactful - new reactivity system based on JavaScript Proxies. The current Vue reactivity system is based on Object.defineProperty, which has some limitations. The most common and frustrating one is the fact that Vue is not able to track property additions/deletions of reactive objects. We needed to use Vue.set and Vue.delete for this purposes to keep the reactivity system working as intended. With JS Proxies we can finally get rid of this ugly workaround.

// Adding a new property to reacitve object in Vue 2.x
Vue.set(this.myObject, key, value) 
// Adding a new property to reactive object in Vue 3
this.myObject[key] = value

The real impact of Proxies can be seen in much faster component initialization and patching. According to tests it’s around 2x faster!

Vue School Prices Will Increase Piggy Bank illustration

The reason for this improvement is especially significant due to the fact, that with getters/setters Vue had to recursively go through all the objects and their properties and transform them. With proxies this process is much easier.

It’s important to mention that by using JS Proxies Vue 3 will drop support for Internet Explorer (not Edge) but don’t worry - there will be a compatibility build for those who want to support IE.

Time slicing

Update According to tweet from Evan You this feature is not gonna be included in Vue 3.

Another really exciting but rarely mentioned performance feature of Vue 3 is experimental support for Time Slicing.

I will use a metaphor to explain what Time Slicing is. I want you to imagine a line for ice creams. A very long one, because those are the best ice creams in town. After one person is served, there is another, after another, etc. And for some reason, there is no information about the available flavors. To get this information, you need to ask the lady that sells the ice cream directly.

With such a situation, we will probably end up with 2 lines - one for people convinced that they want to buy ice cream (waiting in patience) and one for people who wants more information about the flavors before they decide if they want ice cream or not. The latest should get this information as soon as possible. Unfortunately, there is only one lady selling ice creams, and she will not answer any question before serving all the clients in the “main” line.

This is not the best experience for not yet convinced clients and most of them will probably find it not worth waiting. To solve this problem, the lady could answer one question after every 2-3 served clients. Both groups should be happy with this solution.

This is exactly how CPU works with web apps. We have a “main” line (which is called “main thread”) that needs to fulfill all of its main tasks (scripting, rendering etc) before it can respond to user interactions. For some pages, this can result in a very bad user experience depending on how much it takes for your Vue components to load or re-render.

To make it more reliable it’s much better to “cut” this script evaluation into pieces and see if there is user input to be processed after each of them. This way app will remain responsive no matter how many renders or re-renders need to happen. That's exactly how it’ll work in Vue 3.

This is how Evan You presented the Time Slicing feature in Vue 3. Noticed the small gaps in the script execution timeline, these are meant to handle user input.

Photo from Vue Mastery

Ability to easily identify why component re-rendered

Tools are equally important as out of the box performance. According to this, we can see a new lifecycle hook in Vue 3 - renderTriggered. We can use it to track and eliminate unnecessary component re-renders, which is a very powerful weapon in runtime performance optimization when we combine it with Time Slicing.

const Component = {
  // other properties
  renderTriggered (event) {
     console.log(`Re-render of ` + this.$options.name + ` component`, event)
  }
}

What else

There is much more than what you’ve seen above in Vue 3, but those are probably the most impactful changes. The majority of unmentioned improvements will lie hidden in the code generated by the Vue compiler or is tied to implementation details and algorithms

There are few improvements worth mentioning though:

  • Output code will be easier to optimize for JavaScript compiler
  • Output code will be generally much better optimized
  • Unnecessary parent / children re-renders will be avoided due to improved patching algorithms

Also, in upcoming days, you can expect an in-depth article from Evan You about performance optimizations they made specifically for Vue compiler (I’ll update the article with a link once it’s published).

Summary

Even though Vue is already established as one of the best-performing frameworks out there, we will see major improvements in the 3rd release. Especially in its bundle size and runtime performance. There are also countless micro-optimizations made. I think Vue 3 fits perfectly in the modern mobile-first and performance-oriented web.

Don’t forget that Vue is the only major framework fully driven by the community. All changes listed in this article (and many more) are discussed in the form of RFCs here together with the community. You can help the core team and express your opinion about active RFCs or even propose your own improvements. Let’s make Vue better together 😉

What’s next

In the next article, we will explore how new Vue 3 APIs will affect the way we’re writing web apps. We will take a look at various APIs, including recently popular Composition API, and see how we can use it to write better and more maintainable code.

Vue School Prices Will Increase Piggy Bank illustration

Leave a Reply

Your email address will not be published. Required fields are marked *

Up Next:

Lazy Loading Individual Vue Components and Prefetching

Lazy Loading Individual Vue Components and Prefetching