vue 通过 parseHTML 转成 AST

302 阅读1分钟

@method: parseHTML

@params: html(vue中的template),options(特殊配置)

@return match(AST树)

例子:

html = “

1-2
1-3

1、进入while循环

2、记录第一个字符串<的位置,区分是否需要清楚文本内容

3、startTagOpen正则匹配标签的开始部分,包括了标签带有的属性

4、对匹配到的标签进行属性匹配,循环将并将属性添加到match.attr数组中。

5、每次匹配后都会用index记录当前位置,并将html向前移动指定位置

function parseHTML (html,options) {
  const stack = []  const expectHTML = options.expectHTML  const isUnaryTag = options.isUnaryTag || no  const canBeLeftOpenTag = options.canBeLeftOpenTag || no  let index = 0  let last, lastTag  while (html) {    last = html    // Make sure we're not in a plaintext content element like script/style    if (!lastTag || !isPlainTextElement(lastTag)) {      let textEnd = html.indexOf('<')      if (textEnd === 0) {        // Comment:        if (comment.test(html)) {          const commentEnd = html.indexOf('-->')          if (commentEnd >= 0) {            if (options.shouldKeepComment) {              options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)            }            advance(commentEnd + 3)            continue          }        }        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment        if (conditionalComment.test(html)) {          const conditionalEnd = html.indexOf(']>')          if (conditionalEnd >= 0) {            advance(conditionalEnd + 2)            continue          }        }        // Doctype:        const doctypeMatch = html.match(doctype)        if (doctypeMatch) {          advance(doctypeMatch[0].length)          continue        }        // End tag:        const endTagMatch = html.match(endTag)        if (endTagMatch) {          const curIndex = index          advance(endTagMatch[0].length)          parseEndTag(endTagMatch[1], curIndex, index)          continue        }        // Start tag:        const startTagMatch = parseStartTag()        if (startTagMatch) {          handleStartTag(startTagMatch)          if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {            advance(1)          }          continue        }      }      let text, rest, next      if (textEnd >= 0) {        rest = html.slice(textEnd)        while (          !endTag.test(rest) &&          !startTagOpen.test(rest) &&          !comment.test(rest) &&          !conditionalComment.test(rest)        ) {          // < in plain text, be forgiving and treat it as text          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 {      let endTagLength = 0      const stackedTag = lastTag.toLowerCase()      const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))      const rest = html.replace(reStackedTag, function (all, text, endTag) {        endTagLength = endTag.length        if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {          text = text            .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298            .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1')        }        if (shouldIgnoreFirstNewline(stackedTag, text)) {          text = text.slice(1)        }        if (options.chars) {          options.chars(text)        }        return ''      })      index += html.length - rest.length      html = rest      parseEndTag(stackedTag, index - endTagLength, index)    }    if (html === last) {      options.chars && options.chars(html)      if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {        options.warn(`Mal-formatted tag at end of template: "${html}"`, { start: index + html.length })      }      break    }  }
}