木木剑光

mmjg

Full Stack Developer | Ant design Contributor

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

What is Vapor mode#

The direct translation of "Vapor mode" in Chinese is "蒸汽模式".

This is a description taken from the Vapor repository on GitHub.

image

To develop Vapor mode, the Vue team forked a new repository called "core-vapor" from the main branch of Vue 3. The goal is to achieve Vue's "No Virtual DOM" rendering mode.

For those who are familiar with the development process of Vue, you may ask, "Isn't Vue 1.0 a version without Virtual DOM? Why did it go back after so many years?" To answer this question, we need to review the "Architectural Evolution" 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 was the reactive implementation based on "Data Proxy + 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," which can help you quickly establish an understanding of Vue 1.0.

image

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

  • Use "defineProperty" to implement data proxying, aiming to intercept all "get" and "set" operations on data. This process is also known as "getter/setter" transformation.

  • The main purpose of the "getter" of the property is to collect dependencies. The term "dependency" 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 as a unified concept called a "Watcher" and are 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 has a fine granularity. Any place that accesses "reactive" data will be collected as a "dependency".

Speaking at a higher level, the "data" and its corresponding "UI" form a binding relationship. This is the fundamental reason why Vue has high efficiency in "view updates". 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 weakness. As the project grows, more and more Watchers and Deps 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 Dependency Collection Granularity and Introducing Virtual DOM#

In order to have better performance on large-scale projects, Vue 2.0 made significant adjustments. From the following "flowchart," we can see that Vue's architectural system 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 crucial 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 Single File Components (SFC). Since the ".vue" file is a "magic" provided by the framework and cannot be directly executed by the browser, a tool is needed to break the "magic" and convert the ".vue" file into ".js". The core of this tool is the "compiler," and the process of breaking the "magic" is called "compilation". In other words, 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 transformed into the "render" function during this stage.

image

Although the 1.0 version also had a "compile" process, it has undergone significant changes.

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

Through a simple comparison, we can see that although the execution stage of compilation is completely different, the parsing of the template 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 was introduced with a completely restructured approach.

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

Under the "Composition API" mode, using hooks can effectively combine business functions and achieve cohesive logic.

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

  • Pre-stringification
  • Static node hoisting
  • Patch flags

Observant students may have already noticed that these optimizations belong to "compile-time" optimizations rather than "runtime" optimizations because there are not many optimization directions at runtime, mainly focusing on the "diff" algorithm during the patch process.

Speaking of 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 was inspired by SolidJS.

As mentioned earlier, based on Vue's architectural system, almost the only way to optimize the "runtime" is to find the changed parts in the "Virtual DOM" faster.

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

  • "Dependencies are determined at runtime." The system needs to parse directives and collect dependencies during the compilation phase, which increases the "first render" time.

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

Now let's shift the 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.