Image

advanced-components

10 min read
Last update: December 19, 2021

Template Compilation

In this lesson we will be learning about template compilation & render function.

Why is it useful to know about this?

  • So you can get better at debugging, by knowing what’s going on under the hood.
  • When your run up against a limitation in the Vue template engine. The template engine will work for you 95% of the time, but sometimes a render function may give you the functionality you need.
  • In order to create functional components, which are cheaper to render than normal components (covered in lesson 5).
  • To send in a Render function as a component property (covered in lesson 6).

Rendering our component’s template happens after the reactivity system initializes (which we learned about in the last lesson), and there are two steps:

Step 1 - Compilation

As you see above, if we have a template it will need to get compiled into a Render function.

Step 2 - Running the Render Function

Whenever we need to render or re-render this component (like when dependencies change) we run the the Render Function.

Each time it returns a Virtual DOM node (Also known as a VNode). This VNode can be used later to create or update parts of the actual DOM.

Using Vue CLI v2.x

If you’ve used the Vue CLI v2.x you’ve probably see this prompt before:

What this is really asking is:

For what gets sent to the browser, do you want to include the code that is able to compile templates?

If I select Runtime + Compiler the Vue.js source file that my application will ship with will be ~32kb mininfied and compressed, and if I select Runtime-only the Vue.js file my application will ship with will be ~22kb mininfied and compressed. The compiler code which knows how to take templates and turn them into render functions is about ~10kb.

So, if I pick Runtime-only I must put all my templates in .vue files, which will get compiled into render functions at build time.

CLI v3.x defaults to Runtime-only, and doesn’t ask if you want to ship the compiler or not. This makes sense as most production Vue.js applications will compile templates on the server-side for the speed improvement.

This leads to our next question:

What does a render function look like, what is a VNode, and what is this Virtual DOM thing?

You might already know this, and feel free to skip this section if you do. The DOM or Document Object Model refers to our browser’s interface (API) to change what gets displayed on the screen. Each HTML document is a series of nodes (like below) that we can manipulate using code which then updates what the user sees.

When we run the following code against the webpage, what happens?

var item = document.getElementsByTagName("h1")[0];
item.textContent = "New Heading";

When we run this JavaScript we use the DOM API to get all the elements with the h1 tag, get the first one [0], and set the textContext value of that node to New Heading . This updates the DOM and what gets shown in our browser changes.

DOM trees can be huge and have thousands of nodes. Luckily we don’t have to run low level commands on the DOM anymore. We have frameworks (like Vue.js) that call this DOM API under the hood.

However, even with frameworks doing the heavy lifting searching and updating our DOM with thousands of nodes can be slow. This is one of the reasons the Virtual DOM was created. This is not unique to Vue.js, many frameworks have the concept of the Virtual DOM.

What is the Virtual Dom and what does it look like?

The Virtual DOM is a way of representing the Actual DOM, but with JavaScript objects. It’s a simpler abstraction of what we want to build onto our webpage. As you can see below, our html can be represented by a JavaScript object, also know as a virtual DOM node (or VNode): When we’re ready to insert this into the actual DOM for the first time, our framework (Vue) knows how to take VNodes (like the one above) and very efficiently build actual DOM elements.

Later if a piece of data changes (like our text) it could trigger our render function to run again, creating another VNode. Vue then knows how to diff the new VNode from the old VNode and make updates to the actual DOM in the most efficient way possible.

The Blueprints to our Building

You can think of the Virtual Dom as the blueprints to our building (the actual DOM).

When we change some data on the 29th floor of our building, like moving the furniture around and adding a new kitchen island, we have two options:

  • Demolish everything on the 29th and start from scratch.
  • Create new blueprints, compare the old and new blueprints, and make updates.

Obviously, I want to do the minimal amount of work, the latter.

Walking through an App

If we have the following code:

<div id="app"></div>

<script src="vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        template: '<div>hello</div>' // <-- There's our template
    })
</script>

The render function that gets generated from the above looks something like this (simplified)

app.$options.render = (createElement) => {
return createElement('div', 'hello')
}

When this render function is called, it returns the following JavaScript object (or VNode)

{
tag: "div"
children: [
{
text: "Hello"
}
]
}

How can we create our own Render Function

One of the ways Vue.js is flexible is by letting us create own render functions (instead of using a template which gets compiled). We might have a component that looks like this:

<div id="app"></div>

<script src="vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        render(createElement) { // Here's the render function
            return createElement('div', 'hello')
        }
    })
</script>

Notice we defined our render function. It called createElement(‘div’, ‘hello’) which returned a VNode that looks like this (when you remove all the extra null properties):

