In the last section, “Compiling template Strings into abstract Syntax tree AST”, we implemented the logic of template rendering as AST, which brings us one step closer to our final goal of “rendering as a real DOM”!

In this section, we continue to implement the AST compiled into the render() function.

In this section, the target

  • Compile the abstract syntax tree AST into a rendering functionrender()

Complete code:Online example: Abstract syntax tree AST compiles to render() -js Bin

This is the AST object we compiled in the previous section:

{
    "type": 1."tag": "div"."children": [{"type": 2."expression": "_s(msg)"."tokens": [{"@binding": "msg"}]."text": "{{msg}}"}}]Copy the code

Compile to render function render() :

function render() {
  with(this) {
   return _c('div',[_v(_s(msg))])
  }
}
Copy the code

We don’t need to understand rendering functions for now, but we’ll get to that later.

What is a render functionrender()?

The render function is an intermediary between the AST and the virtual DOM node, which is essentially a JS function that returns the object of the virtual node based on the “runtime”.

In vue.js 2, a virtual DOM node is obtained by performing a “render function”, which is used to Diff the virtual node and eventually generate the real DOM.

Lifecycle. Js # l189-l191

updateComponent = () = > {
  vm._update(vm._render(), hydrating)
}
Copy the code

In the above three lines of source code, the vm._render() call is the “render function” that returns the “virtual DOM node”.

Passing the virtual DOM node as an argument to vm._update() begins the famous “virtual DOM Diff.”

Core principles

1. Convert string function bodies to functions

When writing JS, we can create functions in the form of declarations or expressions.

But to “create functions” during JS execution we need the New Function() API, which is the constructor of a Function in JS.

We can convert the body of a function of type string into an executable JS function by calling its constructor:

