模板预编译原理
Vue 模板预编译的过程和作用
问题
template 预编译是什么?它是如何工作的?
解答
模板预编译是指在构建阶段将模板字符串编译成渲染函数,而不是在浏览器运行时编译。
为什么需要预编译
// 运行时编译:浏览器中执行
// 需要包含编译器,体积大,性能差
new Vue({
template: '<div>{{ message }}</div>'
})
// 预编译:构建时已完成编译
// 不需要编译器,体积小,性能好
new Vue({
render(h) {
return h('div', this.message)
}
})
编译过程
模板编译分为三个阶段:
// 1. 解析(Parse)- 模板字符串 -> AST
const template = '<div id="app">{{ message }}</div>'
const ast = {
tag: 'div',
attrs: [{ name: 'id', value: 'app' }],
children: [
{ type: 2, expression: '_s(message)', text: '{{ message }}' }
]
}
// 2. 优化(Optimize)- 标记静态节点
// 静态节点在 diff 时可以跳过
ast.static = false
ast.children[0].static = false
// 3. 生成(Generate)- AST -> 渲染函数代码
const code = `with(this){return _c('div',{attrs:{"id":"app"}},[_v(_s(message))])}`
简化版编译器实现
// 简化的模板编译器
function compile(template) {
// 1. 解析:提取标签和内容
const ast = parse(template)
// 2. 生成:转换为渲染函数
const code = generate(ast)
return new Function('h', code)
}
// 解析函数
function parse(template) {
const tagMatch = template.match(/<(\w+)>(.+)<\/\1>/)
if (tagMatch) {
return {
tag: tagMatch[1],
children: parseChildren(tagMatch[2])
}
}
}
// 解析子节点
function parseChildren(content) {
const children = []
// 匹配插值表达式 {{ xxx }}
const interpMatch = content.match(/\{\{\s*(\w+)\s*\}\}/)
if (interpMatch) {
children.push({
type: 'interpolation',
expression: interpMatch[1]
})
} else {
children.push({
type: 'text',
content
})
}
return children
}
// 生成渲染函数代码
function generate(ast) {
const children = ast.children.map(child => {
if (child.type === 'interpolation') {
return `this.${child.expression}`
}
return `"${child.content}"`
}).join(',')
return `return h("${ast.tag}", null, ${children})`
}
// 使用
const render = compile('<div>{{ message }}</div>')
// 生成: function(h) { return h("div", null, this.message) }
预编译的实际应用
// vue-loader 在构建时将 .vue 文件中的 template 预编译
// 编译前:App.vue
/*
<template>
<div class="app">
<span>{{ count }}</span>
<button @click="add">+1</button>
</div>
</template>
*/
// 编译后:App.js
export default {
render(_ctx) {
return _createVNode("div", { class: "app" }, [
_createVNode("span", null, _toDisplayString(_ctx.count)),
_createVNode("button", { onClick: _ctx.add }, "+1")
])
}
}
运行时 vs 预编译对比
| 特性 | 运行时编译 | 预编译 |
|---|---|---|
| 编译时机 | 浏览器中 | 构建时 |
| 包体积 | 大(含编译器) | 小 |
| 首屏性能 | 慢 | 快 |
| 使用场景 | 动态模板 | 静态模板 |
关键点
- 预编译在构建阶段完成,运行时无需编译器,减少包体积
- 编译三阶段:解析(Parse)→ 优化(Optimize)→ 生成(Generate)
- 解析阶段将模板字符串转换为 AST 抽象语法树
- 优化阶段标记静态节点,diff 时可跳过
- 生成阶段将 AST 转换为可执行的渲染函数
目录