一、模板解析
模板编译
function compile(template,options={}){
return baseCompile(template,extend({},parserOptions,options,{
nodeTransforms:[
...DOMNodeTransforms,
...(options.nodeTransforms || [])
],
directiveTransforms:extend({},DOMDirectiveTransforms,options.directiveTransforms || {}),
transformsHoist:null
}))
}
function baseCompile(template,options={}){
const prefixIdentifiers = false
// 解析 template 生成 AST
const ast = isString(template)?baseParset(template,options):template
const [nodeTransforms,directiveTransforms] = getBaseTransformPreset()
// AST 转换
transform(ast,extend({},options,{
prefixIdentifiers,
nodeTransforms:[
...nodeTransforms,
...(options.nodeTransforms || [])
]
}))
// 生成代码
return generate(ast,extend({},options,{
prefixIdentifiers
}))
}
function baseParse(content,options={}){
// 创建解析上下文
const context = createParserContext(context,options)
const start = getCursor(context)
// 解析子节点并创建 AST
return createRoot(parseChildren(context,getSelection(context,start)))
}
创建解析上下文
// 默认解析配置
const defaultParseOptions = {
delimiers:[`{{`,`}}`],
getNamespace:()=>0,
getTextMode:()=>0,
isVoidTag:No,
isPreTag:No,
isCustomElement:No,
decodeEntitties:(rawText)=>rawText.replace(decodeRE,(_,p1)=>decodeMap[p1]),
onError:defaultOnError,
onWarn:defaultOnWarn,
comments:false
}
function createParserContext(context,rawOptions){
const options = extend({},defaultParseOptions)
for(const key in rawOptions){
options[key] = rawOptions[key] || defaultParseOptions[key]
}
return {
options, // 解析相关的配置
colums:1, // 当前代码列号
line:1, // 当前代码行号
offset:0, // 当前代码相对于原始代码的偏移量
originalSource:content, // 原始代码
source:content, // 当前代码
inPre:false, // 代码是否在 pre 标签内
inVPre:false, // 代码是否在 v-pre 指令的环境下
onWarn:options.onWarn // 表示发出警告的回调函数
}
}
解析子节点
// 解析模板并创建 AST 节点数组
function parseChildren(context,mode,ancestors){
const parent = last(ancestors)
const ns = parent?parent.ns
const nodes = []
while(!isEnd(context,mode,ancestors)){
const s = context.source
let node = undefault
if(mode === 0 || mode === 1){
// 插值处理
if(!context.inVPre && startWith(s,context.options.delimiters[0])){
node = parse Interpolation(context,mode)
}
// ...
// pushNode(nodes,node[i])
}
}
let removedWhitespace = false
// 空白符处理
return removedWhitespace ? nodes.filter(Boolean):nodes
}
parseComment, // 注释节点
parseBogusComment // <!DOCTYPE 节点
parseCDATA // <![CDATA[ 节点
parseBogusComment //
parseTag
parseElement
parseText
创建 AST 根节点
function createRoot(children,loc = locStub){
return {
type:0, // 定义节点类型,0表示根节点
children:[], // 存储子节点
helpers:[], // 存储辅助函数
components:[], // 存储组件数组
directives:[], // 存储指令
hoists:[], // 存储需要提升的静态节点
imports:[], // 存储导入声明
cached:0, // 缓存计数器
temps:0, // 临时变量
codegenNode:undefined, // 代码生成节点
loc // 位置信息
}
}
生成 template AST
{{msg}}
测试APP
{
"type":0,
"children":[
{
"type":1,
"ns":0,
"tag":"div",
"tagType":0,
"props":[
{
"type":6,
"name":"class",
"value":{
"type":2,
"content":"app",
"loc":{
// 代码位置信息
}
},
"loc":{
// 代码位置信息
}
}
],
"isSelfClosing":false,
"children":[
{
"type":3,
"content":" 这是一段注释",
"loc":{
// 代码位置信息
}
},
{
"type":1,
"ns":0,
"tag":"hello",
"tagType":1,
"props":[],
"isSelfClosing":false,
"children":[
{
"type":1,
"ns":0,
"tag":"p",
"tagType":0,
"props":[],
"isSelfClosing":false,
"children":[
{
"type":5,
"content":{
"type":4,
"isStatic":false,
"constType":0,
"content":"msg",
"loc":{
// 代码位置信息
}
},
"loc":{
// 代码位置信息
}
}
],
"loc":{
// 代码位置信息
}
}
]
}
],
"loc":{}
}
],
"helpers":[],
"components":[],
"directives":[],
"hoists":[],
"imports":[],
"cache":0,
"temps":0,
"loc":{}
}
二、AST 转换
节点指令转换函数
function getBaseTransformPreset(prefixIdentifiers){
return [
[
transformOnce,
transformIf,
transformFor,
...((process.evn.NODE_ENV !== 'production')?[transformExpression]:[]),
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText
],
{
on:transformOn,
bind:transformBind,
model:transformModel
}
]
}
创建 transform 上下文
function createTransformContext(
root: RootNode,
{
filename = '',
prefixIdentifiers = false,
hoistStatic = false,
cacheHandlers = false,
nodeTransforms = [],
directiveTransforms = {},
transformHoist = null,
isBuiltInComponent = NOOP,
isCustomElement = NOOP,
expressionPlugins = [],
scopeId = null,
slotted = true,
ssr = false,
inSSR = false,
ssrCssVars = ``,
bindingMetadata = EMPTY_OBJ,
inline = false,
isTS = false,
onError = defaultOnError,
onWarn = defaultOnWarn,
compatConfig
}: TransformOptions
): TransformContext {
const nameMatch = filename.replace(/?.*$/, '').match(/([^/\]+).\w+$/)
const context: TransformContext = {
// ---------- 编译选项 ----------
// 组件自身的名称,用于生成代码时的标识
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
// 是否为标识符添加前缀,用于避免作用域冲突
prefixIdentifiers,
// 是否开启静态节点提升优化
hoistStatic,
// 是否缓存事件处理函数
cacheHandlers,
// 节点转换函数数组
nodeTransforms,
// 指令转换函数映射表
directiveTransforms,
// 静态提升转换函数
transformHoist,
// 判断是否为内置组件的函数
isBuiltInComponent,
// 判断是否为自定义元素的函数
isCustomElement,
// 表达式解析插件
expressionPlugins,
// 作用域 ID,用于 CSS 作用域
scopeId,
// 是否启用插槽
slotted,
// 服务端渲染相关配置
ssr,
inSSR,
ssrCssVars,
// 绑定元数据
bindingMetadata,
// 是否内联模式
inline,
// 是否为 TypeScript
isTS,
// 错误处理函数
onError,
// 警告处理函数
onWarn,
// 兼容性配置
compatConfig,
// ---------- 编译状态 ----------
// AST 根节点
root,
// 辅助函数使用计数映射
helpers: new Map(),
// 模板中使用的组件集合
components: new Set(),
// 模板中使用的指令集合
directives: new Set(),
// 被提升的静态节点数组
hoists: [],
// 导入语句数组
imports: [],
// 常量缓存映射
constantCache: new Map(),
// 临时变量计数器
temps: 0,
// 缓存变量计数器
cached: 0,
// 标识符使用计数映射
identifiers: Object.create(null),
// 各类指令的作用域计数
scopes: {
vFor: 0, // v-for 作用域层级
vSlot: 0, // v-slot 作用域层级
vPre: 0, // v-pre 作用域层级
vOnce: 0 // v-once 作用域层级
},
// 当前节点的父节点
parent: null,
// 当前节点在父节点 children 中的索引
childIndex: 0,
// 当前正在处理的节点
currentNode: root,
// 是否在 v-once 指令内部
inVOnce: false,
// ---------- 工具方法 ----------
// 注册并返回辅助函数
helper(name) {
const count = context.helpers.get(name) || 0
context.helpers.set(name, count + 1)
return name
},
// 移除辅助函数的引用计数
removeHelper(name) {
const count = context.helpers.get(name)
if (count) {
const currentCount = count - 1
if (!currentCount) {
context.helpers.delete(name)
} else {
context.helpers.set(name, currentCount)
}
}
},
// 获取辅助函数的字符串名称
helperString(name) {
return `_${helperNameMap[context.helper(name)]}`
},
// 替换当前节点
replaceNode(node) {
/* istanbul ignore if */
if (__DEV__) {
if (!context.currentNode) {
throw new Error(`Node being replaced is already removed.`)
}
if (!context.parent) {
throw new Error(`Cannot replace root node.`)
}
}
context.parent!.children[context.childIndex] = context.currentNode = node
},
// 移除节点
removeNode(node) {
if (__DEV__ && !context.parent) {
throw new Error(`Cannot remove root node.`)
}
const list = context.parent!.children
const removalIndex = node
? list.indexOf(node)
: context.currentNode
? context.childIndex
: -1
/* istanbul ignore if */
if (__DEV__ && removalIndex < 0) {
throw new Error(`node being removed is not a child of current parent`)
}
if (!node || node === context.currentNode) {
// current node removed
context.currentNode = null
context.onNodeRemoved()
} else {
// sibling node removed
if (context.childIndex > removalIndex) {
context.childIndex--
context.onNodeRemoved()
}
}
context.parent!.children.splice(removalIndex, 1)
},
// 节点移除的回调函数
onNodeRemoved: () => {},
// 添加标识符引用计数
addIdentifiers(exp) {
// identifier tracking only happens in non-browser builds.
if (!__BROWSER__) {
if (isString(exp)) {
addId(exp)
} else if (exp.identifiers) {
exp.identifiers.forEach(addId)
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
addId(exp.content)
}
}
},
// 移除标识符引用计数
removeIdentifiers(exp) {
if (!__BROWSER__) {
if (isString(exp)) {
removeId(exp)
} else if (exp.identifiers) {
exp.identifiers.forEach(removeId)
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
removeId(exp.content)
}
}
},
// 静态提升节点
hoist(exp) {
if (isString(exp)) exp = createSimpleExpression(exp)
context.hoists.push(exp)
const identifier = createSimpleExpression(
`_hoisted_${context.hoists.length}`,
false,
exp.loc,
ConstantTypes.CAN_HOIST
)
identifier.hoisted = exp
return identifier
},
// 创建缓存表达式
cache(exp, isVNode = false) {
return createCacheExpression(context.cached++, exp, isVNode)
}
}
if (__COMPAT__) {
context.filters = new Set()
}
function addId(id: string) {
const { identifiers } = context
if (identifiers[id] === undefined) {
identifiers[id] = 0
}
identifiers[id]!++
}
function removeId(id: string) {
context.identifiers[id]!--
}
return context
}
遍历 AST 节点
function traverseNode(
// 要遍历的节点,可以是根节点或模板子节点
node: RootNode | TemplateChildNode,
// 转换上下文,包含编译过程中的状态和工具方法
context: TransformContext
) {
// 将当前处理的节点设置到上下文中
context.currentNode = node
// 从上下文中获取所有节点转换函数
const { nodeTransforms } = context
// 用于存储转换函数返回的退出函数
const exitFns = []
// 遍历所有节点转换函数
for (let i = 0; i < nodeTransforms.length; i++) {
// 执行当前转换函数,传入节点和上下文
const onExit = nodeTransforms[i](node, context)
// 如果转换函数返回了退出函数
if (onExit) {
// 如果返回值是数组,将数组中的所有函数添加到 exitFns
if (isArray(onExit)) {
exitFns.push(...onExit)
} else {
// 如果是单个函数,直接添加到 exitFns
exitFns.push(onExit)
}
}
// 如果当前节点被移除,直接返回
if (!context.currentNode) {
return
} else {
// 如果节点被替换,更新当前节点
node = context.currentNode
}
}
// 根据节点类型进行不同的处理
switch (node.type) {
case NodeTypes.COMMENT:
// 如果是注释节点且不是服务端渲染
if (!context.ssr) {
// 注入创建注释节点的辅助函数
context.helper(CREATE_COMMENT)
}
break
case NodeTypes.INTERPOLATION:
// 如果是插值节点且不是服务端渲染
if (!context.ssr) {
// 注入转换显示字符串的辅助函数
context.helper(TO_DISPLAY_STRING)
}
break
// 处理包含子节点的容器类型节点
case NodeTypes.IF:
// 对于 if 节点,遍历所有分支节点
for (let i = 0; i < node.branches.length; i++) {
traverseNode(node.branches[i], context)
}
break
// 对于以下节点类型,都需要遍历其子节点
case NodeTypes.IF_BRANCH: // if 分支节点
case NodeTypes.FOR: // for 循环节点
case NodeTypes.ELEMENT: // 元素节点
case NodeTypes.ROOT: // 根节点
// 调用 traverseChildren 处理子节点
traverseChildren(node, context)
break
}
// 重新设置当前节点,因为在处理子节点时可能已经改变
context.currentNode = node
// 获取退出函数的数量
let i = exitFns.length
// 从后往前执行所有退出函数
while (i--) {
exitFns[i]()
}
}
Element 节点转换函数
const transformElement: NodeTransform = (node, context) => {
// 在退出时执行工作,此时所有子表达式都已被处理和合并
return function postTransformElement() {
node = context.currentNode!
// 检查节点是否为元素或组件
if (
!(
node.type === NodeTypes.ELEMENT &&
(node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT)
)
) {
return
}
const { tag, props } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
// 此转换的目标是创建实现 VNodeCall 接口的 codegenNode
let vnodeTag = isComponent
? resolveComponentType(node as ComponentNode, context)
: `"${tag}"`
// 判断是否为动态组件
const isDynamicComponent =
isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
let vnodeProps: VNodeCall['props']
let vnodeChildren: VNodeCall['children']
let vnodePatchFlag: VNodeCall['patchFlag']
let patchFlag: number = 0
let vnodeDynamicProps: VNodeCall['dynamicProps']
let dynamicPropNames: string[] | undefined
let vnodeDirectives: VNodeCall['directives']
// 确定是否需要使用 block
let shouldUseBlock =
isDynamicComponent ||
vnodeTag === TELEPORT ||
vnodeTag === SUSPENSE ||
(!isComponent && (tag === 'svg' || tag === 'foreignObject'))
// 处理 props
if (props.length > 0) {
const propsBuildResult = buildProps(
node,
context,
undefined,
isComponent,
isDynamicComponent
)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
const directives = propsBuildResult.directives
vnodeDirectives =
directives && directives.length
? (createArrayExpression(
directives.map(dir => buildDirectiveArgs(dir, context))
) as DirectiveArguments)
: undefined
if (propsBuildResult.shouldUseBlock) {
shouldUseBlock = true
}
}
// 处理子节点
if (node.children.length > 0) {
if (vnodeTag === KEEP_ALIVE) {
shouldUseBlock = true
patchFlag |= PatchFlags.DYNAMIC_SLOTS
if (__DEV__ && node.children.length > 1) {
context.onError(
createCompilerError(ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN, {
start: node.children[0].loc.start,
end: node.children[node.children.length - 1].loc.end,
source: ''
})
)
}
}
const shouldBuildAsSlots =
isComponent &&
vnodeTag !== TELEPORT &&
vnodeTag !== KEEP_ALIVE
if (shouldBuildAsSlots) {
const { slots, hasDynamicSlots } = buildSlots(node, context)
vnodeChildren = slots
if (hasDynamicSlots) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
} else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
const child = node.children[0]
const type = child.type
const hasDynamicTextChild =
type === NodeTypes.INTERPOLATION ||
type === NodeTypes.COMPOUND_EXPRESSION
if (
hasDynamicTextChild &&
getConstantType(child, context) === ConstantTypes.NOT_CONSTANT
) {
patchFlag |= PatchFlags.TEXT
}
if (hasDynamicTextChild || type === NodeTypes.TEXT) {
vnodeChildren = child as TemplateTextChildNode
} else {
vnodeChildren = node.children
}
} else {
vnodeChildren = node.children
}
}
// 处理 patchFlag 和 dynamicPropNames
if (patchFlag !== 0) {
if (__DEV__) {
if (patchFlag < 0) {
vnodePatchFlag =
patchFlag + ` /* ${PatchFlagNames[patchFlag as PatchFlags]} */`
} else {
const flagNames = Object.keys(PatchFlagNames)
.map(Number)
.filter(n => n > 0 && patchFlag & n)
.map(n => PatchFlagNames[n as PatchFlags])
.join(`, `)
vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
}
} else {
vnodePatchFlag = String(patchFlag)
}
if (dynamicPropNames && dynamicPropNames.length) {
vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames)
}
}
// 创建最终的 VNode 调用
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren,
vnodePatchFlag,
vnodeDynamicProps,
vnodeDirectives,
!!shouldUseBlock,
false /* disableTracking */,
isComponent,
node.loc
)
}
}
示例:
<div class="app"></div>
// 转换后的 node.codegenNode
{
"children":[
// 子节点
],
"directives":undefined,
"dynamicProps":undefined,
"isBlock":false,
"isForBlock":false,
"patchFlag":undefined,
"props":{
},
"tag":"div",
"type":13
}
表达式节点转换函数
// 主要转换插值和元素指令中的动态表达式
const transformExpression: NodeTransform = (node, context) => {
// 处理插值表达式
if (node.type === NodeTypes.INTERPOLATION) {
node.content = processExpression(
node.content as SimpleExpressionNode,
context
)
}
// 处理元素节点
else if (node.type === NodeTypes.ELEMENT) {
// 遍历处理元素上的所有指令
for (let i = 0; i < node.props.length; i++) {
const dir = node.props[i]
// 跳过 v-on 和 v-for 指令,因为它们需要特殊处理
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
const exp = dir.exp
const arg = dir.arg
// 如果是 v-on 指令且带有参数,则跳过表达式处理
if (
exp &&
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
!(dir.name === 'on' && arg)
) {
dir.exp = processExpression(
exp,
context,
// 如果是 slot 指令,需要将参数作为函数参数处理
dir.name === 'slot'
)
}
// 处理指令参数
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
dir.arg = processExpression(arg, context)
}
}
}
}
}
示例:
{{msg + text}}
// parse
{
"type":4,
"isStatic":false,
"isConstant":false,
"content":"msg + test"
}
// processExpression
{
"type":8,
"children":[
{
"type":4,
"isStatic":false,
"isConstant":false,
"content":"_ctx.msg"
}
" + ",
{
"type":4,
"isStatic":false,
"isConstant":false,
"content":"_ctx.test"
}
],
"identifiers":[]
}
Text 节点转换函数
transformText 主要的目的就是合并一些相邻的文件节点,然后为内部每一个文本节点创建一个代码生成节点,然后把子节点中的相邻文本节点合并成一个。
const transformText: NodeTransform = (node, context) => {
// 只处理根节点、元素节点、for 循环节点和 if 分支节点
if (
node.type === NodeTypes.ROOT ||
node.type === NodeTypes.ELEMENT ||
node.type === NodeTypes.FOR ||
node.type === NodeTypes.IF_BRANCH
) {
// 在节点退出时执行转换,确保所有表达式都已经被处理过
return () => {
const children = node.children
let currentContainer: CompoundExpressionNode | undefined = undefined
let hasText = false
// 遍历子节点
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child)) {
hasText = true
// 向后查找相邻的文本节点
for (let j = i + 1; j < children.length; j++) {
const next = children[j]
if (isText(next)) {
if (!currentContainer) {
// 创建复合表达式容器
currentContainer = children[i] = createCompoundExpression(
[child],
child.loc
)
}
// 将相邻文本节点合并到当前容器中
currentContainer.children.push(` + `, next)
children.splice(j, 1)
j--
} else {
// 遇到非文本节点,重置容器
currentContainer = undefined
break
}
}
}
}
// 以下情况不需要进行转换:
// 1. 没有文本节点
// 2. 只有一个文本子节点的普通元素(运行时有专门的快速路径)
// 3. 组件根节点(总是被标准化)
if (
!hasText ||
(children.length === 1 &&
(node.type === NodeTypes.ROOT ||
(node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT &&
// 避免自定义指令可能添加的 DOM 元素被覆盖
!node.props.find(
p =>
p.type === NodeTypes.DIRECTIVE &&
!context.directiveTransforms[p.name]
) &&
// 兼容模式下,没有特殊指令的 template 标签会被渲染为片段
!(__COMPAT__ && node.tag === 'template'))))
) {
return
}
// 将文本节点预转换为 createTextVNode(text) 调用
// 避免运行时标准化
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
const callArgs: CallExpression['arguments'] = []
// 如果是单个空格,可以省略参数以节省代码体积
if (child.type !== NodeTypes.TEXT || child.content !== ' ') {
callArgs.push(child)
}
// 标记动态文本,使其在块中被修补
if (
!context.ssr &&
getConstantType(child, context) === ConstantTypes.NOT_CONSTANT
) {
callArgs.push(
PatchFlags.TEXT +
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``)
)
}
// 创建文本调用节点
children[i] = {
type: NodeTypes.TEXT_CALL,
content: child,
loc: child.loc,
codegenNode: createCallExpression(
context.helper(CREATE_TEXT),
callArgs
)
}
}
}
}
}
}
示例:
<p>hello {{msg + test}}</p>
// 转换后的结果
{
"type":8,
"children":[
{
"type":2,
"content":"hello"
},
" + ",
{
"type":5,
"content":{
"type":8,
"children":[
{
"type":4,
"isStatic":false,
"isConstant":false,
"content":"_ctx.msg"
}
" + ",
{
"type":4,
"isStatic":false,
"isConstant":false,
"content":"_ctx.test"
}
]
}
}
],
"identifiers":[]
}
条件节点转换函数
transformIf 只处理元素节点,只有元素节点才有 v-if 指令
processIf 函数主要用来处理 v-if 以及 v-if 的相邻节点。
const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/, // 匹配 v-if、v-else、v-else-if 指令的正则表达式
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// 获取当前节点的兄弟节点
const siblings = context.parent!.children
let i = siblings.indexOf(ifNode)
let key = 0
// 计算当前节点之前的 v-if 分支总数,用于生成唯一的 key
while (i-- >= 0) {
const sibling = siblings[i]
if (sibling && sibling.type === NodeTypes.IF) {
key += sibling.branches.length
}
}
// 返回一个回调函数,在所有子节点转换完成后执行
return () => {
if (isRoot) {
// 如果是根节点,创建条件表达式的代码生成节点
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// 如果不是根节点,将当前分支的代码生成节点添加到父 v-if 的 alternate 中
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
v-if 处理逻辑
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
const isTemplateIf = node.tagType === ElementTypes.TEMPLATE
return {
type: NodeTypes.IF_BRANCH,
loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`),
isTemplateIf
}
}
因为 v-if 节点内部的子节点可以属于一个分支,v-else-if 和 v-else 节点内部的子节点也都可以属于一个分支,而最终页面渲染哪个分支,取决与哪个分支节点的 condition 为 true。
对于 v-if 和 v-else-if 节点,他们的 condition 就是指令对应的表达式 dir.exp ,而对于 v-else 节点,condition 是 undefined
processIf 处理逻辑
在执行 processIf 函数的时候,会传入一个 processCodegen 函数来创建退出函数,在处理 v-if 指令节点时,会执行这个函数,并把它们的返回值作为退出函数。
对于 v-if 节点,通过 createCodegenNodeForBranch 来创建 v-if 节点的代码生成节点。
export function processIf(
node: ElementNode, // 当前元素节点
dir: DirectiveNode, // v-if 相关指令节点
context: TransformContext, // 转换上下文
processCodegen?: (
// 可选的代码生成处理函数
node: IfNode,
branch: IfBranchNode,
isRoot: boolean
) => (() => void) | undefined
) {
// 检查指令表达式的合法性
if (
dir.name !== 'else' &&
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
) {
// 如果不是 v-else 且没有表达式或表达式为空,则报错
const loc = dir.exp ? dir.exp.loc : node.loc
context.onError(
createCompilerError(ErrorCodes.X_V_IF_NO_EXPRESSION, dir.loc)
)
// 设置默认表达式为 true
dir.exp = createSimpleExpression(`true`, false, loc)
}
// 非浏览器环境下处理标识符前缀
if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
}
// 开发环境下验证浏览器表达式
if (__DEV__ && __BROWSER__ && dir.exp) {
validateBrowserExpression(dir.exp as SimpleExpressionNode, context)
}
if (dir.name === 'if') {
// 处理 v-if 指令
const branch = createIfBranch(node, dir)
const ifNode: IfNode = {
type: NodeTypes.IF,
loc: node.loc,
branches: [branch]
}
// 用 if 节点替换原始节点
context.replaceNode(ifNode)
if (processCodegen) {
return processCodegen(ifNode, branch, true)
}
} else {
// 处理 v-else 和 v-else-if
const siblings = context.parent!.children
const comments = []
let i = siblings.indexOf(node)
// 向前查找相邻的 v-if 节点
while (i-- >= -1) {
const sibling = siblings[i]
// 处理注释节点
if (sibling && sibling.type === NodeTypes.COMMENT) {
context.removeNode(sibling)
__DEV__ && comments.unshift(sibling)
continue
}
// 处理空文本节点
if (
sibling &&
sibling.type === NodeTypes.TEXT &&
!sibling.content.trim().length
) {
context.removeNode(sibling)
continue
}
if (sibling && sibling.type === NodeTypes.IF) {
// 检查 v-else-if 之前是否有 v-else
if (
dir.name === 'else-if' &&
sibling.branches[sibling.branches.length - 1].condition === undefined
) {
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
// 将当前节点添加到找到的 v-if 节点的分支中
context.removeNode()
const branch = createIfBranch(node, dir)
// 开发环境下处理注释
if (
__DEV__ &&
comments.length &&
!(
context.parent &&
context.parent.type === NodeTypes.ELEMENT &&
isBuiltInType(context.parent.tag, 'transition')
)
) {
branch.children = [...comments, ...branch.children]
}
// 检查分支间的 key 是否重复
if (__DEV__ || !__BROWSER__) {
const key = branch.userKey
if (key) {
sibling.branches.forEach(({ userKey }) => {
if (isSameKey(userKey, key)) {
context.onError(
createCompilerError(
ErrorCodes.X_V_IF_SAME_KEY,
branch.userKey!.loc
)
)
}
})
}
}
// 添加分支并处理代码生成
sibling.branches.push(branch)
const onExit = processCodegen && processCodegen(sibling, branch, false)
traverseNode(branch, context)
if (onExit) onExit()
context.currentNode = null
} else {
// 没有找到相邻的 v-if 节点,报错
context.onError(
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, node.loc)
)
}
break
}
}
}
createCodegenNodeForBranch 处理逻辑
当分支节点存在 condition 的时候,比如 v-if 和 v-else-if ,它会通过 createConditionalExpression 来创建一个条件表达式节点。
function createCodegenNodeForBranch(
branch: IfBranchNode, // if 分支节点
keyIndex: number, // 用于生成唯一 key 的索引
context: TransformContext // 转换上下文
): IfConditionalExpression | BlockCodegenNode | MemoExpression {
if (branch.condition) {
// 如果分支有条件表达式(v-if 或 v-else-if)
return createConditionalExpression(
branch.condition,
// 创建分支子节点的代码生成节点
createChildrenCodegenNode(branch, keyIndex, context),
// 创建注释节点作为 fallback
createCallExpression(context.helper(CREATE_COMMENT), [
__DEV__ ? '"v-if"' : '""',
'true'
])
) as IfConditionalExpression
} else {
// 如果分支没有条件(v-else),直接创建子节点的代码生成节点
return createChildrenCodegenNode(branch, keyIndex, context)
}
}
createConditionalExpression 处理逻辑
consequent 是主 branch 的子节点对应的代码生成节点,alternate 是候补 branch 子节点对应的代码生成节点。
export function createConditionalExpression(
test: ConditionalExpression['test'],
consequent: ConditionalExpression['consequent'],
alternate: ConditionalExpression['alternate'],
newline = true
): ConditionalExpression {
return {
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
test,
consequent,
alternate,
newline,
loc: locStub
}
}
createChildrenCodegenNode 处理逻辑
createChildrenCodegenNode 主要判断每个分支节点是不是一个 vnodeCall ,如果是则把它转变成一个 BlockCall ,既让 v-if 的每一个分支都可以创建一个 Block。
因为 v-if 是条件渲染的,在某些条件下某些分支是不会渲染的,那么它内部的动态节点就不能添加到外部的 Block 中,所以需要单独创建一个 Block 来维护分支内部的动态节点,这样就形成了 Block tree。
function createChildrenCodegenNode(
branch: IfBranchNode, // if 分支节点
keyIndex: number, // key 索引
context: TransformContext // 转换上下文
): BlockCodegenNode | MemoExpression {
const { helper } = context
// 创建 key 属性
const keyProperty = createObjectProperty(
`key`,
createSimpleExpression(
`${keyIndex}`,
false,
locStub,
ConstantTypes.CAN_HOIST
)
)
const { children } = branch
const firstChild = children[0]
// 判断是否需要 Fragment 包装
const needFragmentWrapper =
children.length !== 1 || firstChild.type !== NodeTypes.ELEMENT
if (needFragmentWrapper) {
if (children.length === 1 && firstChild.type === NodeTypes.FOR) {
// 优化:当只有一个 v-for 子节点时,避免嵌套 Fragment
const vnodeCall = firstChild.codegenNode!
injectProp(vnodeCall, keyProperty, context)
return vnodeCall
} else {
// 创建 Fragment 节点
let patchFlag = PatchFlags.STABLE_FRAGMENT
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
// 开发环境下检查是否只有一个有效子节点
if (
__DEV__ &&
!branch.isTemplateIf &&
children.filter(c => c.type !== NodeTypes.COMMENT).length === 1
) {
patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT
patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}`
}
return createVNodeCall(
context,
helper(FRAGMENT),
createObjectExpression([keyProperty]),
children,
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
undefined,
undefined,
true,
false,
false,
branch.loc
)
}
} else {
// 不需要 Fragment 包装时的处理
const ret = (firstChild as ElementNode).codegenNode as
| BlockCodegenNode
| MemoExpression
const vnodeCall = getMemoedVNodeCall(ret)
// 将 createVNode 转换为 createBlock
if (vnodeCall.type === NodeTypes.VNODE_CALL) {
convertToBlock(vnodeCall, context)
}
// 注入 key 属性
injectProp(vnodeCall, keyProperty, context)
return ret
}
}
示例:
<hello v-if="flag"></hello>
<div v-else>
<p>hello {{msg + test}}</p>
<p>static</p>
<p>static</p>
</div>
// 模板 AST
[
{
"children":[],
"codegenNode":undefined,
"isSeltClosing":false,
"ns":0,
"props":[
{
type:7,
"name":"if",
"exp":{
"type":4,
"content":flag",
"isConstant":false,
"isStatic":false
},
"arg":undefined,
"modiflers":[]
}
],
"tag":"hellpo",
"tagType":1,
"type":1
},
{
"children":[
// 子节点
],
"codegenNode":undefined,
"isSelfClosing":false,
"ns":0,
"props":[
{
"type":7,
"name":"else",
"exp":underfined,
"arg":underfined,
"modifiers":[]
}
],
"tag":"div",
"tagType":0,
"type":1
}
]
最终生成的条件节点:
{
"type":9,
"branches":[
{
"type":10,
"children":[
{
"type":1,
"tagType":1,
"tag":"hello"
}
],
"condition":{
"type":4,
"content":"_ctx.flag"
}
},
{
"type":10,
"children":[
{
"type":1,
"tagType":0,
"tag":"div",
"children":[
// 子节点
]
}
],
"condition":undefined
}
],
"codegenNode":{
"type":19,
"cosequent":{
"type":13,
"tag":"_component_hello",
"children":undefined,
"dynamicProps":undefined,
"directives":undefined,
"isBlock":true,
"patchFlag":undefined
},
"alternate":{
"type":13,
"tag":"div",
"children":[
// 子节点
],
"directives":undefined,
"dynamicProps":undefined,
"isBlock":true,
"patchFlag":undefined
}
}
}
静态提升
节点转换完毕后,会判断编译配置中是否配置了 hoistStatic ,如果是就会执行 hoistStatic 做静态提升。
静态提升是 Vue.js 3在编译阶段设计的一个优化策略。
静态提升不依赖动态数据,一旦创建就不会改变。
function hoistStatic(root: RootNode, context: TransformContext) {
walk(
root,
context,
// 根节点不能被提升,因为可能存在父级的 fallthrough 属性
isSingleElementRoot(root, root.children[0])
)
}
function walk(
node: ParentNode, // 当前要处理的父节点
context: TransformContext, // 转换上下文
doNotHoistNode: boolean = false // 是否不对节点进行提升的标志
) {
const { children } = node
const originalCount = children.length // 记录原始子节点数量
let hoistedCount = 0 // 记录被提升的节点数量
// 遍历所有子节点
for (let i = 0; i < children.length; i++) {
const child = children[i]
// 第一步: 处理可以被提升的普通元素
if (
child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.ELEMENT
) {
// 获取节点的常量类型
const constantType = doNotHoistNode
? ConstantTypes.NOT_CONSTANT
: getConstantType(child, context)
if (constantType > ConstantTypes.NOT_CONSTANT) {
// 如果节点可以被提升
if (constantType >= ConstantTypes.CAN_HOIST) {
// 标记该节点已被提升
;(child.codegenNode as VNodeCall).patchFlag =
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
// 提升节点的代码生成节点
child.codegenNode = context.hoist(child.codegenNode!)
hoistedCount++
continue
}
} else {
// 第二步: 处理不能完全提升但 props 可以提升的节点
const codegenNode = child.codegenNode!
if (codegenNode.type === NodeTypes.VNODE_CALL) {
const flag = getPatchFlag(codegenNode)
// 检查节点的 patch 标志和 props 的常量类型
if (
(!flag ||
flag === PatchFlags.NEED_PATCH ||
flag === PatchFlags.TEXT) &&
getGeneratedPropsConstantType(child, context) >=
ConstantTypes.CAN_HOIST
) {
// 提升节点的 props
const props = getNodeProps(child)
if (props) {
codegenNode.props = context.hoist(props)
}
}
// 提升动态 props
if (codegenNode.dynamicProps) {
codegenNode.dynamicProps = context.hoist(codegenNode.dynamicProps)
}
}
}
}
// 第三步: 递归处理子节点
if (child.type === NodeTypes.ELEMENT) {
// 处理组件
const isComponent = child.tagType === ElementTypes.COMPONENT
if (isComponent) {
context.scopes.vSlot++ // 进入插槽作用域
}
walk(child, context)
if (isComponent) {
context.scopes.vSlot-- // 退出插槽作用域
}
} else if (child.type === NodeTypes.FOR) {
// 处理 v-for 节点
walk(child, context, child.children.length === 1)
} else if (child.type === NodeTypes.IF) {
// 处理 v-if 节点的所有分支
for (let i = 0; i < child.branches.length; i++) {
walk(
child.branches[i],
context,
child.branches[i].children.length === 1
)
}
}
}
// 第四步: 处理提升后的转换
if (hoistedCount && context.transformHoist) {
context.transformHoist(children, context, node)
}
// 第五步: 如果所有子节点都被提升,尝试提升整个 children 数组
if (
hoistedCount &&
hoistedCount === originalCount &&
node.type === NodeTypes.ELEMENT &&
node.tagType === ElementTypes.ELEMENT &&
node.codegenNode &&
node.codegenNode.type === NodeTypes.VNODE_CALL &&
isArray(node.codegenNode.children)
) {
node.codegenNode.children = context.hoist(
createArrayExpression(node.codegenNode.children)
)
}
}
示例:
<p>hello {{msg + test}}</p>
<p>static</p>
<p>static</p>
// 编译后的代码:
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, vModelText as _vModelText, withDirectives as _withDirectives, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p",null,"static",-1 /* HOISTED*/)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p",null,"static",-1 /* HOISTED*/)
export function render(_ctx,_cache,$props,$setup,$data,$options){
return (_openBlcok(),_createElementBlock(_Fragment,null,[
_createElemtVNode("p",null,"hello " + _toDisplayString(_ctx.msg + _ctx.test),1 /* TEXT */),
_hoisted_1,
_hoisted_2
],64 /* STABLE_FRAGMENT */))
}
静态提升创建的节点放在了 render 函数外部,render 函数内部始终会保留对静态节点的引用,导致组件被销毁,静态提升的节点所占用的内存也不会被释放。
创建根节点
createRootCodegen 的目的就是为 root 这个虚拟的 AST 根节点创建一个代码生成节点。
root 创建完成后,把转换 AST 节点过程中创建的一些上下文数据赋值给 root 节点对应的属性。
function createRootCodegen(root: RootNode, context: TransformContext) {
const { helper } = context
const { children } = root
// 处理只有一个子节点的情况
if (children.length === 1) {
const child = children[0]
// 如果这个唯一的子节点是元素节点,将其转换为 block
if (isSingleElementRoot(root, child) && child.codegenNode) {
// 单个元素根节点永远不会被提升,所以 codegenNode 不会是 SimpleExpressionNode
const codegenNode = child.codegenNode
if (codegenNode.type === NodeTypes.VNODE_CALL) {
// 将 VNode 调用转换为 Block
convertToBlock(codegenNode, context)
}
root.codegenNode = codegenNode
} else {
// 以下情况会直接使用子节点作为 codegenNode:
// - 单个 <slot/> 节点
// - 单个 v-if 节点
// - 单个 v-for 节点
// - 单个文本节点
root.codegenNode = child
}
}
// 处理有多个子节点的情况
else if (children.length > 1) {
// 根节点有多个子节点时,返回一个 Fragment 块
let patchFlag = PatchFlags.STABLE_FRAGMENT
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
// 检查 Fragment 是否只包含一个有效的子节点,其余都是注释节点
if (
__DEV__ &&
children.filter(c => c.type !== NodeTypes.COMMENT).length === 1
) {
patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT
patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}`
}
// 创建 Fragment 的 VNode 调用
root.codegenNode = createVNodeCall(
context,
helper(FRAGMENT), // Fragment 标识
undefined, // props
root.children, // children
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``), // patchFlag
undefined, // dynamicProps
undefined, // directives
true, // isBlock
undefined, // disableTracking
false // isComponent
)
}
// 没有子节点的情况
else {
// 没有子节点时,不生成代码
}
}
三、生成代码
示例:
<div>
<hello v-if="flag"></hello>
<div v-else>
<p>hello {{ msg + test}}</p>
<p>static</p>
<p>static</p>
</div>
</div>
// 生成的代码结果
import {
openBlock as _openBlock,
createElementBlock as _createElementBlock,
createCommentVNode as _createCommentVNode,
toDisplayString as _toDisplayString,
createElementVNode as _createElementVNode,
vModelText as _vModelText,
withDirectives as _withDirectives,
Fragment as _Fragment
} from "vue"
const _hoisted_1 = {class:"app"}
const _hoisted_2 = {key:1}
const _hoisted_3 = /*#__PURE__*/_createElementVNode("p",null,"static",-1 /* HOISTED */)
const _hoisted_4 = /*#__PURE*/_createElementVNode("p",null,"static",-1 /* HOISTED */)
export function render(_ctx,_cache,$prps,$setup,$data,$options){
const _component_hello = _resolveComponent("hello")
return (_openBlock(),_createElementBlock("div",_hoisted_1,[
(_ctx.flag)?(_openBlock(),_createBlock(_component_heelo,{key:0})):
(_openBlock(),_createElementBlock("div",_hoisted_2,[
_createElementVNode("p",null,"hello" + _toDisplayString(_ctx.msg + _ctx.test),1 /* TEXT */),
_hoisted_3,
_hoisted_4
]))
]))
}
generate 函数是 Vue编译器的核心部分,负责将 AST 转换为可执行的渲染函数代码。
function generate(
ast: RootNode,
options: CodegenOptions & {
onContextCreated?: (context: CodegenContext) => void
} = {}
): CodegenResult {
// 创建代码生成上下文
const context = createCodegenContext(ast, options)
if (options.onContextCreated) options.onContextCreated(context)
// 从上下文中解构需要使用的工具函数和状态
const {
mode, // 模式: module | function
push, // 向输出中推入代码
prefixIdentifiers, // 是否需要标识符前缀
indent, // 缩进
deindent, // 取消缩进
newline, // 换行
scopeId, // 作用域ID
ssr // 是否服务端渲染
} = context
// 获取AST中使用的所有helpers
const helpers = Array.from(ast.helpers)
const hasHelpers = helpers.length > 0
// 是否使用with块 - 当不需要标识符前缀且不是module模式时使用
const useWithBlock = !prefixIdentifiers && mode !== 'module'
// 是否需要生成scopeId - 仅在非浏览器环境且有scopeId且为module模式时需要
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
// 是否内联setup - 非浏览器环境且options.inline为true时
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)
}
// 生成渲染函数声明
const functionName = ssr ? `ssrRender` : `render`
const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
// 添加绑定优化参数
if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
args.push('$props', '$setup', '$data', '$options')
}
// 生成函数签名
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`)
push(`\n`)
newline()
}
}
// 生成资源解析语句(组件、指令等)
if (ast.components.length) {
genAssets(ast.components, 'component', context)
if (ast.directives.length || ast.temps > 0) {
newline()
}
}
if (ast.directives.length) {
genAssets(ast.directives, 'directive', context)
if (ast.temps > 0) {
newline()
}
}
if (__COMPAT__ && ast.filters && ast.filters.length) {
newline()
genAssets(ast.filters, 'filter', context)
newline()
}
if (ast.temps > 0) {
push(`let `)
for (let i = 0; i < ast.temps; i++) {
push(`${i > 0 ? `, ` : ``}_temp${i}`)
}
}
if (ast.components.length || ast.directives.length || ast.temps) {
push(`\n`)
newline()
}
// 生成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 as any).toJSON() : undefined
}
}
创建代码生成上下文
该上下文对象 context 维护了 genreate 过程的一些配置,也维护了 genreate 过程的一些状态数据。
function createCodegenContext(
ast: RootNode,
{
// 代码生成的基本配置选项,设置默认值
mode = 'function', // 代码生成模式,默认为function模式
prefixIdentifiers = mode === 'module', // 是否添加标识符前缀,module模式下默认为true
sourceMap = false, // 是否生成sourceMap
filename = `template.vue.html`, // 模板文件名
scopeId = null, // 作用域ID,用于CSS作用域隔离
optimizeImports = false, // 是否优化导入语句
runtimeGlobalName = `Vue`, // Vue运行时的全局变量名
runtimeModuleName = `vue`, // Vue运行时的模块名
ssrRuntimeModuleName = 'vue/server-renderer', // 服务端渲染运行时模块名
ssr = false, // 是否为服务端渲染模式
isTS = false, // 是否生成TypeScript代码
inSSR = false // 是否在SSR上下文中
}: CodegenOptions
): CodegenContext {
// 创建代码生成上下文对象
const context: CodegenContext = {
// 配置相关字段
mode, // 代码生成模式
prefixIdentifiers, // 是否需要标识符前缀
sourceMap, // 是否生成sourceMap
filename, // 文件名
scopeId, // 作用域ID
optimizeImports, // 是否优化导入
runtimeGlobalName, // 运行时全局名称
runtimeModuleName, // 运行时模块名
ssrRuntimeModuleName, // SSR运行时模块名
ssr, // 是否SSR
isTS, // 是否TypeScript
inSSR, // 是否在SSR环境
// 代码生成状态相关字段
source: ast.loc.source, // 源代码
code: ``, // 生成的代码字符串
column: 1, // 当前列号
line: 1, // 当前行号
offset: 0, // 当前字符偏移量
indentLevel: 0, // 缩进层级
pure: false, // 是否为纯函数调用
map: undefined, // sourceMap对象
// 生成helper函数名的方法
helper(key) {
return `_${helperNameMap[key]}`
},
// 向生成的代码中追加内容的方法
push(code, node) {
context.code += code
// 非浏览器环境下处理sourceMap
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
}
}
// 添加起始位置映射
addMapping(node.loc.start, name)
}
// 更新位置信息
advancePositionWithMutation(context, code)
// 添加结束位置映射
if (node && node.loc !== locStub) {
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))
}
// 内部工具函数:添加sourceMap映射
function addMapping(loc: Position, name?: string) {
context.map!.addMapping({
name,
source: context.filename,
original: {
line: loc.line,
column: loc.column - 1 // sourceMap列号从0开始
},
generated: {
line: context.line,
column: context.column - 1
}
})
}
// 非浏览器环境且需要sourceMap时,初始化sourceMap生成器
if (!__BROWSER__ && sourceMap) {
context.map = new SourceMapGenerator()
context.map!.setSourceContent(filename, context.source)
}
return context
}
生成预设代码
function genModulePreamble(
ast: RootNode,
context: CodegenContext,
genScopeId: boolean, // 是否需要生成作用域ID
inline?: boolean // 是否为内联模式
) {
const {
push, // 向输出添加代码
newline, // 换行
optimizeImports, // 是否优化导入
runtimeModuleName, // 运行时模块名称
ssrRuntimeModuleName // SSR运行时模块名称
} = context
// 当需要生成scopeId且存在提升节点时,添加作用域ID相关的辅助函数
if (genScopeId && ast.hoists.length) {
ast.helpers.add(PUSH_SCOPE_ID) // 添加推入作用域ID的辅助函数
ast.helpers.add(POP_SCOPE_ID) // 添加弹出作用域ID的辅助函数
}
// 处理辅助函数的导入
if (ast.helpers.size) {
const helpers = Array.from(ast.helpers)
if (optimizeImports) {
// 优化模式:先导入后赋值,优化webpack代码分割
push(
`import { ${helpers
.map(s => helperNameMap[s])
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
)
// 将导入的辅助函数赋值给变量,避免代码分割时的包装开销
push(
`\n// Binding optimization for webpack code-split\nconst ${helpers
.map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
.join(', ')}\n`
)
} else {
// 普通模式:直接导入并重命名
push(
`import { ${helpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`
)
}
}
// 处理SSR相关辅助函数的导入
if (ast.ssrHelpers && ast.ssrHelpers.length) {
push(
`import { ${ast.ssrHelpers
.map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
.join(', ')} } from "${ssrRuntimeModuleName}"\n`
)
}
// 处理其他导入语句
if (ast.imports.length) {
genImports(ast.imports, context)
newline()
}
// 生成提升的静态节点
genHoists(ast.hoists, context)
newline()
// 非内联模式下添加export关键字
if (!inline) {
push(`export `)
}
}
生成渲染函数的名称和参数
// 生成渲染函数声明
const functionName = ssr ? `ssrRender` : `render`
const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
// 添加绑定优化参数
if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
args.push('$props', '$setup', '$data', '$options')
}
// 生成函数签名
const signature =
!__BROWSER__ && options.isTS
? args.map(arg => `${arg}: any`).join(',')
: args.join(', ')
// 生成函数开始部分
if (isSetupInlined) {
push(`(${signature}) => {`)
} else {
push(`function ${functionName}(${signature}) {`)
}
indent()
生成资源声明代码
function genAssets(
assets: string[], // 资源数组(组件、指令或过滤器的名称列表)
type: 'component' | 'directive' | 'filter', // 资源类型
{ helper, push, newline, isTS }: CodegenContext // 代码生成上下文
) {
// 根据资源类型选择对应的解析helper函数
const resolver = helper(
__COMPAT__ && type === 'filter'
? RESOLVE_FILTER // 过滤器解析helper
: type === 'component'
? RESOLVE_COMPONENT // 组件解析helper
: RESOLVE_DIRECTIVE // 指令解析helper
)
// 遍历资源数组生成解析代码
for (let i = 0; i < assets.length; i++) {
let id = assets[i]
// 检查是否为组件的隐式自引用(从SFC文件名推断)
const maybeSelfReference = id.endsWith('__self')
if (maybeSelfReference) {
id = id.slice(0, -6) // 移除'__self'后缀
}
// 生成资源解析语句
push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
maybeSelfReference ? `, true` : `` // 如果是自引用,添加true参数
})${isTS ? `!` : ``}` // TypeScript下添加非空断言
)
// 除最后一个资源外,每个资源后添加换行
if (i < assets.length - 1) {
newline()
}
}
}
生成创建 vnode 树的表达式
genNode 主要根据不同节点类型,生成不同的代码。
function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
// 处理字符串节点
if (isString(node)) {
context.push(node)
return
}
// 处理符号节点(通常是helper函数)
if (isSymbol(node)) {
context.push(context.helper(node))
return
}
// 根据节点类型分发处理
switch (node.type) {
// 模板节点类型
case NodeTypes.ELEMENT: // 元素节点
case NodeTypes.IF: // if条件节点
case NodeTypes.FOR: // for循环节点
// 开发环境下确保节点已经过转换
__DEV__ && assert(
node.codegenNode != null,
`代码生成节点缺失,请先进行相应转换`
)
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: // 模板字面量
genTemplateLiteral(node, context)
break
case NodeTypes.JS_IF_STATEMENT: // if语句
genIfStatement(node, context)
break
case NodeTypes.JS_ASSIGNMENT_EXPRESSION: // 赋值表达式
genAssignmentExpression(node, context)
break
case NodeTypes.JS_SEQUENCE_EXPRESSION: // 序列表达式
genSequenceExpression(node, context)
break
case NodeTypes.JS_RETURN_STATEMENT: // return语句
genReturnStatement(node, context)
break
case NodeTypes.IF_BRANCH: // if分支
// 不需要处理
break
default:
// 开发环境下对未处理的节点类型报错
if (__DEV__) {
assert(false, `未处理的代码生成节点类型: ${(node as any).type}`)
const exhaustiveCheck: never = node
return exhaustiveCheck
}
}
}
运行时优化
首先,我们来看一下 openBlock 的实现:
const blockStack = []
let currentBlock = null
function openBlock(disableTracking = false){
blockStack.push(currentBlock = disableTracking?null:[])
}
Vue.js 3 在运行时设计了一个 blockStack 和 currentBlock,其中 blockStack 表示一个 BlockTree。
currentBlock 表示当前的 Block.
openBlock 就是往当前的 blockStack 添加一个新的 Block,作为 currentBlock.
设计 Block 的目的就是动态收集 vnode 节点,这样才能在 patch 节点只比对这些动态 vnode 节点,避免进行不必要的静态节点比对,从而优化了性能。
动态 vnode 节点是什么时候被收集的呢?
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
// 创建基础vnode对象
const vnode = {
__v_isVNode: true, // 标识是VNode
__v_skip: true, // 跳过响应式
type, // 节点类型
props, // 属性
key: props && normalizeKey(props), // 标准化key
ref: props && normalizeRef(props), // 标准化ref
scopeId: currentScopeId, // 作用域ID
slotScopeIds: null, // 插槽作用域ID
children, // 子节点
component: null, // 组件实例
suspense: null, // suspense实例
ssContent: null, // suspense内容
ssFallback: null, // suspense后备内容
dirs: null, // 指令
transition: null, // 过渡
el: null, // 真实DOM元素
anchor: null, // Fragment锚点
target: null, // Teleport目标
targetAnchor: null, // Teleport目标锚点
staticCount: 0, // 静态节点计数
shapeFlag, // 节点类型标记
patchFlag, // 更新标记
dynamicProps, // 动态属性
dynamicChildren: null, // 动态子节点
appContext: null, // 应用上下文
ctx: currentRenderingInstance // 渲染实例上下文
} as VNode
// 需要完整的子节点标准化处理
if (needFullChildrenNormalization) {
normalizeChildren(vnode, children)
// 标准化 suspense 子节点
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).normalize(vnode)
}
} else if (children) {
// 简单的子节点处理 - 只处理字符串或数组类型
vnode.shapeFlag |= isString(children)
? ShapeFlags.TEXT_CHILDREN // 文本子节点
: ShapeFlags.ARRAY_CHILDREN // 数组子节点
}
// 开发环境下验证key
if (__DEV__ && vnode.key !== vnode.key) {
warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type)
}
// 处理Block树追踪
if (
isBlockTreeEnabled > 0 && // Block追踪开启
!isBlockNode && // 非Block节点
currentBlock && // 存在当前Block
(vnode.patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) && // 需要更新或是组件
vnode.patchFlag !== PatchFlags.HYDRATE_EVENTS // 非仅hydrate事件
) {
currentBlock.push(vnode)
}
// 兼容性处理
if (__COMPAT__) {
convertLegacyVModelProps(vnode)
defineLegacyVNodeProperties(vnode)
}
return vnode
}
总结
代码生成阶段也是一个自顶向下的过程,它主要依据前面转换的 AST 对象去生成响应的代码。
在创建 vnode 树的过程中,会通过 genNode 针对不同的节点执行不同的代码生成逻辑,这个过程还可能存在递归执行 genNode 的情况,完成整个 vnode 树的代码构建
在整个编译阶段,会给动态节点打上相应的 patchFlag ,这样在运行阶段,就可以收集到所有的动态节点,形成一颗 Block Tree。在 patch 阶段更新组件的时候,就可以遍历 Block Tree,只比对这些动态节点,从而达到性能优化的目的。