• 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
      }))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    解析 template 生成 AST

    问题:AST 是什么?

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

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

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

    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)
              }
              // 处理 
              else if (startsWith(s, ')) {
                node = parseBogusComment(context)
              }
              // 处理 
              else if (startsWith(s, ')) {
                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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191

    解析子节点

    解析注释节点
    /**
     * 解析注释节点
     */
    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('
          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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    解析插值
    /**
     * 解析插值
     */
    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) // 插值代码开头和结束的位置信息
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    解析普通文本
    /**
     * 解析普通文本
     */
    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)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    解析元素节点
    /**
     * 解析元素节点
     * 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
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    问题:在 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
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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--) { }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198

    遍历 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
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    转换表达式节点
    /**
     * 转换表达式节点(只有在 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 节点修改,最终转换生成新的表达式对象
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    转换文本节点
    /**
     * 转换文本节点 - 只处理根节点、元素节点、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 // 参数
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    转换 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
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180

    静态提升

    /**
     * 静态提升
     */
    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    问题:静态提升的好处是针对静态节点不用每次在 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
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    生成代码

    生成代码流程:

    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
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    创建代码生成上下文

    /**
     * 创建代码生成上下文
     */
    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    生成预设代码

    /**
     * 生成 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    生成渲染函数

    // 无额外定义的函数
    
    • 1

    生成资源声明代码

    /**
     * 生成自定义组件/指令声明代码
     */
    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, '_')}`;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    生成创建 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(` `)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205

    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)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
  • 相关阅读:
    【Leetcode】剑指Offer10:斐波那契数列
    基于Python实现并对比线性分类器与非线性分类器
    TCP重头戏来!了!(2) —— 小林图解学习摘记
    奥威BI做数据可视化大屏报表,不踩坑更省心
    html+css制作3D七夕表白旋转相册特效
    Maven 命令行环境
    视频如何压缩?视频压缩到100M以内这样做
    安卓全机型 玩机 搞机 ADB FAST 各种指令解析说明与操作
    Python-算法编程100例-滑动窗口(入门级)
    c++中的宏#define用途
  • 原文地址:https://blog.csdn.net/Jackson_Mseven/article/details/134520173