preface

I have shared the working principle of React before, so what is the compiling principle of Vue? After several days of reading and watching the video, I would like to share it with you now.

reference

When sharing the Vue responsiveness principle, we have seen a diagram that reflects the entire life cycle of a Vue instance in detail, so today we will use this diagram as the entry point

New Vue(options) executes the this._init(options) initializer, which is defined in init.js

/ / the entry file SRC/core/instance/index. The js / / delete redundant code import {initMixin} from '. / init function Vue (options) {/ / _init This._init(options)} initMixin(Vue) // initialize the _init method, including init options, render, events, BeforCreated, created etc export default Vue // Export Vue constructorCopy the code

What does the initMixin module do? code position : src/core/instance/init.js

export function initMixin (Vue) {
 // this._init(options)
  Vue.prototype._init = function (options) {
    const vm = this
    / / merge options
    vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
    )
    vm._self = vm 
    initLifecycle(vm) // Initialize the life cycle
    initEvents(vm) // Initialize the event
    initRender(vm) // Initialize render
    callHook(vm, 'beforeCreate') // Execute the pre-creation life cycle
    initInjections(vm) / / data/props
    initState(vm) // Initialize the state
    initProvide(vm) // Initialize provide
    callHook(vm, 'created') // After the creation
    
    // Mount the instance using the $mount method
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
Copy the code

At this point, the initialization phase of the entire Vue instance is temporarily concluded, which can be summarized as:

New Vue(options) => call _init() to merge opstions with opstions => initialize a series of life cycle events, Render, provider and inject => finally call $mount to mount the instance

Until $mount, let’s go back to today’s theme template compilation. As the name implies, how does Vue render the template to a real DOM on the page, which naturally happens at mount time

$mount

$mount = $render; $mount = $render; $mount = $render; $render = $render; $render = $render

  1. Render function => vNode virtual DOM => real DOM
  2. Template => Ast abstract syntax tree => render => vNode virtual DOM => real DOM

The topic we share today is template compilation, which is the second one

No template compilation is required, $mount is defined on the prototype

src/platforms/web/runtime/index.js

import { mountComponent } from 'core/instance/lifecycle';

Vue.prototype.$mount = function(
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && inBrowser ? query(el) : undefined;
  
  return mountComponent(this, el, hydrating);
};
Copy the code

Template compilation is required, referring to $mount defined on the prototype ↑ above

src/platforms/web/entry-runtime-with-compiler.js

Remove redundant code

import Vue from './runtime/index';
import { compileToFunctions } from './compiler/index' 

// Cache the above $mount prototype method and call it last
const mount = Vue.prototype.$mount; 
// Override the prototype method $mount for the compiled template
Vue.prototype.$mount = function(
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && query(el);

  // Cannot mount to body and HTML
  if (el === document.body || el === document.documentElement) {     
    return this;
  }

  const options = this.$options;
  
  // If there is no render function, create a render function
  // and mount to the current options, from the compileToFunctions method
  if(! options.render) {const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV ! = ='production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters : options.delimiters,
        comments : options.comments,
      }, this);

      options.render = render;
      options.staticRenderFns = staticRenderFns;
  }
  // Call the $mount method cached above
  return mount.call(this, el, hydrating);
};
Copy the code

Saw two $mount method entity, actually rewrite $mount just convert the template template to render, Return mount.call(this, el, hydrating);

Template compilation

From the above analysis, we can see that without render, we need to call the compileToFunctions method to convert the template string to the render method

CompileToFuncitons from import {compileToFunctions} from ‘./compiler/index

/* @flow */
import { parse } from './parser/index' 
import { optimize } from './optimizer' 
import { generate } from './codegen/index' 
The // compileToFunctions method comes from here
import { createCompilerCreator } from './create-compiler'

