Demystifying Vue.js internals

Dhruv Patel
js@imaginea
Published in
6 min readApr 24, 2018

When it comes to JavaScript frameworks, Vue.js is a trending UI framework.(just crossed 90k Github ⭐️ and more than 13k 🍴, very much closer to React). The most obvious reason for me to get attracted to Vue.js is — its learning curve - which is very less. Personally, I feel like I’m working in a jQuery sort of thing. After few days of digging, you can start building your app.

I started exploring Vue.js an year back and built a few apps. But few days back, a craving to look deep into Vue.js’ code started inside me. I have gone through the source on github and did rounds of debugging to understand whats going on under the hood. And this is what I’ll be writing about in the current post.

So, let’s start with the actual stuff, the post will try and give you the answers of 4 questions here —

  1. What happens when you create a Vue.js instance?
  2. What happens to a template internally?
  3. What is the significance of Virtual DOM?
  4. How does the template re-rendering happens when a property changes?

The component will have a template which must pass from various stages before appearing in the browser. Let’s write a small template and drive the post using it as an example.

<div id="app">
<span v-if="dynamic">Dynamic text</span>
<span><p>Static text</p></span>
<button @click="toggleFlag">Toggle Dynamic</button>
</div>

I am not writing JS logic of the component as template is self explanatory.

The compilation phase

The Vue compiler reads a component’s template, takes it through stages like parsing, optimizing, codegen and ultimately creates a render function as shown below. This function is responsible for creating a VNode which is used by Virtual DOM patch process to create the actual DOM.

Parsing stage

This stage of compilation plays with the markup sent as template for a particular component. As you can see in the image, first of all the parser parses the template into HTML parser and which in turn converts it to AST. (i.e. Abstract syntax tree).

AST — After Parsing stage

The AST contains the information like attributes, parent, children, tags, etc. The parsing process will parse directives similar to elements. The structural directives like v-for, v-if, v-once will be represented as key-value pairs in the AST for a particular element. The v-if directive in our template, after parsing, will be pushed into the attrsMap as object like {v-if: “dynamic”}.

Optimization stage

The goal of the optimizer is to walk through the generated AST and detect the sub-trees that are purely static, i.e. parts of the DOM that never need a change. As shown in the image, these elements will be marked as static.

AST — After optimization

Once it detects the static sub-trees, Vue will hoist them into constants, so that Vue will not create fresh nodes for them on each re-render. These nodes will be skipped completely during the patching process of the virtual DOM.

CodeGen stage

The last stage of compiler is the codegen stage, a stage where the actual render function will be created and will be used in patch process.

Hierarchy of render functions

In above image, you can see that the hierarchy of templates got converted into the hierarchy of render functions. Based on the static flag provided by the optimizer, the codegen will bifurcate the render function into 2 separate functions. One is a simple render function and other is static render function.

At the end render functions will be used to create VNode while triggering actual render process.

Note: The template compilation will take place ahead-of-time if you are using a build step. e.g. single file components.

The observer and watcher — Reactive component

Observer

Under the hood Vue will walk through all the properties that we define into the data and converts them to getter/setters using Object.defineProperty.

When any data property gets a new value then the set function will notify the Watchers.

Watcher

A Watcher is created for each component when a Vue application is initialized. It parses an expression, collects subscribers and fires callback when the expression value changes. This is used for both — $watch api and directives. Every component instance has a corresponding watcher instance, which records any properties as “touched” during the rendering of the component as dependencies. Later on, when a dependency’s setter is triggered, it notifies the watcher, which will ultimately trigger the patch process.

Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. All the watchers are added into the queue. Each watcher has unique Id in incremental order, so if the same watcher is triggered multiple times, it will be pushed into the queue only once and before consuming it, the queue will be sorted because the watcher should run from parent to child.

Internally Vue tries a native Promise.then and MessageChannel for the asynchronous queuing with setTimeout(fn, 0).

nextTick function will consume and flush all Watchers within the queue. Once all watchers have been consumed and flushed, the render process will be initiate from the Watcher’s run() function.

The Patch process

The patch process is basically a process which efficiently interacts with actual DOM using the Virtual DOM. A Virtual DOM is just a JavaScript object which represents a Document Object Model(DOM). Vue.js is internally using snabbdom library. So, let’s look what exactly what’s going into this patch process —

The process is all about the game of old VNode (Virtual DOM Node) and new VNode. Ultimately both will be compared with each other.

The algorithm will work in following way —

  1. It will first check the whether Old VNode is present or not and if its not then create the DOM element for each VNode. When you land first time in app and the first render process initiates in that case the old VNode won’t be there.
  2. The other case, if the old VNode is present then the process of comparing childrens of both will start — The common node will remain as it is in DOM and the new node will be added and older unmatched node will be removed from Virtual DOM as well as from the actual DOM.
  3. Moreover, the styles, class, dataset and event-listener for matched node will be updated (the new thing will be added or things which are not require will be removed) if needed.

The same process will recursively take place for all the nodes.

Moreover, I would like to remind you something — The static node, which we discussed in optimizer stage. The tree of static node will be untouched and used as it is. The meaning of this is — we don’t need to interact with the actual DOM for this sort of tree.

Life cycle hooks

Let’s discuss the life span for particular component —

The lifecycle of component can be segregated into for four sections —

  • The Creation
  • The Mounting
  • The Updating
  • The Destruction.

I’ll try to fit them in above discussed topics.

As soon as the new instance of Vue is executed, the process of creating component starts.

beforeCreation: Before the process of collecting the events, the data required for the component. In other words — process of collecting watchers/dependencies.

created: When Vue is done with setup data and watchers.

beforeMount: Before the patch process. The VNode are getting created based on data and watchers.

mount: After patch process.

beforeUpdate: The watcher updates the VNode and re-initiated the patch process again if the data changes.

update: Patch process is done.

beforeDestroy: Before destroying the component. Here, component is still fully present and functional.

destroyed: Teardown watchers and remove event listners or child component that were attached to it have been removed.

Originally posted at blog.imaginea.com

--

--