Vue3 源码解读系列(十五)——编译

编译

web 模式的编译主要做了 3 件事:

  1. 解析 template 生成 AST
  2. AST 转换
  3. 生成代码
/**
 * web 编译
 * @param {string} template - 待编译的模板字符串
 * @param {string} options - 配置对象
 */
function compile(template, options = {}) {
  return baseCompile(template, extend({}, parserOptions, options, {
    nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])],
    directiveTransforms: extend({}, DOMDirectiveTransforms, opotins.directiveTransforms || {}),
    transformHoist: null
  }))
}

/**
 * 基础编译
 * baseCompile 主要做了 3 件事:
 * 1、解析 template 生成 AST
 * 2、AST 转换
 * 3、生成代码
 */
function baseCompile(template, options = {}) {
  const prefixIdentifiers = false

  // 1、解析 template 生成 AST
  const ast = isString(template) ? baseParse(template, options) : template
  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset()

  // 2、AST 转换
  transform(ast, extend({}, options, {
    prefixIdentifiers,
    nodeTransforms: [
      ...nodeTransforms,
      ...(options.nodeTransforms || [])
    ],
    directiveTransforms: extend({}, directiveTransforms, options.directiveTransforms || {})
  }))

  // 3、生成代码
  return generage(ast, extend({}, options, {
    prefixIdentifiers
  }))
}

解析 template 生成 AST

问题:AST 是什么?

答:AST 是抽象语法树,用于描述节点在模板中完整的映射信息。

问题:为什么 AST 的根节点是虚拟节点?

答:因为 Vue3 中支持 <Fragment> 语法,即:组件可以有多个根节点,但是树状结构只能有一个根节点,所以需要在外层套一个虚拟节点。

HTML 的嵌套结构的解析过程其实就是一个递归解析元素节点的过程。

/**
 * 解析 template 生成 AST
 * baseParse 主要做了 2 件事:
 * 1、创建解析上下文
 * 2、解析子节点,并创建 AST 根节点
 */
function baseParse(content, options = {}) {
  // 1、创建解析上下文
  const context = createParserContext(content, options)
  const start = getCursor(context)

  // 2、解析子节点,并创建 AST 根节点
  return createRoot(parseChildren(context, 0/* DATA */, []), getSelection(context, start))
}

// 默认解析配置
const defaultParserOptions = {
  delimiters: [`{{`, `}}`],
  getNamespace: () => 0/* HTML */,
  getTextMode: () => 0/* DATA */,
  isVoidTag: NO,
  isPreTag: NO,
  isCustomElement: NO,
  decodeEntities: (rawText) => rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
  onError: defaultOnError
}

/**
 * 创建解析器上下文
 */
function createParserContext(content, options) {
  return {
    options: extend({}, defaultParserOptions, options), // 解析相关配置
    column: 1, // 当前代码的列号
    line: 1, // 当前代码的行号
    offset: 0, // 当前代码相对于原始代码的偏移量
    originalSource: content, // 最初的原始代码
    source: content, // 当前代码
    inPre: false, // 当前代码是否在 pre 标签内
    inVPre: false //  当前代码是否在 VPre 指定的环境下
  }
}

/**
 * 解析子节点
 * 目的:解析并创建 AST 节点数组
 * parseChildren 主要做了 2 件事:
 * 1、自顶向下分析代码,生成 nodes
 * 2、空白字符管理
 */
