We all know that vue needs to write code in a fixed format, such as the following:

<template>
  <div>{{msg}}</div>
</template>

<script>
export default {
  data () {
    return {
      msg: ' '}}}</script>
Copy the code

After writing HTML to a template, vue first converts the template into an AST abstract syntax tree, then converts the AST into an executable render function, and finally generates the DOM. So today we’ll look at how templates are generated into an AST.

The first AST structure to generate is as follows:

function createASTElement (tag, attrs, parent) {
  return {
    type: 1.tag: tag,
    attrsList: attrs,
    attrsMap: attrs,
    rawAttrsMap: {},
    parent: parent,
    children: []}}Copy the code

The next step is to iterate over the contents of the template, matching the contents of the tag with the re.

var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3 001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;
// Regular Expressions for parsing tags and attributes
var attribute = /^\s*([^\s"'<>\/=]+)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /;
var dynamicArgAttribute = /^\s*((? :v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(? :\s*(=)\s*(? :"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))? /;
var ncname = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + (unicodeRegExp.source) + "] *";
var qnameCapture = "((? :" + ncname + "\ \ :)? + ncname + ")";
var startTagOpen = new RegExp(("^" " + qnameCapture));
var startTagClose = /^\s*(\/?) >/;
var endTag = new RegExp(("^ < \ \ /" + qnameCapture + "[^ >] * >"));
var doctype = / ^ 
      ]+>/i;
// #7298: escape - to avoid being passed as HTML comment when inlined in page
var reCache = {};
var comment = / ^ 
      ;
var conditionalComment = / ^ 
      ;
Copy the code

These re are pasted inside the source code, including matching start tags, end tags, comments and so on.

function parseHTML(html, options) {
  var stack = [];
  var expectHTML = options.expectHTML;
  var isUnaryTag$$1 = options.isUnaryTag || no;
  var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
  var index = 0;
  var last, lastTag;
  while (html) {
    last = html;
    // Make sure it is not a script or style-textarea tag
    if(! lastTag || ! isPlainTextElement(lastTag)) {var textEnd = html.indexOf('<');
      if (textEnd === 0) {
        // End tag: matches the End tag
        var endTagMatch = html.match(endTag);
        if (endTagMatch) {
          var curIndex = index;
          advance(endTagMatch[0].length);
          parseEndTag(endTagMatch[1], curIndex, index);
          continue
        }

        // Start tag: matches the Start tag
        var startTagMatch = parseStartTag();
        if (startTagMatch) {
          handleStartTag(startTagMatch);
          continue}}var text = (void 0), rest = (void 0), next = (void 0);
      if (textEnd >= 0) {
        rest = html.slice(textEnd);
        while(! endTag.test(rest) && ! startTagOpen.test(rest) ) {Hello  fetch hello
          next = rest.indexOf('<'.1);
          if (next < 0) { break }
          textEnd += next;
          rest = html.slice(textEnd);
        }
        text = html.substring(0, textEnd);
      }

      if (textEnd < 0) {
        text = html;
      }
      if (text) {
        advance(text.length); 
      }

      if(options.chars && text) { options.chars(text, index - text.length, index); }}else {
      var endTagLength = 0;
      var stackedTag = lastTag.toLowerCase();
      var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?) (< / ' + stackedTag + '[^ >] * >)'.'i'));
      var rest$1 = html.replace(reStackedTag, function (all, text, endTag) {
        endTagLength = endTag.length;
        return ' '
      });
      index += html.length - rest$1.length;
      html = rest$1; parseEndTag(stackedTag, index - endTagLength, index); }}// Clean up any remaining tags
  parseEndTag();

  function advance(n) {
    index += n;
    html = html.substring(n);
  }

  function parseStartTag() {
    var start = html.match(startTagOpen);
    // [ '<div', 'div', index: 0, input: '<div id="1" bg="2">hello</div>', groups: undefined ]
    if (start) {
      var match = {
        tagName: start[1].attrs: [].start: index
      };
      advance(start[0].length); Id ="1" bg="2">hello
      var end, attr;
      Id ="1" bg="2"
      while(! (end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) { attr.start = index; advance(attr[0].length);
        attr.end = index;
        match.attrs.push(attr);
      }
      if (end) {
        match.unarySlash = end[1];
        advance(end[0].length); // remove the > or /> to leave hello
        match.end = index;
        return match
      }
    }
  }

  function handleStartTag(match) {
    var tagName = match.tagName;
    var unarySlash = match.unarySlash; // The self-closing label is/The double label is' 'empty

    varunary = !! unarySlash;var l = match.attrs.length;
    var attrs = new Array(l);
    for (var i = 0; i < l; i++) {
      var args = match.attrs[i];
      var value = args[3] || args[4] || args[5] | |' ';
      attrs[i] = {
        name: args[1].value: value
      };
      {name: value} {id: 1}
      if (options.outputSourceRange) {
        attrs[i].start = args.start + args[0].match(/^\s*/).length; attrs[i].end = args.end; }}/ / double label
    if(! unary) { stack.push({tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end });
      lastTag = tagName;
    }
    if(options.start) { options.start(tagName, attrs, unary, match.start, match.end); }}// stackedTag, index - endTagLength, index
  function parseEndTag(tagName, start, end) {
    var pos, lowerCasedTagName;
    if (start == null) { start = index; }
    if (end == null) { end = index; }

    // Find the closest opened tag of the same type
    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase();
      for (pos = stack.length - 1; pos >= 0; pos--) {
        if (stack[pos].lowerCasedTag === lowerCasedTagName) {
          break}}}else {
      // If no tag name is provided, clean shop
      pos = 0;
    }
    if (pos >= 0) {
      // Close all the open elements, up the stack
      for (var i = stack.length - 1; i >= pos; i--) {
        if(options.end) { options.end(stack[i].tag, start, end); }}// Remove the open elements from the stack
      stack.length = pos;
      lastTag = pos && stack[pos - 1].tag; }}}Copy the code

The above is to process all the tags, according to the sequence of traversal after the AST can be generated.

var currentParent;
var stack = [];
var root;

function end(tag, start) {
  var element = stack[stack.length - 1];
  // pop the stack
  stack.length -= 1;
  currentParent = stack[stack.length - 1];
  closeElement(element);
}

function start(tag, attrs, unary, start, end) {
  var element = createASTElement(tag, attrs, currentParent);
  if(! root) { root = element; }if(! unary) { currentParent = element;// push the stackstack.push(element); }}function chars(text, start, end) {
  var children = currentParent.children;
  if (text) {
    var child;
    child = {
      type: 2.expression: ' '.tokens: ' '.text: text
    };
    if(child) { children.push(child); }}}function closeElement (element) {
  if(currentParent && ! element.forbidden) { currentParent.children.push(element); element.parent = currentParent; } element.children = element.children.filter(function (c) { return! (c).slotScope; }); }function createASTElement (tag, attrs, parent) {
  return {
    type: 1.tag: tag,
    attrsList: attrs,
    attrsMap: attrs,
    rawAttrsMap: {},
    parent: parent,
    children: []}}const content = '<template><div bg="1">hello<span>11</span></div></template>'

parseHTML(content, {
  start: start,
  end: end,
  chars: chars
});

console.log(root) // ast
Copy the code