Dissecting Vue 3: Template Compilation

Angel Sola
The Glovo Tech Blog
5 min readMar 3, 2020

In our last post we dissected the mounting this simple application:

<div id="app">
<h1>Hello, {{ name }}</h1>
</div>
<script>
const { createApp } = Vue
createApp({
data: () => ({ name: 'Rick' })
}).mount('#app')
</script>

Inspecting the source code we discovered that mounting is a two steps process:

mount app = create vNode + render

We took a look at the first step, the generation of the application’s top component as a vNode. Today, we’ll analyze the rendering, concretely, the part where a render function is derived from a component’s template.

Template Compilation

To render a component, Vue compiles its template into a render function, which is a function used to produce the component’s final HTML. This compilation is a three-step process. Eliding some details, the compile function looks like follows:

function compile(template) {
const ast = parse(template)
transform(ast, transformations)
return generate(ast)
}

Vue first parses the template into an AST in a process known as the template parsing:

parse(template) = AST

This AST is a tree representation of the template that Vue uses to perform optimizations like for instance, figuring out what branches are static and therefore won’t change. These static branches can be safely ignored by Vue in the change detection process.

Every node in the AST has a type property, which instructs Vue about the nature of it. You can look up in the source code all available node types. Depending on the type, nodes have a different set of properties. Here’s a diagram of the parsed AST for our simple application’s component, with only the basic data in each node:

The AST then undergoes some transformations, like those applied by directives. Lastly, the transformed AST is used to generate the code for a render function:

generate(AST) = Render Function’s Code

This three-step process is better understood with a diagram:

Render Function

When executed, a render function produces the complete virtual node tree representation of a component. The resulting vNode tree is later used to patch the DOM every time a change happens in the component.

render function → Component’s vNode Tree

Let’s do a quick debugging exercise with our simple app to see what the render function looks like for the “hello” component.

Transforming the Template into a Render Function

Let’s open our app in Chrome and set a debug breakpoint inside the finishComponentSetup function, where the component’s template gets compiled:

In this function, if the component has a template assigned and therender attribute isn’t present, a render function is generated by the compile function and assigned to the component. This compile references the compileToFunction function defined inside the vue package.

After this template compilation step, the component’s render function is set, as we can see in Chrome’s debugger:

Let’s check what this function looks like. Chrome gives us the link to the compiled function, which we can follow by clicking on the value to the right of [[FunctionLocation]] :VM349:formatted:4 in the figure above.

Here’s the function’s code to render our simple hello component:

Inside the top-level function, a function named render is defined and returned. This inner function extends the resolution scope chain by prepending it the this context by means of the with block (line 5).

Let’s take a quick look at an example of using the “with” block, as it’s not something we, JS developers, use very often. The with block includes in the scope whatever is passed to it:

const person = { name: 'Morty' }with (person) {
// Here, we gain access to person
console.log(`Hi, ${name}`)
}

Notice how we didn’t need to specify person.name , but only name instead. That’s because the resolution scope within the block has been extended to include the person object.

Thethis inside the with block is bound to the component the function renders. To be more precise, it’s bound not directly to the component, but to a Proxy object targeting the component. By extending the resolution scope with the component’s proxy, we gain access to its props/data/methods, etc… which explains why the render function has access to the name defined in the component (line 15).

So our “<h1>Hello, {{ name }}</h1>” template got transformed into a render function whose execution generates a virtual DOM which is used to figure out what changes need to be patched into the real DOM.

Let’s represent this template compilation step visually:

Template compilation

You can find this compileToFunction function inside the vue package, in its index.ts file.

If you want to explore how the render function’s code for a given template looks like, you have this awesome template explorer app online:

The code for the template explorer is also part of Vue’s repo. You can find it inside the template-explorer package. Note that, the online version of the app shows the code that Vue2 compiles, which is slightly different from that in Vue 3.

Quick Recap

In this second post about dissecting the Vue 3 source code, we took a look at the template compilation process. This process, comprised of three stages, is in charge of transforming a component’s template into a render function.

A render function for a component generates its virtual DOM, which is later used by Vue to figure out what changes need to be applied to the HTML document.

--

--

Angel Sola
The Glovo Tech Blog

I'm a Mechanical Engineer who turned into a Software professional. I'm mostly interested in software that solves engineering tasks. Author of InkStructure.