preface

This article is mainly written Vue2.0 source code – template compilation principle

Last article we mainly introduced the responsive principle of Vue data for the advanced front-end responsive principle is the basic interview Vue must test the source code base class if not very clear it was basically passed so today we write the template compilation principle is also a point of Vue interview more frequently And it’s a little bit more complicated than the reactive principle and it involves the AST and a lot of regeformat matching and you can look at the mind map and hand write it together to impress you

Applicable to the crowd: no time to look at the official source code or look at the source code to see the more meng and do not want to see the students

Suggestion: students who want to learn regular expressions can take a look at this article


The body of the

// Instantiate Vue
new Vue({
  el: "#app".data() {
    return {
      a: 111}; },// render(h) {
  // return h('div',{id:'a'},'hello')
  // },
  // template:`<div id="a">hello</div>`
});
Copy the code

You can manually configure template or render in the options option

Note 1: In normal development, we use the Vue version with no compiled version (run-time only) and pass the template option directly in options

Note 2: The template option passed in here should not be confused with the <template> template in the. Vue file. The template component of the vue single-file component needs to be processed by the Vue-Loader

The el or template options we pass in will be resolved to render to keep the template parsing consistent

1. Template compilation entry

// src/init.js

import { initState } from "./state";
import { compileToFunctions } from "./compiler/index";
export function initMixin(Vue) {
  Vue.prototype._init = function (options) {
    const vm = this;
    // This represents the object (instance object) on which the _init method was called.
    // this.$options is the property passed in when the user new Vue
    vm.$options = options;
    // Initialize the state
    initState(vm);

    // Render the template if there is an EL attribute
    if(vm.$options.el) { vm.$mount(vm.$options.el); }};// This code is stored in entry-Runtime-with-compiler. js
  // the Vue source code contains the compile function. This is separate from the runtime-only version
  Vue.prototype.$mount = function (el) {
    const vm = this;
    const options = vm.$options;
    el = document.querySelector(el);

    // If the render attribute does not exist
    if(! options.render) {// If there is a template attribute
      let template = options.template;

      if(! template && el) {// If render and template do not exist but el attribute does exist, assign the template to the outer HTML structure where EL is located.
        template = el.outerHTML;
      }

      // Finally we need to convert the Tempalte template to render function
      if (template) {
        constrender = compileToFunctions(template); options.render = render; }}}; }Copy the code

The main concern is that the $mount method eventually turns the rendered template into the render function

2. Templates convert core compileToFunctions

// src/compiler/index.js

