这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战
Vue
是怎样将HTML
转译为AST
的,其大致过程是比较复杂的,在开始真正的编写代码之前,先来看一个简单的编译例子
首先明确这么一段HTML
进行编译
<div id="app">
<p>hello</p>
</div>
复制代码
编译为AST
(简易版本)
通过结果可以反推出第一个工具函数:创建AST
根对象
const createASTElement = (tagName, attrs) => {
return {
tag: tagName, // 标签名
type: ELEMENT_TYPE, // 节点类型
children: [], // 子节点
attrs, // 属性
parent: null, // 父节点
};
}
复制代码
每个AST
的对象都具有type
属性,代表当前节点的DOM类型,标签节点为1,文本节点为3
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
复制代码
流程详解
上述的HTML
在处理开始时,被传入进来拿到的是一段字符串
let html = '<div id="app"><p>hello</p></div>';
复制代码
在开始解析前需要定义两个变量
let currentParent = null; // 标识当前的parent
let stack = [];
复制代码
开始解析上述字符串,一开始便会通过正则startTagOpen
判断其是一个开始标签,可以获取到其标签名,且type
为1
这个时候便遇到了问题,如何继续获取其对应的属性?
在Vue
中的处理方式是:截取字符串,通过截取html
,然后将截取后到数据继续递归的处理,比如上述解析到<div
是一个开始标签,可以明确知道其需要截取长度是4,得到新的html
字符串
html = 'id="app"><p>hello</p></div>';
复制代码
由于在解析的过程中需要不断的对html
进行截取,可将其进行封装
const advance = (n) => {
html = html.substring(n);
};
复制代码
继续对html
进行处理,正则attribute
匹配到数据,可以得到其属性的name和value
let attr = html.match(attribute);
const name = arr[1];
const value = attr[3] || attr[4] || attr[5];
复制代码
至此对于开始标签的处理完成,通过上述可以创建对应的AST
节点,对于所有的开始标签处理提取到函数start
中
const start = (tagName, attrs = []) => {
// 遇到开始标签 创建一个 ast 元素
let element = createASTElement(tagName, attrs);
if (!root) {
root = element;
}
// 把当前元素标记为 parent
currentParent = element;
// 将开始标签 存放到栈中
stack.push(element);
};
复制代码
完成之后需要再次调用advance
进行截取html
,此时html
为
html = '><p>hello</p></div>';
复制代码
继续处理,正则startTagClose
匹配到数据,说明此时起是一个标签的结束符号>
,代表当前这次解析完成
继续解析遇到p
标签,与上述流程一致,一直到将其解析完成,这个时候html
html = 'hello</p></div>';
复制代码
此时可以判断其为文本节点,直接创建对应的AST
,将其提取为函数chars
const chars = (text) => {
// 移除多余的空格
text = text.replace(/\s/g, "");
if (text) {
return {
text,
type: TEXT_TYPE,
};
}
};
复制代码
此时stack
为
stack = ['divAST', 'pAST'];
复制代码
目前html
为
html = '</p></div>';
复制代码
此时通过判断其为结束标签,且可以在stack
找到对应的开始标签节点的位置,依次进行收尾的处理,至此整个HTML
至AST
的大致流程便树立完成,其中还有大量的细节并没有进行处理,例如对于注释节点的处理等