function parseChildren(context, mode, ancestors) {
  const parent = last(ancestors) // 父节点
  const ns = parent ? parent.ns : 0/* HTML */
  const nodes = []

  // 1、自顶向下分析代码,生成 nodes(AST 节点数组)
  // 自顶向下遍历代码,然后根据不同情况去解析代码
  while (!isEnd(context, mode, ancestors)) {
    const s = context.source
    let node = undefined
    if (mode === 0/* DATA */ || mode === 1/* RCDATA */) {
      // 处理 {{ 插值代码
      if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
        node = parseInterpolation(context, mode)
      }
      // 处理 < 开头的代码
      else if (mode === 0/* DATA */ && s[0] === '<') {
        // s 长度为 1,说明代码结尾是 <,报错
        if (s.length === 1) {
          emitError(context, 5/* EOF_BEFORE_TAG_NAME */, 1)
        }
        // 处理 <! 开头的代码
        else if (s[1] === '!') {
          // 处理注释节点
          if (startsWith(s, '<!--')) {
            node = parseComment(context)
          }
          // 处理 <!DOCTYPE 节点
          else if (startsWith(s, '<!DOCTYPE')) {
            node = parseBogusComment(context)
          }
          // 处理 <![CDATA] 节点
          else if (startsWith(s, '<![CDATA]')) {
            if (ns !== 0/* HTML */) {
              node = parseCDATA(context, ancestors)
            } else {
              emitError(context, 1/* CDATA_IN_HTML_CONTENT */)
              node = parseBogusComment(context)
            }
          }
          // 处理其他情况
          else {
            emitError(context, 11/* INCORRECTLY_OPENED_COMMENT */)
            node = parseBogusComment(context)
          }
        }
        // 处理 </ 结束标签
        else if (s[1] === '/') {
          // s 长度为2,说明代码结尾是 </,报错
          if (s.length === 2) {
            emitError(context, 5/* EOF_BEFORE_TAG_NAME */, 2)
          }
          // </> 缺少结束标签,报错
          else if (s[2] === '>') {
            emitError(context, 14/* MISSING_END_TAG_NAME */, 2)
            advanceBy(context, 3)
            continue
          }
          // 多余的结束标签
          else if (/[a-z]/i.test(s[2])) {
            emitError(context, 23/* X_INVALID_END_TAG */)
            parseTag(context, 1/* END */, parent)
            continue
          } else {
            emitError(context, 12/* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 2)
            node = parseBogusComment(context)
          }
        }
        // 解析标签元素节点
        else if (/[a-z]/i.test(s[1])) {
          node = parseElement(context, ancestors)
        } else if (s[1] === '?') {
          emitError(context, 21/* UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME */, 1)
          node = parseBogusComment(context)
        } else {
          emitError(context, 12/* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 1)
        }
      }
    }
    // 解析普通文本节点
    if (!node) {
      node = parseText(context, mode)
    }
    // 如果 node 是数组,则遍历添加
    if (isArray(node)) {
      for (let i = 0; i < node.length; i++) {
        pushNode(nodes, node[i])
      }
    }
    // 否则,添加单个 node
    else {
      pushNode(nodes, node)
    }
  }

  // 2、空白字符管理(用于提高编译效率)
  let removedWhitespace = false
  if (mode !== 2/* RAWTEXT */) {
    if (!context.inPre) {
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i]
        if (node.type === 2 /* TEXT */) {
          if (!/[Atr\n\f]/.test(node.content)) {
            // 匹配空白字符
            const prev = nodes[i - 1]
            const next = nodes[i + 1]
            // 如果 空白字符是开头或者结尾节点 或者 空白字符与注释节点相连 或者 空白字符在两个元素之间并包含换行符 那么这些空白字符节点都应该被移除
            if (!prev || !next || prev.type === 3/* COMMENT */ || next.type === 3/* COMMENT */ ||
              (prev.type === 1/* ELEMENT */ && next.type === 1/* ELEMENT */ && /[\r\n]/.test(node.content))) {
              removedWhitespace = true
              nodes[i] = null
            }
            // 否则压缩这些空白字符到一个空格
            else {
              node.content = ''
            }
          }
          // 替换内容中的空白空间到一个空格
          else {
            node.content = node.content.replace(/[\t\r\n\f]+/g, '')
          }
        }
        // 生产环境移除注释节点
        else if (!(process.env.NODE_ENV !== 'production') && node.type === 3/* COMMENT */) {
          removedWhitespace = true
          nodes[i] = null
        }
      }
    }
    // 根据 HTML 规范删除前导换行符
    else if (parent && context.options.isPreTag(parent.tag)) {
      const first = nodes[0]
      if (first && first.type === 2/* TEXT */) {
        first.content = first.content.replace(/^\r?\n/, '')
      }
    }
  }

  // 过滤空白字符节点
  return removedWhitespace ? nodes.filter(Boolean) : nodes
}

解析子节点

解析注释节点
/**
 * 解析注释节点
 */
function parseComment(context) {
  const start = getCursor(context)
  let content

  const match = /--(\!)?>/.exec(context.source) // 注释结束符

  // 如果没有匹配的注释结束符,则报错
  if (!match) {
    content = context.source.slice(4)
    advanceBy(context, context.source.length)
    emitError(context, 7/* EOF_IN_COMMENT */)
  } else {
    // 非法的注释符号
    if (match.index <= 3) {
      emitError(context, 0/* ABRUPT_CLOSING_OF_EMPTY_COMMENT */)
    }
    // 注释结束符不正确
    if (match[1]) {
      emitError(context, 10/* INCORRECTLY_CLOSED_COMMENT */)
    }
    content = context.source.slice(4, match.index) // 获取注释的内容
    const s = context.source.slice(0, match.index) // 截取到注释结尾之间的代码,用于后续判断嵌套注释
    let prevIndex = 1, nestedIndex = 0

    // 判断嵌套注释符的情况,存在即报错
    while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
      advanceBy(context, nestedIndex - prevIndex + 1)
      if (nestedIndex + 4 < s.length) {
        emitError(context, 16/* NESTED_COMMENT */)
      }
      prevIndex = nestedIndex + 1
    }

    // 前进代码到注释符结束后,目的是用来前进代码,更新 context 解析上下文
    advanceBy(context, match.index + match[0].length - prevIndex + 1)
  }
  return {
    type: 3/* COMMENT */,
    content,
    loc: getSelection(context, start)
  }
}

/**
 * 前进代码
 */
function advanceBy(context, numberOfCharacters) {
  const { source } = context

  // 更新 context 的 offset?line?column?
  advancePositionWithMutation(context, source, numberOfCharacters)

  // 更新 context 的 source
  context.source = source.slice(numberOfCharacters)
}

/**
 * 更新 context 的 offset?line?column?
 */
function advancePositionWithMutation(pos, source, numberOfCharacters = source.length) {
  let linesCount = 0
  let lastNewLinePos = -1
  for (let i = 0; i < numberOfCharacters; i++) {
    if (source.charCodeAt(i) === 10/* newline char code */) {
      linesCount++
      lastNewLinePos = i
    }
  }
  pos.offset += numberOfCharacters
  pos.line += linesCount
  pos.column = lastNewLinePos === -1 ? pos.column + numberOfCharacters : numberOfCharacters - lastNewLinePos
  return pos
}
解析插值
/**
 * 解析插值
 */
function parseInterpolation(context, mode) {
  // 从配置中获取插值开始和结束分隔符,默认是 {{ 和 }}
  const [open, close] = context.options.delimiters
  const closeIndex = context.source.indexOf(close, open, length)

  // 没有结束符,报错
  if (closeIndex === -1) {
    emitError(context, 25/* X_MISSING_INTERPOLATION_END */)
    return undefined
  }

  const start = getCursor(context)

  // 代码前进到插值开始分隔符后
  advanceBy(context, open.length)
  const innerStart = getCursor(context) // 内部插值开始位置
  const innerEnd = getCursor(context) // 内部插值结束位置
  const rawContentLength = closeIndex - open.length // 插值原始内容的长度
  const rawContent = context.source.slice(0, rawContentLength) // 插值原始内容
  const preTrimContent = parseTextData(context, rawContentLength, mode) // 获取插值的内容,并前进代码到插值的内容后
  const content = preTrimContent.trim()
  const startOffset = preTrimContent.indexOf(contend) // 内容相对于插值开始分隔符的头偏移

  // 更新内部插值开始位置
  if (startOffset > 0) {
    advancePositionWithMutation(innerStart, rawContent, startOffset)
  }

  // 内容相对于插值结束分隔符的尾偏移
  const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset)

  // 更新内部插值结束位置
  advancePositionWithMutation(innerEnd, rawContent, endOffset)

  // 前进代码到插值结束分隔符后
  advanceBy(context, close.length)

  // 返回一个描述节点的对象
  return {
    type: 5/* INTERPOLATION */, // 插值节点
    content: { // 表达式节点的对象
      type: 4/* SIMPLE_EXPRESSION */, // 表达式节点
      isStatic: false,
      isConstant: false,
      content, // 插值的内容
      loc: getSelection(context, innerStart, innerEnd) // 内容代码开头和结束的位置信息
    },
    loc: getSelection(context, start) // 插值代码开头和结束的位置信息
  }
}
解析普通文本
/**
 * 解析普通文本
 */
function parseText(context, mode) {
  const endTokens = ['<', context.options.delimiters[0]] // 文本结束符 

  // CDATA 标记 XML 中的纯文本
  if (mode === 3/* CDATA */) {
    endTokens.push(']]>')
  }
  let endIndex = context.source.length

  // 遍历文本结束符,匹配找到结束的位置
  for (let i = 0; i < endTokens.length; i++) {
    const index = context.source.indexOf(endTokens[i], 1)
    if (index !== -1 && endIndex > index) {
      endIndex = index
    }
  }

  // 获取文本的内容,并前进代码到文本的内容后
  const start = getCursor(context)
  const content = parseTextData(context, endIndex, mode)
  return {
    type: 2/* TEXT */,
    content,
    loc: getSelection(context, start)
  }
}
解析元素节点
/**
 * 解析元素节点
 * parseElement 主要做了 3 件事:
 * 1、解析开始标签
 * 2、解析子节点
 * 3、解析结束标签
 */
function parseElement(context, ancestors) {
  const wasInPre = context.inPre // 是否在 pre 标签内
  const wasInVPre = context.inVPre // 是否在 v-pre 指令内
  const isPreBoundary = context.inPre && !wasInPre // 是否在 pre 标签的边界
  const isVPreBoundary = context.inVPre && !wasInVPre // 是否在 v-pre 指令的边界

  // 获取当前元素的父标签节点
  const parent = last(ancestors)
  // 1、解析开始标签,生成一个标签节点,并前进代码到开始标签后
  const element = parseTag(context, 0/* Start */, parent)
  // 如果是自闭和标签,直接返回标签节点
  if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
    return element
  }
  // 下面是处理子节点的逻辑
  // 先把标签节点添加到 ancestors,入栈
  ancestors.push(element)
  const mode = context.options.getTextMode(element, parent)

  // 2、递归解析子节点,传入 ancestors
  const children = parseChildren(context, mode, ancestors)
  // ancestors 出栈
  ancestors.pop()
  // 添加到 children 属性中
  element.children = children
  // 结束标签
  if (startsWithEndTagOpen(context.source, element.tag)) {
    // 3、解析结束标签,并前进代码到结束标签后
    parseTag(context, 1/* End */, parent)
  }
  else {
    emitError(context, 24/* X_MISSING_END_TAG */, 0, element.loc.start);
    if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
      const first = children[0];
      if (first && startsWith(first.loc.source, '<!--')) {
        emitError(context, 8/* EOF_IN SCRIPT_HTML COMMENT_LIKE_TEXT */)
      }
    }
  }
  // 更新标签节点的代码位置,结束位置到结束标签后
  element.loc = getSelection(context, element.loc.start)
  if (isPreBoundary) {
    context.inPre = false
  }
  if (isVPreBoundary) {
    context.inVPre = false
  }
  return element
}

/**
 * 解析并创建一个标签节点
 */