{
tag: "div"
children: [
{
text: "Hello"
}
]
}

Looks familiar right?

The h() method

Another way you might see this render function written is:

render(h) { // Here's the render function
return h('div', 'hello')
}

When you’re working with a virtual DOM, it’s a common convention to alias createElement to “h”, which really just stands for Hyperscript. You may already know that HTML stands for"hyper-text markup language," and in this case “Hyperscript” stands for “script that generates HTML structures.”

When does the VNode get inserted into the DOM?

Well, we first initialize our Component it goes through these steps:

  • Initialize Events & Lifecycle
  • Initialize Injections & Reactivity (AKA State)
  • If template exists compile it into a Render function.
  • Start mounting - This refers to the process of calling the render function which returns the VNode, fetching the EL (in our case div#app), and mounting our VNode to the Actual DOM.
  • Once mounted, we’ll see “hello” in our browser.

Now with the proper lifecycle hooks, this looks like:

Mounting with Reactivity and Rendering

Remember in lesson 1 when we watched this code for reactive properties?

watcher(() => {
data.total = data.price * data.quantity
})

In the Vue lifecycle.js codebase we find:

export function mountComponent (...) {
...
callHook(vm, 'beforeMount')
let updateComponent = () => {
vm._update(vm._render(), hydrating) // Notice our render function is called
}
new Watcher(vm, updateComponent, noop, null, true)
// Here's our watcher which will call render, and while it's calling render it will be listening for reactive properties.

So if we are rendering with reactive data, here’s how it all starts to work together:

If we already have the component loaded onto the page, and we change the reactive data, like on the console, here’s what we see:

This image should make a little more sense now, this time with annotations.

Notice how it’s the render function that is triggered by the Watcher when one of the reactive properties gets changed. Do you see how the Component render function returns the Virtual Dom Node? Yes it says “Virtual DOM Tree”, our VNode could have a tree of nodes as children, so it’s not wrong.

More createElement syntax options

There’s lots of creative ways to leverage the power of the render function, and our trusty h method. Here are a few simpler examples:

render(h) {
return h('ul', [
h('li', 'Gregg'),
h('li', 'Adam'),
h('li', 'Melissa')
])

As you can see, if the second parameter is an array, it’s children nodes.

What do you think this would render?

render(h) {
return h('div',
{
attrs:
{
id: 'people'
},
class: 'sideBar'
},
"Gregg and Chase")
}

It renders:

<div id="people" class="sideBar">Gregg and Chase</div>

The second argument has now become a data object where we can send in attributes that we would use in a template. The third argument just creates text. The Vue docs detail all the options you have in this data object.

Adding JSX

If you’re coming from the React world you might already be familiar with JSX. It’s a preprocessor that adds XML syntax to JavaScript. With the help of this Babel plugin you could use JSX to write a render function like:

render(h) {
return (
<div id="people" class="sideBar">Gregg and Chase</div>
)
}

If you prefer this type of syntax for writing templates, now you know how!

A Template Limitation Example

As I mentioned at the start of this lesson, one use-case for using the render function is when you run into a limitation with the template engine. In the Vue documentation they walk through the following problem:

Problem: I want to have an <h#> tag, with the # being supplied out of the database.

So maybe I want to do this:

```html
<div id="app">
    <custom-header :level="mylevel">Hey there</custom-header>
</div>
<script src="vue.js"></script>
<script>
    Vue.component('custom-header', {
        props: ['level'],
        render: function(h) {
            return h(
                // In here we want to write the code that will result in:
                // <h1>Hey there</h1>
            )
        },
    })
    new Vue({
        el: '#app',
        data: {
            mylevel: 1, // This could be dynamic
        }
    })
    ``
    `

Using what we know so far we can easily use a render function to dynamically output the tag.

`` javascript render: function(h) { return h( 'h' + this.level, // tag name this.$slots.default // array of children ) }, ``

And we get exactly what we’re looking for, now with a dynamic tag name. Definitely check out the documentation to see the full use-case.

Sending in a Component to Render

Sometimes we may want to render a component inside our render function. If we send an object into the first argument of our “h” function it will automatically treat that object as options for a component. Like so:

` `` html < div id = "app" > < /div> < script src = "vue.js" > ' } var app = new Vue({ el: '#app', render(h) { return h(GreetComponent) } }) ```

![](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1578371687668_15.png?alt=media&token=07615c19-d56e-4130-8299-30b6b4e484e1)

We can create some powerful design patterns by having access to this render function, which we’ll see in the next few lessons. It’s also something you’ll encounter when using component libraries with Vue, so it’s good that you know the basics now.