什么是AST语法树
用于描述dom结构的一种树状的数据结构
如下图所示
AST语法树于虚拟dom的关系
如何将模板节点转换成AST
代码总览
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; // aa-aa
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; //aa:aa
/*
?: 匹配不捕获
*/
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 可以匹配到标签名 [1]
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); //[0] 标签的结束名字
// style="xxx" style='xxx' style=xxx
const attribute =
/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// 匹配属性的 aaa="aaa" aa='aaa' a = aaa
const startTagClose = /^\s*(\/?)>/; //匹配标签结束的
export function parseHTML(html) {
function createASTElement(tag, attrs) {
//vue3里面支持多个根节点(外层加了一个空元素),但是vue2中只有一个根节点
return {
tag,
type: 1,
children: [],
attrs,
parent: null,
};
}
// AST 语法树
let root = null; //根元素
let currentParent;
let stack = [];
//根据开始标签 结束标签 文本内容 生成ast语法树
function start(tagName, attrs) {//解析开始标签
//标签属性
console.log("start----------------", tagName);
let element = createASTElement(tagName, attrs);
if (!root) {//入果是刚开始解析,根元素不存在,就使用 当前元素作为根元素
root = element;
}
currentParent = element;//让父元素等于当前元素
stack.push(element);//并且将元素添加到stack栈中
}
function end(tagName) {//解析结束标签
console.log("end----------------", tagName);
let element = stack.pop();//解析结束将stack中的元素删除
currentParent = stack[stack.length - 1];
if (currentParent) {
// 父元素和子元素关系互记
element.parent = currentParent;
currentParent.children.push(element);
}
}
function chars(text) {//解析文本标签
console.log("chars----------------", text);
// 将文本添加到父元素中
text = text.replace(/\s/g, "");
if (text) {
currentParent.children.push({
type: 3,//元素节点的 nodeType为 1 文本节点的nodeType 为 3
text,
});
}
}
function advance(n) {
//将 html中处理过的 开始标签 属性 文本 结束标签 删除掉
//整个处理逻辑是 将处理过的html字符串删除,处理剩余的,知道全部处理完成 html的值为空 停止while循环
html = html.substring(n);
}
function parseStartTag() {
let start = html.match(startTagOpen);
// 通过正则 捕获到开始标签的信息
if (start) {
console.log("start", start);
let match = {
tagName: start[1],//标签名
attrs: [],
};
advance(start[0].length); //开始标签已经处理完成 将其删除
// 查找属性
let end, attr;
//不是开头标签结尾就一直解析
while (
!(end = html.match(startTagClose)) &&
(attr = html.match(attribute))
) {
advance(attr[0].length);
match.attrs.push({
name: attr[1],
value: attr[2] || attr[3] || attr[4] || true,
});
}
if (end) {
advance(end[0].length);
return match;
}
}
}
//
while (html) {
// 一层一层解析
let text;
let textEnd = html.indexOf("<"); //看尖角号的位置 可能是开始标签,也可能是结束标签
if (textEnd == 0) {
let startTagMatch = parseStartTag();
console.log("startTagMatch", startTagMatch);
if (startTagMatch) {
//开始标签
console.log("开始标签", startTagMatch.tagName);
start(startTagMatch.tagName, startTagMatch.attrs);
continue;
}
// 结束标签
let endTagMatch = html.match(endTag);
if (endTagMatch) {
//匹配到结尾了
advance(endTagMatch[0].length);
end(endTagMatch[1]);
console.log("结尾", endTagMatch[1]);
continue;
}
}
if (textEnd > 0) {
//开始解析文本
text = html.substring(0, textEnd);
}
if (text) {
advance(text.length);
chars(text);
console.log("文本", text);
}
// break;
}
console.log("html000000000000000000000000", html);
return root
}