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日

相关文章

  • Vue3的路由传参方法超全汇总

    Vue3的路由传参方法超全汇总 1、在路由路径中传递参数 在路由路径中传递参数是最基本的方法。在定义路由时,可以在路由路径中使用/:参数名表示该参数。例如: const routes = [ { path: ‘/user/:id’, component: User } ] 在上面的代码中,我们定义了一个名为id的参数,使用时路由路径类似于/user/123,…

    Vue 2023年5月27日
    00
  • element动态路由面包屑的实现示例

    下面是关于“element动态路由面包屑的实现示例”的详细攻略。 什么是动态路由面包屑? 在前端项目中,路由和面包屑导航都是非常重要的概念。路由决定了页面的展示,而面包屑则可以让用户更好地了解当前页面所在的位置和路径。而基于element组件库,可以实现动态路由面包屑,也就是根据用户的页面访问路径,自动生成面包屑导航,而不需要手动配置。 实现步骤 下面是el…

    Vue 2023年5月28日
    00
  • vue-router钩子函数实现路由守卫

    下面为你详细讲解Vue Router钩子函数实现路由守卫的完整攻略。 钩子函数介绍 Vue Router提供了多个钩子函数,可以在路由发生变化时执行一些操作。以下是常用的钩子函数: beforeEach(to, from, next) :进入路由之前触发,next必须调用,才能进入下一个钩子。其中参数to和from分别表示即将进入的路由和即将离开的路由。 a…

    Vue 2023年5月28日
    00
  • Vue实现红包雨小游戏的示例代码

    首先我们需要了解什么是Vue.js和红包雨小游戏。 Vue.js是一个流行的JavaScript库,用于构建动态的Web应用程序。它被广泛应用于许多前端开发工程中,具有代码简洁、易于维护、高效等特点。 红包雨小游戏是一种神奇的小游戏,玩家将会在游戏中追逐红包,在一定时间内尝试抢到更多的红包。这种游戏具有趣味性和竞争性,并且可以在不同的设备上进行玩耍,如PC、…

    Vue 2023年5月27日
    00
  • Vue非父子组件之间的通信方式详解

    Vue非父子组件之间的通信方式详解 在Vue中,父子组件之间的数据传递和通信比较容易实现,但是在非父子组件之间的通信则需要通过一些特殊的方式来实现。本文将详细介绍Vue非父子组件之间的通信方式。 1. 公共事件总线 公共事件总线是一种非常简单和常用的非父子组件通信方式,它利用Vue实例作为一个中央事件总线来进行通信。具体实现步骤如下: 实例化一个Vue实例并…

    Vue 2023年5月27日
    00
  • 让你30分钟快速掌握vue3教程

    下面是详细讲解“让你30分钟快速掌握Vue3教程”的完整攻略: 1. 前置知识 在学习Vue3之前,最好了解以下知识: 基本的HTML、CSS和JavaScript知识。 Vue.js的基本概念和语法。如果你不了解Vue.js,请先学习Vue.js的教程。 2. 安装以及项目搭建 首先要安装Vue.js 3。可以使用以下命令安装: npm install -…

    Vue 2023年5月27日
    00
  • vscode 插件开发 + vue的操作方法

    Vscode 插件开发 + Vue 操作方法 在本文中,我们将介绍如何使用 VSCode 开发插件,并在插件中使用 Vue。 环境要求 在开始使用 Vue 进行开发之前,我们需要先安装好以下环境: Node.js Visual Studio Code Vue CLI 如果您的电脑上还没有这些环境,请先安装好它们。 创建一个 VSCode 插件项目 使用以下命…

    Vue 2023年5月28日
    00
  • Vue中this.$nextTick()的理解与使用方法

    理解this.$nextTick()方法 在Vue中,数据绑定是异步执行的,这意味着当我们改变了数据,没有立即反应到页面上。Vue的响应式系统会在下一次事件循环(Event Loop)中重新计算 DOM,并更新 DOM,这样可以保证性能。为了确保在DOM更新后再执行回调函数,可以使用Vue提供的方法:this.$nextTick()。 this.$nextT…

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