main
2023年07月28日
一、src/platforms/web/runtime-with-compiler.ts $mount 编译器入口
/**
* 编译器
*/
export default function mount(vm) {
if (!vm.$options.render) { // 没有提供 render 选项,则编译生成 render 函数
// 获取模版
let template = ''
if (vm.$options.template) {
// 模版存在
template = vm.$options.template
} else if (vm.$options.el) {
// 存在挂载点
template = document.querySelector(vm.$options.el).outerHTML
// 在实例上记录挂载点,this._update 中会用到
vm.$el = document.querySelector(vm.$options.el)
}
// 生成渲染函数
const render = compileToFunction(template)
// 将渲染函数挂载到 $options 上
vm.$options.render = render
}
}
二、src/compiler/to-function.ts compileToFunction()
/**
* 解析模版字符串,得到 AST 语法树
* 将 AST 语法树生成渲染函数
* @param { String } template 模版字符串
* @returns 渲染函数
*/
export default function compileToFunction(template) {
// 解析模版,生成 ast
const ast = parse(template)
// 将 ast 生成渲染函数
const render = generate(ast)
return render
}
三、/src/compiler/parse.js parse()
/**
* 解析模版字符串,生成 AST 语法树
* @param {*} template 模版字符串
* @returns {AST} root ast 语法树
*/
export default function parse(template) {
// 存放所有的未配对的开始标签的 AST 对象
const stack = []
// 最终的 AST 语法树
let root = null
let html = template
while (html.trim()) {
// 过滤注释标签
if (html.indexOf('<!--') === 0) {
// 说明开始位置是一个注释标签,忽略掉
html = html.slice(html.indexOf('-->') + 3)
continue
}
// 匹配开始标签
const startIdx = html.indexOf('<')
if (startIdx === 0) {
if (html.indexOf('</') === 0) {
// 说明是闭合标签
parseEnd()
} else {
// 处理开始标签
parseStartTag()
}
} else if (startIdx > 0) {
// 说明在开始标签之间有一段文本内容,在 html 中找到下一个标签的开始位置
const nextStartIdx = html.indexOf('<')
// 如果栈为空,则说明这段文本不属于任何一个元素,直接丢掉,不做处理
if (stack.length) {
// 走到这里说说明栈不为空,则处理这段文本,并将其放到栈顶元素的肚子里
processChars(html.slice(0, nextStartIdx))
}
html = html.slice(nextStartIdx)
} else {
// 说明没有匹配到开始标签,整个 html 就是一段文本
}
}
return root
}