Vue3 compile模块 —— 学习编写AST抽象语法树

240 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

最近在看vue3.0的compile模块,用到了AST抽象语法树,在网上找资料发现国内比较缺乏相关内容,看到热门的是用一个插件去解析和操纵JavaScript版的AST语法树,但我想了解的是单页面应用里的标签是怎么形成语法树的,自己能否学着写一下。


一、AST抽象语法树是什么?

AST(Abstract Syntax Tree)是源代码的抽象语法结构树状表现形式

个人理解为将源代码转化为各种关键字再以树的形式呈现

二、看一眼AST语法树

vue3.0模板资源管理器 vue-next-template-explorer.netlify.app/ 按F12 打开控制台,我们就能看到vue转换过来的AST语法树 在这里插入图片描述 我们了解到语法树长什么样子,我们就能尝试去写属于我们的AST抽象语法树了。

三、写一下AST语法树

// 首先我们编写以字符串形式的标签
const templateStr = `<div id="app">
<h1 @click="add" class="item" :id="name">{{count}}</h1>
<p>摸鱼真快乐</p>
</div>`
// 将标签放入我们的主函数中
let ast = parse(template)
function parse(template) {
	//第一步
	const tokens = semanticSplit(template)
}

第1步、语义拆分,将标签拆分成数组

function semanticSplit(template) {
	// 我们的目标是返回一个数组
	// [{ value: 'div', type: 'tagstart' }, { type: props }]
	let res = []
	let type = ''
	let val  = ''
	function push() {
		if(val) {
			if(type === 'tagstart') val = val.slice(1) // 过滤 <
			if(type === 'tagend') val = val.slice(2) // 过滤 />
			tokens.push({
				type, val
			})
			val = ''
		}
	}
	for(let i = 0; i <= template.length; i++){
		let ch = template[i]
		if(ch === '<') {
			push() // 以 < 开头的字符,语义转换,将之前收集的val push到 res 中
			if(template[i+1] === '/') {
				type = 'tagend' // </ 结束标签
			} else {
				type = 'tagstrat' // <div 开始标签
			}
		}
		if(ch === '>') {
		 	// 标签结束
		 	push()
		 	type = 'text'
		 	continue
		} else if(/[\s]/.test(ch)) {
			push()
			type = 'props'
		 	continue
		}
		val += ch
	}
	return res
}

在这里插入图片描述 这样我们就完成第一步从标签转换成数组。

第2步、数组转换成树,利用递归实现AST

function parse(template) {
	//第一步
	const tokens = semanticSplit(template)
	// 数组 => 树
	let cur = 0 // 数组下标从0开始计数
	// 树的根节点
	let ast = {
		type: 'root',
		props: [],
		children: [] // 子元素
	}
	while(cur < tokens.length) {
		ast.children.push(walk())
	}
	return ast
	// 递归函数
	function walk() {
		let token = tokens[cur]
		if(token.type === 'tagstart') {
			let node = { // 开始标签 新建一个node
				type: 'element',
				tag: token.val,
				props: [],
				children: []
			}
			token = tokens[++cur]
			// 往下走 一直走到 tagend or tagstart 之中,都是当前的标签的内容
			while(token.type !== 'tagend') {
				if(token.type === 'props') {
					node.props.push(walk())
				} else {
					node.children.push(walk())
				}
			}
			cur ++
			return node
		}
		if(token.type === 'tagend') {
			cur++
		}
		if(token.type === 'text') {
			cur++
			return token
		}
		if(token.type === 'props') {
			cur++
			// id = name
			const [key,val] = token.val.split('=')
			return { key, val }
		}
	}
}

在这里插入图片描述 这就是我们生成的AST抽象语法树了,由于是编写语法树的学习,功能和边界问题没有过多得考虑,如果想更深入了解可以下载 Vue3源码github.com/vuejs/vue-n… 目录为 packages > compiler-core > src > parse.ts 在parseChildren函数中有更完整的写法

也可以看the-super-tiny-compiler的源码github.com/jamiebuilds… 在parse函数中也有类似的写法


相信到这对AST抽象语法树有一定的了解了,不知道你对抽象语法树的理解是怎么样的呢?