Vue 模版渲染 -> AST(抽象语法树)转化为渲染函数(小白文)

273 阅读2分钟

大致过程

首先,compile 方法会通过 baseCompile 函数将模板字符串编译为 AST(抽象语法树)

接下来,编译器会根据 AST 进行优化处理,包括静态节点提升、静态属性提升、事件侦听器缓存等。这一步骤主要是调用 optimize 函数。

然后,编译器会将优化后的 AST 生成渲染函数所需要的代码,这一步骤主要是调用 generate 函数生成渲染函数代码。

最后,编译器会封装生成的渲染函数、静态节点等信息,返回最终的编译结果对象。

AST(抽象语法树)

在 baseParse 将template转成ast 抽象语法树

开始前template的样子 转的结果

在compiler-core中的parse方法中parseChildren 循环处理 template

静态提升

静态提升是Vue3编译优化中的其中一个优化点。所谓的静态提升,就是指在编译器编译的过程中,将一些静态的节点或属性提升到渲染函数之外。类似于 baseCompile方法中调用transform 方法 将ast 丰满起来 (codegenNode )

  if (options.hoistStatic) {
    hoistStatic(root, context)
  }

<p>static text</p> 会被提出去,不用每次都去重新渲染

翻译成渲染函数

通过其中 genFunctionPreamble 在context 的code 参数 从ast 编译成 code, 同时在这里将hoist 翻译成字符串

function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
  const {
    ssr,
    prefixIdentifiers,
    push,
    newline,
    runtimeModuleName,
    runtimeGlobalName,
    ssrRuntimeModuleName
  } = context
  const VueBinding =
    !__BROWSER__ && ssr
      ? `require(${JSON.stringify(runtimeModuleName)})`
      : runtimeGlobalName
  // Generate const declaration for helpers
  // In prefix mode, we place the const declaration at top so it's done
  // only once; But if we not prefixing, we place the declaration inside the
  // with block so it doesn't incur the `in` check cost for every helper access.
  const helpers = Array.from(ast.helpers)
  if (helpers.length > 0) {
    if (!__BROWSER__ && prefixIdentifiers) {
      push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`)
    } else {
      // "with" mode.
      // save Vue in a separate variable to avoid collision
      push(`const _Vue = ${VueBinding}\n`)
      // in "with" mode, helpers are declared inside the with block to avoid
      // has check cost, but hoists are lifted out of the function - we need
      // to provide the helper here.
      if (ast.hoists.length) {
        const staticHelpers = [
          CREATE_VNODE,
          CREATE_ELEMENT_VNODE,
          CREATE_COMMENT,
          CREATE_TEXT,
          CREATE_STATIC
        ]
          .filter(helper => helpers.includes(helper))
          .map(aliasHelper)
          .join(', ')
        push(`const { ${staticHelpers} } = _Vue\n`)
      }
    }
  }
  // generate variables for ssr helpers
  if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
    // ssr guarantees prefixIdentifier: true
    push(
      `const { ${ast.ssrHelpers
        .map(aliasHelper)
        .join(', ')} } = require("${ssrRuntimeModuleName}")\n`
    )
  }
  genHoists(ast.hoists, context)
  newline()
  push(`return `)
}

"const _Vue = Vue const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode } = _Vue const _hoisted_1 = { class: "todoapp" } const _hoisted_2 = { class: "header" } const _hoisted_3 = /#PURE/_createElementVNode("h1", null, "todos", -1 /* HOISTED /) const _hoisted_4 = ["onUpdate:modelValue", "onKeyup"] const _hoisted_5 = { class: "main" } const _hoisted_6 = ["onUpdate:modelValue"] const _hoisted_7 = /#PURE*/_createElementVNode("label", { for: "toggle-all" }, "Mark all as complete", -1 /* HOISTED */) const _hoisted_8 = { class: "todo-list" } const _hoisted_9 = { class: "view" } const _hoisted_10 = ["onUpdate:modelValue"] const _hoisted_11 = ["onDblclick"] const _hoisted_12 = ["onClick"] const _hoisted_13 = ["onUpdate:modelValue", "onBlur", "onKeyup"] const _hoisted_14 = { class: "footer" } const _hoisted_15 = { class: "todo-count" } const _hoisted_16 = { class: "filters" } const _hoisted_17 = ["onClick"] return function render(_ctx, _cache) {" 但是这个时候,并没有完成翻译成code码,继续~

generate 方法中

  const functionName = ssr ? `ssrRender` : `render`
  const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
  if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
    // binding optimization args
    args.push('$props', '$setup', '$data', '$options')
  }
  const signature =
    !__BROWSER__ && options.isTS
      ? args.map(arg => `${arg}: any`).join(',')
      : args.join(', ')

  if (isSetupInlined) {
    push(`(${signature}) => {`)
  } else {
    push(`function ${functionName}(${signature}) {`)
  }
  indent()

  if (useWithBlock) {
    push(`with (_ctx) {`)
    indent()
    // function mode const declarations should be inside with block
    // also they should be renamed to avoid collision with user properties
    if (hasHelpers) {
      push(`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue`)
      push(`\n`)
      newline()
    }
  }

  // generate asset resolution statements
  if (ast.components.length) {
    genAssets(ast.components, 'component', context)
    if (ast.directives.length || ast.temps > 0) {
      newline()
    }
  }
  if (ast.directives.length) {
    genAssets(ast.directives, 'directive', context)
    if (ast.temps > 0) {
      newline()
    }
  }
  if (__COMPAT__ && ast.filters && ast.filters.length) {
    newline()
    genAssets(ast.filters, 'filter', context)
    newline()
  }

  if (ast.temps > 0) {
    push(`let `)
    for (let i = 0; i < ast.temps; i++) {
      push(`${i > 0 ? `, ` : ``}_temp${i}`)
    }
  }
  if (ast.components.length || ast.directives.length || ast.temps) {
    push(`\n`)
    newline()
  }

  // generate the VNode tree expression
  if (!ssr) {
    push(`return `)
  }
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context)
  } else {
    push(`null`)
  }

  if (useWithBlock) {
    deindent()
    push(`}`)
  }

  deindent()
  push(`}`)

执行这部分代码后 code 加了下面部分字符串

return function render(_ctx, _cache) { with (_ctx) { const { createElementVNode: _createElementVNode, vModelText: _vModelText, withKeys: _withKeys, withDirectives: _withDirectives, vModelCheckbox: _vModelCheckbox, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, resolveDirective: _resolveDirective, normalizeClass: _normalizeClass, vShow: _vShow, createTextVNode: _createTextVNode } = _Vue"

结果