vue parseHTML 函数源码解析AST基本形成

下面是关于“vue parseHTML 函数源码解析AST基本形成”的完整攻略:

什么是parseHTML函数

parseHTML是Vue.js中的一个函数,主要用来将HTML字符串解析成AST对象。AST(Abstract Syntax Tree)是指抽象语法树,它是源代码的抽象语法结构的树状表现形式。Vue的模板就是由HTML模板和vue上下文中的数据组合而成。parseHTML的作用是将HTML模板转化为AST对象,方便后续编译和渲染。

parseHTML的源码解析

下面是parseHTML的基本实现过程:

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 we're not in a plaintext content element like script/style
    if (!lastTag || !isPlainTextElement(lastTag)) {
      var textEnd = html.indexOf('<')
      if (textEnd === 0) {
        // Comment
        if (comment.test(html)) {
          var commentEnd = html.indexOf('-->')

          if (commentEnd >= 0) {
            advance(commentEnd + 3)
            continue
          }
        }

        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
        if (conditionalComment.test(html)) {
          var conditionalEnd = html.indexOf(']>')

          if (conditionalEnd >= 0) {
            advance(conditionalEnd + 2)
            continue
          }
        }

        // Doctype
        var doctypeMatch = html.match(doctype)
        if (doctypeMatch) {
          advance(doctypeMatch[0].length)
          continue
        }

        // 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)
          if (shouldIgnoreFirstNewline(last, html)) {
            advance(1)
          }
          continue
        }
      }

      var text = void 0
      if (textEnd >= 0) {
        text = html.substring(0, textEnd)
        advance(textEnd)
      } else {
        text = html
        html = ''
      }

      if (options.chars && text) {
        options.chars(text)
      }
    } else {
      var stackedTag = lastTag.toLowerCase()
      var reStackedTag = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i')
      var match = html.match(reStackedTag)
      if (match) {
        var text$$1 = match[1]
        if (options.chars) {
          options.chars(text$$1)
        }
        advance(text$$1.length + match[2].length)
        parseEndTag(stackedTag, index - text$$1.length, index)
      }

      if (!canBeLeftOpenTag$$1(stackedTag)) {
        parseEndTag(stackedTag)
      }
    }

    if (html === last) {
      options.chars && options.chars(html)
      break
    }
  }

  // Clean up any remaining tags
  parseEndTag()

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

  function parseStartTag() {
    var start = html.match(startTagOpen)
    if (start) {
      var match = {
        tagName: start[1],
        attrs: [],
        start: index
      }
      advance(start[0].length)
      var end, attr
      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)
        match.end = index
        return match
      }
    }
  }

  function handleStartTag(match) {
    var tagName = match.tagName
    var unarySlash = match.unarySlash

    if (expectHTML) {
      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
        parseEndTag(lastTag)
      }
      if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
        parseEndTag(tagName)
      }
    }

    var unary = isUnaryTag$$1(tagName) || !!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] || ''
      var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
        ? options.shouldDecodeNewlinesForHref
        : options.shouldDecodeNewlines
      attrs[i] = {
        name: args[1],
        value: decodeAttr(value, shouldDecodeNewlines)
      }
      if (process.env.NODE_ENV !== 'production' && 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 })
      lastTag = tagName
      unarySlash = ''
    }

    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }

  function parseEndTag(tagName, start, end) {
    var pos, lowerCasedTagName
    if (start == null) {
      start = index
    }
    if (end == null) {
      end = index
    }

    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase()
    }

    // Find the closest opened tag of the same type
    if (tagName) {
      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 OPENED tags that appear before the CLOSE tag
      for (var i = stack.length - 1; i >= pos; i--) {
        if (process.env.NODE_ENV !== 'production' && (i > pos || !tagName) && options.warn) {
          options.warn(
            ("tag <" + (stack[i].tag) + "> has no matching end tag.")
          )
        }
        if (options.end) {
          options.end(stack[i].tag, start, end)
        }
      }

      // Remove the closed tags from the stack
      stack.length = pos
      lastTag = pos && stack[pos - 1].tag
    } else if (lowerCasedTagName === 'br') {
      if (options.start) {
        options.start(tagName, [], true, start, end)
      }
    } else if (lowerCasedTagName === 'p') {
      if (options.start) {
        options.start(tagName, [], false, start, end)
      }
      if (options.end) {
        options.end(tagName, start, end)
      }
    }
  }
}

实现逻辑解析

上述代码实现了parseHTML函数,其实现逻辑主要有以下几个步骤:

  1. 首先定义了一个stack数组,用于存储解析中遍历到的元素信息。
  2. 循环处理html字符串,如果当前的元素不是纯文本节点,则查找接下来的标签,并依次处理。
  3. 如果发现标签是注释节点,则跳过。
  4. 如果发现标签是条件注释,则跳过。
  5. 如果发现标签是文档类型声明,则跳过。
  6. 如果发现标签是结束标签,则使用parseEndTag函数处理。
  7. 如果发现标签是开始标签,则使用parseStartTag函数处理。
  8. 处理结束后,再次使用parseEndTag函数处理stack数组中剩余的元素。

总体上,parseStartTag函数负责解析开始标签,handleStartTag函数负责处理解析到的开始标签;parseEndTag函数负责解析结束标签。在这三个函数的协调下,parseHTML函数完成了将HTML解析为AST对象的过程。

示例说明

下面给出两个使用parseHTML的示例,以更清晰地展示这个函数的用法:

示例1

<div>
  <p>这是一段HTML模板</p>
  <span>{{message}}</span>