const func = new Function('console.log(' new function '))

/ * func = = = ƒ anonymous () {the console. The log (` new function `)} * /

func() // Prints' new function '
Copy the code

With the New Function()API, we have the ability to generate the Function body during JS execution and finally declare the Function.

2. Generate the function body in string format based on AST

With the ability to declare functions, we can compile the AST into a “string-formatted function body” and turn it into an executable function.

For example, we have an AST corresponding to

:

{
    "type": 1."tag": "div"."children": [],}Copy the code

The body of the function that wants to compile the AST as a rendering function: _c(‘div’).

We simply iterate over the AST and concatenate the desired function body based on the tag attribute:

function generate(ast) {
  if (ast.tag) {
    return `_c('${ast.tag}') `}}Copy the code

If the Children property of the AST is not empty, we continue to perform depth-first recursive search on it, and we can continue to increase the body of the rendering function, eventually generating various complex rendering functions and rendering the complex DOM, for example:

const render = function () {
  with (this) {
    return _c(
      'div', {attrs: {"id": "app"}},
      [
        _c('h1', [_v("Hello vue-template-babel-compiler")]),
        _v(""), (optional? .chaining) ? _c('h2', [_v("\\n Optional Chaining enabled: "+ _s(optional? .chaining) +"\\n ")])
          : _e()
      ]
    )
  }
}
Copy the code

Var code = generate(ast, options); var code = generate(ast, options); With console.log(code), after NPM run serve runs, you can see the rendering function compiled from your own.vue file in the console.

Specific steps

This time, the code logic is much simpler, requiring only 41 lines of code.

Increased 1.CodeGeneratorClasses and their invocation

We use CodeGenerator to encapsulate the logic for compiling AST as a rendering function, with a method called generate(AST),

Passing the AST as an argument returns an object with the render() function as the property value:

class CodeGenerator {
    generate(ast) {
      debugger
      var code = this.genElement(ast)

      return {
        render: ("with(this){return " + code + "}"),}}}Copy the code

Joining togetherrenderAt the time of thewith(this) {}What’s the use?

Js single file component (.vue file, SFC) can render the secret of this.msg without writing this keyword.

With keyword document-mDN

By using the with(this) keyword in the render function, this can be used as a global variable in its scope (similar to window, global). Variables in {} braces take the attribute of this directly.

Such as:

with (Math) {
  val = random()
}
console.log(val) // Call the return value of math.random ()
Copy the code

2. Compile the parent element in the AST

Let’s add a genElement method to the class,

This method takes an AST node and does two things:

  • Continue compiling the children of the AST nodechildren
  • Concatenate strings that compile the current AST node into a rendering function
genElement(el) {
  var children = this.genChildren(el)
  const code = `_c('${el.tag}'${children ? `,${children}` : ' '}) `
  return code
}
Copy the code

GenElement is used to combine AST:

{
    "type": 1."tag": "div"."children": [],}Copy the code

Compile to string function body: _c(‘div’)

3. Compile the child elements in the AST

Next we compile the ast.children child element

Children is an array that may have multiple children, so we need to do a.map() traversal to process each child separately.

genChildren (el, state) {
  var children = el.children
  if (children.length) {
    return ` [${children.map(c => this.genNode(c, state)).join(', ')}] `}}Copy the code

We also add a genElement method to the class that calls genChildren:

  genElement(el) {
    debugger
    var children = this.genChildren(el)
    const code = `_c('${el.tag}'${children ? `,${children}` : ' '}) `
    return code
  }
Copy the code

4. Process each child element separately

We use the genNode(node) method for child elements,

In a production environment, there are multiple child elements, including text, comments, and HTML elements, so use if (Node. type === 2) to determine the type of the child element.

genNode(node) {
  if (node.type === 2) {
    return this.genText(node)
  }
  // TODO else if (node.type === otherType) {}
}
Copy the code

We only need to process “text” (Node.type === 2) this time, so we add a genText(text) to process.

genText(text) {
  return `_v(${text.expression}) `
}
Copy the code

In the compiling AST phase, we have compiled {{MSG}} into a JS object:

  {
    "type": 2."expression": "_s(msg)"."tokens": [{"@binding": "msg"}]."text": "{{msg}}"
  }
Copy the code

Now we just take the expression property, which is the corresponding render function.

In short, _s() is a built-in method in vue.js that generates a corresponding virtual DOM node from an incoming string.

We’ll cover the meaning and implementation of _S (MSG) in more detail later.

5. Splice into string function bodies and generate rendering functions

With (this){return _c(‘div’,[_v(_s(MSG))])},

To convert the render property, which is still the body of a string Function, into an executable Function, we add a new Function(code) logic,

And declare createFunction (code) to the VueCompiler class for the final call:

createFunction (code) {
  try {
    return new Function(code)
  } catch (err) {
    throw err
  }
}
Copy the code

Finally, let’s make a unified call.

In the compile of VueCompiler class (template) add CodeGenerator instance and enclosing CodeGenerator. Generate (ast) call:

class VueCompiler {
  HTMLParser = new HTMLParser()
  CodeGenerator = new CodeGenerator()

  compile(template) {
    const ast = this.parse(template)
    console.log(Template string is compiled into abstract syntax tree AST)
    console.log(`ast = The ${JSON.stringify(ast, null.2)}`)

    const code = this.CodeGenerator.generate(ast)
    const render = this.createFunction(code.render)
    console.log(Abstract syntax tree AST compiles to render())
    console.log(`render() = ${render}`)
    return render
  }
}
Copy the code

This.compiler.compile (this.options.template) will render() = : this.compiler.compile(this.options.template)

Complete code:Online example: Abstract syntax tree AST compiles to render() -js Bin


“8 Minutes to learn Vue. Js Principle” series, a total of 5 parts:

  • The template string is compiled into the abstract syntax tree AST
  • AST compiles render() implementation principle
  • Render () to generate virtual node vNode
  • The virtual node vNode generates the real DOM
  • 5. Data-driven DOM updates – Watcher Observer and Dep

Is in full swing update, welcome to exchange ~ welcome to urge more ~