Image

advanced-components

8 min read
Last update: December 19, 2021

The Mounting Process

In previous lessons we looked at the Vue internals for reactivity and rendering, but we didn’t talk about the mounting process. Learning more about how this works will help improve our understanding of the Vue internals and improve our debugging skills.

Remember the mountComponent function?

export function mountComponent (...) {
...
callHook(vm, 'beforeMount') // <-- Lifecycle hook let updateComponent=()=> {
    vm._update(vm._render(), hydrating) // <-- This is our target method } new Watcher(vm, updateComponent, noop, null, true) // <-- Our watcher, which runs our target method, and reruns it when needed ... ``` `_render` calls our component render function which returns a Virtual Node (VNode), and `_update` knows how to take that VNode and make updates to the actual DOM. ![](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1578371323027_0.jpg?alt=media&token=a1ae0cbf-ebc4-4d02-8e54-854d9e3b5a2e) But that doesn’t answer the following questions: * Where is `mountComponent` actually called? * Where does a template get compiled into a render function? * What does `_render` do? * What does `_update` do? In this lesson we will do our best to answer these questions starting with the first, by looking for `mountComponent`. Our search starts back in our `_init` method that should look familiar by now: /src/core/instance/index.js ```javascript export function initMixin (Vue: Class<Component>) {
        Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        ...
        // expose real self
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')

        if (vm.$options.el) {
        vm.$mount(vm.$options.el) // <-- Where the template is compiled (if needed), and mountComponent must be eventually. } } } ``` The definition of `$mount` is determined by the kind of Vue build we’re running, and if we look inside `/scripts/config.js` inside the Vue source we’ll see a bunch of builds, including the following two: /scripts/config.js ```javascript const builds={ // runtime-only build (Browser) "web-runtime-dev" : { entry: resolve("web/entry-runtime.js"), // This mount only includes the code to run render functions. dest: resolve("dist/vue.runtime.js"), ... }, // Runtime+compiler development build (Browser) "web-full-dev" : { entry: resolve("web/entry-runtime-with-compiler.js"), // includes code to compile templates into render functions and run them dest: resolve("dist/vue.js"), ... } }; ``` The definition of `$mount` we’re calling in this walkthrough can be found inside `entry-runtime-with-compiler.js` and is listed below. We want both the runtime and the compiler, because in our example we’ll be both compiling our templates into render functions and rendering them. Remember, if we’re pre-compiling our templates then our user’s browser doesn’t need to know how to compile, and our `$mount` would be different. /src/platforms/web/entry-runtime-with-compiler.js ```javascript const mount=Vue.prototype.$mount // This version of mount is defined inside runtime/index.js Vue.prototype.$mount=function (el?: string | Element, hydrating?: boolean): Component { el=el && query(el) const options=this.$options // Only compile a template if we don't already have a render function if (!options.render) { let template=options.template if (template) { // do compilation in here! const { render, staticRenderFns }=compileToFunctions(template, { ... }, this) options.render=render // Now we have compiled .. see below } } // Call previously defined mount before returning return mount.call(this, el, hydrating) } ``` It’s our `compileToFunctions` call that converts our component template that looks like this: ```javascript template: `<h1>{{ this.name }}</h1>`
            ```

            Into something that looks more like:

            ```javascript
            {
            with(this){return _c('h1',[_v(_s(this.name))])}
            }
            ```

            As you see above, this becomes the value of options.render, or more specifically `this.$options.render`. If our component has a render function, this step just gets skipped.

            You might be wondering what the underscore methods are (\_c, \_v, and \_s). Inside `initRender` we find this for \_c:

            ```javascript
            vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
            ```

            It calls our createElement function with the proper context. For the others if we look inside:

            /src/core/instance/render-helpers.js

            ```javascript
            target._o = markOnce
            target._n = toNumber
            target._s = toString // <------- target._l=renderList target._t=renderSlot target._q=looseEqual target._i=looseIndexOf target._m=renderStatic target._f=resolveFilter target._k=checkKeyCodes target._b=bindObjectProps target._v=createTextVNode // <------ target._e=createEmptyVNode target._u=resolveScopedSlots target._g=bindObjectListeners ``` If we were to extrapolate our render function we’d have: ```javascript { with(this){return createElement('h1',[ createTextVNode ( toString(this.name))])} } ``` Once we compile our render function and get to the bottom of our `$mount` call we find `mount.call(this, el, hydrating)`. This calls our previously defined `$mount`: /src/platforms/web/runtime.js ```javascript // public mount method Vue.prototype.$mount=function ( el?: string | Element, hydrating?: boolean ): Component { el=el && inBrowser ? query(el) : undefined // Get or create the DOM element if we haven't yet return mountComponent(this, el, hydrating) // <-- Here's our mountComponent call! } ``` We found it, we found the `mountComponent` call which leads us back to lifecycle.js: /src/core/instance/lifecycle.js ```javascript export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el=el ... callHook(vm, 'beforeMount' ) let updateComponent updateComponent=()=> {
                vm._update(vm._render(), hydrating)
                }

                new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)

                hydrating = false
                return vm
                }
                ```

                Remember the watcher from this diagram?

                ![Reactivity Cycle](https://vuejs.org/images/data.png)

                We send our `updateComponent` function into the watcher, and it takes care of invoking it for the first time. When it gets invoked you can see it first calls `_render` which returns the VNode (like in the diagram) and then calls `_update` which updates the actual DOM. This leads to our final two questions:

                ## What are this \_render() & \_update() functions doing?

                The `_render` function can be found inside instance/render.js:

                /src/core/instance/render.js

                ```javascript
                Vue.prototype._render = function (): VNode {
                const vm: Component = this
                const { render, _parentVnode } = vm.$options

                // set parent vnode. this allows render functions to have access
                // to the data on the placeholder node.
                vm.$vnode = _parentVnode
                // render self
                let vnode
                try {
                vnode = render.call(vm._renderProxy, vm.$createElement) // <--- Executes our render function, returning a VNode } catch (e) { ... } // set parent vnode.parent=_parentVnode return vnode } ``` See where our render function is called? This runs this render function which we compiled earlier: ```javascript (function() { with(this){return _c('h1',[_v(_s(this.name))])} }) ``` Since `this.name` is reactive, this is where our `getter` method gets invoked for the first time (from lesson 2 & 3), and we call `dep.depend()`. If we had written our render function from scratch inside our component (like we did on lesson 4) this is where that function would get invoked. Our `render` function returns a VNode and then our `_update` function gets called, found here: /src/core/instance/lifecycle.js ```javascript Vue.prototype._update=function (vnode: VNode, hydrating?: boolean) { const vm: Component=this if (vm._isMounted) { // If we've already mounted this node on the DOM callHook(vm, 'beforeUpdate' ) } const prevVnode=vm._vnode // Store the previous VNode vm._vnode=vnode // set current vnode to the one we just generated and // passed into the _update method if (!prevVnode) { // If no previous VNode then DOM node created and inserted vm.$el=vm.__patch__(vm.$el, vnode, hydrating, false, vm.$options._parentElm, vm.$options._refElm) } else { // Updates happen here. Notice we're sending in the prevVnode and the new // one, so that the least amount of DOM updates occur. vm.$el=vm.__patch__(prevVnode, vnode) } } ``` As you can see with the comments I left above, `_update` eventually calls the `__patch__` function, sending in the information it needs to change the actual DOM. The `__patch__` function itself gets set based on what environment we’re in. Since we’re on the web, the following code get’s called: /src/platforms/web/runtime/index.js ```javascript Vue.prototype.__patch__=inBrowser ? patch : noop ``` We are `inBrowser` so this leads us to the path function: /src/platforms/web/runtime/patch.js ```javascript import * as nodeOps from 'web/runtime/node-ops' ... export const patch: Function=createPatchFunction({ nodeOps, modules }) ``` And `createPatchFunction` leads us to the Virtual DOM or `vdom` library: /src/core/vdom/patch.js ```javascript export function createPatchFunction (backend) { ... } ``` We’re not actually going to look inside the `createPatchFunction`, as that would be diving into the internals of how the Virtual DOM does it’s thing (we’d need another lesson for that). However, what’s interesting here is `nodeOps` which gets passed into it. `nodeOps` basically contains all of the functions needed to interact with the DOM, like these two: /src/platforms/web/runtime/node-ops.js ```javascript export function createElement(tagName: string, vnode: VNode): Element { const elm=document.createElement(tagName); } export function createTextNode(text: string): Text { return document.createTextNode(text); } ... // A bunch more ``` What’s interesting about the separation of the Virtual DOM and the operations needed to manipulate the actual DOM, is that you can begin to understand how you might add another entire platform… Like maybe Android? Pay attention to the folder paths: ![](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1578371345950_2.jpg?alt=media&token=f311c5cb-9222-4a95-9b40-2cb939fda91c) I’ve been told that Vue isn’t quite this modular yet, and there are parts of core that still reference `inBrowser`. However, as Vue evolves I it will make it easier for this to be possible. I hope this gives you a greater understanding of the Vue internals. If you haven’t yet, I highly encourage you to download [the core source](https://github.com/vuejs/vue) and take a read.