</div>
import { parseHTML } from 'vue/src/compiler/parser/html-parser'

const html = '<div><p>这是一段HTML模板</p><span>{{message}}</span></div>'
const ast = parseHTML(html,{
  expectHTML: true,
  isUnaryTag: function () {},
  canBeLeftOpenTag: function () {},
  chars: function () {}
})
console.log(ast)

上述代码中,我们定义了一个HTML字符串,然后通过parseHTML函数将其解析为AST对象,并输出到控制台。其中,parseHTML函数的参数中包含一些配置项,包括是否期望解析为HTML,是否将指定标签解析为一元标签等。

示例2

<template>
  <div>
    <p>这是一段HTML模板</p>
    <span>{{message}}</span>
  </div>
</template>
import { parseHTML } from 'vue/src/compiler/parser/html-parser'

const html = '<div><p>这是一段HTML模板</p><span>{{message}}</span></div>'
const ast = parseHTML(html,{
  expectHTML: true,
  isUnaryTag: function (tag) {
    return tag === 'template'
  },
  canBeLeftOpenTag: function () {},
  chars: function () {}
})
console.log(ast)

上述代码中,我们定义了一个HTML字符串,其中包含了一个template标签。在解析时,我们通过将isUnaryTag函数的返回值设为true,将template标签解析为一元标签,而不是普通标签。

通过以上两个示例,我们可以看到,parseHTML函数具有很强的灵活性,可以通过不同的配置项对不同的HTML模板进行不同的解析处理。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:vue parseHTML 函数源码解析AST基本形成 - Python技术站

(0)
上一篇 2023年5月27日
下一篇 2023年5月27日

相关文章

  • vue基本使用–refs获取组件或元素的实例

    首先,需要了解refs是Vue提供的一个用于获取组件或DOM元素的实例的方法。因为在Vue中,父组件无法直接访问到子组件的实例或子节点的DOM元素,因此refs可以有效地帮助我们访问到这些内容。下面就是refs的使用攻略。 一、创建ref 我们可以通过在DOM元素上添加特殊的ref属性来创建refs。这个属性的值一般是一个字符串,被用来唯一标识该DOM元素或…

    Vue 2023年5月28日
    00
  • 尤大大新活petite-vue的实现

    首先,需要说明的是,Petite Vue是Vue的一个迷你版本,它依赖Vue 3,并且只有5KB的大小。它是为了在代码质量和性能之间找到一个理想的平衡点,以便开发者可以轻松编写高质量的代码并快速创建精美的交互效果。 Petite Vue使用与Vue 3相同的选项API,但是与Vue相比,它有一些不同之处,例如没有Virtual DOM和响应式系统的支持。在本…

    Vue 2023年5月28日
    00
  • 基于vue v-for 多层循环嵌套获取行数的方法

    要在Vue的模板中多层循环嵌套并获取每层循环的行数,可以使用如下的方法: <template> <div> <div v-for="(group, index) in groups" :key="index"> <div v-for="(item, i) in gro…

    Vue 2023年5月28日
    00
  • Vue + Webpack + Vue-loader学习教程之相关配置篇

    关于“Vue + Webpack + Vue-loader学习教程之相关配置篇”的完整攻略,我们需要从以下几个方面来讲解。 1. 安装Webpack 首先,在开始之前我们需要安装Webpack,可以在终端中输入以下命令进行安装: npm install –save-dev webpack 2. 安装Vue.js和Vue-loader 接下来,我们需要安装V…

    Vue 2023年5月28日
    00
  • React中Portals与错误边界处理实现

    当React应用程序遇到问题或出现错误时,错误边界(error boundaries)特别有用。错误边界是React组件,它会在渲染期间捕获并打印任何在树的子组件中抛出的JavaScript错误,并且,相当于错误被“停留”在边界内,防止应用程序崩溃。下面就让我们来详细讲解React中的错误边界处理以及Portals的使用。 错误边界(Error Bounda…

    Vue 2023年5月28日
    00
  • 浅谈vue2的$refs在vue3组合式API中的替代方法

    下面是关于“浅谈vue2的$refs在vue3组合式API中的替代方法”的详细讲解攻略: 1、什么是$refs 在Vue2中,我们可以通过在模板中给DOM元素添加ref属性,然后通过this.$refs来访问这个元素或组件实例,这个访问实例的方式就是Vue2中的$refs。 2、在vue3组合式API中$refs有什么改变 在Vue3中,Vue官方推荐使用组…

    Vue 2023年5月28日
    00
  • Vue.delete()删除对象的属性说明

    下面就来详细讲解一下Vue.delete()删除对象的属性说明。 Vue.delete() 概述 Vue框架中可以通过Vue.delete()方法来删除一个已有的对象属性。因为Vue.js做了很多数据方面的封装,如果我们直接改变对象属性值可能会导致一些问题,所以我们需要使用特定的方法来删除对象属性。 具体来说,Vue.delete()是Vue提供的全局方法,…

    Vue 2023年5月28日
    00
  • 使用命令行工具npm新创建一个vue项目的方法

    创建Vue项目的步骤如下: 安装Node.js和npm 首先需要在电脑上安装Node.js和npm(Node.js包管理工具)。Node.js是运行于服务器端的JavaScript解释器,而npm是随同Node.js一起安装的包管理器,用于管理Node.js模块和软件包。 使用命令行工具创建一个文件夹,用于存储Vue项目文件 在命令行工具(如Git Bash…

    Vue 2023年5月28日
    00
合作推广
合作推广
分享本页
返回顶部