【Vue3】源码探索之旅:compiler-core之parseChildren函数(二)

简言

parseChildren函数是在baseParse函数中作为createRoot函数的子节点参数传入的,今天来探索下parseChildren函数。
parseChildren在 compiler-core/src/parse.ts文件内
在这里插入图片描述

parseChildren

这个函数就是用来解析模板字符串内容的 ,里面有个while循环以栈的形式不断读取模板字符串进行解析,直到解析完毕,然后进行空白处理并返回对应的节点数据。

传参:

  • context ---- 解析上下文(createParserContext函数根据模板字符串和配置得到的)。
  • mode ---- 文本模式 。vue将模板字符串分为5个类型(见下面:DATA代表普通的元素标签,RCDATA代表textarea标签,RAWTEXT代表文本)。
  • ancestors — 祖先节点,类型为元素节点数组。

文本模式:

export const enum TextModes {//          | Elements | Entities | End sign              | Inside ofDATA, //    | ✔        | ✔        | End tags of ancestors |RCDATA, //  | ✘        | ✔        | End tag of the parent | <textarea>RAWTEXT, // | ✘        | ✘        | End tag of the parent | <style>,<script>CDATA,ATTRIBUTE_VALUE
}

源码

function parseChildren(context: ParserContext,mode: TextModes,ancestors: ElementNode[]
): TemplateChildNode[] {const parent = last(ancestors)const ns = parent ? parent.ns : Namespaces.HTMLconst nodes: TemplateChildNode[] = []while (!isEnd(context, mode, ancestors)) {__TEST__ && assert(context.source.length > 0)const s = context.sourcelet node: TemplateChildNode | TemplateChildNode[] | undefined = undefinedif (mode === TextModes.DATA || mode === TextModes.RCDATA) {if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {// '{{'node = parseInterpolation(context, mode)} else if (mode === TextModes.DATA && s[0] === '<') {// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-stateif (s.length === 1) {emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)} else if (s[1] === '!') {// https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-stateif (startsWith(s, '<!--')) {node = parseComment(context)} else if (startsWith(s, '<!DOCTYPE')) {// Ignore DOCTYPE by a limitation.node = parseBogusComment(context)} else if (startsWith(s, '<![CDATA[')) {if (ns !== Namespaces.HTML) {node = parseCDATA(context, ancestors)} else {emitError(context, ErrorCodes.CDATA_IN_HTML_CONTENT)node = parseBogusComment(context)}} else {emitError(context, ErrorCodes.INCORRECTLY_OPENED_COMMENT)node = parseBogusComment(context)}} else if (s[1] === '/') {// https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-stateif (s.length === 2) {emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 2)} else if (s[2] === '>') {emitError(context, ErrorCodes.MISSING_END_TAG_NAME, 2)advanceBy(context, 3)continue} else if (/[a-z]/i.test(s[2])) {emitError(context, ErrorCodes.X_INVALID_END_TAG)parseTag(context, TagType.End, parent)continue} else {emitError(context,ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME,2)node = parseBogusComment(context)}} else if (/[a-z]/i.test(s[1])) {node = parseElement(context, ancestors)// 2.x <template> with no directive compatif (__COMPAT__ &&isCompatEnabled(CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,context) &&node &&node.tag === 'template' &&!node.props.some(p =>p.type === NodeTypes.DIRECTIVE &&isSpecialTemplateDirective(p.name))) {__DEV__ &&warnDeprecation(CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,context,node.loc)node = node.children}} else if (s[1] === '?') {emitError(context,ErrorCodes.UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME,1)node = parseBogusComment(context)} else {emitError(context, ErrorCodes.INVALID_FIRST_CHARACTER_OF_TAG_NAME, 1)}}}if (!node) {node = parseText(context, mode)}if (isArray(node)) {for (let i = 0; i < node.length; i++) {pushNode(nodes, node[i])}} else {pushNode(nodes, node)}}// Whitespace handling strategy like v2let removedWhitespace = falseif (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {const shouldCondense = context.options.whitespace !== 'preserve'for (let i = 0; i < nodes.length; i++) {const node = nodes[i]if (node.type === NodeTypes.TEXT) {if (!context.inPre) {if (!/[^\t\r\n\f ]/.test(node.content)) {const prev = nodes[i - 1]const next = nodes[i + 1]// Remove if:// - the whitespace is the first or last node, or:// - (condense mode) the whitespace is between twos comments, or:// - (condense mode) the whitespace is between comment and element, or:// - (condense mode) the whitespace is between two elements AND contains newlineif (!prev ||!next ||(shouldCondense &&((prev.type === NodeTypes.COMMENT &&next.type === NodeTypes.COMMENT) ||(prev.type === NodeTypes.COMMENT &&next.type === NodeTypes.ELEMENT) ||(prev.type === NodeTypes.ELEMENT &&next.type === NodeTypes.COMMENT) ||(prev.type === NodeTypes.ELEMENT &&next.type === NodeTypes.ELEMENT &&/[\r\n]/.test(node.content))))) {removedWhitespace = truenodes[i] = null as any} else {// Otherwise, the whitespace is condensed into a single spacenode.content = ' '}} else if (shouldCondense) {// in condense mode, consecutive whitespaces in text are condensed// down to a single space.node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')}} else {// #6410 normalize windows newlines in <pre>:// in SSR, browsers normalize server-rendered \r\n into a single \n// in the DOMnode.content = node.content.replace(/\r\n/g, '\n')}}// Remove comment nodes if desired by configuration.else if (node.type === NodeTypes.COMMENT && !context.options.comments) {removedWhitespace = truenodes[i] = null as any}}if (context.inPre && parent && context.options.isPreTag(parent.tag)) {// remove leading newline per html spec// https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-elementconst first = nodes[0]if (first && first.type === NodeTypes.TEXT) {first.content = first.content.replace(/^\r?\n/, '')}}}return removedWhitespace ? nodes.filter(Boolean) : nodes
}

下面的流程只描述大致的流程信息,若有误请联系作者改正。

流程:

  • 创建nodes数组用来承载解析的数据,进入while循环。
  • 执行isEnd函数判断是否读取完毕,是则跳出while循环。
  • 创建node用于承载此次循环解析的数据,并获取当前读取的内容。
  • TextModes.DATA和TextModes.RCDATA下进行特定的分类解析。
  • 若是是否是以{{开头,parseInterpolation函数进行文本插值解析。
  • 若是TextModes.DATA下且以<开头,进行标签解析,进入标签类型的解析。
  • 若以<开头且下一个字符是!,说明是注释标签、文档声明标签、<![CDATA[]]>标签(忽略xml的转义);分别进行对应的解析。
  • 若以<开头且下一个字符是/,有可能是闭合标签,则进行parseTag函数标签解析。
  • 若以<开头且下一个字符是a-z任意字符(不分大小写),进行parseElement函数解析。
  • 若node还没值,执行parseText函数按文本进行解析。
  • 将node的值添加到nodes中,执行第2步
  • 将nodes值按照配置的空白策略进行空白处理
  • 返回nodes。

使用到的一些函数

只挑选用到的一些函数进行分析。

isEnd

判断是否读取完毕。
参考:

  • startsWith函数是 以什么开头。
  • startsWithEndTagOpen 是否是完整的相匹配的闭合标签。
    true的情况:
  • TextModes.DATA下以</开头且是匹配的闭合标签,返回true
  • TextModes.RCDATA(textarea)和TextModes.RAWTEXT(文本)下,存在父节点,且匹配父节点的闭合标签,返回true。
  • TextModes.CDATA(xml中的忽略转义的写法)下,以]]>开头,返回true。
  • s 为假值,返回true
function isEnd(context: ParserContext,mode: TextModes,ancestors: ElementNode[]
): boolean {const s = context.sourceswitch (mode) {case TextModes.DATA:if (startsWith(s, '</')) {// TODO: probably bad performancefor (let i = ancestors.length - 1; i >= 0; --i) {if (startsWithEndTagOpen(s, ancestors[i].tag)) {return true}}}breakcase TextModes.RCDATA:case TextModes.RAWTEXT: {const parent = last(ancestors)if (parent && startsWithEndTagOpen(s, parent.tag)) {return true}break}case TextModes.CDATA:if (startsWith(s, ']]>')) {return true}break}return !s
}

parseInterpolation

解析文本插值内容,也就是{{表达式}}这种,最后返回文本插值类型的结构数据节点。
参考:

  • parseTextData ---- 处理 {{表达式}}内的表达式。
  • getCursor — 获取当前读取位置
  • advanceBy — 改变上下文中的source
  • advancePositionWithMutation ---- 修正读取位置信息描述的函数。

文本插值大家都知道,它是vue的数据在模板显示的主要方式之一,里面可以填表达式或者静态字符串,所以{{}}里面的内容使用parseTextData函数解析,这个函数要返回文本字符串,要么返回一个解析好后的html实体。

function parseInterpolation(context: ParserContext,mode: TextModes
): InterpolationNode | undefined {const [open, close] = context.options.delimiters__TEST__ && assert(startsWith(context.source, open))const closeIndex = context.source.indexOf(close, open.length)if (closeIndex === -1) {emitError(context, ErrorCodes.X_MISSING_INTERPOLATION_END)return undefined}const start = getCursor(context)advanceBy(context, open.length)const innerStart = getCursor(context)const innerEnd = getCursor(context)const rawContentLength = closeIndex - open.lengthconst rawContent = context.source.slice(0, rawContentLength)const preTrimContent = parseTextData(context, rawContentLength, mode)const content = preTrimContent.trim()const startOffset = preTrimContent.indexOf(content)if (startOffset > 0) {advancePositionWithMutation(innerStart, rawContent, startOffset)}const endOffset =rawContentLength - (preTrimContent.length - content.length - startOffset)advancePositionWithMutation(innerEnd, rawContent, endOffset)advanceBy(context, close.length)return {type: NodeTypes.INTERPOLATION,content: {type: NodeTypes.SIMPLE_EXPRESSION,isStatic: false,// Set `isConstant` to false by default and will decide in transformExpressionconstType: ConstantTypes.NOT_CONSTANT,content,loc: getSelection(context, innerStart, innerEnd)},loc: getSelection(context, start)}
}

parseComment

解析注释的函数,返回注释类型的结构数据节点。
参考:

  • emitError 触发报错的函数。
  • getSelection 返回包含目标内容的位置信息对象。
function parseComment(context: ParserContext): CommentNode {__TEST__ && assert(startsWith(context.source, '<!--'))const start = getCursor(context)let content: string// Regular comment.const match = /--(\!)?>/.exec(context.source)if (!match) {content = context.source.slice(4)advanceBy(context, context.source.length)emitError(context, ErrorCodes.EOF_IN_COMMENT)} else {if (match.index <= 3) {emitError(context, ErrorCodes.ABRUPT_CLOSING_OF_EMPTY_COMMENT)}if (match[1]) {emitError(context, ErrorCodes.INCORRECTLY_CLOSED_COMMENT)}content = context.source.slice(4, match.index)// Advancing with reporting nested comments.const s = context.source.slice(0, match.index)let prevIndex = 1,nestedIndex = 0while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {advanceBy(context, nestedIndex - prevIndex + 1)if (nestedIndex + 4 < s.length) {emitError(context, ErrorCodes.NESTED_COMMENT)}prevIndex = nestedIndex + 1}advanceBy(context, match.index + match[0].length - prevIndex + 1)}return {type: NodeTypes.COMMENT,content,loc: getSelection(context, start)}
}

parseElement

解析元素内容的函数,返回元素类型的结构数据节点。
一个普通的元素包含: 开标签 + 子内容 + 闭标签;所以parseElement就是按这个顺序处理的,parseTag处理标签,parseChildren函数处理子内容。
参考:

  • parseTag — 解析标签的函数。
  • checkCompatEnabled — 判断是否可以进行兼容处理。
function parseElement(context: ParserContext,ancestors: ElementNode[]
): ElementNode | undefined {__TEST__ && assert(/^<[a-z]/i.test(context.source))// Start tag.const wasInPre = context.inPreconst wasInVPre = context.inVPreconst parent = last(ancestors)const element = parseTag(context, TagType.Start, parent)const isPreBoundary = context.inPre && !wasInPreconst isVPreBoundary = context.inVPre && !wasInVPreif (element.isSelfClosing || context.options.isVoidTag(element.tag)) {// #4030 self-closing <pre> tagif (isPreBoundary) {context.inPre = false}if (isVPreBoundary) {context.inVPre = false}return element}// Children.ancestors.push(element)const mode = context.options.getTextMode(element, parent)const children = parseChildren(context, mode, ancestors)ancestors.pop()// 2.x inline-template compatif (__COMPAT__) {const inlineTemplateProp = element.props.find(p => p.type === NodeTypes.ATTRIBUTE && p.name === 'inline-template') as AttributeNodeif (inlineTemplateProp &&checkCompatEnabled(CompilerDeprecationTypes.COMPILER_INLINE_TEMPLATE,context,inlineTemplateProp.loc)) {const loc = getSelection(context, element.loc.end)inlineTemplateProp.value = {type: NodeTypes.TEXT,content: loc.source,loc}}}element.children = children// End tag.if (startsWithEndTagOpen(context.source, element.tag)) {parseTag(context, TagType.End, parent)} else {emitError(context, ErrorCodes.X_MISSING_END_TAG, 0, element.loc.start)if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {const first = children[0]if (first && startsWith(first.loc.source, '<!--')) {emitError(context, ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT)}}}element.loc = getSelection(context, element.loc.start)if (isPreBoundary) {context.inPre = false}if (isVPreBoundary) {context.inVPre = false}return element
}

parseText

解析文本内容,返回文本类型的结构数据节点。

function parseText(context: ParserContext, mode: TextModes): TextNode {__TEST__ && assert(context.source.length > 0)const endTokens =mode === TextModes.CDATA ? [']]>'] : ['<', context.options.delimiters[0]]let endIndex = context.source.lengthfor (let i = 0; i < endTokens.length; i++) {const index = context.source.indexOf(endTokens[i], 1)if (index !== -1 && endIndex > index) {endIndex = index}}__TEST__ && assert(endIndex > 0)const start = getCursor(context)const content = parseTextData(context, endIndex, mode)return {type: NodeTypes.TEXT,content,loc: getSelection(context, start)}
}

parseBogusComment

解析伪注释内容,返回注释类型的结构数据节点。
一些内容会被这个函数当作注释解析,并会给出警告信息。

function parseBogusComment(context: ParserContext): CommentNode | undefined {__TEST__ && assert(/^<(?:[\!\?]|\/[^a-z>])/i.test(context.source))const start = getCursor(context)const contentStart = context.source[1] === '?' ? 1 : 2let content: stringconst closeIndex = context.source.indexOf('>')if (closeIndex === -1) {content = context.source.slice(contentStart)advanceBy(context, context.source.length)} else {content = context.source.slice(contentStart, closeIndex)advanceBy(context, closeIndex + 1)}return {type: NodeTypes.COMMENT,content,loc: getSelection(context, start)}
}

结语

上篇:【vue3】vue3源码探索之旅:compiler-core(一)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/663473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS局限属性contain:优化渲染性能的利器

CSS局限属性contain&#xff1a;优化渲染性能的利器 在网页开发中&#xff0c;优化渲染性能是一个重要的目标。CSS局限属性contain是一个强大的工具&#xff0c;可以帮助我们提高网页的渲染性能。本文将介绍contain属性的基本概念、用法和优势&#xff0c;以及如何使用它来优化…

docker-学习-4

docker学习第四天 docker学习第四天1. 回顾1.1. 容器的网络类型1.2. 容器的本质1.3. 数据的持久化1.4. 看有哪些卷1.5. 看卷的详细信息 2. 如何做多台宿主机里的多个容器之间的数据共享2.1. 概念2.2. 搭NFS服务器实现多个容器之间的数据共享的详细步骤2.3. 如果是多台机器&…

web应用课——(第二讲:CSS)

目录 一、实战项目一&#xff1a;Acwing名片 二、实战项目二&#xff1a;Bilibili名片 三、样式定义方式 四、选择器 五、颜色 六、文本 七、字体 八、背景 九、边框 十、元素展示格式 十一、内边距与外边距 十二、盒子模型 十三、位置 十四、浮动 十五、flex布…

T113-Pro的buildroot添加gdisk ( GPT disks )出现gptfdisk needs a toolchain w/ C++的解决方法

问题背景&#xff1a; 最近入手了百问网的全志T113-Pro&#xff0c;用Emmc启动发现一张32GB的SD卡在烧录了百问网镜像 100ask-t113-pro_sdcard.img 的系统后&#xff0c;仅有200多M的存储空间。第一时间上百问网论坛看是否有板友也出现类似情况&#xff0c;发现了一个帖子正是描…

幻兽帕鲁能在Mac上运行吗?幻兽帕鲁Palworld新手攻略

幻兽帕鲁能在Mac上运行吗&#xff1f; 《幻兽帕鲁》目前还未正式登陆Mac平台&#xff0c;不过通过一些方法是可以让游戏在该平台运行的。 虽然游戏不能在最高配置下运行&#xff0c;但如果你安装了CrossOver这个软件&#xff0c;就可以玩了。这是为Mac、Linux和ChromeOS等设计…

2024最新版IntelliJ IDEA安装使用指南

2024最新版IntelliJ IDEA安装使用指南 Installation and Usage Guide to the Latest JetBrains IntelliJ IDEA Community Editionn in 2024 By JacksonML JetBrains公司开发的IntelliJ IDEA一经问世&#xff0c;就受到全球Java/Kotlin开发者的热捧。这款集成开发环境&#xf…

Spring: 实体类转换工具总结

文章目录 一、MapStruct1、介绍2、原理3、使用4、问题处理&#xff08;1&#xff09;IDEA编译报错&#xff1a;NullPointerException 一、MapStruct 1、介绍 MapStruct是一个实体类属性映射工具&#xff0c;通过注解的方式实现将一个实体类的属性值映射到另外一个实体类中。在…

unity3d的海盗王白银城演示

这是一个外网上的下载的海盗王unity3d制作的白银城演示场景。 地图只含有白银城区&#xff0c;没有野外和怪物。 当然也没有服务器端的。 我对灯光、摄像头、天空背景等做过调整&#xff0c;使它显示起来比较鲜丽。 它的模型和贴图是直接拿了海盗的&#xff0c;没有做过优化调整…

Kotlin 协程:深入理解 ‘lifecycleScope‘

Kotlin 协程&#xff1a;深入理解 ‘lifecycleScope’ Kotlin 协程是一种强大的异步编程工具&#xff0c;它提供了一种简洁、易读的方式来处理并发和异步操作。在 Kotlin 协程库中&#xff0c;lifecycleScope 是一个关键的概念&#xff0c;它允许我们将协程的生命周期绑定到 An…

第二十一回 阎婆大闹郓城县 朱仝义释宋公明-FreeBSD Linux 使用Rsync备份

阎婆状告宋江杀死她女儿阎婆惜&#xff0c;知县有意偏袒宋江&#xff0c;只是一味的拷打唐牛儿&#xff0c;但无奈张三张文远说刀子是宋江的&#xff0c;知县不得已差人拿宋江来审问。第一次没见到人&#xff0c;第二次派朱仝雷横两个人去。 朱仝到地窖里找到了躲藏的宋江&…

QT使用QFileSystemModel实现的文件资源管理器(开源)

文章目录 效果图现实的功能总体框架功能介绍视图双击进入处理复制与剪切粘贴重命名&#xff0c;新建显示文件详细信息文件路径导航栏 总结 效果图 现实的功能 支持文件/文件夹复制&#xff0c;粘贴&#xff0c;剪切&#xff0c;删除&#xff0c;重命名的基本操作支持打开图片&…

【算法分析与设计】交换两个节点

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;算法分析与设计 ⛺️稳中求进&#xff0c;晒太阳 题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本…

Python程序设计 函数基础

简单函数 函数&#xff1a;就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用。 函数的使用包含两个步骤&#xff1a; 定义函数 —— 封装 独立的功能 调用函数 —— 享受 封装 的成果 函数的作用&#xff0c;在开发程序时&#xff0c;使用…

【毕业日记】2024.01 - 慢下来,静待花开

转眼距离930离开鹅厂已经120天了&#xff0c;我是很能拖延的&#xff0c;或者是很懂自我麻痹的&#xff0c;这三个多月&#xff0c;一直想要写点东西纪念&#xff0c;一直拖一直拖一直拖…… 疫情这几年经济下行里裁员是个茶余饭后“嬉笑”之余经常被提起的词&#xff0c;部门滚…

【Qt基本功修炼】Qt线程的两种运行模式

1. 前言 QThread是Qt中的线程类&#xff0c;用于实现多线程运行。 QThread有两种工作模式&#xff0c;即 消息循环模式无消息循环模式 两种模式分别适用于不同的场景。下面我们将从多个方面&#xff0c;讲解QThread两种工作模式的区别。 2. 消息循环模式 2.1 实现原理 Q…

PAT-Apat甲级题1004(python和c++实现)

PTA | 1004 Counting Leaves 1004 Counting Leaves 作者 CHEN, Yue 单位 浙江大学 A family hierarchy is usually presented by a pedigree tree. Your job is to count those family members who have no child. Input Specification: Each input file contains one te…

排序(5)——归并排序

六、归并排序 1.简介 归并排序也是一种很经典的排序算法&#xff0c;采用分治的思想方法进行数据的处理。归并讲究的是先拆后合&#xff0c;也就是分治中的分而治之。在拿到一组数据后&#xff0c;程序会将整个数据进行不断拆分直至有序&#xff0c;因为单个元素必然有序&…

教你怎么给接口定义错误码

错误码一般包括三个要素&#xff1a; 前缀标识&#xff1a;区分错误类型&#xff0c;比如请求不合法&#xff0c;还是服务器处理错误。模块标识&#xff1a;区分到底是哪个模块的错误。错误代码&#xff1a;区分具体是什么问题。 从错误码的符号组成上看又分为三类&#xff1…

网络发展历程及SD-WAN的优势

自网络出现以来&#xff0c;经历了不同时期的发展阶段&#xff0c;而SD-WAN作为网络发展的新趋势&#xff0c;以其独特的优势满足了当今企业对于高效、灵活和成本控制的网络需求。 一、网络发展历程 &#xff08;1&#xff09;初始阶段&#xff1a;专线网络 从1980年代到2000…

VBA数据库解决方案第八讲:SQL语句及打开记录集

《VBA数据库解决方案》教程&#xff08;版权10090845&#xff09;是我推出的第二套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;是学完字典后的另一个专题讲解。数据库是数据处理的利器&#xff0c;教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…