function parseTag(context, type, parent) {
  const start = getCursor(context) // 开始标签

  // 1、匹配标签文本结束的位置
  const match = /^<\/?([a-z][^\t\r\n\f/>]*)/i.exec(context.source)
  const tag = match[1]
  const ns = context.options.getNamesxpace(tag, parent)
  // 前进代码到标签文本结束位置
  advanceBy(context, match[0].length)

  // 2、前进代码到标签文本后面的空白字符后
  advanceSpaces(context)
  // 保存当前状态以防我们需要用 v-pre 重新解析属性
  const cursor = getCursor(context)
  const currentSource = context.source

  // 3、解析标签中的属性,并前进代码到属性后
  let props = parseAttributes(context, type)

  // 4、检查是不是一个 pre 标签
  if (context.options.isPreTag(tag)) {
    context.inPre = true
  }

  // 5、检查属性中有没有 v-pre 指令,如果有,则设置 context.inVPre 为 true,并重置上下文 context 和 重新解析属性
  if (!context.inVPre && props.some(p => p.type === 7/* DIRECTIVE */ && p.name === 'pre')) {
    context.inVPre = true;
    // 重置 context
    extend(context, cursor);
    context.source = currentSource;
    // 重新解析属性,并把 v-pre 过滤了
    props = parseAttributes(context, type).filter(p => p.name !== 'v-pre');
  }


  let isSelfClosing = false; // 是否是闭合标签
  // 6、判断是否自闭合标签
  if (context.source.length === 0) {
    emitError(context, 9/* EOF IN TAG */);
  } else {
    isSelfClosing = startsWith(context.source, '/>')
    if (type === 1/* End */ && isSelfClosing) {
      // 结束标签不应该是自闭和标签
      emitError(context, 4/* END_TAG_WITH_TRAILING_SOLIDUS */);
    }
    // 前进代码到闭合标签后
    advanceBy(context, isSelfClosing ? 2 : 1);
  }

  let tagType = 0/* ELEMENT */
  const options = context.options
  // 7、判断标签类型是组件、插槽还是模板
  if (!context.inVPre && !options.isCustomElement(tag)) {
    const hasVIs = props.some(p => p.type === 7/* DIRECTIVE */ && p.name === 'is') // 判断是否有 is 属性
    if (options.isNativeTag && !hasVIs) {
      if (!options.isNativeTag(tag)) {
        tagType = 1/* COMPONENT*/
      }
    } else if (hasVIs || isCoreComponent(tag) || (options.isBuiltInComponent && options.isBuiltInComponent(tag)) || /^[A-Z]/.test(tag) || tag === 'component') {
      tagType = 1/* COMPONENT */
    }
    if (tag === 'slot') {
      tagType = 2/* SLOT */
    } else if (tag === 'template' && props.some(p => {
      return (p.type === 7/* DIRECTIVE */ && isSpecialTemplateDirective(p.name));
    })) {
      tagType = 3/* TEMPLATE */
    }
  }

  // 返回描述标签节点的对象
  return {
    type: 1/* ELEMENT */, // 节点类型
    ns,
    tag, // 标签名
    tagType, // 标签类型
    props,
    isSelfClosing, // 是否是闭合标签
    children: [], // 标签子节点数组(初始化为空)
    loc: getSelection(context, start), // 文本的开头和结尾信息
    codegenNode: undefined
  }
}

问题:在 parseTag 的过程中,如果解析的属性有 v-pre 标签,为什么要回到之前的 context,重新解析一次?

答:避免标签中的内容不会被其他属性影响。

创建 AST 根节点

/**
 * 创建 AST 根节点
 */
function createRoot(children, loc = locStub) {
  return {
    type: 0/* ROOT */,
    children,
    helpers: [],
    components: [],
    directives: [],
    hoists: [],
    imports: [],
    cached: 0,
    temps: 0,
    codegenNode: undefined,
    loc
  }
}

AST 转换

AST 转换流程:

  1. 创建 transform 上下文

  2. 遍历 AST 节点

  3. 静态提升

    好处:针对静态节点不用每次在 render 阶段都执行一次 createVNode 创建 vnode 对象。

  4. 创建根代码生成节点

AST 转换其实就是语法分析阶段,把 AST 节点做一层转换,构造出语义化更强信息更丰富的 codegenCode,它在后续的代码生成阶段起非常重要的作用。

创建 transform 上下文

/**
 * 获取节点和指令转换的方法
 */
function getBaseTransformPreset(prefixIdentifiers) {
  return [
    [
      transformOnce,
      transformIf,
      transformFor,
      transformExpression,
      transformSlotOutlet,
      transformElement,
      trackSlotScopes,
      transformText,
    ],
    {
      on: transformOn,
      bind: transformBind,
      model: transformModel
    }
  ]
}

/**
 * Node 环境的转换
 * transform 主要做了 4 件事:
 * 1、创建 transform 上下文
 * 2、遍历 AST 节点
 * 3、静态变量提升
 * 4、创建根代码的生成节点
 */
function transform(root, options) {
  // 1、创建 transform 上下文
  const context = createTransformContext(root, options)

  // 2、遍历 AST 节点
  traverseNode(root, context)

  // 3、静态变量提升
  if (options.hoistStatic) {
    hoistStatic(root, context)
  }

  // 4、创建根代码的生成节点
  if (!options.ssr) {
    createRootCodegen(root, context)
  }
  root.helpers = [...context.helpers]
  root.components = [...context.components]
  root.directives = [...context.directives]
  root.imports = [...context.imports]
  root.hoists = context.hoists
  root.temps = context.temps
  root.cached = context.cached
}

/**
 * 创建转换上下文
 */
function createTransformContext(root, { prefixIdentifiers = false, hoistStatic = false, cacheHandlers = false, nodeTransforms = [], directiveTransforms = {}, transformHoist = null, isBuiltInComponents = NOOP, expressionPlugins = [], scopeId = null, ssr = false, onError = defaultOnError }) {
  // transform 过程中的一些配置 和 状态数据等
  const context = {
    // 配置
    prefixIdentifiers,
    hoistStatic,
    cacheHandlers,
    nodeTransforms,
    directiveTransforms,
    transformHoist,
    isBuiltInComponents,
    expressionPlugins,
    scopeId,
    ssr,
    onError,
    // 状态数据
    root,
    helpers: new Set(),
    components: new Set(),
    directives: new Set(),
    hoists: [],
    imports: new Set(),
    temps: 0,
    cached: 0,
    identifiers: {},
    scopes: {
      vFor: 0,
      vSlot: 0,
      vPre: 0,
      vOnce: 0
    },
    parent: null,
    currentNode: root,
    childIndex: 0,
    // methods
    helper(name) {
      context.helpers.add(name)
      return name
    },
    helperString(name) {
      return `_${helperNameMap[context.helper(name)]}`
    },
    replaceNode(node) {
      context.parent.children[context.childIndex] = context.currentNode = node
    },
    removeNode(node) {
      const list = context.parent.children
      const removalIndex = node ? list.indexOf(node) : context.currentNode ? context.childIndex : -1

      // 移除当前节点
      if (!node || node === context.currentNode) {
        context.currentNode = null
        context.onNodeRemoved()
      }
      // 移除兄弟节点
      else {
        if (context.childIndex > removalIndex) {
          context.childIndex--
          context.onNodeRemoved()
        }
      }

      // 移除节点
      context.parent.children.splice(removalIndex, 1)
    },
    onNodeRemoved: () => { },
    addIdentifiers(exp) { },
    removeIdentifiers(exp) { },
    hoist(exp) {
      context.hoists.push(exp)
      const identifier = createSimpleExpression(`_hoisted_${context.hoists.length}`, false, exp.loc, true)
      identifier.hoisted = exp
      return identifier
    },
    cache(exp, isVNode = false) {
      return createCacheExpression(++context.cached, exp, isVNode)
    }
  }
  return context
}

/**
 * 遍历 AST 节点
 */
