blog icon indicating copy to clipboard operation
blog copied to clipboard

vue模板编译原理之生成AST

Open wuxianqiang opened this issue 3 years ago • 0 comments


var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\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 = /^<!DOCTYPE [^>]+>/i;
// #7298: escape - to avoid being passed as HTML comment when inlined in page
var reCache = {};
var comment = /^<!\--/;
var conditionalComment = /^<!\[/;

var isPlainTextElement = makeMap('script,style,textarea', true);

function makeMap(
  str,
  expectsLowerCase
) {
  var map = Object.create(null);
  var list = str.split(',');
  for (var i = 0; i < list.length; i++) {
    map[list[i]] = true;
  }
  return expectsLowerCase
    ? function (val) { return map[val.toLowerCase()]; }
    : function (val) { return map[val]; }
}

var no = function (a, b, c) { return false; };

function parseHTML(html, options) {
  var stack = [];
  var index = 0;
  var last, lastTag;
  while (html) {
    last = html;
    var textEnd = html.indexOf('<');
    if (textEnd === 0) {
      // End tag:
      var endTagMatch = html.match(endTag);
      if (endTagMatch) {
        var curIndex = index;
        advance(endTagMatch[0].length);
        parseEndTag(endTagMatch[1], curIndex, index);
        continue
      }

      // Start tag:
      var startTagMatch = parseStartTag();
      if (startTagMatch) {
        handleStartTag(startTagMatch);
        continue
      }
    }
    var text = (void 0), rest = (void 0), next = (void 0);
    if (textEnd >= 0) {
      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);
    }
  }

  // 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); // 去除 <div,剩下 id="1" bg="2">hello</div>
      var end, attr;
      // 把属性都存储起来 id="1" bg="2" 去除属性之后剩下 >hello</div>
      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); // 把>或者/>去除剩下 hello</div>
        match.end = index;
        return match
      }
    }
  }

  function handleStartTag(match) {
    var tagName = match.tagName;
    var unarySlash = match.unarySlash; // 自闭合标签为/ 双标签为‘’空

    var unary = !!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;
      }
    }
    // 双标签
    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
  // 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;
    }
  }
}
var currentParent;
var stack = [];
var root;

function end(tag, start) {
  var element = stack[stack.length - 1];
  // pop 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 stack 入栈
    stack.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 = '<div bg="1">hello<span>11</span></div>'

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

console.log(root) // ast


解析v-if指令

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

function end(tag, start) {
  var element = stack[stack.length - 1];
  // pop 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;
  }
  processIf(element);
  if (!unary) {
    currentParent = element;
    // push stack 入栈
    stack.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 makeAttrsMap (attrs) {
  var map = {};
  for (var i = 0, l = attrs.length; i < l; i++) {
    if (
      map[attrs[i].name] && !isIE && !isEdge
    ) {
      // warn$1('duplicate attribute: ' + attrs[i].name, attrs[i]);
    }
    map[attrs[i].name] = attrs[i].value;
  }
  return map
}

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

function getAndRemoveAttrByRegex (
  el,
  name
) {
  var list = el.attrsList;
  for (var i = 0, l = list.length; i < l; i++) {
    var attr = list[i];
    if (name.test(attr.name)) {
      list.splice(i, 1);
      return attr
    }
  }
}
// 删除attrsList,或者attrsMap的属性,根据name找到value
function getAndRemoveAttr (
  el,
  name,
  removeFromMap
) {
  var val;
  if ((val = el.attrsMap[name]) != null) {
    var list = el.attrsList;
    for (var i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1);
        break
      }
    }
  }
  if (removeFromMap) {
    delete el.attrsMap[name];
  }
  return val
}

function processIf (el) {
  var exp = getAndRemoveAttr(el, 'v-if');
  if (exp) {
    el.if = exp;
    addIfCondition(el, {
      exp: exp,
      block: el
    });
  }
}

function addIfCondition (el, condition) {
  if (!el.ifConditions) {
    el.ifConditions = [];
  }
  el.ifConditions.push(condition);
}

const content = '<div><span v-if="msg">11</span></div>'

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

console.log(root) // ast

解析v-for指令

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

function end(tag, start) {
  var element = stack[stack.length - 1];
  // pop 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;
  }
  processFor(element);
  if (!unary) {
    currentParent = element;
    // push stack 入栈
    stack.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 makeAttrsMap (attrs) {
  var map = {};
  for (var i = 0, l = attrs.length; i < l; i++) {
    if (
      map[attrs[i].name] && !isIE && !isEdge
    ) {
      // warn$1('duplicate attribute: ' + attrs[i].name, attrs[i]);
    }
    map[attrs[i].name] = attrs[i].value;
  }
  return map
}

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

function getAndRemoveAttrByRegex (
  el,
  name
) {
  var list = el.attrsList;
  for (var i = 0, l = list.length; i < l; i++) {
    var attr = list[i];
    if (name.test(attr.name)) {
      list.splice(i, 1);
      return attr
    }
  }
}
// 删除attrsList,或者attrsMap的属性,根据name找到value
function getAndRemoveAttr (
  el,
  name,
  removeFromMap
) {
  var val;
  if ((val = el.attrsMap[name]) != null) {
    var list = el.attrsList;
    for (var i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1);
        break
      }
    }
  }
  if (removeFromMap) {
    delete el.attrsMap[name];
  }
  return val
}

function processFor (el) {
  var exp;
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    var res = parseFor(exp);
    console.log(res)
    if (res) {
      extend(el, res);
    } else {
      // warn$1(
      //   ("Invalid v-for expression: " + exp),
      //   el.rawAttrsMap['v-for']
      // );
    }
  }
}
var stripParensRE = /^\(|\)$/g;
var forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
var forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;

function parseFor (exp) {
  var inMatch = exp.match(forAliasRE);
  if (!inMatch) { return }
  var res = {};
  res.for = inMatch[2].trim();
  var alias = inMatch[1].trim().replace(stripParensRE, '');
  var iteratorMatch = alias.match(forIteratorRE);
  if (iteratorMatch) {
    res.alias = alias.replace(forIteratorRE, '').trim();
    res.iterator1 = iteratorMatch[1].trim();
    if (iteratorMatch[2]) {
      res.iterator2 = iteratorMatch[2].trim();
    }
  } else {
    res.alias = alias;
  }
  return res
}

function extend (to, _from) {
  for (var key in _from) {
    to[key] = _from[key];
  }
  return to
}

const content = '<div><span v-for="item in list">11</span></div>'

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

console.log(root) // ast

解析v-once指令

  function processOnce (el) {
    var once$$1 = getAndRemoveAttr(el, 'v-once');
    if (once$$1 != null) {
      el.once = true;
    }
  }

wuxianqiang avatar Sep 11 '21 00:09 wuxianqiang