HTML模版
<div id="app" style="color:beige">
<div style="color:red">{{name}}</div>
<div>{{age}}</div>
</div>
转换成ast语法树
代码
// 正则表达式系列
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾的 </div>
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性的
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束的 >
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
// ✅定义一些变量
let root = null; // ast语法树的树根
let currentParent; // 标识当前父亲是谁,指向栈顶元素
let stack = [];
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
// 创建一个虚拟的AST节点
function createASTElement(tagName, attrs) {
return {
tag: tagName,
type: 1,
children: [],
attrs,
parent: null
}
}
// ✅ 需要转换成一颗抽象语法树
/**
* 使用一个栈模拟(原理:栈括号配对算法题)
*/
function start(tagName, attrs) {
// 遇到开始标签 就创建一个ast元素s
let element = createASTElement(tagName,attrs);
if(!root){
root = element;
}
currentParent = element; // 把当前元素标记成父ast树
stack.push(element); // 将开始标签存放到✅栈中
}
function chars(text) { // 将当前文本放到 父ast树的子节点
text = text.replace(/\s/g,'');
if(text){
currentParent.children.push({
text,
type:3
})
}
}
function end(tagName) {
let element = stack.pop(); // 拿到的是ast对象
// 我要标识当前这个p是属于这个div的儿子的
currentParent = stack[stack.length-1]; // 指向 剩下标签的 栈顶
if(currentParent){
// 双向赋值的保存
element.parent = currentParent; // </div>: 先入栈的为
currentParent.children.push(element); // 实现了一个树的父子关系
}
}
// 函数入口
export function compileToFunction(template) {
// console.log(template);
let ast = parseHTML(template);
// 1) 解析html字符串 将html字符串 => ast语法树
// 需要将ast语法树生成最终的render函数 就是字符串拼接 (模板引擎)
}
function parseHTML(html) { // <div>hello</div>
// debugger;
while (html) {
// debugger;
let textEnd = html.indexOf('<');
if (textEnd == 0) { // 是一个开始标签或者结束标签
// 如果当前索引为0 肯定是一个标签 开始标签 结束标签
let startTagMatch = parseStartag(); // 通过这个方法获取到匹配的结果 tagName,attrs
// console.log(startTagMatch);
if (startTagMatch) { //这里说明开始标签已经有匹配了
// 处理获取的字符
start(startTagMatch.tagName, startTagMatch.attrs); // 1解析开始标签
continue; // 如果开始标签匹配完毕后 继续下一次 匹配
}
// 不是开始标签的话 就执行这个 startTagMatch为null
let endTagMatch = html.match(endTag);
if (endTagMatch) { //匹配结束标签
advance(endTagMatch[0].length);
// 处理获取的字符
end(endTagMatch[1]); // 2解析结束标签
continue;
}
}
let text; //文本标签
if (textEnd >= 0) {// 说明是文本结束位置
text = html.substring(0, textEnd);
}
if (text) {
advance(text.length);
// 处理获取的字符
chars(text); // 3解析文本
}
}
console.log('------------');
console.log(root);
function parseStartag() {
/**
* 拿到开始标签, 以<div 为例:
* start[0] = '<div'
* start[1] = 'div'
*/
let start = html.match(startTagOpen);
if(start) {
const match = { //将开始标签 组成一个对象
tagName:start[1], //'div'
attrs:[]
}
advance(start[0].length) //'<div'
// console.log(start);
// console.log(html);
// console.log('-----以上为截取 <div -------');
let startToEnd; // 开始标签的 > 符号,注意,这里每次的定义 html.match(startTagClose)) 不能写在外面
let startAttribute; // 匹配开始标签里面的属性
// console.log(startToEnd);
// console.log(startAttribute);
while(!(startToEnd = html.match(startTagClose)) && (startAttribute = html.match(attribute))) {
advance(startAttribute[0].length) //删除匹配的字符(属性)
// console.log(startAttribute); //
// 核心
match.attrs.push({ //将 id='app' 存到match的attr数组中
name: startAttribute[1],
value: startAttribute[3] || startAttribute[4] || startAttribute[5]
})
// console.log(match);
}
if(startToEnd) { // 这里用while为什么不行?
advance(startToEnd[0].length) // 删掉标签的 '>'
}
return match;
}
// console.log(start);
// console.log(html);
// console.log('-----以上为截取 <div 里面的属性-------');
return false;
}
function advance(n) {
html = html.substring(n);
}
}