function traverseNode(node, context) {
  context.currentNode = node

  // 节点转换函数
  const { nodeTransforms } = context
  const exitFns = []
  for (let i = 0; i < nodeTransforms.length; i++) {
    // 有些转换函数会涉及一个退出函数,在处理完子节点后执行
    const onExit = nodeTransforms[i](node, context)
    if (onExit) {
      if (isArray(onExit)) {
        exitFns.push(...onExit)
      } else {
        exitFns.push(onExit)
      }
    }
    // 节点被移除
    if (!context.currentNode) return
    // 因为在转换的过程中节点可能被替代,恢复到之前的节点
    else {
      node = context.currentNode
    }
  }
  switch (node.type) {
    case 3/* COMMENT */:
      if (!context.ssr) {
        // 需要导入 createComment 符主函数
        context.helper(CREATE_COMMENT)
      }
      break
    case 5/* INTERPOLATION */:
      // 需要导入 toString 辅助函数
      if (!context.ssr) {
        context.helper(TO_DISPLAY_STRING)
      }
      break
    case 9/* IF */:
      // 递归遍历每个分支节点
      for (let i = 0; i < node.branches.length; i++) {
        traverseNode(node.branches[i], context)
      }
      break
    case 10/* IF_BRANCH */:
    case 11/* FOR */:
    case 1/* ELEMENT */:
    case 0/* ROOT */:
      // 遍历子节点
      traverseChildren(node, context)
      break
  }

  // 执行转换函数返回的退出函数
  let i = exitFns.length
  while (i--) { }
}

遍历 AST 节点

转换元素节点
/**
 * 转换元素节点
 */
const transformElement = (node, context) => {
  // 如果节点不会 元素 或 组件,则直接返回
  if (!(node.type === 1/* ELEMENT */ && (node.tagType === 0/* ELEMENT */ || node.tagType === 1/* COMPONENT */))) return

  // 返回退出函数,在所有子表达式处理并合并后执行
  return function postTransformElement() {
    // 转换的目标是创建一个实现 VNodeCall 接口的代码生成节点
    const { tag, props } = node
    const isComponent = node.tagType === 1/* COMPONENT */
    const vnodeTag = isComponent ? resolveComponentType(node, context) : `"${tag}"`
    const isDynamicComponent = isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
    let vnodeProps // 属性
    let vnodeChildren // 子节点

    // 标记更新的类型标识,用于运行时优化
    let vnodePatchFlag
    let patchFlag = 0

    // 动态绑定的属性
    let vnodeDynamicProps
    let dynamicPropNames
    let vnodeDirectives

    // 动态组件、svg、foreignObejct 标签以及动态绑定 prop 的节点都被视作为一个 Block
    let shouldUseBlock = isDynamicComponent || (!isComponent && (tag === 'svg' || tag === 'foreignObject' || findProp(node, 'key', true)))

    // 处理 props - 从 AST 节点的 props 对象中进一步解析出指令 directives、动态属性 dynamicPropsNames、更新标识 patchFlag
    if (props.length > 0) {
      const propsBuildResult = buildProps(node, context)
      vnodeProps = propsBuildResult.props
      patchFlag = propsBuildResult.patchFlag // 更新标识 - 用于标识节点更新的类型,在组件更新的过程中会用到
      dynamicPropNames = propsBuildResult.dynamicPropsNames // 动态属性
      const directives = propsBuildResult.directives // 指令
      vnodeDirectives = directives && directives.length ? createArrayExpression(directives.map(dir => buildDirectiveArgs(dir, context))) : undefined
    }

    // 处理 children
    if (node.children.length > 0) {
      // 把 KeepAlive 看做是一个 Block,这样可以避免它的子节点的动态节点被父 Block 收集
      if (vnodeTag === KEEP_ALIVE) {
        shouldUseBlock = true
        // 确保它始终更新
        patchFlag |= 1024/* DYNAMIC_SLOTS */
        if ((propcess.env.NODE_ENV !== 'production') && node.children.length > 1) {
          context.onError(createCompilertError(42/* X_KEEP_ALIVE_INVALID_CHILDREN */, {
            start: node.children[0].loc.start,
            end: node.children[node.children.length - 1].loc.end,
            source: ''
          }))
        }
      }

      // Teleport 不是一个真正的组件,它有专门的运行时处理
      const shouldBuildAsSlots = isComponent && vnodeTag !== TELEPORT && vnodeTag !== KEEP_ALIVE

      // 如果节点只有一个子节点,并且子节点是一个普通的文本节点插值或表达式,则直接把节点赋值给 vnode.children
      if (shouldBuildAsSlots) {
        // 组件有 children,则处理插槽
        const { slots, hasDynamicSlots } = buildSlots(node, context)
        vnodeChildren = slots
        if (hasDynamicSlots) {
          patchFlag |= 1024/* DYNAMIC_SLOTS */
        }
      } else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
        const child = node.children[0]
        const type = child.type
        const hasDyanmicTextChild = type === 5/* INTERPOLATION */ || type === 8/* COMPOUND_EXPRESSION */
        if (hasDynamicTextChild && !getStaticType(child)) {
          patchFlag |= 1/* TEXT */
        }

        // 如果只是一个普通文本节点、插值或者表达式,直接把节点赋值给 vnodeChildren
        if (hasDynamicTextChild || type === 2/* TEXT */) {
          vnodeChildren = child
        } else {
          vnodeChildren = node.children
        }
      } else {
        vnodeChildren = node.children
      }
    }

    // 处理 patchFlag 和 dynamicPropNames
    if (patchFlag !== 0) {
      // 根据 patchFlag 的值从 patchFlagNames 中获取 flag 对应的名字,从而生成注释
      if ((process.env.NODE_ENV !== 'production')) {
        if (patchFlag < 0) {
          vnodePatchFlag = patchFlag + `/*${PatchFlagNames[patchFlag]}*/`
        } else {
          const flagNames = object.keys(PatchFlagNames).map(Number).filter(m => n > 0 && patchFlag & n).map(n => PatchFlagNames[n]).join(`,`)
          vnodePatchFlag = patchFlag + `/* ${flagNames} */`
        }
      } else {
        vnodePatchFlag = String(patchFlag)
      }
      // 将 dynamicPropNames 转化为 vnodeDynamicProps 字符串,便于后续对节点生成代码逻辑的处理
      if (dynamicPropNames && dynamicPropNames.length) {
        vnodeDynamicProps = stringifyFynamicPropNames(dynamicPropNames)
      }
    }
    // 通过 createVNodeCall 创建了实现 VNodeCall 接口的代码生成节点
    node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren, vnodeFlag, vnodeDynamicProps, vnodeDirectives, !!shouldUseBlock, false/* disableTrancking */, node.loc)
  }
}

/**
 * 创建了实现 VNodeCall 接口的代码生成节点
 */
function createVNodeCall(context, tag, props, children, patchFlag, dynamicProps, directives, isBlock = false, disableTranking = false, loc = locStub) {
  if (context) {
    // context.helper 会将一些 simple 对象添加到 context.helps 数组中,目的是为了后续代码生成阶段生成一些辅助代码
    if (isBlock) {
      context.helper(OPEN_BLOCK)
      context.helper(CREATE_BLOCK)
    } else {
      context.helper(CREATE_VNODE)
    }
    if (directives) {
      context.helper(WITH_DIRECTIVES)
    }
  }

  // 返回一个对象,包含了传入的参数
  return {
    type: 13/* VNODE_CALL */,
    tag,
    props,
    children,
    patchFlag,
    dynamicProps,
    directives,
    isBlock,
    disableTracking,
    loc
  }
}
转换表达式节点
/**
 * 转换表达式节点(只有在 node 环境下的编译或者是 web 端的非生产环境下才会执行)
 * transformExpression 主要做了 2 件事:
 * 1、转换插值中的动态表达式
 * 2、转换元素指令中的动态表达式
 */
