This is the 16th day of my participation in Gwen Challenge

One, foreword

In part 1, Generating ast syntax trees – Constructing tree structures

  • Based on THE characteristics of HTML, the stack data structure is used to record the parent-child relationship
  • Start tag, end tag, and text processing
  • Code reconstruction and ast syntax tree construction process analysis

This article uses the AST syntax tree to generate the Render function-code splicing part


Second, generate render function – code concatenation

1. Review

As mentioned in Article 12, the compileToFunction method is the portal to Vue compilation

The compileToFunction method does two things:

  1. ParserHTML: Compiles template content into an AST syntax tree
  2. Generate: generate a render function according to the AST syntax tree;

The end product of the Vue compilation phase is the Render function

// src/compiler/index.js

export function compileToFunction(template) {
  // 1, the template is compiled as an AST syntax tree
  let ast = parserHTML(template);
  // 2, use AST to generate render function
  let code = generate(ast);
}
Copy the code

In the previous articles, YOU compiled HTML templates into ast syntax trees using parserHTML

Next, generate the render function using ast through generate

Generate method: Generate ast as a render function

Generate code by calling the generate method, passing in the AST

The render function was briefly mentioned earlier

The template on the left -> generates the render function on the right

  • _c is equivalent to createElement creating an element
  • _v is the same thing as _vode
  • _s is equivalent to stringify

Render function generate(ast)

The way the code is generated is by concatenating strings

String concatenation mimics the render method shown above

// src/compiler/index.js

function generate(ast) {
 let code = `_c('${ast.tag}',${
  ast.attrs.length? JSON.stringify({}):'undefined'	//Properties are not processed for now, and will be processed separately later}${
  ast.children?` `, []:' '		//Do not deal with the son for now, and will deal with it separately later}) `

 return code;
}

// _c('div',{},[]}
Copy the code

GenProps (ast.attrs)

// src/compiler/index.js

// Format attrs array as: {key=val,key=val,}
function genProps(attrs) {
  let str = ' ';
  for(let i = 0; i< attrs.length; i++){
    let attr = attrs[i];
    // Convert value to string using json. stringify
    str += `${attr.name}:The ${JSON.stringify(attr.value)}, `
  }
  return ` {${str.slice(0, -1)}} `;// Remove the last superfluous comma and put {} around it.
 }

function generate(ast) {
 let code = `_c('${ast.tag}',${
  ast.attrs.length? genProps(ast.attrs):'undefined'
 }${
  ast.children?` `, []:' '
 }) `
 return code;
}

export function compileToFunction(template) {
  let ast = parserHTML(template);
  let code = generate(ast);
  console.log(code)
}

// _c('div',{id:"app",a:"1",b:"2"},[]}
Copy the code

4. Handle styles in attributes

In the style property, there is a style, which also needs to be handled in the property

<div id="app" a='1' b=2 style="color: red; background: blue;">
  <p>{{message}} 
    <span>Hello Vue</span>
  </p>
</div>
Copy the code

Continue processing the style as an object:

// src/compiler/index.js#genProps

