Vue中AST如何生成可运行代码
2021-10-4
| 2023-2-19
0  |  0 分钟
password
Created
Feb 19, 2023 03:38 PM
type
Post
status
Published
date
Oct 4, 2021
slug
summary
Vue中AST如何生成可运行代码
tags
Vue
Vue源码
category
源码
icon
Vue使用generate函数来生成可供执行的代码.
在这里以modemodule, 配置prefixIdentifierstrue, hoistStatictrue的场景来对整个generate过程来进行分析.
我们先看下需要转换的代码及通过generate转换之后的结果
需要转换的代码:
 <div class="app">    <hello v-if="flag"></hello>    <div v-else>      <p>hello {{ msg + test }}</p>      <p>static</p>      <p>static</p>    </div>  </div>
转换后的结果:
 import { resolveComponent as _resolveComponent, createVNode as _createVNode, createCommentVNode as _createCommentVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"    const _hoisted_1 = { class: "app" }  const _hoisted_2 = { key: 1 }  const _hoisted_3 = /*#__PURE__*/_createVNode("p",null,"static",-1/* HOISTED */)  const _hoisted_4 = /*#__PURE__*/_createVNode("p",null,"static",-1/* HOISTED */)    export function render(_ctx, _cache) {    const _component_hello = resolveComponent("hello")    return (_openBlock(), _createBlock("div",_hoisted_1,[     (_ctx.flag)        ?_createVNode(_component_hello,{key:0})       :(_openBlock(),_createBlock("div",_hoisted_2,[          _createVNode("p",null,"hello"+_toDisplayString(_ctx.msg+_ctx.test),1/* TEXT */),          _hoisted_3,          _hoisted_4       ]))   ]))  }

generate

  1. 创建代码生成上下文
  1. 生成预设代码
  1. 生成渲染函数
  1. 生成资源声明代码
  1. 生成创建VNode树的表达式
 function generate(ast, options = {}) {    // 创建代码生成上下文    const context = createCodegenContext(ast, options)    const { mode, push, prefixIdentifiers, indent, deindent, newline, scopeId, ssr } = context    const hasHelpers = ast.helpers.length > 0    const useWithBlock = !prefixIdentifiers && mode !== 'module'    const genScopeId = scopeId != null && mode === 'module'    // 生成预设代码    if (mode === 'module') {      genModulePreamble(ast, context, genScopeId)   } else {      genFunctionPreamble(ast, context)   }    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()     }   }    // 生成自定义组件声明代码    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`)      if (let i = 0; i < ast.temps; i++) {        push(`${i>0 ? `,` : ``}_temp${i}`)     }   }    if (ast.components.length || ast.directives.length || ast.temps) {      push(`\n`)      newline()   }    if (!ssr) {      push(`return`)   }    // 生成创建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. createCodegenContext 创建代码生成上下文

 function createCodegenContext(ast, { mode = 'function', prefixIdentifiers = mode === 'module', sourceMap = false, filename = `template.vue.html`, scopeId = null, optimizeBindings = false, runtimeGlobalName = `Vue`, runtimeModuleName = `vue`, ssr = false }) {    const context = {      // 主要维护的是generate过程中的一些配置和状态数据, 辅助函数等, 不再赘述   }  }

2. genModulePreamble 生成预设代码

 function genModulePreamble(ast, context, genScopeId) {    const { push, newline, optimizeBindings, runtimeModuleName } = context    // 处理scopeId    if (ast.helpers.length) {      // 生成import声明代码      if (optimizeBindings) {        push(`import {${ast.helpers.map(s => helperNameMap[s]).join(',')}} from ${JSON.stringify(runtimeModuleName)}\n`)        push(`\n// Binging optimization for webpack code-split \n const ${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`)  }
其中, ast.helpers是用Symbol定义的一个数组
 [    Symbol(resolveComponent),    Symbol(createVNode),    // ...  ]
对应的helperNameMap则是Symbol对应的字符串
 const helperNameMap = {   [FRAGMENT]: `Fragment`,   [TELEPORT]: `Teleport`,   // ...  }

3. 生成渲染函数

4. 生成资源声明代码

5. genNode 生成创建VNode数的表达式

 function genNode(node, context) {    if (shared.isString(node)) {      context.push(node)      return   }    if (shared.isSymbol(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      // ...   }  }

6.

源码
  • Vue
  • Vue源码
  • Vue中的ASTJavaScript中的异步与EventLoop
    目录