const transformExpression = (node, context) => {
  // 处理插值中的动态表达式
  if (node.type === 5/* INTERPOLATION */) {
    node.content = processExpression(node.content, context)
  }
  // 处理元素指令中的动态表达式
  else if (node.type === 1/* ELEMENT */) {
    for (let i = 0; i < node.props.length; i++) {
      const dir = node.props[i]
      // v-on 和 v-for 不处理,因为它们都有各自的处理逻辑
      if (dir.type === 7/* DIRECTIVE */ && dir.name !== 'for') {
        const exp = dir.exp
        const arg = dir.arg
        if (exp && exp.type === 4/*SIMPLE EXPRESSION*/ && !(dir.name === 'on' && arg)) {
          dir.exp = processExpression(exp, context, dir.name === 'slot')
        }
        if (arg && argtype === 4 /* SIMPLE_EXPRESSION */ && !arg.isStatic) {
          dir.arg = processExpresstion(arg, context)
        }
      }
    }
  }
}
/*
  processExpression 的实现:
  1、内部依赖了 @babel/parser 库去解析表达式生成 AST 节点
  2、依赖了 estree-walker 库去遍历这个 AST 节点,然后对节点分析去判断是否需要加前缀
  3、接着对 AST 节点修改,最终转换生成新的表达式对象
*/
转换文本节点
/**
 * 转换文本节点 - 只处理根节点、元素节点、v-for、v-if分支
 * 目的:合并一些相邻的文本节点,然后为内部每一个文本节点创建一个代码生成节点,
 */
const transformText = (node, context) => {
  // 在节点退出时执行转换,保证所有表达式都已经被处理
  if (node.type === 0/* ROOT */ || node.type === 1/* ELEMENT */ || node.type === 11/* FOR */ || node.type === 10/* IF_BRANCH */) {
    // 返回退出函数,因为要保证所有表达式节点都已经被处理才执行转换逻辑
    return () => {
      const children = node.children
      let currentContainer = 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] = {
                  type: 8/* COMPOUND_EXPRESSION */,
                  loc: child.loc,
                  children: [child]
                }
              }
              currentContainer.children.push(`+`, next)
              children.splice(j, 1)
              j--
            }
            else {
              currentContainer = undefined
              break
            }
          }
        }
      }

      // 如果是一个带有单个文本子元素的纯元素节点,则什么都不需要转换,因为这种情况在运行时可以直接设置元素的 textContent 来更新文本
      if (!hasText || (children.length === 1 && (node.type === 0/* ROOT */ || (node.type === 1/* ELEMENT */ &&
        node.tagType === 0/* ELEMENT*/)))) {
        return
      }

      // 为子文本节点创建一个调用函数表达式的代码生成节点
      for (let i = 0; i < children.length; i++) {
        const child = children[i]
        if (isText(child) || child.type === 8/* COMPOUND_EXPRESSION */) {
          const callArgs = []
          // 为 createTextVNode 添加执行参数
          if (child.type !== 2/* TEXT */ || child.content !== '') {
            callArgs.push(child)
          }
          // 标记动态文本
          if (!context.ssr && child.type !== 2/* TEXT */) {
            callArgs.push(`${1/* TEXT */}/* ${PatchFlagNames[1/* TEXT */]} */`)
          }
          children[i] = {
            type: 12/* TEXT_CALL */,
            content: child,
            loc: child.loc,
            codegenNode: createCallExpression(context.helper(CREATETEXT), callArgs)
          }
        }
      }
    }
  }
}

/**
 * 创建调用函数表达式的代码生成节点
 * @description 创建的函数表达式所生成的节点,对应的函数名是 createTextVNode,参数 callArgs 是子节点本身 child,如果是动态插值节点,那么参数还会多一个 TEXT 的 patchFlag
 */
function createCallExpression(callee, args = [], loc = locStub) {
  // 返回一个类型为 JS_CALL_EXPRESSION 的对象,并包含了执行的函数名和参数
  return {
    type: 14/* JS_CALL_EXPRESSION */, // 类型
    loc, // 位置
    callee, // 函数名
    arguments: args // 参数
  }
}
转换 v-if 节点
/**
 * 转换 v-if 节点
 * 通过 createStructuralDirectiveTransform 创建的一个结构化指令的转换函数
 */
const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/, (node, dir, context) => {
  return processIf(node, dir, context, (ifNode, branch, isRoot) => {
    // 返回退出回调函数,当所有子节点转换完成执行
    return () => {
      if (isRoot) {
        // v-if 节点的退出函数
        // 创建 IF 节点的 codegenNode
        ifNode.codegenNode = createCodegenNodeForBranch(branch, 0, context)
      } else {
        // v-else-if、v-else节点的退出函数
        // 将此分支的 codegenNode 附加到 上一个条件节点的 codegenNode 的 alternate 中
        let parentCondition = ifNode.codegenNode
        while (parentCondition.alternate.type === 19/* JS_CONDITIONAL_EXPRESSION */) {
          parentCondition = parentCondition.alternate
        }
        // 更新候选节点
        parentCondition.alternate = createCodegenNodeForBranch(branch, ifNode.branches.length, 1, context)
      }
    }
  })
})

/**
 * 创建结构指令转换
 * @param {string} name - 指令名
 * @param {Function} fn - 构造转换退出函数的方法
 */
function createStructuralDirectiveTransform(name, fn) {
  const matches = isString(name) ? (n) => n === name : (n) => name.test(n)

  // 返回 transformIf 转换函数
  return (node, context) => {
    // 只处理元素节点,因为只有元素节点才会有 v-if 指令
    if (node.type === 1/* ELEMENT */) {
      const { props } = node

      // 结构化指令的转换与插槽无关,插槽相关处理逻辑在 vSlot.ts 中
      if (node.tagType === 3/* TEMPLATE */ && props.some(isVSlot)) return
      const exitFns = []
      for (let i = 0; i < props.length; i++) {
        const prop = props[i]
        // 删除结构指令以避免无限递归
        if (prop.type === 7/* DIRECTIVE */ && matches(prop.name)) {
          props.splice(i, 1)
          i--

          // 执行 fn 获取退出函数
          const onExit = fn(node, prop, context)
          if (onExit) {
            exitFns.push(onExit)
          }
        }
      }
      return exitFns
    }
  }
}

/**
 * 处理 if 分支节点
 */
function processIf(node, dir, context, processCodegen) {
  // 处理 if 节点
  if (dir.name === 'if') {
    // 创建分支节点
    const branch = createIfBranch(node, dir)
    // 创建 IF 节点,替换当前节点
    const ifNode = {
      type: 9 /* IF*/,
      loc: node.loc,
      branches: [branch]
    }
    context.replaceNode(ifNode)
    if (processCodegen) {
      return processCodegen(ifNode, branch, true)
    }
  }
  // 处理 v-else-if、v-else 节点
  else {
    const siblings = context.parent.children
    let i = siblings.indexof(node)
    while (i-- >= -1) {
      const sibling = siblings[i]
      if (sibling && siblingtype === 9/* IF */) {
        // 把节点移动到 IF 节点的 branches 中
        context.removeNode()
        const branch = createIfBranch(node, dir)
        sibling.branches.push(branch)
        const onExit = processCodegen && processCodegen(sibling, branch, false)
        // 因为分支已被删除,所以它的子节点需要在这里遍历
        traverseNode(branch, context)
        // 执行退出函数
        if (onExit) {
          onExit()
        }
        // 恢复 currentNode 为 null,因为它已经被移除
        context.currentNode = null
      } else {
        context.onError(createCompilerError(28/* X_V_ELSE_NO_ADJACENT_IF */, node.loc))
      }
      break
    }
  }
}

/**
 * 创建分支节点
 */