// Format attrs array as: {key=val,key=val,}
function genProps(attrs) {
  let str = ' ';
  for(let i = 0; i< attrs.length; i++){
    let attr = attrs[i];
    {name:id, value:'app'}
    if(attr.name == "style") {// 
      
// Use replace for regular matching, and replace styles with keys and values / / ^; : not semicolons (split attributes and values), colons (end) let styles = {}; attr.value.replace(/([^;:]+):([^;:]+)/g.function () { styles[arguments[1]] = arguments[2] }) attr.value = styles; } str += `${attr.name}:The ${JSON.stringify(attr.value)}, ` } return ` {${str.slice(0, -1)}} `; } // Print output: // _c('div', // {id:"app",a:"1",b:"2",style:{"color":" red","background":" blue"}}, / / []} Copy the code

Test output:

5. Recursive deep processing of sons: genChildren

Continue processing the son, demo is as follows:

// _c(div,{},c1,c2,c3...)
function generate(ast) {
  let children = genChildren(ast);
  let code = `_c('${ast.tag}',${
    ast.attrs.length? genProps(ast.attrs):'undefined'
  }${
    children?`,${children}`:' '
  }) `
  return code;
}

function genChildren(el) {
  console.log("===== genChildren =====")
  let children = el.children;
  if(children){
    console.log("Children exist, start traversal processing child nodes...", children)
    let result = children.map(item= > gen(item)).join(', ');
    console.log("Child node processing completed, result =" + JSON.stringify(result))
    return result
  }
  console.log("No children, return false")
  return false;
}

function gen(el) {
  console.log("===== gen ===== el = ",el)
  if(el.type == 1) {console.log("Element tag ="+el.tag+", generate continues recursive processing")
    return generate(el);// Process the current element recursively
  }else{
    console.log("Text type,text =" + el.text)
    return el.text
  }
}

// _c('div',{id:"app",a:"1",b:"2",style:{"color":" red","background":" blue"}},
// _c('p',undefined,_v(_s(message)),
// _c('span',undefined,_v('HelloVue1')),
// _c('span',undefined,_v('HelloVue2')),
// _c('span',undefined,_v('HelloVue3'))
/ /)
// )
Copy the code

6, wrap _v for the text type

function gen(el) {
  console.log("===== gen ===== el = ",el)
  if(el.type == 1) {// 
    console.log("Element tag ="+el.tag+", generate continues recursive processing")
    return generate(el);// If it is an element, it is generated recursively
  }else{// Text type
    let text = el.text
    console.log("Text type,text =" + text)
    return `_v('${text}') `  / / _v packing}}Copy the code

7, wrap _s for variable

TODO: Further optimizes the description by adding interception analysis and log tracing of the expression part

  • Text -> wrap _v
  • Variable -> wrapper _s
  • String -> Wrap “”

{{name}} :

  • Name may be an object that needs to be converted to a string using json.stringify

Check if text contains {{}} :

  • Contains, indicating an expression;
  • If not, return _v(‘${text}’);

If {{}} is included, use the re defaultTagRE:

If yes, the value is an expression. You need to concatenate the expression and common values

['aaa',_s(name),'bbb'].join('+') ==> _v('aaa' + s_(name) + 'bbb')
Copy the code

_v(${tokens. Join (‘+’)})

8. Complete implementation

// src/compiler/index.js#gen
const defaultTagRE = / \ {\ {((? :.|\r? \n)+?) \}\}/g

function gen(el) {
  console.log("===== gen ===== el = ",el)
  if(el.type == 1) {// 
    console.log("Element tag ="+el.tag+", generate continues recursive processing")
    return generate(el);// If it is an element, it is generated recursively
  }else{// Text type
    let text = el.text
    console.log("Text type,text =" + text)
    if(! defaultTagRE.test(text)){return `_v('${text}') `  // Plain text, wrapped _v
    }else{
      // There is a {{}} expression, which needs to be concatenated with ordinary values
      / / target: [' aaa '_s (name),' BBB ']. Join (' + ') = = > _v (' aaa + s_ (name) + 'BBB')
      let lastIndex = defaultTagRE.lastIndex = 0;
      let tokens = []; // <div>aaa {{name}} bbb</div>
      let match
      while(match = defaultTagRE.exec(text)){
        console.log("Match content" + text)
        let index = match.index;// match. Index: indicates the current captured location
        console.log("Current lastIndex =" + lastIndex)
        console.log("Match. Index =" + index)
        if(index > lastIndex){  // Add aaa in tokens as in 
      
aaa
let preText = text.slice(lastIndex, index) console.log("Match to expression - find the part before the beginning of the expression:" + preText) tokens.push(JSON.stringify(preText))// use json.stringify to enclose double quotes } console.log("Match to expression:" + match[1].trim()) {{name}} (match[1] is the middle part of curly braces and handles possible newlines or carriage returns) tokens.push(`_s(${match[1].trim()}) `) // Update lastIndex to '
aaa {{name}}'
lastIndex = index + match[0].length; // Update lastIndex to '
aaa {{name}}'
} // Add BBB in tokens after the while loop. For example: BBB if(lastIndex < text.length){ let lastText = text.slice(lastIndex); console.log("After expression processing is complete, there is still something to continue processing:"+lastText) tokens.push(JSON.stringify(lastText))// from lastIndex to last } return `_v(${tokens.join('+')}) `}}}Copy the code

Processing logic for text:

  1. If there is no special expression, return directly
  2. If there is an expression, match and intercept it

When using the re to capture, complex situations may exist, such as:

<div>aaa {{name}} bbb</div>
<! - or - > 
<div>aaa {{name}} bbb {{age}} ccc</div>
Copy the code
  1. Using the regular defaultTagRE to capture an expression,
    Add the array of tokens into aaa ‘. Note: After capturing the tokens, the offset values are displayed behind the expression and adjusted after the expression processing is complete.
  2. Insert the captured expression name name into the tokens array and modify the matching offset. Continue with the rest of the tokens. Note: after each capture, repeat steps 1,2
  3. Insert BBB in ‘BBB’ into the tokens array if the length of the tokens is still larger than the current offset
  4. Put them all in the tokens array, join them and return_v(${tokens.join('+')})


Three, the end

This article mainly introduces the generation of render function – code concatenation

  • Render function: generate(AST)
  • Processing properties: genProps(ast.attrs)
  • Handles styles in properties
  • Recursive deep processing son: genChildren

At this point, the code splicing inside the render function with has completed the next chapter, generating the render function – function generation