本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Vue编译的核心主要包括三步:生成AST树,优化AST树,生成最终代码。之前我们分析了Vue编译核心流程的parse过程(点击这里跳转),optimize过程(点击这里跳转)。本节我们来分析最后一个过程:genCode。
codegen(代码生成)
genCode的过程是较为复杂的,内容较多,这里我们以简单的场景的方式来分析下,genCode的整体流程:
例如模板中的定义:
<template>
<div :class="isBlack" class="red" v-if="isShowTag">
{{message}}:{{age}}
</div>
</template>
上面这个模板最终生成的code是:
with(this) {
return (isShowTag) ?
_c('div', {
staticClass: 'red',
class: isBlack
},
[_v(_s(message) + ":" + _s(age))])
) : _e()
}
可以看到这里面有_c、_v、_e、_s等函数,其中_c是定义在src/core/instance/render.js中:
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
而_v、_e、_s定义在 src/core/instance/render-helpers/index.js 中:
export function installRenderHelpers (target: any) {
target._o = markOnce
target._n = toNumber
target._s = toString
target._l = renderList
target._t = renderSlot
target._q = looseEqual
target._i = looseIndexOf
target._m = renderStatic
target._f = resolveFilter
target._k = checkKeyCodes
target._b = bindObjectProps
target._v = createTextVNode
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
}
其实这些是一些辅助函数的简写形式,例如_v是createTextVNode,创建一个文本节点,_s是toString,转换成字符串,_e是createEmptyVNode,创建一个空节点。
下面我们来看下code代码是如何通过template生成的:
generate
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
// 生成code
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
generate函数定义在src/compiler/codegen/index.js 中:
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
// 配置项逻辑,跳过
const state = new CodegenState(options)
// 核心函数
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
code生成的核心函数是genElement:
genElement
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.staticRoot && !el.staticProcessed) {
// ast树中有staticRoot属性,执行genStatic函数
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
// ast树中有once属性,进入该逻辑
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
// ast树中有for属性,进入该逻辑
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
// ast树中有if属性,进入该逻辑
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) {
// 如果是tag是template
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
// slot标签,进入该逻辑
return genSlot(el, state)
} else {
// component or element
// 组件或者是元素进入该逻辑
let code
if (el.component) {
// 组件
code = genComponent(el.component, el, state)
} else {
// 元素
const data = el.plain ? undefined : genData(el, state)
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
本案例中,有if标签,首先会执行genIf(el, state)函数
genIf
export function genIf (
el: any,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
// isProcessed属性赋值为true,避免循环执行genIf函数
el.ifProcessed = true // avoid recursion
// 执行genIfConditions,ifConditions是个数组,包含ast节点属性对象,之前的文章介绍过
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (
conditions: ASTIfConditions,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
// 如果ASTIfConditions数组为空,直接返回空节点
if (!conditions.length) {
return altEmpty || '_e()'
}
// 取出第一个值
const condition = conditions.shift()
// 有表达式的情况下,案例中的exp为isShowTag,
// 返回值为`(isShowTag)?${genElement(el, state)}:${genIfConditions(conditions, state, altGen, altEmpty)}`
if (condition.exp) {
return `(${condition.exp})?${
genTernaryExp(condition.block)
}:${
genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
可以进入genIf的逻辑后会生成一个三元表达式:
`(isShowTag)?${genElement(el, state)}:${genIfConditions(conditions, state, altGen, altEmpty)}`
这里会继续递归执行genElement函数及genIfConditions函数;
案例中再次执行genIfConditions函数后,ASTIfConditions数组为空,直接会返回_e(),所以三元表达式为:
`(isShowTag)?${genElement(el, state)}:_e()`
继续递归执行genElement函数,这时候会进入节点为元素的判断条件逻辑:
// 元素
const data = el.plain ? undefined : genData(el, state)
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
首先会执行genData方法,
genData
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
// directives first.
// directives may mutate the el's other properties before they are generated.
const dirs = genDirectives(el, state)
if (dirs) data += dirs + ','
// key
if (el.key) {
...
}
// ref
if (el.ref) {
...
}
if (el.refInFor) {
...
}
// pre
if (el.pre) {
...
}
// record original tag name for components using "is" attribute
if (el.component) {
...
}
// module data generation functions
// 本案例会执行这个函数,`staticClass:red,class:isBlack`
for (let i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el)
}
......
data = data.replace(/,$/, '') + '}'
......
// 本案例返回结果 `{staticClass:red,class:isBlack}`
return data
}
dataGenFns的定义:
this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
function genData (el: ASTElement): string {
let data = ''
// 本案例中staticClass是"red"
if (el.staticClass) {
data += `staticClass:${el.staticClass},`
}
// 本案例中classBinding是表达式isBlack
if (el.classBinding) {
data += `class:${el.classBinding},`
}
// 返回的结果是 `staticClass:red,class:isBlack`
return data
}
到此时,第二次genElement的生成结果为:
code = `_c('div', {staticClass:red,class:isBlack}, ${
children ? `,${children}` : '' // children
})`
完成genData后,会执行genChildren,生成子节点的code,我们继续往下看:
genChildren
export function genChildren (
el: ASTElement,
state: CodegenState,
checkSkip?: boolean,
altGenElement?: Function,
altGenNode?: Function
): string | void {
const children = el.children
if (children.length) {
const el: any = children[0]
// optimize single v-for
// 不进入这个逻辑
if (children.length === 1 &&
el.for &&
el.tag !== 'template' &&
el.tag !== 'slot'
) {
return (altGenElement || genElement)(el, state)
}
// 这是规范化,直接跳过
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
// altGenNode为空执行genNode
const gen = altGenNode || genNode
// 使用map遍历children数组执行gen函数,并将最终结果拼接起来,
// 这儿的返回结果为 `[_v(_s(message) + ":" + _s(age))])`
return `[${children.map(c => gen(c, state)).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
}
function genNode (node: ASTNode, state: CodegenState): string {
if (node.type === 1) {
// 如果子节点是元素节点,继续执行genElement
return genElement(node, state)
} if (node.type === 3 && node.isComment) {
// 如果子节点是注释节点,执行genComment
return genComment(node)
} else {
// 本案例中是文本节点,进入这个方法
return genText(node)
}
}
export function genText (text: ASTText | ASTExpression): string {
// type为2是表达式节点,也就是插值表达式里面定义的表达式,否则就是普通文本节点
return `_v(${text.type === 2
? text.expression // no need for () because already wrapped in _s()
: transformSpecialNewlines(JSON.stringify(text.text))
})`
}
到此时,第二次genElement的生成结果为:
code = `_c('div', {
staticClass:red,
class:isBlack
},
[_v(_s(message) + ":" + _s(age))])
)`
结合第一次genELement的执行结果:
`(isShowTag)?${genElement(el, state)}:_e()`,
最终生成的代码串:
(isShowTag) ?
_c('div', {
staticClass: 'red',
class: isBlack
},
[_v(_s(message) + ":" + _s(age))])
) : _e()
所以最终的render函数:
render: `with(this){return ${code}}`,
// 本案例中
render: `with(this){return (isShowTag) ?
_c('div', {
staticClass: 'red',
class: isBlack
},
[_v(_s(message) + ":" + _s(age))])
) : _e()}`
这就是genCode的最终执行结果。
可以看到这里是个字符串,函数怎么会是个字符串呢?
其实在编译入口解析这篇文章中我们分析过,最终这条字符串,会通过new Function(code)的方法生成匿名函数。
总结
code生成的过程,其实就是遍历ast树,结合ast树的属性,递归执行genElement函数,拼接生成代码字符串。
到此为止,编译的三大流程,大致的给大家介绍完了,有想了解的,欢迎留言一起探讨。