function createIfBranch(node, dir) {
  return {
    type: 10/* IF_BRANCH */,
    loc: node.loc,
    condition: dir.name === 'else' ? undefined : dir.exp,
    // 如果节点 node 不是 template,那么 children 指向的就是这个单个 node 构造的数组
    children: node.tagType === 3/* TEMPLATE */ ? node.children : [node]
  }
}

/**
 * 创建 IF 节点的 codegenNode
 */
function createCodegenNodeForBranch(branch, index, context) {
  if (branch.condition) {
    // 当分支节点存在 condition 的时候,比如 v-if 和 v-else-if,它通过 createConditionalExpression 返回一个条件表达式节点
    return createConditionalExpression(branch.condition, createChildrenCodegenNode(branch, index, context), createCallExpression(context.helper(CREATE_COMMENT), [(process.env.NODE_ENV !== 'production') ? '"v-if"' : '""', 'true']))
  } else {
    return createChildrenCodegenNode(branch, index, context)
  }
}

/**
 * 当分支节点存在 condition 时,返回条件表达式节点
 */
function createConditionalExpression(test, consequent, alterater, newline = true) {
  return {
    type: 19/* JS_CONDIITONAL_EXPRESSION */,
    test,
    consequent, // if 分支的子节点对应的代码生成节点
    alternate,// 后面分支的子节点对应的代码生成节点
    newline,
    loc: locStub
  }
}

/**
 * 判断每个分支子节点是否是 vnodeCall
 */
function createChildrenCodegenNode(branch, index, context) {
  const { helper } = context
  //根据 index 创建 key 属性
  const keyProperty = createObjectProperty(`key`, createSimpleExpression(index + '', false))
  const { children } = branch
  const firstChild = children[0]
  const needFragmentWrapper = children.length !== 1 || firstChild.type !== 1/* ELEMENT */
  if (needFragmentWrapper) {
    if (children.length === 1 && firstChild.type === 11/* FOR */) {
      const vnodeCall = firstChild.codegenNode
      injectProp(vnodeCall, keyProperty, context)
      return vnodeCall
    } else {
      return createVNodeCall(context, helper(FRAGMENT), createObjectExpression([keyProperty]), children, `${64 /* STABLE_FRAGMENT */} /* ${PatchFlagNames[64/* STABLE_FRAGMENT */]}*/`, undefined, undefined, true, false, branch.loc)
    }
  } else {
    const vnodeCall = firstChild.codegenNode
    // 把 createVNode 改变为 createBlock
    if (vnodeCall.type === 13/* VNODE_CALL */ && /* 组件节点的 children 会被视为插槽,不需要添加 block */ (firstChildtagType !== 1/* COMPONENT */ || vnodeCall.tag === TELEPORT)) {
      vnodeCallisBlock = true
      // 创建 block 的辅助代码
      helper(OPEN_BLOCK)
      helper(CREATE_BLOCK)
    }
    // 给 branhc 注入 key 属性
    injectProp(vnodeCall, keyProperty, context)
    return vnodeCall
  }
}

静态提升

/**
 * 静态提升
 */
function hoistStatic(root, context) {
  // 注意:根节点无法实现静态提升
  walk(root, context, new Map(), isSingleElementRoot(root, root.children[0]));
}

/**
 * 移动
 */
function walk(node, context, resultCache, doNotHoistNode = false) {
  let hasHoistedNode = false
  let hasRuntimeConstant = false // 是否包含运行时常量
  const { children } = node
  for (leti = 0; i < children.length; i++) {
    const child = children[i]
    // 普通元素可以静态提升
    if (child.type === 1/* ELEMENT */ && child.tagType === 0/* ELEMENT */) {
      let staticType
      // 获取静态节点的类型,如果是元素,则递归检查它的子节点
      if (!doNotHoistNode && (staticType = getStaticType(child, resultCache)) > 0) {
        // 运行时常量,值只有在运行时才能被确定,所以不能静态提升
        if (staticType === 2/* HAS_RUNTIME_CONSTANT */) {
          hasRuntimeConstant = true
        }
        // 更新 patchFlag
        child.codegenNode.patchFlag = -1/* HOISTED */ + ((process.env.NODE_ENV !== 'production') ? `/* HOISTED  */` : ``)
        // 更新节点的 codegenNode
        child.codegenNode = context.hoist(child.codegenNode)
        hasHoistedNode = true
        continue
      }
      // 节点可能会包含一些动态子节点,但它的静态属性还是可以被静态提升
      else {
        const codegenNode = child.codegenNode
        if (codegenNode.type === 13/* VNODE_CALL */) {
          const flag = getPatchFlag(codegenNode)
          if ((!flag || flag === 512/* NEED_PATCH */ || flag === 1/* TEXT */) && !hasDynamicKeyOrRef(child) && !hasCachedProps()) {
            const props = getNodeProps(child)
            if (props) {
              codegenNode.props = context.hoist(props)
            }
          }
        }
      }
    }
    // 文本节点也可以静态提升
    else if (child.type === 12/* TEXT_CALL */) {
      const staticType = getStaticType(child.content, resultCache)
      if (staticType > 0) {
        // 运行时常量,由于它的值只能在运行时才能被确定,所以不能静态提升
        if (staticType === 2/* HAS_RUNTIME_CONSTANT */) {
          hasRuntimeConstant = true
        }
        child.codegenNode = context.hoist(child.codegenNode)
        hasHoistedNode = true
      }
    }

    // 递归遍历子节点
    if (child.type === 1/* ELEMENT */) {
      walk(child, context, resultCache)
    } else if (child.type === 11/* FOR */) {
      walk(child, context, resultCache, child.children.length === 1)
    } else if (child.type === 9/* IF */) {
      for (leti = 0; i < child.branches.length; i++) {
        walk(child.branches[i], context, resultCache, child.branches[i].children.length === 1)
      }
    }
  }

  // 如果编译配置了 transformHoist,则执行
  if (!hasRuntimeConstant && hasHoistedNode && context.transformHoist) {
    context.transformHoist(children, context, node)
  }
}

/**
 * 提升
 */
function hoist(exp) {
  context.hoists.push(exp)
  const identifier = createSimpleExpression(`_hoisted_${context.hoists.length}`, false, exp.loc, true)
  identifier.hoisted = exp
  return identifier
}
child.codegenNode = context.hoist(child.codegenNode)

问题:静态提升的好处是针对静态节点不用每次在 render 阶段都执行一次 createVNode 创建 vnode 对象,但它有没有成本呢?为什么?

答:有成本。

  1. 在初始化时需要判断区分动态节点和静态节点,这会带来一定的性能损耗
  2. 需要内存去保存静态节点,因此会占用更多的内存

创建根代码生成节点

/**
 * 创建根代码生成节点
 */
function createRootCodegen(root, context) {
  const { helper } = context
  const { children } = root
  const child = children[0]
  if (children.length === 1) {
    // 如果子节点是单个元素节点,则将其转换成一个 block
    if (isSingleElementRoot(root, child) && child.codegenNode) {
      const codegenNode = child.codegenNode
      if (codegenNode.type === 13/* VNODE_CALL */) {
        codegenNode.isBlock = true
        helper(OPEN_BLOCK)
        helper(CREATE_BLOCK)
      }
      root.codegenNode = codegenNode
    } else {
      root.codegenNode = child;
    }
  } else if (children.length > 1) {
    // 如果子节点是多个节点,则返回一个 fragement 的代码生成节点
    root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children, `${64 /* STABLE_FRAGMENT */} /* ${PatchFlagNames[64 /* STABLE_FRAGMENT */]} */`, undefined, undefined, true)
  }
}

生成代码

生成代码流程:

  1. 创建代码生产上下文
  2. 生成预设代码
  3. 生成渲染函数
  4. 生成资源声明代码
  5. 生成创建 VNode 树的表达式
