The biggest upgrade to Vue. Js from 1.x to 2.0 is the introduction of the virtual DOM concept, which provides the basis for server-side rendering and cross-platform Weex.

Vue.js 2.x has developed for a long time, and now the surrounding ecological facilities have been very perfect, and for vue.js users, it almost meets all the needs of our daily development. You might think vue.js 2.x is good enough, but in the eyes of vue.js authors, it’s not perfect enough. In the process of iteration 2.x version, Xiaoright found a lot of pain points that need to be solved, such as the maintenance of the source code itself, the performance problems of rendering and updating brought by the large amount of data, some want to abandon but in order to compatibility has been retained chicken rib API; In addition, He wants to give developers a better programming experience, such as better TypeScript support and better logic reuse practices, so he wants to optimize the framework from three aspects: source code, performance, and syntax apis.

So let’s take a look at the specific optimizations made in Vue.js 3.0

The source code optimization

The first is source optimization, that is, optimization for the development of vue.js framework itself, which aims to make the code easier to develop and maintain.

Source optimization involves managing and developing source code using Monorepo and TypeScript to improve the maintainability of your own code. Let’s take a look at the specific changes in these two areas.

1. Better code management: Monorepo

First of all, the optimization of source code is reflected in the way of code management. The source code for vue.js 2.x is hosted in the SRC directory, Platforms (Platforms only), Server (server-side renderings), SFC (.vue), platforms (Platforms only) and platforms (Platforms only) Single file parsing related code), shared (shared tool code) and other directories:

With vue.js 3.0, the entire source code is maintained using monorepo, which splits different modules into different subdirectories under the Packages directory according to their functionality:

As you can see, monorepo split these modules into different packages, each with its own API, type definition, and tests, in contrast to the source organization of vue.js 2.x. In this way, module separation is more detailed, responsibility division is more clear, the dependency between modules is more clear, developers are easier to read, understand and change all module source code, improve the maintainability of the code.

Other packages (such as the ReActivity reactive library) can be used independently of vue.js, so that users who want to use the reactive capabilities of vue.js 3.0 can rely on the reactive library alone rather than the entire vue.js, reducing the size of the reference package. Vue.js 2. x does not do this.

Typed JavaScript: TypeScript

Second, source optimization is also reflected in the adoption of TypeScript development in Vue.js 3.0 itself. Vue. Js 1. X version of the source code is not type language, small right with JavaScript development of the entire framework, but for the complex framework project development, the use of type language is very conducive to the maintenance of code, because it can help you do type check during the coding, to avoid some errors caused by type problems; It can also be used to define the type of the interface and to derive variable types from the IDE.

So in refactoring 2.0, Xiaoright chose Flow, but in Vue. Js 3.0, xiaoright abandoned Flow and refactored the whole project in TypeScript. There are two reasons, which we’ll talk about in detail.

First of all, Flow is a JavaScript static type checker from Facebook. It can migrate existing JavaScript code at a very low cost and is very flexible, which was one of the considerations when it was selected for Vue. However, Flow does not support checking for some complex scenario types very well. Vue. Js2. X source code, in the comments of a line of code to see Flow, such as in the component update props:

const propOptions: any = vm.$options.props // wtf flow?
Copy the code

What does that mean? $options.props = vm.$options.props = vm.$options.props In addition, he also made fun of the Flow team on the community platform.

Second, vue.js 3.0 abandoned Flow and refactored the entire project in TypeScript. TypeScript provides better type checking and supports complex type derivation; Since the source code is written in TypeScript, there is no need to maintain separate D.ts files. In terms of the TypeScript ecosystem as a whole, the TypeScript team is getting better and better. TypeScript itself continues to iterate and update frequently and supports more features.

In addition, Righty and the TypeScript team have been in good communication, and we can expect TypeScript support for vue.js to get better and better.

Performance optimization

Performance optimization has always been a front end cliche. So for vue.js 2.x, which front-end framework is good enough, what can be the breakthrough in its performance optimization?

1. Source volume optimization

The first is optimizing the size of the source code. We often try to optimize the size of static resources in our daily work, because the smaller the size of JavaScript packages, the shorter the network transfer time, the faster the JavaScript engine can parse packages.

