代码生成(Codegen)的过程
代码生成(Codegen)的过程
Vue3的编译器将模板转换为渲染函数时,代码生成是最后的关键阶段。这个阶段接收经过转换和优化的AST,输出可执行的JavaScript代码字符串,最终形成组件的render函数。
模板AST到渲染代码的转换
编译器遍历AST节点,根据节点类型生成对应的JavaScript代码片段。例如一个简单的div元素:
<div class="container">Hello</div>
对应的AST节点结构大致如下:
{
"type": 1,
"tag": "div",
"attrsList": [{"name": "class", "value": "container"}],
"children": [{"type": 2, "content": "Hello"}]
}
代码生成器会将其转换为:
const _Vue = Vue
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode("div", { class: "container" }, "Hello")
}
}
静态提升优化
Vue3会对静态节点进行提升优化,避免重复创建。例如:
<div>
<span>static</span>
<span>{{ dynamic }}</span>
</div>
生成的代码会分离静态和动态部分:
const _hoisted_1 = _createVNode("span", null, "static")
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode("div", null, [
_hoisted_1,
_createVNode("span", null, dynamic)
])
}
}
事件处理生成
对于事件绑定,代码生成器会做特殊处理:
<button @click="handleClick">Click</button>
生成代码时会将事件转换为正确的格式:
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => handleClick(...args))
}, "Click")
}
}
插槽内容生成
插槽的处理是代码生成中较复杂的部分。对于具名插槽:
<slot name="header" :user="user"></slot>
生成的代码会包含插槽的上下文信息:
return function render(_ctx, _cache) {
with (_ctx) {
return _renderSlot(_ctx.$slots, "header", {
user: user
})
}
}
条件渲染生成
v-if/v-else指令会转换为条件表达式:
<div v-if="show">A</div>
<div v-else>B</div>
生成的三元表达式代码:
return function render(_ctx, _cache) {
with (_ctx) {
return show
? _createVNode("div", null, "A")
: _createVNode("div", null, "B")
}
}
列表渲染生成
v-for指令会转换为数组map操作:
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
生成的渲染逻辑:
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode("ul", null,
items.map(item =>
_createVNode("li", { key: item.id }, item.text)
)
)
}
}
组件生成
自定义组件的生成会包含更多上下文信息:
<MyComponent :msg="message" @update="handleUpdate" />
生成的组件调用代码:
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode(MyComponent, {
msg: message,
onUpdate: _cache[1] || (_cache[1] = (...args) => handleUpdate(...args))
})
}
}
静态属性合并
对于静态和动态属性混合的情况:
<div class="static" :class="dynamicClass"></div>
生成器会合并属性处理:
return function render(_ctx, _cache) {
with (_ctx) {
return _createVNode("div", {
class: _normalizeClass(["static", dynamicClass])
})
}
}
指令生成
自定义指令需要特殊处理:
<div v-my-directive:arg.modif="value"></div>
生成的指令代码包含完整指令信息:
return function render(_ctx, _cache) {
with (_ctx) {
return _withDirectives(_createVNode("div"), [
[_resolveDirective("my-directive"), value, "arg", { modif: true }]
])
}
}
生成函数的结构
完整的生成函数通常包含这些部分:
export function generate(ast, options) {
const context = createCodegenContext(ast, options)
const { push, indent, deindent } = context
// 生成函数前缀
push(`const _Vue = Vue\n`)
push(`return function render(_ctx, _cache) {\n`)
indent()
push(`with (_ctx) {\n`)
indent()
// 生成主体内容
genNode(ast, context)
// 生成函数后缀
deindent()
push(`}\n`)
deindent()
push(`}`)
return {
code: context.code,
ast,
map: context.map
}
}
源码中的关键函数
在Vue源码中,几个关键函数负责不同部分的生成:
// packages/compiler-core/src/codegen.ts
function genElement(node: ElementNode, context: CodegenContext) {
const { push, helper } = context
const { tag, props, children } = node
if (tag === 'slot') {
genSlot(node, context)
} else if (tag === 'template') {
genChildren(node, context)
} else {
// 组件或原生元素
push(`${helper(CREATE_VNODE)}(`)
genNodeList([tag, props, children], context)
push(`)`)
}
}
function genChildren(node: ParentNode, context: CodegenContext) {
const { push, children } = context
if (children.length) {
push('[')
genNodeList(children, context)
push(']')
}
}
性能优化处理
生成器会针对不同场景应用优化策略:
- 静态节点提升
- 事件处理缓存
- 属性合并
- 块标记(Block tree)
例如带v-if的区块:
// 生成的代码会标记为可动态更新的区块
return function render(_ctx, _cache) {
with (_ctx) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "Static"),
show ? _createVNode("p", null, "Dynamic") : _createCommentVNode("v-if")
]))
}
}
源码映射生成
开发环境下会生成source map便于调试:
function generate(
ast: RootNode,
options: CodegenOptions
): CodegenResult {
const context = createCodegenContext(ast, options)
// ...生成代码
return {
code: context.code,
ast,
map: context.map ? context.map.toJSON() : undefined
}
}
服务端渲染差异
服务端渲染的代码生成有所不同:
// 客户端渲染
_createVNode("div", { class: "foo" }, "hello")
// 服务端渲染
_createSSRNode("div", { class: "foo" }, "hello")
生成器的可扩展性
Vue的代码生成器设计支持插件扩展:
interface CodegenPlugin {
(ast: RootNode, context: CodegenContext): void
}
function createCodegenContext(
ast: RootNode,
options: CodegenOptions
) {
const plugins = options.plugins || []
const context = { /* ... */ }
plugins.forEach(plugin => plugin(ast, context))
return context
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:静态分析的实现方法