/**
 * 生成代码
 * @param {Object} ast - 转换后的 AST 树
 * generate 主要做 5 件事:
 * 1、创建代码生成上下文
 * 2、生成预设代码
 * 3、生成渲染函数
 * 4、生成资源声明代码
 * 5、生成创建 VNode 树的表达式
 */
function generate(ast, options = {}) {
  // 1、创建代码生成上下文
  const context = createCodegenContext(ast, options);
  const { mode, push, prefixldentifiers, indent, deindent, newline, scopeld, ssr } = context;
  const hasHelpers = ast.helpers.length > 0;
  const useWithBlock = !prefixldentifiers && mode !== 'module'
  const genScopeld = scopeld != null && mode === 'module';

  // 2、生成预设代码
  if (mode === 'module') {
    genModulePreamble(ast, context, genScopeId)
  } else {
    genFunctionPreamble(ast, context);
  }

  // 3、生成渲染函数
  if (!ssr) {
    push(`function render(_ctx, _cache) {`);
  } else {
    push('function ssrRender( _ctx, _push,_parent,_attrs) {');
  }
  indent();
  if (useWithBlock) {
    // 处理带 with 的情况,Web 端运行时编译
    push(`with (_ctx) {`);
    indent();
    if (hasHelpers) {
      push(`const { ${ast.helpers.map(s => `${helperNameMap[s]}:_${helperNameMap[s]}`).join(',')}} = _Vue`);
      push(`\n`);
      newline();
    }
  }

  // 4、生成资源声明代码
  // 生成自定义组件声明代码
  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 (ast.temps > 0) {
    push('let');
    for (let i = 0; i < ast.temps; i++) {
      push('Si>0?,:]_tempsli');
    }
  }
  // 如果生成了资源的声明代码,则在尾部添加一个换行符,然后再生成一个空行
  if (ast.components.length || ast.directives.length || ast.temps) {
    push(`\n`);
    newline();
  }
  // 如果不是 ssr,则再添加一个 return 字符串
  if (!ssr) {
    push(`return`);
  }

  // 5、生成创建 VNode 树的表达式
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context)
  } else {
    push(`null`);
  }

  if (useWithBlock) {
    deindent();
    push(`}`);
  }
  deindent();
  push(`}`);
  return {
    ast,
    code: context.code,
    map: context.map ? context.map.toJSON() : undefined
  }
}

创建代码生成上下文

/**
 * 创建代码生成上下文
 */
function createCodegenContext(ast, { mode = 'function', prefixldentifiers = mode === 'module', sourceMap = false, filename = `template.vue.html`, scopeld = null, optimizeBindings = false, runtimeGlobalName = `Vue`, runtimeModuleName = `vue`, ssr = false }) {
  // 上下文对象 context 维护了 generate 过程的一些配置、状态数据、辅助函数
  const context = {
    mode,
    prefixIdentifiers,
    sourceMap,
    filename,
    scopeId,
    optimizeBindings,
    runtimeGlobalName,
    runtimeModuleName,
    ssr,
    source: ast.loc.source,
    code: ``,
    column: 1,
    line: 1,
    offset: 0,
    indentLevel: 0,
    pure: false,
    map: undefined,
    helper(key) {
      return `${SfhelperNameMap[key]}`
    },
    // context.code 后追加 code 来更新它的值
    push(code) {
      context.code += code
    },
    // 增加代码的缩进,让上下文维护的代码缩进 context.indentLevel 加 1
    indent() {
      // 执行 newline 方法,添加一个换行符,以及两倍 indentLevel 对应的空格来表示缩进的长度
      newline(++context.indentLevel)
    },
    // 减少代码的缩进,让上下文维护的代码缩进 context.indentLevel 减 1
    deindent(withoutNewLine = false) {
      if (withoutNewLine) {
        --context.indentLevel
      } else {
        // 执行 newline 方法去添加一个换行符,并减少两倍 indentLevel 对应的空格的缩进长度
        newline(--context.indentLevel)
      }
    },
    // 添加换行符
    newline() {
      newline(context.indentLevel)
    }
  }
  function newline(n) {
    context.push('n' + ``.repeat(n))
  }
  return context
}

生成预设代码

/**
 * 生成 module 模式的预设代码
 */
function genModulePreamble(ast, context, genScopeId) {
  const { push, newline, optimizeBindings, runtimeModuleName }
    = context

  // 处理 scopeId...

  // 生成 import 声明代码
  // ast.helpers 是在 transform 阶段通过 context.helper 添加的
  if (ast.helpers.length) {
    if (optimizeBindings) {
      push(`import { ${ast.helpers.map(s => helperNameMap[s]).join(',')}}} from ${JSON.stringif(runtimeModule)} \n`)
      push(`\n// Binding optimization for webpack code-split \nconst ${ast.helpers.map(s => `${helperNameMap[s]}= ${helperNameMap[s]}`).join(',')}\n`)
    } else {
      push(`import { ${ast.helpers.map(s => `${helperNameMap[s]}as_${helperNameMap[s]}`).join(',')}} from ${JSON.stringify(runtimeModuleName)} \n`)
    }
  }
  // 处理 ssrHelpers...

  // 处理imports...

  // 处理scopeId...

  genHoists(ast.hoists, context)
  newline()
  push(`export `)
}

/**
 * 生成静态提升的相关代码
 */
function genHoists(hoists, context) {
  if (!hoists.length) return
  context.pure = true
  const [push, newline] = context

  // 调用 newline 生成空行
  newline()

  // 遍历 hoists 数组生成静态变量提升的方法
  hoists.forEach((exp, i) => {
    if (exp) {
      push(`const_hoisted_${i + 1}=`)
      genNode(exp, context)
      newline()
    }
  })
  context.pure = false
}

生成渲染函数

// 无额外定义的函数

生成资源声明代码

/**
 * 生成自定义组件/指令声明代码
 */
function genAssets(assets, type, { helper, push, newline }) {
  // 获取自定义组件/指令
  const resolver = helper(type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE)

  // 遍历 assets 数组,生成自定义组件声明代码
  for (let i = 0; i < assets.length; i++) {
    const id = assetslil
    push(`const ${toValidAssetId(id, type)}=
  ${resolver}(${JSON.stringify(id)})`)
    if (i < assets.length - 1) {
      newline()
    }
  }
}

/**
 * 包装变量
 */
function toValidAssetId(name, type) {
  return `_${type}_${name.replace(/[^\w]/g, '_')}`;
}

生成创建 VNode 树的表达式

/**
 * 生成创建 VNode 树的表达式
 */
function genNode(node, context) {
  if (shared.isString(node)) {
    context.push(node)
    return
  }
  if (sharedisSymbol(node)) {
    context.push(context.helper(node))
    return
  }

  // 根据不同类型生成不同代码
  switch (node.type) {
    case 1/* ELEMENT*/:
    case 9 /*IF*/:
    case 11/* FOR */:
      genNode(node.codegenNode, context)
      break
    case 2/* TEXT */:
      genText(node, context)
      break
    case 4/* SIMPLE_EXPRESSION */:
      genExpression(node, context)
      break
    case 5/* INTERPOLATION */:
      genInterpolation(node, context)
      break
    case 12/* TEXT_CALL */:
      genNode(node.codegenNode, context)
      break
    case 8/* COMPOUND EXPRESSION */:
      genCompoundExpression(node, context)
      break
    case 3/* COMMENT */:
      break
    case 13/* VNODE_CALL */:
      genVNodeCall(node, context)
      break
    case 14/* JS_CALL_EXPRESSION */:
      genCallExpression(node, context)
      break
    case 15/* JS_OBJECT_EXPRESSION */:
      genObjectExpression(node, context)
      break
    case 17/* JS_ARRAY_EXPRESSION */:
      genArrayExpression(node, context)
      break
    case 18/* JS_FUNCTION_EXPRESSION */:
      genFunctionExpression(node, context)
      break
    case 19/* JS CONDITIONAL_EXPRESSION */:
      genConditionalExpression(node, context)
      break
    case 20/* JS_CACHE_EXPRESSION */:
      genCacheExpression(node, context)
      break
    // SSR only types
    case 21/* JS_BLOCK_STATEMENT */:
      genNodeList(node.body, context, true, false)
      break
    case 22/* JS_TEMPLATE_LITERAL */:
      genTemplateLiteral(node, context)
      break
    case 23/* JS_IF_STATEMENT */:
      genIfStatement(node, context)
      break
    case 24/* JS_ASSIGNMENT_EXPRESSION */:
      genAssignmentExpression(node, context)
      break
    case 25/* JS_SEQUENCE_EXPRESSION */:
      genSequenceExpression(node, context)
      break
    case 26/* JS_RETURN_STATEMENT */:
      genReturnStatement(node, context)
      break
  }
}

