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
函数来生成可供执行的代码.在这里以
mode
为module
, 配置prefixIdentifiers
为true
, hoistStatic
为true
的场景来对整个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
- 创建代码生成上下文
- 生成预设代码
- 生成渲染函数
- 生成资源声明代码
- 生成创建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 // ... } }