import { parse } from "./parse";
import { generate } from "./codegen";
export function compileToFunctions(template) {
  // We need to turn the HTML string into the render function
  // 1. Convert HTML code into an AST syntax tree. The AST is used to describe the code itself, forming a tree structure that describes not only HTML but also CSS and JS syntax
  // Many libraries use ast, such as Webpack, Babel, esLint, etc
  let ast = parse(template);
  // 2. Optimize static nodes
  // This interested can see the source code does not affect the core function is not implemented
  // if (options.optimize ! == false) {
  // optimize(ast, options);
  / /}

  // 3. Regenerate the code from the AST
  // Our final generated code needs to be the same as the render function
  / / similar _c (' div '{id: "app"}, _c (' div', undefined, _v (" hello "+ _s (name)), _c (' span, undefined, _v (" world"))))
  // _c for create element, _v for create text, _s for text json. stringify-- parses the object into text
  let code = generate(ast);
  // Use the call to render to change the value of the variable in this convenience code
  let renderFn = new Function(`with(this){return ${code}} `);
  return renderFn;
}
Copy the code

Creating a compiler folder means that the core of compileToFunctions is compiled. There are three main steps to exporting compileToFunctions. Generate the AST 2. Optimize static nodes 3. Generate the render function based on the AST

3. Parse HTML and generate an AST

// src/compiler/parse.js

// The following is the source of the regular expression is not clear students can refer to xiaobian before the article (front advanced salary must see - regular article);
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // The name of the matching tag is abc-123
const qnameCapture = ` ((? :${ncname}\ \ :)?${ncname}) `; // Match special tags such as ABC :234 before ABC: optional
const startTagOpen = new RegExp(` ^ <${qnameCapture}`); // The matching tag starts with the form 
const startTagClose = /^\s*(\/?) >/; // Match tag end >
const endTag = new RegExp(` ^ < \ \ /${qnameCapture}[^ >] * > `); // Match tag endings such as  to capture tag names inside
const attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /; // Match attribute like id="app"

let root, currentParent; // represents the root node and the current parent
// The stack structure represents the start and end tags
let stack = [];
// Identify the element and text type
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
// Generate the AST method
function createASTElement(tagName, attrs) {
  return {
    tag: tagName,
    type: ELEMENT_TYPE,
    children: [],
    attrs,
    parent: null}; }// Handle the start tag
function handleStartTag({ tagName, attrs }) {
  let element = createASTElement(tagName, attrs);
  if(! root) { root = element; } currentParent = element; stack.push(element); }// Handle the closing tag
function handleEndTag(tagName) {
  // Stack structure []
  
      
< /div>
let element = stack.pop(); // The current parent element is the one at the top of the stack currentParent = stack[stack.length - 1]; // Establish a relationship between parent and children if(currentParent) { element.parent = currentParent; currentParent.children.push(element); }}// Processing the text function handleChars(text) { // Remove Spaces text = text.replace(/\s/g.""); if (text) { currentParent.children.push({ type: TEXT_TYPE, text, }); }}// Parse the tag to generate the AST core export function parse(html) { while (html) { / / find the < let textEnd = html.indexOf("<"); // If < is first then the proof is followed by a tag whether it is the beginning or the end tag if (textEnd === 0) { // If the start tag parsing results const startTagMatch = parseStartTag(); if (startTagMatch) { // Parse the tag names and attributes to generate an AST handleStartTag(startTagMatch); continue; } // Match the end tag </ const endTagMatch = html.match(endTag); if (endTagMatch) { advance(endTagMatch[0].length); handleEndTag(endTagMatch[1]); continue; }}let text; / / like hello < div > < / div > if (textEnd >= 0) { // Get the text text = html.substring(0, textEnd); } if(text) { advance(text.length); handleChars(text); }}// Match the start tag function parseStartTag() { const start = html.match(startTagOpen); if (start) { const match = { tagName: start[1].attrs: [],};// Cut off the start tag when it matches advance(start[0].length); // Start matching attributes // End indicates the end symbol > if the end tag is not matched // attr indicates the matching attribute let end, attr; while ( !(end = html.match(startTagClose)) && (attr = html.match(attribute)) ) { advance(attr[0].length); attr = { name: attr[1].value: attr[3] || attr[4] || attr[5].// This is because the re captures attribute values that support double quotes, single quotes, and no quotes }; match.attrs.push(attr); } if (end) { // If a tag matches the end >, the start tag is resolved advance(1); returnmatch; }}}// Cut the HTML string and continue matching each time it matches function advance(n) { html = html.substring(n); } // Return the generated AST return root; } Copy the code

After the start tag, the end tag, and the text are parsed, the corresponding AST is generated and the corresponding parent-child association is established. Advance constantly intercepts the rest of the string until all the HTML is parsed The main thing we’ve written here is the parseStartTag attribute

4. Regenerate the code based on the AST

// src/compiler/codegen.js

const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g; // Match the curly braces {{}} to capture what's inside the curly braces

function gen(node) {
  // Check the node type
  // Mainly contains processing text core
  // The source code contains complex processing such as v-once v-for V-if custom instruction slot, etc. We only consider ordinary text and variable expression {{}} processing here

  // If it is an element type
  if (node.type == 1) {
    // Create recursively
    return generate(node);
  } else {
    // If it is a text node
    let text = node.text;
    // There is no braced variable expression
    if(! defaultTagRE.test(text)) {return `_v(The ${JSON.stringify(text)}) `;
    }
    // Regex is a global pattern. You need to reset the lastIndex property of the regex every time otherwise a matching bug will be raised
    let lastIndex = (defaultTagRE.lastIndex = 0);
    let tokens = [];
    let match, index;

    while ((match = defaultTagRE.exec(text))) {
      // Index indicates the matched position
      index = match.index;
      if (index > lastIndex) {
        // Insert normal text into tokens at the matched {{position
        tokens.push(JSON.stringify(text.slice(lastIndex, index)));
      }
      // Put the contents of the captured variable
      tokens.push(`_s(${match[1].trim()}) `);
      // The match pointer moves backward
      lastIndex = index + match[0].length;
    }
    // Continue push if there is still plain text left inside the curly braces
    if (lastIndex < text.length) {
      tokens.push(JSON.stringify(text.slice(lastIndex)));
    }
    // _v creates text
    return `_v(${tokens.join("+")}) `; }}// Process the ATTRs attribute
function genProps(attrs) {
  let str = "";
  for (let i = 0; i < attrs.length; i++) {
    let attr = attrs[i];
    // Make the style in the ATTRs attribute special
    if (attr.name === "style") {
      let obj = {};
      attr.value.split(";").forEach((item) = > {
        let [key, value] = item.split(":");
        obj[key] = value;
      });
      attr.value = obj;
    }
    str += `${attr.name}:The ${JSON.stringify(attr.value)}, `;
  }
  return ` {${str.slice(0, -1)}} `;
}

// Generate a child node and call the gen function to recursively create it
function getChildren(el) {
  const children = el.children;
  if (children) {
    return `${children.map((c) => gen(c)).join(",")}`; }}// Create code recursively
export function generate(el) {
  let children = getChildren(el);
  let code = `_c('${el.tag}',${
    el.attrs.length ? `${genProps(el.attrs)}` : "undefined"
  }${children ? `,${children}` : ""}) `;
  return code;
}
Copy the code

So once you’ve got the ast that’s made, you need to take the AST Into a similar _c (‘ div ‘{id: “app”}, _c (‘ div’, undefined, _v (” hello “+ _s (name)), _c (‘ span, undefined, _v (” world”)))) this string

5. Code string generates render function

export function compileToFunctions(template) {
  let code = generate(ast);
  // Use the with syntax to change the scope to this and then call the render function to use the call to change the value of the variable in this code
  let renderFn = new Function(`with(this){return ${code}} `);
  return renderFn;
}
Copy the code

6. Mind maps compiled from templates

summary

So far, Vue template compiling principle has been completed, you can look at the mind map to write their own core code ha, it is important to note that this is a large number of use of string concatenation and regular knowledge related to the place you do not understand can consult more information is also welcome to comment comments

Finally, if you find this article helpful, remember to like it three times. Thank you very much!

Series of links (will be updated later)

  • Handwriting Vue2.0 source code (a) – response data principle
  • Handwriting Vue2.0 source code (2) – template compilation principle
  • Handwriting Vue2.0 source code (three) – initial rendering principle
  • Handwriting Vue2.0 source code (four) – rendering update principle
  • Handwriting Vue2.0 source code (five) – asynchronous update principle
  • Handwriting Vue2.0 source code (six) -diff algorithm principle
  • Handwriting Vue2.0 source code (seven) -Mixin Mixin principle
  • Handwriting Vue2.0 source code (eight) – component principle
  • Handwriting Vue2.0 source code (nine) – listening attribute principle
  • Handwriting Vue2.0 source code (ten) – the principle of computing attributes
  • Handwriting Vue2.0 source code (eleven) – global API principle
  • The most complete Vue interview questions + detailed answers
  • Handwritten vue-Router source code
  • Write vuex source code
  • Handwriting vue3.0 source code

Shark brother front touch fish technology group

Welcome technical exchanges within the fish can be pushed for help – link