Create Your Own Vue.js From Scratch - Part 3 (Building The VDOM)
Tags: vue, javascript
Published: 2020-03-29 10:29:44

Create Your Own Vue.js From Scratch - Part 3 (Building The VDOM)

_If you like this article, chances are you'd like what I tweet as well. If you are curious, have a look at my [Twitter profile](https://twitter.com/_marcba). 🚀_

This is this third part of a series called _Create Your Own Vue.js From Scratch_, where I teach you how to create the fundamentals of a reactive framework such as Vue.js. To follow this blog post, I suggest you read about the [first](https://marc.dev/blog/vue-from-scratch-part-1) and [second](https://marc.dev/blog/vue-from-scratch-part-2) part of this series.

This post might be long at first, but probably not as technical as it looks like. It describe every step of the code, that's why it looks pretty complicated. But bear with me, all of this will make perfect sense at the end 😊

# Roadmap 🚘

1. [Introduction](https://marc.dev/blog/vue-from-scratch-part-1)
2. [Virtual DOM basics](https://marc.dev/blog/vue-from-scratch-part-2)
3. Implementing the virtual DOM & rendering (this post)
4. [Building reactivity](https://dev.to/_marcba/create-your-own-vue-js-from-scratch-part-3-building-the-reactivity-5162)
5. [Bringing it all together](https://dev.to/_marcba/create-your-own-vue-js-from-scratch-part-5-bringing-it-all-together-4eb3)

# Building the Virtual DOM

## The skeleton

In the [second part of this series](https://marc.dev/blog/vue-from-scratch-part-2), we learned about the basics of how the virtual DOM works. You copy the VDOM skeleton from the last point from [this gist](https://gist.github.com/themarcba/2a471e1f0592201fc5036cb40bdbee1f). We use that code to follow along. You'll also find there the finished version of the VDOM engine. I also created a [Codepen](https://codepen.io/_marcba/pen/qBdLeqj), where you can play around with it.

## Creating a virtual node

So, to create a virtual node, we need the **tag**, **properties**, and **children**. So, our function looks something like this:

```javascript
function h(tag, props, children){ ... }
```

**(In Vue, the function for creating virtual nodes is named `h`, so that's how we're going to call it here.)**

In this function, we need a JavaScript object of the following structure.

```javascript
{
    tag: 'div',
    props: {
        class: 'container'
    },
    children: ...
}
```

To achieve this, we need to wrap the tag, properties, and child nodes parameters in an object and return it:

```javascript
function h(tag, props, children) {
    return {
        tag,
        props,
        children,
    }
}
```

That's it already for the virtual node creation.

## Mount a virtual node to the DOM

What I mean with _mount_ the virtual node to the DOM is, appending it to any given container. This node can be the original container (in our example, the `#app`-div) or another virtual node where it will be mounted on (for example, mountaing a `<span>` inside a `<div>`).

This will be a recursive function, because we will have to walk through all of the nodes' children and _mount_ the to the respective containers.

Our `mount` function will look like this:

```javascript
function mount(vnode, container) { ... }
```

### 1) We need to create a DOM element

```javascript
const el = (vnode.el = document.createElement(vnode.tag))
```

### 2) We need to set the properties (`props`) as attributes to the DOM element:

We do this by iterating over them, like such:

```javacript
for (const key in vnode.props) {
    el.setAttribute(key, vnode.props[key])
}
```

### 3) We need to mount the children inside the element

Remember, there are two types of children:

-   A simple text
-   An array of virtual nodes

We handle both:

```javascript
// Children is a string/text
if (typeof vnode.children === 'string') {
    el.textContent = vnode.children
}

// Chilren are virtual nodes
else {
    vnode.children.forEach(child => {
        mount(child, el) // Recursively mount the children
    })
}
```

As you can see in the second part of this code, the children are being mounted with the same `mount` function. This continues recursively until there are only "text nodes" left. Then the recursion stops.

### As the last part of this mounting function, we need to add the created DOM element to the respective container:

```javascript
container.appendChild(el)
```

## Unmount a virtual node from the DOM

In the `unmount` function, we remove a given virtual node from its parent in the real DOM. The function only takes the virtual node as a parameter.

```javascript
function unmount(vnode) {
    vnode.el.parentNode.removeChild(vnode.el)
}
```

## Patch a virtual node

This means taking two virtual nodes, compare them, and figure out what's the difference between them.

This is by far the most extensive function we'll write for the virtual DOM, but bear with me.

### 1) Assign the DOM element we will work with

```javascript
const el = (n2.el = n1.el)
```

### 2) Check if the nodes are of different tags

If the nodes are of different tags, we can assume that the content is entirely different, and we'd just replace the node entirely. We do this by mounting the new node and unmounting the old one.

```javascript
if (n1.tag !== n2.tag) {
    // Replace node
    mount(n2, el.parentNode)
    unmount(n1)
} else {
    // Nodes have different tags
}
```

If the nodes are of the same tags; however, it can mean two different things:

-   The new node has string children
-   The new node has an array of children

### 3) Case where a node has string children

In this case, we just go ahead and replace the `textContent` of the element with the "children" (which in reality is just a string).

```javascript
...
    // Nodes have different tags
    if (typeof n2.children === 'string') {
        el.textContent = n2.children
    }
...
```

### 4) If the node has an array of children

In this case, we have to check the differences between the children. There are three scenarios:

-   The length of the children is the same
-   The old node has more children than the new node. In this case, we need to remove the "exceed" children from the DOM
-   The new node has more children than the old node. In this case, we need to add additional children to the DOM.

So first, we need to determine the _common length_ of children, or in other terms, the minimal of the children count each of the nodes have:

```javascript
const c1 = n1.children
const c2 = n2.children
const commonLength = Math.min(c1.length, c2.length)
```

### 5) Patch common children

For each of the cases from point `4)`, we need to `patch` the children that the nodes have in common:

```javascript
for (let i = 0; i < commonLength; i++) {
    patch(c1[i], c2[i])
}
```

In the case where the lengths are equal, this is already it. There is nothing left to do.

### 6) Remove unneeded children from the DOM

If the new node has fewer children than the old node, these need to be removed from the DOM. We already wrote the `unmount` function for this, so now we need to iterate through the extra children and unmount them:

```javascript
if (c1.length > c2.length) {
    c1.slice(c2.length).forEach(child => {
        unmount(child)
    })
}
```

### 7) Add additional children to the DOM

If the new node has more children than the old node, we need to add those to the DOM. We also already wrote the `mount` function for that. We now need to iterate through the additional children and mount them:

```javascript
else if (c2.length > c1.length) {
    c2.slice(c1.length).forEach(child => {
        mount(child, el)
    })
}
```

_That's it. We found every difference between the nodes and corrected the DOM accordingly. What this solution does not implement though, is the patching of properties. It would make the blog post even longer and would miss the point._

## Rendering a virtual tree in the real DOM

Our virtual DOM engine is ready now. To demonstrate it, we can create some nodes and render them. Let's assume we want the following HTML structure:

```html
<div class="container">
    <h1>Hello World 🌍</h1>
    <p>Thanks for reading the marc.dev blog 😊</p>
</div>
```

### 1) Create the virtual node with `h`

```javascript
const node1 = h('div', { class: 'container' }, [
    h('div', null, 'X'),
    h('span', null, 'hello'),
    h('span', null, 'world'),
])
```

### 2) Mount the node to the DOM

We want to mount the newly created DOM. Where? To the `#app`-div at the very top of the file:

```javascript
mount(node1, document.getElementById('app'))
```

The result should look something like this:

![VDOM Demo](//images.ctfassets.net/r1crnaxclhxs/7JWFiBn6qnHcUeYgjlFYzG/eb0b07c5a5af563a3d90406a976feeb3/Screenshot_2020-03-29_at_11.02.34.png)

### 3) Create a second virtual node

Now, we can create a second node with some changes in it. Let's add a few nodes so that the result will be this:

```html
<div class="container">
    <h1>Hello Dev 💻</h1>
    <p><span>Thanks for reading the </span><a href="https://marc.dev">marc.dev</a><span> blog</span></p>
    <img src="https://media.giphy.com/media/26gsjCZpPolPr3sBy/giphy.gif" style="width: 350px; border-radius: 0.5rem;" />
</div>
```

This is the code for creating that node:

```javascript
const node2 = h('div', { class: 'container' }, [
    h('h1', null, 'Hello Dev 💻'),
    h('p', null, [
        h('span', null, 'Thanks for reading the '),
        h('a', { href: 'https://marc.dev' }, 'marc.dev'),
        h('span', null, ' blog'),
    ]),
    h(
        'img',
        {
            src: 'https://media.giphy.com/media/26gsjCZpPolPr3sBy/giphy.gif',
            style: 'width: 350px; border-radius: 0.5rem;',
        },
        [],
    ),
])
```

As you can see, we added some nodes, and also changed a node.

### 4) Render the second node

We want to replace the first node with the second one, so we don't use `mount`. What we want to do is to find out the difference between the two, make changes, and then render it. So we `patch` it:

```javascript
setTimeout(() => {
    patch(node1, node2)
}, 3000)
```

_I added a timeout here, so you can see the code DOM changing. If not, you would only see the new VDOM rendered._

# Summary

That's it! We have a very basic version of a DOM engine which lets us:
- Create virtual nodes
- Mount virtual nodes to the DOM
- Remove virtual nodes from the DOM
- Find differences between two virtual nodes and update the DOM accordingly

You can find the code we did in this post, on a [Github Gist I prepared for you](https://gist.github.com/themarcba/2a471e1f0592201fc5036cb40bdbee1f). If you just want to play around with it, I also created a [Codepen](https://codepen.io/_marcba/pen/qBdLeqj), so you can do that.

If you have any more questions about this, feel free to reach out to me via [Twitter](https://www.twitter.com/_marcba).

*Original cover photo by [Joshua Earle](https://unsplash.com/@joshuaearle?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unplash, edited by [Marc Backes](https://marc.dev/).*