So what does vue.js 3.0 do to reduce the size of the source code?

  • First, remove some unpopular features (filter, inline-template, etc.).

  • Second, introduce tree-shaking technology to reduce packaging volume.

Tree-shaking depends on the static structure of the ES2015 module syntax (import and export). It finds modules that are not brought in and flags them through static analysis at compile time.

For example, a Math module defines two methods square(x) and Cube (x) :

export function square(x) {
  return x * x
}
export function cube(x) {
  return x * x * x
}
Copy the code

We have only introduced the cube method outside of this module:

import { cube } from './math.js'
// do something with cube
Copy the code

Eventually the Math module will be packaged by Webpack to generate the following code:

/* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { 'use strict'; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__['a'] = cube; function square(x) { return x * x; } function cube(x) { return x * x * x; }});Copy the code

As you can see, the unintroduced Square module is marked, and the compression phase actually removes the unused code using compression tools such as Uglip-js and Terser.

That is, with tree-shaking, if you don’t include Transition, KeepAlive, etc. in your project, the corresponding code is not packaged, thus indirectly reducing the size of vue.js packages that your project introduces.

2. Data hijacking optimization

The second is data hijacking optimization. One of the things that differentiates Vue.js from React is that its data is responsive. This feature has been around since vue.js 1.x, which is one of the reasons vue.js fans like vue.js. DOM is a mapping of data, and DOM updates automatically when data changes. The user only needs to focus on the modification of the data, there is no other mental burden.

Inside vue.js, there is a price to pay for this functionality, which is that you have to hijack data access and updates. When the data changes, in order to update the DOM automatically, the update of the data must be hijacked. In other words, when the data changes, some code can be automatically executed to update the DOM. Then the question arises. Because we access the data while rendering the DOM, we can access hijack it so that we have an internal dependency and know what the DOM of the data is. This is a more complex implementation and relies on a watcher data structure for dependency management.

Both vue.js 1.x and vue.js 2.x use the Object.defineProperty API to hijack getters and setters for data like this:

Object.defineProperty(data, 'a',{
  get(){
    // track
  },
  set(){
    // trigger
  }
})
Copy the code

However, this API has some drawbacks. It must know in advance what key is being intercepted, so it cannot detect the addition and removal of object attributes. Although vue.js provides set and set and set and DELETE instance methods to solve this problem, it still adds a certain mental burden to the user.

There is another problem with object.defineProperty, for example, for nested objects:

export default {
  data: {
    a: {
      b: {
        c: {
          d: 1
        }
      }
    }
  }
}
Copy the code

Since Vue. Js can’t tell which properties you’re accessing at run time, if you want to hijack deep Object changes, you need to recursively traverse the Object, executing Object.defineProperty to make each layer of Object data responsive. There is no doubt that there is a considerable performance burden if we define reactive data that is too complex.

To solve the above two problems, vue.js 3.0 uses the Proxy API to do data hijacking, which looks like this internally:

observed = new Proxy(data, {
  get() {
    // track
  },
  set() {
    // trigger
  }
})
Copy the code

Since it hijacks the entire object, the addition and deletion of attributes of the object are naturally detected.

However, it is important to note that the Proxy API does not listen for internal deep-level object changes, so vue.js 3.0 does this by recursing responses in getters. The advantage of this is that internal objects that are actually accessed become responsive, rather than mineless recursion, which also greatly improves performance. I’ll explain how it works in detail in the analysis of responsiveness section below.

3. Compilation optimization

Finally, compilation optimization. To make it easier to understand, let’s take a look at a diagram:

This is how vue.js 2.x renders DOM from new Vue. The reactive process described above takes place in the init phase. In addition, the process of template compile to Render function can be completed offline by vue-loader during webpack compilation, not necessarily at runtime.

Therefore, in order to optimize the whole vue.js runtime, except for the optimization of the data hijacking part, we can find a way in the patch phase which is relatively time-consuming. Vue.js 3.0 also does this, and it realizes the optimization of the patch process in the runtime by optimizing the compilation result in the compilation phase.

We know that through data hijacking and dependency collection, the granularity of vue.js 2.x’s data updates and triggers rerendering is component-level:

While Vue minimizes the number of components that trigger updates, we still need to traverse the entire VNode tree within a single component. For example, if we want to update this component:

<template>
  <div id="content">
    <p class="text">static text</p>
    <p class="text">static text</p>
    <p class="text">{{message}}</p>
    <p class="text">static text</p>
    <p class="text">static text</p>
  </div>
</template>
Copy the code

The whole diff process is shown in the figure:

As you can see, because this code is only one dynamic node, so there are many diff and traversal is actually don’t need, this will lead to the performance of the vnode with template size is relevant, has nothing to do with the number of dynamic node, when some components of the template in only a small amount of dynamic node, these traversal is the performance of the waste.

For the example above, ideally all you need is the P tag of the binding message dynamic node, diff.

Vue.js 3.0 does this by compiling the Block Tree through static template analysis at compile time. Block Tree is a nested Block that cuts the template based on dynamic node instructions. The node structure inside each Block is fixed, and each Block only needs an Array to track the dynamic nodes it contains. With Block Tree, vue.js improves the vNode update performance from being related to the overall size of the template to being related to the amount of dynamic content. This is a very big performance breakthrough, and I will analyze how it is achieved in detail in the following chapters.

In addition, vue.js 3.0 includes Slot compilation optimizations at compile time, cache optimizations for event-listening functions, and a run-time rewrite of the diff algorithm, which I’ll share with you in a specific section later.

Syntax API optimization: Composition API

In addition to the source code and performance aspects, vue.js 3.0 has improved the syntax, mainly providing a Composition API, so let’s take a look at what it can do for us.

1. Optimize logical organization

The first is to optimize logical organization.

In Vue. Js 1.x and 2.x, writing a component is essentially writing an “object that contains Options that describe the component.” We call it the Options API, which is intuitive and easy for beginners to understand. This is one of the reasons why so many people like vue.js.

The Options API is designed to be classified according to methods, computed, data, and props. When components are small, this classification is easy to see. However, in a large component, a component may have multiple logical concerns. When using the Options API, each concern has its own Options. If you need to modify a logical point concern, you need to constantly switch up and down in a single file.

An official example is the Vue CLI UI File Explorer, a complex file browser component in the VUE-CLI GUI application. This component needs to handle many different logical concerns:

  • Tracks the current folder state and displays its contents
  • Handles folder navigation (such as open, close, refresh, and so on)
  • Handles the creation of new folders
  • Toggle display favorites
  • Toggle to show hidden folders
  • Handles changes to the current working directory

If we color-code the logical concerns, we can see that when writing components using the Options API, the logical concerns are quite scattered:

Vue.js 3.0 provides a new API, the Composition API, which has a great mechanism for solving the problem of putting all the code related to a logical concern into a function so that when a function needs to be modified, there is no need to jump through the file.

Here’s a quick look at the Composition API for logical organization:

2. Optimize logic reuse

Second, optimize logic reuse.

As our development projects become more complex, we inevitably need to abstract out some reusable logic. In vue.js 2.x, we usually use mixins to reuse logic. For example, we write mousePositionMixin:

const mousePositionMixin = {
  data() {
    return {
      x: 0,
      y: 0
    }
  },
  mounted() {
    window.addEventListener('mousemove', this.update)
  },
  destroyed() {
    window.removeEventListener('mousemove', this.update)
  },
  methods: {
    update(e) {
      this.x = e.pageX
      this.y = e.pageY
    }
  }
}
export default mousePositionMixin
Copy the code

Then use it in the component:

<template>
  <div>
    Mouse position: x {{ x }} / y {{ y }}
  </div>
</template>
<script>
import mousePositionMixin from './mouse'
export default {
  mixins: [mousePositionMixin]
}
</script>
Copy the code

Using a single mixin may not seem like a problem, but when we mix a lot of different mixins into one component, there are two very obvious problems: naming conflicts and unclear data sources.

First, each mixin can define its own props and data, which are insensitive to each other, so it is easy to define the same variables, leading to naming conflicts. In addition, for components, if the template uses variables that are not defined in the current component, it is not easy to know where these variables are defined, which is why the data source is not clear. But vue.js 3.0’s Composition API does a good job of addressing both of these mixins problems.

Let’s look at how to write this example in vue.js 3.0:

import { ref, onMounted, onUnmounted } from 'vue' export default function useMousePosition() { const x = ref(0) const y = ref(0) const update = e  => { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y } }Copy the code

Here we declare useMousePosition as a hook function and use it in the component:

<template>
  <div>
    Mouse position: x {{ x }} / y {{ y }}
  </div>
</template>
<script>
  import useMousePosition from './mouse'
  export default {
    setup() {
      const { x, y } = useMousePosition()
      return { x, y }
    }
  }
</script>
Copy the code

As you can see, the entire data source is clear, and you can write more hook functions without naming conflicts.

In addition to the advantages of logic reuse, Composition apis also have better type support because they are all functions that naturally derive all types when called, unlike the Options API which uses this for everything. Additionally, the Composition API is tree-shaking friendly and the code is easier to compress.

While the Composition API has many advantages, it is not without its drawbacks, and we will explain its usage and design principles in more detail in a subsequent chapter. It should also be noted that the Composition API is an ENHANCEMENT of the API. It is not the paradigm for vue.js 3.0 component development. If your components are simple enough, you can still use the Options API.

Introduce RFC: Make each version change manageable

As the author of a popular open source framework, Righty probably receives feature requests every day. However, the framework does not support new features as soon as the community requests them, because as vue.js becomes more and more popular, The framework will pay more attention to stability, carefully consider every change it makes that might affect end users, and consciously prevent new apis from increasing the complexity of the framework’s own implementation.

So, late in the development of vue.js 2.x, Righty enabled RFC, which stands For Request For Comments, to provide a consistent and controlled path For new features into the framework. When the community has some idea for a new requirement, it can submit an RFC, which is then discussed by the community and the vue.js core team, and if the RFC is finally approved, it will be implemented. For example, the changes to the slot API in version 2.6 are in this RFC.

With Vue.js 3.0, Righty enabled RFC on a large scale before implementing the code to ensure that his changes and designs were discussed and validated, so as to avoid detdetments. Vue. Js 3.0 has many major changes, and each change has its corresponding RFC. By reading these RFC, you can understand the cause and effect of each feature’s adoption or abandonment.

The RFCS that vue.js 3.0 has implemented and merged so far are here, and by reading them you can also get an overview of some of the changes that vue.js 3.0 has made, and why they have come about, to help you understand the context.

The transition period

Major versions of frameworks typically have a long transition from upgrade to mass adoption. However, the transition period from vue.js 1.x to vue.js 2.0 was not long, mainly because there were not many users of vue.js at that time, and the ecosystem was not perfect. Many users were directly using the 2.0 version, without the historical burden of the old project.

The development of vue.js 2.x has gone through more than 3 years, with a large number of users, and the surrounding ecology has been very perfect. There is usually a lot of breaking change in the Major version, which means that a project that wants to upgrade from 2.x to 3.0 will need to change the code, not only the code of the project will need to be changed, but the surrounding ecology that it depends on will also need to be updated. This is actually quite a lot of work and involves some risk, so if your project is large and relatively stable with no particular pain points, be cautious about upgrading.

Vue.js 3.0 is developed using ES2015 syntax. Some apis, such as Proxy, are not polyfilled, which means that a separate version of IE11 compat is required to support IE11. If your project needs to be ie11-compatible, you have to be careful with certain apis, which brings some extra mental baggage.

As a result, large and complex projects may not be considered for upgrading for a long time until vue.js 3.0 comes out, while smaller, new projects with less browser compatibility requirements may be considered for upgrading.

Vue. Js 2.x will continue to be maintained for 18 months. If you have a project that never plans to upgrade Vue.

However, while vue.js 3.0 is still a long way from being used on a large scale, the sooner you start learning, the better you’ll be in control for the future. During this time, you can pay attention to its development, learn its design ideas, and contribute code to its ecological construction, thus improving your technical ability. In addition, you can try to apply VUe.js 3.0 in some small projects. Not only can you enjoy the performance advantages of VUe.js 3.0 and the convenience of Composition API in logic reuse, but also provide technical reserves for the full upgrade of Vue.js 3.0 in the future.

conclusion

This article mainly explains several aspects of Vue. Js 3.0 upgrade optimization, and why these optimizations are needed. I hope that after learning, we can also look at our own work like the author of VUE and see what pain points there are. We can find the direction for improvement and efforts and implement it. Only in this way can we continuously improve our ability and achieve good output in work.