Vue3编译器

编译:将源代码翻译成目标代码的过程

完整编译过程:
完整编译过程
整个编译过程可以分为编译前端和编译后端。编译前端包含词法分析、语法分析和语义分析,通常与目标平台无关,仅负责分析源代码。编译后端通常与目标平台有关,涉及中间代码的生成、优化及目标代码生成。

Vue编译流程

对Vue的模板编译器来说,源代码就是组件的模板,标代码就是渲染函数
编译器工作流程
AST是 abstract snytax tree 的首字母缩写,即抽象语法树,是一个具有层级结构的对象。
模板AST就是用来描述模板的抽象语法树,具有与模板同构的嵌套结构。

  • 不同类型的节点通过type属性区分
  • 子节点存储在children数组中
  • 标签节点的属性节点和指令节点存储在props数组中
  • 不同类型的节点会使用不同的对象属性进行描述
const ast = {
   
	// 逻辑根节点
	type: 'Root',
	children: [
		// div标签节点
		{
   
			type: 'Element',
			tag: 'div',
			children: [
				// h1标签节点
				{
   
					type: 'Element',
					tag: 'h1',
					props: [
						// v-if指令节点
						{
   
							type: 'Directive',
							name: 'if'
							exp: {
   
								// 表达式节点
								type: 'Expression',
								content: 'ok'
							}
						}
					]
				}
			]
		}
	
	]
}

对具体步骤进行封装

  • parse():完成对模板的词法分析和语法分析,得到模板AST
  • transform():完成对模板的语义分析,实现模板AST到JavaScript AST的转换工作
  • generate():根据JavaScript AST生成渲染函数
    在这里插入图片描述
// 目标代码生成过程
const templateAST = parse(template)
const jsAST = transform(templateAST)
const code = generate(jsAST)

parse()

将模板字符串解析为模板AST的解析器。

解析器的入参是字符串模板,解析器会逐个读取字符串模板中的字符,根据一定的规则通过有限状态自动机进行切割。

有限状态指有限个状态,自动机指随着字符的输入,解析器会自动在不同状态间迁移。

有限状态自动机可以帮助我们完成对模板的标记化

文本模式指的是解析器在工作时所进入的一些特殊状态,在不同的特殊状态下,解析器对文本的解析行为会有所不同。解析器行为规范

模式 能否解析标签 是否支持HTML实体
DATA
RCDATA
RAWTEXT
CDATA

解析器的状态机图

const parse = (str) => {
   
  // 定义上下文对象
  const context = {
   
    // source是模板内容,用于在解析过程中进行消费
    source: str,
    // 解析器当前处于的文本模式,初始模式为DATA
    mode: TextModes.DATA,
    // 消费指定数量的字符
    advanceBy(num) {
   
      context.source = context.source.slice(num);
    },
    // 消费空白字符
    advanceSpaces() {
   
      // 匹配空白字符
      const match = /^[\t\r\n\f ]+/.exec(context.source);
      if (match) {
   
        // 消费空白字符
        context.advanceBy(match[0].length);
      }
    },
  };
  // 调用parseChildren函数开始解析, 它返回解析后得到的子节点
  const nodes = parseChildren(context, []);

  return {
   
    type: "Root",
    children: nodes,
  };
};

解析子元素

function parseChildren(context, ancestors) {
   
  // 定义nodes数组存储子节点, 是最终的返回值
  let nodes = [];
  // 从上下文对象中取得当前状态,包括模式mode和模板内容source
  const {
    mode, source } = context;

  // 开启while循环,只要满足条件就会一直对字符串进行解析
  while (!isEnd(context, ancestors)) {
   
    let node;
    // 只有DATA模式和RCDATA模式才支持插值节点的解析
    if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
   
      // 只有DATA模式才支持标签节点的解析
      if (mode === TextModes.DATA && source[0] === "<") {
   
        if (source[1] === "!") {
   
          if (source.startsWith("<!--")) {
   
            // 注释
            node = parseComment(context);
          } else if (source.startsWith("<![CDATA[")) {
   
            // CDATA
            node = parseCDATA(context, ancestors);
          }
        } else if (source[1] === "/") {
   
          // 结束标签
          // 状态机缺少与之对应的开始标签
          console.log("无效的结束标签");
          continue;
        } else if (/[a-z]/i.test(source[1])) {
   
          // 标签
          node = parseElement(context, ancestors);
        }
      } else if (source.startsWith("{
   {")) {
   
        // 解析插值
        node = parseInterpolation(context);
      }
    }

    // node不存在,说明处于其他模式,此时一切内容都作为文本处理
    if (!node) {
   
      // 解析文本节点
      node = parseText(context);
    }

    nodes.push(node);
  }

  return nodes;
}


解析器遇到开始标签时,将该标签压入父级节点栈,并开启新的状态机;遇到结束标签且父级节点栈中存在于该标签同名的开始标签节点时,停止当前正在运行的状态机

function isEnd(context, ancestors) {
   
  // 当模版内容解析完毕后,停止
  if (!context.source) return true;

  // 与父级节点栈内所有节点作比较
  for (let i = ancestors.length - 1; i >= 0; --i) {
   
    // 如果遇到结束标签,只要父级节点栈中存在与该标签同名的开始标签节点,就停止
    if (context.source.startWith(`</${
     ancestors[i].tag}`)) {
   
      return true
  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值