木木剑光

mmjg

Full Stack Developer | Ant design Contributor

Prelude to Vapor Mode Research Project: An Analysis of Architectural Evolution History

What is Vapor mode#

Vapor mode is directly translated as 蒸汽模式 in Chinese.

This is a description taken from the Vapor GitHub repository.

image

To develop Vapor mode, the Vue team forked a new repository called "core-vapor" from the main branch of Vue 3, with the goal of implementing a no virtual DOM rendering mode for Vue.

For those who are familiar with the development process of Vue, you may ask, isn't "Vue 1.0" already a version without virtual DOM? Why is it going back after so many years of development? To answer this question, we need to review the "architectural evolution history" of Vue's major versions.

Vue 1.0: Quantum entanglement-like fine-grained binding#

This is a milestone in the early development of Vue. At this time, there was no complete reactive system. The most discussed topic during this period was the reactive implementation based on data interception + dependency collection, as well as the implementation details of the Watcher and Dep siblings.

For those who are not familiar, you can take a look at this "rendering process diagram" to quickly establish your understanding of Vue 1.0.

image

Before the introduction of SFC (Single File Components), Vue can be considered as a solid "runtime" framework. The above diagram briefly shows the series of processes that occur when we do new Vue({...}).

  • Use defineProperty to achieve data interception, with the purpose of proxying data and intercepting all get and set operations on the data. This process is also known as "getter/setter" transformation.

  • The main purpose of the "getter" of the property is to collect dependencies. Here, "dependencies" refers to all the logic that accesses the property, which may be a "computed property", a "watcher", or a "directive" or "interpolation expression" in the template. At the implementation level, these different dependencies are abstracted into a unified concept called Watcher, which is stored in the Dep instance of each property.

  • The "setter" of the property is responsible for notifying the Watcher collected in the Dep to update the view when the data is modified.

In the entire process, Vue's dependency collection "granularity" is very fine. Any place that accesses "reactive" data will be collected as a "dependency".

From a higher level, the "data" is bound to the corresponding "UI", which is the fundamental reason why Vue has high efficiency in "view updating". This binding relationship is like quantum entanglement, where the "UI" can receive notifications and make updates when the "data" changes.

However, everything has two sides. Fine-grained dependency collection is both an advantage and a disadvantage. As the project size grows, more and more Watcher and Dep will be generated at runtime, resulting in excessive memory usage and affecting the performance of the page.

If a solution can only support small-scale projects and have good "performance" performance, it is obviously not the "optimal solution".

Vue 2.0: Adjusting the granularity of dependency collection and introducing virtual DOM#

In order to have better performance on large-scale projects, Vue 2.0 made significant adjustments. From the flowchart below, we can see that Vue's architecture has undergone intuitive changes.

  • Compared to version 1.0, the concept of "components" was introduced.
  • The granularity of dependency collection was adjusted to the "component level", where each component is a Watcher.
  • Virtual DOM was introduced and became a very important part of the rendering process.

image

Overall, "reactive" data no longer focuses on the "dependencies" within the component. After the data is modified, only the component is notified. The component then uses the diff algorithm to find the changed parts in the virtual DOM and updates them to the real DOM. This is essentially a trade-off between time and space, by appropriately reducing the "update efficiency" of the "runtime" to reduce "memory overhead".

During this period, Vue also introduced SFC (.vue files). Since .vue files are provided by the framework as "magic", they cannot be directly executed by the browser. Therefore, a tool is needed to break the "magic" and convert .vue files into .js. The core of this tool is the compiler, and the process of breaking the magic is called compilation, which means that after compilation, the features such as v-if, v-for, and interpolation expressions written in the .vue file will become ordinary javascript logic. The template is also transformed into the render function at this stage.

image

Although there is also a compile process in version 1.0, it has undergone significant changes.

compile1.02.0
StageRuntimeCompilation
PurposeParse directives, interpolations, etc. in the template into corresponding logic and execute themParse the template into an Abstract Syntax Tree (AST) and generate the rendering function

Through a simple comparison, we can see that although the execution stage of compilation is completely different, the "template parsing" is still the main theme.

Actually, at this point, don't you feel that Vue has achieved the ultimate optimization in terms of architecture? Don't rush to conclusions, let's finish reviewing the evolution of Vue 3.0 before making judgments.

Vue 3.0: Composition API#

With the rise of TypeScript in the frontend and the support of proxy, which represents metaprogramming capabilities, by major mainstream browsers, Vue 3.0 arrived with a completely restructured approach.

At the same time, in order to solve the pain point of "option-based API logic being too scattered, making it difficult for developers to write cohesive code elegantly", after being inspired by the React hooks concept, Vue 3.0 also introduced the Composition API. Although there were solutions like mixin during this period, they all had some drawbacks to a certain extent.

In the Composition API mode, hooks can be used to combine business functionalities well and achieve logical cohesion.

In addition to new features, Vue has also made many optimizations in the details, such as:

  • Pre-stringification
  • Static node hoisting
  • Patch flag

Observant students should have noticed that these optimizations belong to compile-time optimizations, rather than runtime optimizations, because there are not many optimization directions at runtime, and they are mainly focused on the diff algorithm in the patch process.

About Vue Vapor#

After reviewing the iterations of Vue, we can return to the topic of Vapor mode. This is also a research conducted by Vue around compile-time optimization, and it is also inspired by SolidJS.

As mentioned earlier, based on Vue's architectural system, runtime optimization can almost only focus on how to find the changed parts in the virtual DOM faster.

If we go back to the form of Vue 1.0 without virtual DOM, at that time, fine-grained binding did not require finding changes and did not require virtual DOM. However, there were pain points:

  • Dependencies are determined at runtime. The system initialization requires parsing directives, etc. during the compilation phase and collecting dependencies, which increases the initial rendering time.

  • Fine-grained dependency collection leads to the generation of a large number of Watcher, resulting in more memory overhead.

Now let's shift our perspective. If we move the determination timing of dependencies from runtime to compile-time, and generate the update logic that needs to be executed when each reactive data changes through compilation, can't we solve the first pain point mentioned above? This is the idea behind Vapor mode.

As for the second pain point, theoretically, as the granularity of dependency collection becomes coarser, it will inevitably increase memory overhead. How will Vapor mode handle this? We have doubts and will wait for its official release before making a judgment.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.