Appearance
第4.3节:codegen 如何将 AST 转换为 render 函数字符串?
概述
代码生成(codegen)是Vue 3编译器的最后一个阶段,负责将经过转换优化的AST转换为可执行的JavaScript代码字符串。这个阶段的核心任务是生成高效的render函数,同时保持代码的可读性和性能优化。
1. 代码生成器架构
1.1 核心入口函数 - generate
typescript
export function generate(
ast: RootNode,
options: CodegenOptions & {
onContextCreated?: (context: CodegenContext) => void
} = {},
): CodegenResult {
const context = createCodegenContext(ast, options)
if (options.onContextCreated) options.onContextCreated(context)
const {
mode,
push,
prefixIdentifiers,
indent,
deindent,
newline,
scopeId,
ssr,
} = context
const helpers = Array.from(ast.helpers)
const hasHelpers = helpers.length > 0
const useWithBlock = !prefixIdentifiers && mode !== 'module'
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
const isSetupInlined = !__BROWSER__ && !!options.inline
// 生成前导码
const preambleContext = isSetupInlined
? createCodegenContext(ast, options)
: context
if (!__BROWSER__ && mode === 'module') {
genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
} else {
genFunctionPreamble(ast, preambleContext)
}
// 生成render函数
const functionName = ssr ? `ssrRender` : `render`
const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
// 函数签名生成
const signature = !__BROWSER__ && options.isTS
? args.map(arg => `${arg}: any`).join(',')
: args.join(', ')
if (isSetupInlined) {
push(`(${signature}) => {`)
} else {
push(`function ${functionName}(${signature}) {`)
}
indent()
// with块处理
if (useWithBlock) {
push(`with (_ctx) {`)
indent()
if (hasHelpers) {
push(
`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
NewlineType.End,
)
newline()
}
}
// 生成资源解析语句
if (ast.components.length) {
genAssets(ast.components, 'component', context)
}
if (ast.directives.length) {
genAssets(ast.directives, 'directive', context)
}
// 生成临时变量
if (ast.temps > 0) {
push(`let `)
for (let i = 0; i < ast.temps; i++) {
push(`${i > 0 ? `, ` : ``}_temp${i}`)
}
}
// 生成VNode树表达式
if (!ssr) {
push(`return `)
}
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
push(`null`)
}
if (useWithBlock) {
deindent()
push(`}`)
}
deindent()
push(`}`)
return {
ast,
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
map: context.map ? context.map.toJSON() : undefined,
}
}1.2 代码生成上下文 - CodegenContext
typescript
export interface CodegenContext
extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
source: string
code: string
line: number
column: number
offset: number
indentLevel: number
pure: boolean
map?: CodegenSourceMapGenerator
helper(key: symbol): string
push(code: string, newlineIndex?: number, node?: CodegenNode): void
indent(): void
deindent(withoutNewLine?: boolean): void
newline(): void
}核心功能:
- 代码累积:通过
push方法逐步构建代码字符串 - 缩进管理:维护代码的格式化和可读性
- 源码映射:生成Source Map用于调试
- 辅助函数管理:处理运行时helper的引用
1.3 createCodegenContext函数
typescript
function createCodegenContext(
ast: RootNode,
{
mode = 'function',
prefixIdentifiers = mode === 'module',
sourceMap = false,
filename = `template.vue.html`,
scopeId = null,
optimizeImports = false,
runtimeGlobalName = `Vue`,
runtimeModuleName = `vue`,
ssrRuntimeModuleName = 'vue/server-renderer',
ssr = false,
isTS = false,
inSSR = false,
}: CodegenOptions,
): CodegenContext {
const context: CodegenContext = {
// 配置选项
mode,
prefixIdentifiers,
sourceMap,
filename,
scopeId,
optimizeImports,
runtimeGlobalName,
runtimeModuleName,
ssrRuntimeModuleName,
ssr,
isTS,
inSSR,
// 状态管理
source: ast.source,
code: ``,
column: 1,
line: 1,
offset: 0,
indentLevel: 0,
pure: false,
map: undefined,
// 核心方法
helper(key) {
return `_${helperNameMap[key]}`
},
push(code, newlineIndex = NewlineType.None, node) {
context.code += code
// Source Map处理
if (!__BROWSER__ && context.map) {
if (node) {
let name
if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
const content = node.content.replace(/^_ctx\./, '')
if (content !== node.content && isSimpleIdentifier(content)) {
name = content
}
}
if (node.loc.source) {
addMapping(node.loc.start, name)
}
}
// 位置更新
if (newlineIndex === NewlineType.Unknown) {
advancePositionWithMutation(context, code)
} else {
context.offset += code.length
if (newlineIndex === NewlineType.None) {
context.column += code.length
} else {
if (newlineIndex === NewlineType.End) {
newlineIndex = code.length - 1
}
context.line++
context.column = code.length - newlineIndex
}
}
if (node && node.loc !== locStub && node.loc.source) {
addMapping(node.loc.end)
}
}
},
indent() {
newline(++context.indentLevel)
},
deindent(withoutNewLine = false) {
if (withoutNewLine) {
--context.indentLevel
} else {
newline(--context.indentLevel)
}
},
newline() {
newline(context.indentLevel)
},
}
function newline(n: number) {
context.push('\n' + ` `.repeat(n), NewlineType.Start)
}
function addMapping(loc: Position, name: string | null = null) {
const { _names, _mappings } = context.map!
if (name !== null && !_names.has(name)) _names.add(name)
_mappings.add({
originalLine: loc.line,
originalColumn: loc.column - 1,
generatedLine: context.line,
generatedColumn: context.column - 1,
source: filename,
name,
})
}
// Source Map初始化
if (!__BROWSER__ && sourceMap) {
context.map = new SourceMapGenerator() as unknown as CodegenSourceMapGenerator
context.map.setSourceContent(filename, context.source)
context.map._sources.add(filename)
}
return context
}2. 运行时辅助函数管理
2.1 运行时Helper定义
typescript
// 核心VNode创建函数
export const CREATE_VNODE: unique symbol = Symbol(__DEV__ ? `createVNode` : ``)
export const CREATE_ELEMENT_VNODE: unique symbol = Symbol(__DEV__ ? `createElementVNode` : ``)
export const CREATE_COMMENT: unique symbol = Symbol(__DEV__ ? `createCommentVNode` : ``)
export const CREATE_TEXT: unique symbol = Symbol(__DEV__ ? `createTextVNode` : ``)
export const CREATE_STATIC: unique symbol = Symbol(__DEV__ ? `createStaticVNode` : ``)
// 块级优化函数
export const OPEN_BLOCK: unique symbol = Symbol(__DEV__ ? `openBlock` : ``)
export const CREATE_BLOCK: unique symbol = Symbol(__DEV__ ? `createBlock` : ``)
export const CREATE_ELEMENT_BLOCK: unique symbol = Symbol(__DEV__ ? `createElementBlock` : ``)
// 组件和指令解析
export const RESOLVE_COMPONENT: unique symbol = Symbol(__DEV__ ? `resolveComponent` : ``)
export const RESOLVE_DYNAMIC_COMPONENT: unique symbol = Symbol(__DEV__ ? `resolveDynamicComponent` : ``)
export const RESOLVE_DIRECTIVE: unique symbol = Symbol(__DEV__ ? `resolveDirective` : ``)
// 指令处理
export const WITH_DIRECTIVES: unique symbol = Symbol(__DEV__ ? `withDirectives` : ``)
// 列表渲染
export const RENDER_LIST: unique symbol = Symbol(__DEV__ ? `renderList` : ``)
// 插槽处理
export const RENDER_SLOT: unique symbol = Symbol(__DEV__ ? `renderSlot` : ``)
export const CREATE_SLOTS: unique symbol = Symbol(__DEV__ ? `createSlots` : ``)
// 工具函数
export const TO_DISPLAY_STRING: unique symbol = Symbol(__DEV__ ? `toDisplayString` : ``)
export const MERGE_PROPS: unique symbol = Symbol(__DEV__ ? `mergeProps` : ``)
export const NORMALIZE_CLASS: unique symbol = Symbol(__DEV__ ? `normalizeClass` : ``)
export const NORMALIZE_STYLE: unique symbol = Symbol(__DEV__ ? `normalizeStyle` : ``)
// 事件处理
export const TO_HANDLERS: unique symbol = Symbol(__DEV__ ? `toHandlers` : ``)
export const WITH_CTX: unique symbol = Symbol(__DEV__ ? `withCtx` : ``)
// 响应式相关
export const UNREF: unique symbol = Symbol(__DEV__ ? `unref` : ``)
export const IS_REF: unique symbol = Symbol(__DEV__ ? `isRef` : ``)
// 缓存优化
export const WITH_MEMO: unique symbol = Symbol(__DEV__ ? `withMemo` : ``)
export const IS_MEMO_SAME: unique symbol = Symbol(__DEV__ ? `isMemoSame` : ``)2.2 Helper名称映射
typescript
export const helperNameMap: Record<symbol, string> = {
[FRAGMENT]: `Fragment`,
[TELEPORT]: `Teleport`,
[SUSPENSE]: `Suspense`,
[KEEP_ALIVE]: `KeepAlive`,
[BASE_TRANSITION]: `BaseTransition`,
[OPEN_BLOCK]: `openBlock`,
[CREATE_BLOCK]: `createBlock`,
[CREATE_ELEMENT_BLOCK]: `createElementBlock`,
[CREATE_VNODE]: `createVNode`,
[CREATE_ELEMENT_VNODE]: `createElementVNode`,
[CREATE_COMMENT]: `createCommentVNode`,
[CREATE_TEXT]: `createTextVNode`,
[CREATE_STATIC]: `createStaticVNode`,
[RESOLVE_COMPONENT]: `resolveComponent`,
[RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
[RESOLVE_DIRECTIVE]: `resolveDirective`,
[WITH_DIRECTIVES]: `withDirectives`,
[RENDER_LIST]: `renderList`,
[RENDER_SLOT]: `renderSlot`,
[CREATE_SLOTS]: `createSlots`,
[TO_DISPLAY_STRING]: `toDisplayString`,
[MERGE_PROPS]: `mergeProps`,
[NORMALIZE_CLASS]: `normalizeClass`,
[NORMALIZE_STYLE]: `normalizeStyle`,
[TO_HANDLERS]: `toHandlers`,
[WITH_CTX]: `withCtx`,
[UNREF]: `unref`,
[IS_REF]: `isRef`,
[WITH_MEMO]: `withMemo`,
[IS_MEMO_SAME]: `isMemoSame`,
}2.3 Helper别名生成
typescript
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
// 生成结果示例:
// "createVNode: _createVNode, openBlock: _openBlock"3. 节点代码生成 - genNode
3.1 核心分发函数
typescript
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
if (isString(node)) {
context.push(node, NewlineType.Unknown)
return
}
if (isSymbol(node)) {
context.push(context.helper(node))
return
}
switch (node.type) {
case NodeTypes.ELEMENT:
case NodeTypes.IF:
case NodeTypes.FOR:
__DEV__ &&
assert(
node.codegenNode != null,
`Codegen node is missing for element/if/for node. ` +
`Apply appropriate transforms first.`,
)
genNode(node.codegenNode!, context)
break
case NodeTypes.TEXT:
genText(node, context)
break
case NodeTypes.SIMPLE_EXPRESSION:
genExpression(node, context)
break
case NodeTypes.INTERPOLATION:
genInterpolation(node, context)
break
case NodeTypes.TEXT_CALL:
genNode(node.codegenNode, context)
break
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, context)
break
case NodeTypes.COMMENT:
genComment(node, context)
break
case NodeTypes.VNODE_CALL:
genVNodeCall(node, context)
break
// JavaScript表达式节点
case NodeTypes.JS_CALL_EXPRESSION:
genCallExpression(node, context)
break
case NodeTypes.JS_OBJECT_EXPRESSION:
genObjectExpression(node, context)
break
case NodeTypes.JS_ARRAY_EXPRESSION:
genArrayExpression(node, context)
break
case NodeTypes.JS_FUNCTION_EXPRESSION:
genFunctionExpression(node, context)
break
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
genConditionalExpression(node, context)
break
case NodeTypes.JS_CACHE_EXPRESSION:
genCacheExpression(node, context)
break
case NodeTypes.JS_BLOCK_STATEMENT:
genNodeList(node.body, context, true, false)
break
// SSR专用节点类型
case NodeTypes.JS_TEMPLATE_LITERAL:
!__BROWSER__ && genTemplateLiteral(node, context)
break
case NodeTypes.JS_IF_STATEMENT:
!__BROWSER__ && genIfStatement(node, context)
break
case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
!__BROWSER__ && genAssignmentExpression(node, context)
break
case NodeTypes.JS_SEQUENCE_EXPRESSION:
!__BROWSER__ && genSequenceExpression(node, context)
break
case NodeTypes.JS_RETURN_STATEMENT:
!__BROWSER__ && genReturnStatement(node, context)
break
case NodeTypes.IF_BRANCH:
// noop
break
default:
if (__DEV__) {
assert(false, `unhandled codegen node type: ${(node as any).type}`)
}
}
}3.2 VNode调用生成 - genVNodeCall
typescript
function genVNodeCall(node: VNodeCall, context: CodegenContext) {
const { push, helper, pure } = context
const {
tag,
props,
children,
patchFlag,
dynamicProps,
directives,
isBlock,
disableTracking,
isComponent,
} = node
// 添加开发模式下的patch flag注释
let patchFlagString
if (patchFlag) {
if (__DEV__) {
if (patchFlag < 0) {
// 特殊标志(负数且互斥)
patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
} else {
// 位运算标志
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n as PatchFlags])
.join(`, `)
patchFlagString = patchFlag + ` /* ${flagNames} */`
}
} else {
patchFlagString = String(patchFlag)
}
}
// 指令包装
if (directives) {
push(helper(WITH_DIRECTIVES) + `(`)
}
// 块级优化
if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
}
// 纯函数标记
if (pure) {
push(PURE_ANNOTATION)
}
// 选择合适的helper函数
const callHelper: symbol = isBlock
? getVNodeBlockHelper(context.inSSR, isComponent)
: getVNodeHelper(context.inSSR, isComponent)
push(helper(callHelper) + `(`, NewlineType.None, node)
// 生成参数列表
genNodeList(
genNullableArgs([tag, props, children, patchFlagString, dynamicProps]),
context,
)
push(`)`)
if (isBlock) {
push(`)`)
}
if (directives) {
push(`, `)
genNode(directives, context)
push(`)`)
}
}4. 静态提升优化
4.1 静态节点提升生成
typescript
function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
if (!hoists.length) {
return
}
context.pure = true
const { push, newline } = context
newline()
for (let i = 0; i < hoists.length; i++) {
const exp = hoists[i]
if (exp) {
push(`const _hoisted_${i + 1} = `)
genNode(exp, context)
newline()
}
}
context.pure = false
}生成示例:
javascript
// 原始模板
<div>
<p>静态文本</p>
<span>{{ message }}</span>
</div>
// 生成的代码
const _hoisted_1 = /*#__PURE__*/ _createElementVNode("p", null, "静态文本", -1)
function render(_ctx, _cache) {
return _openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_createElementVNode("span", null, _toDisplayString(_ctx.message), 1)
])
}4.2 缓存表达式生成
typescript
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
push(`_cache[${node.index}] || (`)
if (node.isVNode) {
indent()
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
newline()
}
push(`_cache[${node.index}] = `)
genNode(node.value, context)
if (node.isVNode) {
push(`,`)
newline()
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
newline()
push(`_cache[${node.index}]`)
deindent()
}
push(`)`)
}5. 作用域管理
5.1 模块模式前导码生成
typescript
function genModulePreamble(
ast: RootNode,
context: CodegenContext,
genScopeId: boolean,
inline?: boolean,
) {
const {
push,
newline,
optimizeImports,
runtimeModuleName,
ssrRuntimeModuleName,
} = context
if (genScopeId && ast.hoists.length) {
ast.hoists.forEach((exp, i) => {
if (exp) {
push(
`const _hoisted_${i + 1} = /*#__PURE__*/ _withScopeId(() => `,
)
genNode(exp, context)
push(`)`)
newline()
}
})
}
// 生成导入语句
if (ast.helpers.size) {
if (optimizeImports) {
// 优化导入:按需导入
push(
`import { ${ast.helpers
.map(s => helperNameMap[s])
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
)
} else {
// 全量导入
push(`import * as _Vue from ${JSON.stringify(runtimeModuleName)}\n`)
}
}
// SSR导入
if (ast.ssrHelpers && ast.ssrHelpers.length) {
push(
`import { ${ast.ssrHelpers
.map(s => helperNameMap[s])
.join(', ')} } from ${JSON.stringify(ssrRuntimeModuleName)}\n`,
)
}
// 生成静态提升
if (ast.hoists.length && !genScopeId) {
genHoists(ast.hoists, context)
}
// 生成导入语句
if (ast.imports.length) {
genImports(ast.imports, context)
newline()
}
genTemps(ast, context)
}5.2 函数模式前导码生成
typescript
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
const {
ssr,
prefixIdentifiers,
push,
newline,
runtimeGlobalName,
runtimeModuleName,
ssrRuntimeModuleName,
} = context
const VueBinding =
!__BROWSER__ && ssr
? `require(${JSON.stringify(runtimeModuleName)})`
: runtimeGlobalName
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
// 生成helper函数声明
if (ast.helpers.size > 0) {
if (!__BROWSER__ && prefixIdentifiers) {
push(
`const { ${Array.from(ast.helpers)
.map(aliasHelper)
.join(', ')} } = ${VueBinding}\n`,
)
} else {
// 在with块内部处理
push(`const _Vue = ${VueBinding}\n`)
if (ast.hoists.length) {
const staticHelpers = [
CREATE_VNODE,
CREATE_ELEMENT_VNODE,
CREATE_COMMENT,
CREATE_TEXT,
CREATE_STATIC,
]
.filter(helper => ast.helpers.has(helper))
.map(aliasHelper)
.join(', ')
push(`const { ${staticHelpers} } = _Vue\n`)
}
}
}
// 生成静态提升
if (ast.hoists.length) {
genHoists(ast.hoists, context)
}
// 生成导入
if (ast.imports.length) {
genImports(ast.imports, context)
newline()
}
genTemps(ast, context)
}6. 优化输出策略
6.1 代码压缩优化
typescript
// 1. 移除不必要的参数
function genNullableArgs(args: any[]): CallExpression['arguments'] {
let i = args.length
while (i--) {
if (args[i] != null) break
}
return args.slice(0, i + 1).map(arg => arg || `null`)
}
// 2. 内联组件props优化
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline } = context
const { properties } = node
if (!properties.length) {
push(`{}`, NewlineType.None, node)
return
}
const multilines =
properties.length > 1 ||
properties.some(
p =>
p.value.type !== NodeTypes.SIMPLE_EXPRESSION ||
!p.value.isStatic
)
push(multilines ? `{` : `{ `)
multilines && indent()
for (let i = 0; i < properties.length; i++) {
const { key, value } = properties[i]
// key处理
genExpressionAsPropertyKey(key, context)
push(`: `)
// value处理
genNode(value, context)
if (i < properties.length - 1) {
push(`,`)
multilines && newline()
}
}
multilines && deindent()
push(multilines ? `}` : ` }`)
}6.2 事件处理器优化
typescript
// 事件处理器内联优化
function genFunctionExpression(
node: FunctionExpression,
context: CodegenContext,
) {
const { push, indent, deindent } = context
const { params, returns, body, newline, isSlot, isNonScopedSlot } = node
if (isSlot) {
push(`_${helperNameMap[WITH_CTX]}(`)
}
push(`(`, NewlineType.None, node)
if (isArray(params)) {
genNodeList(params, context)
} else if (params) {
genNode(params, context)
}
push(`) => `)
if (newline || body) {
push(`{`)
indent()
}
if (returns) {
if (newline) {
push(`return `)
}
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
}
} else if (body) {
genNode(body, context)
}
if (newline || body) {
deindent()
push(`}`)
}
if (isSlot && !isNonScopedSlot) {
push(`)`)
}
}7. 实际应用示例
7.1 简单模板转换
输入模板:
vue
<template>
<div class="container">
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</div>
</template>生成的render函数:
javascript
function render(_ctx, _cache) {
return _openBlock(), _createElementBlock("div", {
class: "container"
}, [
_createElementVNode("h1", null, _toDisplayString(_ctx.title), 1 /* TEXT */),
_createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
])
}7.2 条件渲染转换
输入模板:
vue
<template>
<div>
<p v-if="show">显示内容</p>
<p v-else>隐藏内容</p>
</div>
</template>生成的render函数:
javascript
function render(_ctx, _cache) {
return _openBlock(), _createElementBlock("div", null, [
_ctx.show
? (_openBlock(), _createElementBlock("p", { key: 0 }, "显示内容"))
: (_openBlock(), _createElementBlock("p", { key: 1 }, "隐藏内容"))
])
}7.3 列表渲染转换
输入模板:
vue
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>生成的render函数:
javascript
function render(_ctx, _cache) {
return _openBlock(), _createElementBlock("ul", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.items, (item) => {
return (_openBlock(), _createElementBlock("li", {
key: item.id
}, _toDisplayString(item.name), 1 /* TEXT */))
}), 128 /* KEYED_FRAGMENT */))
])
}7.4 静态提升优化示例
输入模板:
vue
<template>
<div>
<header>
<h1>静态标题</h1>
<nav>
<a href="/home">首页</a>
<a href="/about">关于</a>
</nav>
</header>
<main>
<p>{{ content }}</p>
</main>
</div>
</template>生成的代码:
javascript
const _hoisted_1 = /*#__PURE__*/ _createElementVNode("h1", null, "静态标题", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/ _createElementVNode("a", { href: "/home" }, "首页", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/ _createElementVNode("a", { href: "/about" }, "关于", -1 /* HOISTED */)
const _hoisted_4 = /*#__PURE__*/ _createElementVNode("nav", null, [
_hoisted_2,
_hoisted_3
], -1 /* HOISTED */)
const _hoisted_5 = /*#__PURE__*/ _createElementVNode("header", null, [
_hoisted_1,
_hoisted_4
], -1 /* HOISTED */)
function render(_ctx, _cache) {
return _openBlock(), _createElementBlock("div", null, [
_hoisted_5,
_createElementVNode("main", null, [
_createElementVNode("p", null, _toDisplayString(_ctx.content), 1 /* TEXT */)
])
])
}8. 性能优化特性
8.1 Patch Flag系统
typescript
// Patch Flag枚举
export const enum PatchFlags {
TEXT = 1, // 动态文本内容
CLASS = 1 << 1, // 动态class
STYLE = 1 << 2, // 动态style
PROPS = 1 << 3, // 动态属性
FULL_PROPS = 1 << 4, // 具有动态key的属性
HYDRATE_EVENTS = 1 << 5, // 事件监听器
STABLE_FRAGMENT = 1 << 6, // 稳定的fragment
KEYED_FRAGMENT = 1 << 7, // 带key的fragment
UNKEYED_FRAGMENT = 1 << 8, // 不带key的fragment
NEED_PATCH = 1 << 9, // 需要patch
DYNAMIC_SLOTS = 1 << 10, // 动态插槽
DEV_ROOT_FRAGMENT = 1 << 11, // 开发模式根fragment
HOISTED = -1, // 静态提升
BAIL = -2, // 差异算法回退
}
// Patch Flag名称映射(开发模式)
export const PatchFlagNames: Record<PatchFlags, string> = {
[PatchFlags.TEXT]: `TEXT`,
[PatchFlags.CLASS]: `CLASS`,
[PatchFlags.STYLE]: `STYLE`,
[PatchFlags.PROPS]: `PROPS`,
[PatchFlags.FULL_PROPS]: `FULL_PROPS`,
[PatchFlags.HYDRATE_EVENTS]: `HYDRATE_EVENTS`,
[PatchFlags.STABLE_FRAGMENT]: `STABLE_FRAGMENT`,
[PatchFlags.KEYED_FRAGMENT]: `KEYED_FRAGMENT`,
[PatchFlags.UNKEYED_FRAGMENT]: `UNKEYED_FRAGMENT`,
[PatchFlags.NEED_PATCH]: `NEED_PATCH`,
[PatchFlags.DYNAMIC_SLOTS]: `DYNAMIC_SLOTS`,
[PatchFlags.DEV_ROOT_FRAGMENT]: `DEV_ROOT_FRAGMENT`,
[PatchFlags.HOISTED]: `HOISTED`,
[PatchFlags.BAIL]: `BAIL`,
}8.2 块级优化
typescript
// 块级优化helper选择
function getVNodeHelper(ssr: boolean, isComponent: boolean) {
return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE
}
function getVNodeBlockHelper(ssr: boolean, isComponent: boolean) {
return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK
}8.3 缓存优化
typescript
// 内联组件props缓存
function genCachedExpression(
node: CacheExpression,
context: CodegenContext
) {
const { push, helper } = context
push(`_cache[${node.index}] || (`)
if (node.isVNode) {
push(`${helper(SET_BLOCK_TRACKING)}(-1), `)
}
push(`_cache[${node.index}] = `)
genNode(node.value, context)
if (node.isVNode) {
push(`, ${helper(SET_BLOCK_TRACKING)}(1), _cache[${node.index}]`)
}
push(`)`)
}9. 调试与开发支持
9.1 Source Map生成
typescript
interface CodegenSourceMapGenerator {
setSourceContent(sourceFile: string, sourceContent: string): void
toJSON(): RawSourceMap
_sources: Set<string>
_names: Set<string>
_mappings: {
add(mapping: MappingItem): void
}
}
interface MappingItem {
source: string
generatedLine: number
generatedColumn: number
originalLine: number
originalColumn: number
name: string | null
}9.2 开发模式增强
typescript
// 开发模式下的patch flag注释
if (__DEV__) {
if (patchFlag < 0) {
patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
} else {
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n as PatchFlags])
.join(`, `)
patchFlagString = patchFlag + ` /* ${flagNames} */`
}
}10. 最佳实践与注意事项
10.1 代码生成最佳实践
合理使用静态提升
- 识别真正的静态内容
- 避免过度提升导致内存占用
- 考虑组件复用场景
优化patch flag使用
- 精确标记动态内容类型
- 避免不必要的FULL_PROPS标记
- 合理使用BAIL标记
缓存策略优化
- 识别适合缓存的表达式
- 避免缓存简单表达式
- 考虑缓存的内存开销
10.2 性能优化建议
减少运行时开销
typescript// 好的做法:使用具体的helper _createElementVNode("div", null, "text") // 避免:使用通用helper _createVNode("div", null, "text")优化事件处理
typescript// 好的做法:内联事件处理器 onClick: $event => (_ctx.count++) // 避免:复杂的事件处理器 onClick: _ctx.handleClick合理使用Fragment
typescript// 好的做法:稳定的Fragment _createElementBlock(_Fragment, null, [ // 稳定的子节点 ], 64 /* STABLE_FRAGMENT */) // 注意:动态Fragment需要key _createElementBlock(_Fragment, null, _renderList(items, item => { // 动态内容 }), 128 /* KEYED_FRAGMENT */)
10.3 常见问题与解决方案
Source Map不准确
- 确保正确设置filename
- 检查位置映射逻辑
- 验证原始源码内容
生成代码过大
- 检查静态提升策略
- 优化helper函数使用
- 考虑代码分割
运行时错误
- 验证helper函数导入
- 检查作用域变量引用
- 确保正确的上下文传递
总结
Vue 3的代码生成阶段是一个高度优化的系统,它将AST转换为高效的JavaScript代码。通过静态提升、patch flag系统、块级优化等技术,生成的代码在运行时具有出色的性能表现。
核心特性:
- 高效的代码生成:通过精确的节点类型分发和优化的helper函数使用
- 静态优化:静态提升和缓存机制减少运行时开销
- 调试支持:完整的Source Map支持和开发模式增强
- 灵活的配置:支持多种输出模式和优化策略
- 性能优化:patch flag系统和块级优化提升更新性能
理解代码生成的原理和优化策略,有助于开发者编写更高效的Vue组件,并在需要时进行性能调优和问题排查。