export const createCompiler = createCompilerCreator(function baseCompile (template: string, options: CompilerOptions) :CompiledResult {
 // Generate the AST from parse
  const ast = parse(template.trim(), options)
  if(options.optimize ! = =false) {
   / / optimization of ast
    optimize(ast, options)
  }
  // Generate the string of the render Function, which is converted to the Function by new Function(code)
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
Copy the code

Compile the core

Ok, so here we know that when we call $mount, we call compileToFunctions to convert the template string to the render function

Process to render :(parse) generate ast => optimize ast => generate render function

parse

Before we share an AST, let’s first know what an AST is and what it does.

Abstract Syntax Tree (AST) is used to represent the Syntax data structure of a code

Take a reference example – approximate structure – parameters may not be all

let str = '
      
I'm text
'
;// Convert to ast letAst = {type:1.// 1: tag, 2: expression, 3: text, native nodeType tag:'div'./ / element parent: undefined./ / parent attrs: [{name: "id".value: "'content'"}]./ / property children: [// Subsets, there may be multiple { type:3.text:'I am text'}}]Copy the code

The source code is quite complex in this area, various regular match, interested can see the source code, SRC /compiler corresponding to the parser

I have heard the public class and read several articles about template compilation. To summarize briefly, Parser is to compose AST syntax tree objects by intercepting and assembling HTML strings. The specific intercepting logic is as follows:

let html = "<div>123</div>"
if(html.indexOf("<") = =0) => Intercept the operation start tagif(html.indexOf("html") > =0) => Intercept the operation textif(html.indexOf(">") = =0) => Intercept the operation end tagCopy the code
  1. Start tag passesindexOfDetermine whether the start tag is included<The way of
  2. End tag As above =>>
  3. Text is length >= 0

Note: Of course it's not that simple, I'm just giving a small example

When the start tag is encountered, an AST object is created. When the end tag is encountered, the current object is recorded globally. This loop will form a multi-layer AST object, such as the reference instance above.

optimize

To optimize the AST, we just add a static property isStatic :true/false for each AST object. Why do we add this property? That’s where Diff comes in, and I’ll write an article about diff later on in the virtual DOM.

IsStatic :true means that the tag or element isStatic, does not need to be compiled or parsed. it contains no variables, is not a slot/component, and so on. It’s a kind of optimization.

generate

It is a bit like React Render after bebal-loader compiles it. Vue is to traverse the AST tree and generate the render function. Then the render function will generate the virtual DOM, and finally create and diff the real DOM. Let’s look at the generate function

SRC/compiler/codegen/index, js line 43

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
) :CodegenResult {
  const state = new CodegenState(options)
  // Convert to _c() form by genElement()
  const code = ast ? genElement(ast, state) : '_c("div")'
  // The result is to return a function wrapped with
  return {
    render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
  }
}
Copy the code

Instead of looking at how each method is implemented, here are some examples of compilation methods that traverse the AST tree:

1. GenElement: Used to generate the basic createElement structure, of the form _c()

2. GenData: Deals with the properties on the AST structure that are used to generate data

3. GenChildren: Handles the children of the AST and calls genElement internally to form the child element’s _c() method

  1. . GenIf, genFor, genStatic, genOnce, etc

For those of you who don’t know much about _c, to tell you the truth, I was also blindfaced when I saw this, and later found out that these are just shorthand for some special methods defined by Vue, such as:

_c: corresponds to createElement, which creates an element (Vnode) _v: creates a text node _s: converts a value to a string (eg: {{data}}) _m: renders static content......Copy the code

Well, after the conversion of generate, this is the form

/ / before compilation
<template>
  <div id="content">
    {{msg}}
    <p>123</p>
  </div>
</template>

/ / the compiled
{
  render: with(this) {
    return _c('div', {
      attrs: {
        "id": "content"
      }
    }, [
    	_v("\n" + _s(msg) + "\n"),
        _c("p",
           _m("123")])},staticRenderFns:... }Copy the code

Render => render => render => render => render => render

Some people may say what the virtual DOM is like. In fact, it is similar to the AST syntax tree. It is also a similar object, except that the AST tree describes the structure of the tree with syntax, while the virtual DOM describes the real DOM structure. What is a tree with syntax, such as: {text: {{MSG}}}? The AST will contain this, but the virtual DOM will not.

with

  // Generate returns an object after execution. There is an unfamiliar with function
  return {
    render: `with(this){return ${code}} `.staticRenderFns: state.staticRenderFns
  }
Copy the code

Now, if you look at this function, it might be a little strange, but it’s just a shorthand for calling objects by node names in the area of code wrapped around the with function, for example

var obj={ name: "hisen", age:18 }; The function f (obj) {the console. The log (` ${obj. Name} : ${obj. Age} `)} / / normal call f (obj); With (obj){console.log(' ${name}:${age} ')} vm744:2 hisen:18Copy the code

The difference is that the writing method is more concise and clear, and there is no need to write the same object repeatedly. The current ES6 deconstruction is actually quite good

The end of the

Finally, a simplified diagram of color cloud coding in the reference article is quoted to describe the whole Vue compilation process

Welcome to like, a little encouragement, a lot of growth

A link to the

  • Front-end visualization platform implementation scheme
  • Vue3 10 minutes takes you seconds to vue3
  • Vue template compilation principle
  • Vue API extends
  • How does VUEX state management work
  • Vue-router source analysis principle
  • How does Vue listen for array changes
  • Handwritten Vue response [object.defineProperty]
  • Vue responsive source sharing