vue3 编译和优化-原理记录

200 阅读17分钟

一、模板解析

模板编译

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,只比对这些动态节点,从而达到性能优化的目的。