ast 生成后,就可以进行
code
的构造了。
<ul v-if="isShow">
<li v-for="(item,index) in list" class="item" :class= "{activeItem: index === activeItem}" @click="selectItem(index)">{{item}}的索引是{{index}}</li>
</ul>
通过var code = generate(ast, options)
转换成code
:
{
render: "with(this){return (isShow)?_c('ul',_l((list),function(item,index){return _c('li',{staticClass:\"item\",class:{activeItem: index === activeItem},on:{\"click\":function($event){return selectItem(index)}}},[_v(_s(item)+\"的索引是\"+_s(index))])}),0):_e()}",
staticRenderFns: []
}
1、入口
这里有template
转换成code
的入口源码是var code = generate(ast, options)
:
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
}
}
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
let data
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = 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
}
}
这里有很多分支逻辑,在当前例子中el.ifProcessed
为false
,首先会执行到genIf
。在if
条件判断时执行到子元素genElement
的逻辑时el.ifProcessed
为true
,进而执行到code
的拼接逻辑,其中会分别获取到data
和children
,这个后面介绍。
2、genIf
export function genIf (
el: any,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
el.ifProcessed = true // avoid recursion
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (
conditions: ASTIfConditions,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
if (!conditions.length) {
return altEmpty || '_e()'
}
const condition = conditions.shift()
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
中先拷贝el.ifConditions.slice()
并将其作为第一个参数传入到genIfConditions
函数中,如果存在condition.exp
,那么则通过(${condition.exp})?${ genTernaryExp(condition.block) }:${ genIfConditions(conditions, state, altGen, altEmpty) }
的方式执行三元运算,其中genTernaryExp
方法在当前例子中会执行到genElement
开始了递归的执行。
genIfConditions
则会继续在conditions.shift()
弹出第一个元素后继续执行genIfConditions
开始自身的递归。
3、genFor
export function genFor (
el: any,
state: CodegenState,
altGen?: Function,
altHelper?: string
): string {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
if (process.env.NODE_ENV !== 'production' &&
state.maybeComponent(el) &&
el.tag !== 'slot' &&
el.tag !== 'template' &&
!el.key
) {
state.warn(
`<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
`v-for should have explicit keys. ` +
`See https://vuejs.org/guide/list.html#key for more info.`,
el.rawAttrsMap['v-for'],
true /* tip */
)
}
el.forProcessed = true // avoid recursion
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` +
'})'
}
这里获取到el
上的参数,修改el.forProcessed = true
后执行${altHelper || '_l'}((${exp}),
+function(${alias}${iterator1}${iterator2}){
+return ${(altGen || genElement)(el, state)}
+'})
进行拼接,执行到genElement
进行递归。
4、genData
在拼接code
的过程中会首先获取data
,即data = genData(el, state)
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) {
data += `key:${el.key},`
}
// ref
if (el.ref) {
data += `ref:${el.ref},`
}
if (el.refInFor) {
data += `refInFor:true,`
}
// pre
if (el.pre) {
data += `pre:true,`
}
// record original tag name for components using "is" attribute
if (el.component) {
data += `tag:"${el.tag}",`
}
// module data generation functions
for (let i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el)
}
// attributes
if (el.attrs) {
data += `attrs:${genProps(el.attrs)},`
}
// DOM props
if (el.props) {
data += `domProps:${genProps(el.props)},`
}
// event handlers
if (el.events) {
data += `${genHandlers(el.events, false)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true)},`
}
// slot target
// only for non-scoped slots
if (el.slotTarget && !el.slotScope) {
data += `slot:${el.slotTarget},`
}
// scoped slots
if (el.scopedSlots) {
data += `${genScopedSlots(el, el.scopedSlots, state)},`
}
// component v-model
if (el.model) {
data += `model:{value:${
el.model.value
},callback:${
el.model.callback
},expression:${
el.model.expression
}},`
}
// inline-template
if (el.inlineTemplate) {
const inlineTemplate = genInlineTemplate(el, state)
if (inlineTemplate) {
data += `${inlineTemplate},`
}
}
data = data.replace(/,$/, '') + '}'
// v-bind dynamic argument wrap
// v-bind with dynamic arguments must be applied using the same v-bind object
// merge helper so that class/style/mustUseProp attrs are handled correctly.
if (el.dynamicAttrs) {
data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
}
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data)
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data)
}
return data
}
这里会通过对el
不同的属性数据进行处理,ul
上无属性,li
上执行后的data
为"{staticClass:"item",class:{activeItem: index === activeItem},on:{"click":function($event){return selectItem(index)}}}"
。获取到data
后会执行
5、genChildren
在拼接code
的过程中会还会获取children
,即data = genChildren(el, state)
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'
) {
const normalizationType = checkSkip
? state.maybeComponent(el) ? `,1` : `,0`
: ``
return `${(altGenElement || genElement)(el, state)}${normalizationType}`
}
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
const gen = altGenNode || genNode
return `[${children.map(c => gen(c, state)).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
}
这里传入的参数是checkSkip
为true
而且是非组件,所以normalizationType
为,0
,然后再执行genElement
的递归。
6、code
合并
当获取到data
和children
后,会进行参数的拼接
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
整个拼接过程都是以children
为桥梁进行递归实现的,其中执行genElement
的过程中会以el.ifProcessed
和el.forProcessed
等属性进行过程控制,首次进入if
或者for
的流程时会进入分支。分支处理后依然会将过程参数el.ifProcessed
和el.forProcessed
设置为true
,进而执行到genElement
,并跳过分支处理逻辑,执行当前节点的code
拼接,获取data
和children
,children
的获取又是一次递归的开始,如此循环,直到所有节点的code
获取,并进行拼接。
小结
整个过程是递归的过程,递归的过程中以
children
为桥梁,不断的进行code
的构建,ast
树变成了可通过new Function()
进行执行的字符串。