/**
 * 生成创建 VNode 节点的表达式代码
 */
function genVNodeCall(node, context) {
  const { push, helper, pure } = context
  const { tag, props, children, patchFlag, dynamicProps, directives, isBlock, disableTracking } = node
  if (directives) {
    push(helper(WITH_DIRECTIVES) + `(`)
  }
  /*
    isBlock 为 true:生成创建 Block 相关代码
    isBlock 为 false,生成创建 VNode 的相关代码
   */
  if (isBlock) {
    push(`${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}),`)
  }
  // 是否生成注释代码
  if (pure) {
    push(PURE_ANNOTATION)
  }
  push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node)
  genNodeList(genNullableArgs([tag, props, children, patchFlag, dynamicProps]), context)
  push(`)`)
  if (isBlock) {
    push(`)`)
  }
  if (directives) {
    push(`,`)
    genNode(directives, context)
    push(`)`)
  }
}

/**
 * 从后往前遍历,直到找到第一个不为 null 的索引,并返回前面(包含)的所有数组
 */
function genNullableArgs(args) {
  let i = args.length
  while (i--) {
    if (args[i] != null) break
  }
  return args.slice(0, i + 1).map(arg => arg || `null`)
}

/**
 * 生成参数相关代码
 */
function genNodeList(nodes, context, multilines = false, comma = true) {
  const { push, newline } = context
  // 遍历 nodes,并判断其类型
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i]
    // 字符转,则直接添加到代码中
    if (shared.isString(node)) {
      push(node)
    }
    // 数组,则生成数组形式代码
    else if (shared.isArray(node)) {
      genNodeListAsArray(node, context)
    }
    // 对象,则递归执行生成节点代码
    else {
      genNode(node, context)
    }
    if (i < nodes.length - 1) {
      if (multilines) {
        comma && push(',')
        newline()
      } else {
        comma && push(',')
      }
    }
  }
}

/**
 * 生成 nodes 第二个元素 - props 参数
 */
function genExpression(node, context) {
  const { content, isStatic } = node
  // 往代码中添加 context 内容
  context.push(isStatic ? JSON.stringify(content) : content, node)
}

/**
 * 生成 nodes 第二个元素 - children 子节点数组
 */
function genNodeListAsArray(nodes, context) {
  const multilines = nodes.length > 3 || nodes.some(n => isArray(n) || !isText$1(n))
  context.push(`[`)
  multilines && context.indent()
  // 递归调用
  genNodeList(nodes, context, multilines)
  multilines && context.deindent()
  context.push(`]`)
}

/**
 * 生成条件表达式代码
 */
function genConditionalExpression(node, context) {
  const { test, consequent, alternate, newline: needNewline } = node
  const { push, indent, deindent, newline } = context
  // 生成条件表达式
  if (test.type === 4/* SIMPLE EXPRESSION*/) {
    const needsParens = !isSimpleldentifier(test.content)
    needsParens && push(`(`)
    genExpression(test, context)
    needsParens && push(`)`)
  } else {
    push(`(`)
    genNode(test, context)
    push(`)`)
  }
  // 换行加缩进
  needNewline && indent()
  context.indentLevel++
  needNewline || push(` `)
  // 生成主逻辑代码
  push(`?`)
  genNode(consequent, context)
  context.indentLevel--
  needNewline && newline()
  needNewline || push(` `)
}

Vue3 优化

/**
 * 运行时优化:Vue3 采用 Block 的概念来优化性能,即只更新渲染动态节点
 */
const blockStack = []
let currentBlock = null

/**
 * 收集动态的 vnode 节点,在 patch 阶段只对比这些动态 vnode 节点
 */
function openBlock(disableTracking = false) {
  blockStack.push((currentBlock = disableTracking ? null : []));
}

/**
 * 创建 vnode 节点
 */
function createVNode(type, props = null, children = null) {
  // 处理 props 相关逻辑,标准化 class 和 style
  // ...

  // 对vnode 类型信息编码
  // ...

  // 创建 vnode 对象
  // ...

  // 标准化子节点,把不同数据类型的 children 转成数组或者文本类型
  // ...

  // 添加动态 vnode 节点到 currentBlock 中
  if (shouldTrack > 0 && !isBlockNode && currentBlock && patchFlag !== 32/* HYDRATE EVENTS */ && (patchFlag > 0 || shapeFlag & 128/* SUSPENSE */ || shapeFlag & 64/* TELEPORT */ || shapeFlag & 4/* STATEFUL_COMPONENT */ || shapeFlag & 2/* FUNCTION_COMPONENT */)) {
    currentBlock.push(vnode)
  }
  return vnode
}

/**
 * 创建 Block
 */
function createBlock(type, props, children, patchFlag, dynamicProps) {
  // 创建 vnode
  const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true/* isBlock:阻止这个 block 收集自身 */)

  // 在 vnode 上保留当前 Block 收集的动态子节点
  vnode.dynamicChildren = currentBlock || EMPTY_ARR
  blockStack.pop()

  // 当前 Block 恢复到父 Block
  currentBlock = blockStack[blockStack.length - 1] || null

  // 节点本身作为父 Block 收集的子节点
  if (currentBlock) {
    currentBlock.push(vnode)
  }
  return vnode
}

/**
 * 重新渲染元素
 */
const patchElement = (nl, n2, parentComponent, parentSuspense, isSVG, optimized) => {
  const el = (n2.el = n1.el)
  const oldProps = (n1 && n1.props) || EMPTY_OBJ
  const newProps = n2.props || EMPTY_OBJ
  // 更新 props
  patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG)
  const areChildrenSVG = isSVG && n2.type !== 'foreignObject'

  // 更新子节点
  // 如果是 blockVNode,则只需要比较动态子节点就行
  if (n2.dynamicChildren) {
    patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, currentContainer, parentComponent, parentSuspense, isSVG);
  } else if (!optimized) {
    patchChildren(n1, n2, currentContainer, currentAnchor, parentComponent, parentSuspense, isSVG)
  }
}

/**
 * 比较动态子节点
 */
const patchBlockChildren = (oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG) => {
  for (leti = 0; i < newChildren.length; i++) {
    const oldVNode = oldChildren[i]
    const newVNode = newChildren[i]
    // 确定待更新节点的容器
    const container =
      // 对于 Fragment,我们需要提供正确的父容器
      oldVNode.type === Fragment ||
        // 在不同节点的情况下,将有一个替换节点,我们也需要正确的父容器
        !isSameVNodeType(oldVNode, newVNode) ||
        // 组件的情况,我们也需要提供一个父容器
        oldVNode.shapeFlag & 6/* COMPONENT */
        ? hostParentNode(oldVNode.el) :
        // 在其他情况下,父容器实际上并没有被使用,所以这里只传递 Block 元素即可
        fallbackContainer
    patch(oldVNode, newVNode, container, null, parentComponent, parentSuspense, isSVG, true)
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Mseven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值