准备
vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
回顾
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
generate code
经过前面对AST进行了优化之后,需要将整个AST变成一个可执行的代码块,也就是render
函数。于是模板编译器使用了generate
对AST进行了代码生成。
generate
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,
};
}
generate
函数很简单,首先传入options
实例化一个CodegenState
用于接下来的代码生成,然后调用genElement
生成代码块。
genElement
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;
}
}
genElement
的逻辑先对当前元素的属性部分进行了代码生成,比如v-if
、v-for
等。之后又对元素的body
部分进行了代码块生成。由于整一个generate
的逻辑非常多,我觉得我们只需宏观的看一下便可。这边我们先对body
部分的进行分析。
body
部分的处理
// 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;
}
如果当前元素是个组件,就调用genComponent
。
如果不是组件元素就会调用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) {
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;
}
我们只需大概扫一眼,genData
生成的代码块是用来生成一些HTML属性的。
回到genElement
。
之后会调用genChildren
。
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"
) {
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}` : ""
}`;
}
}
这边也是大概的扫一眼,有一些template
和slot
标签的判断,之后递归调用genElement
生成子元素(由此可知render
函数也是一个深度优先的方法。在递归创建子元素完毕后又调用了genNode
方法。
genNode
function genNode(node: ASTNode, state: CodegenState): string {
if (node.type === 1) {
return genElement(node, state);
} else if (node.type === 3 && node.isComment) {
return genComment(node);
} else {
return genText(node);
}
}
模板中子元素深入到最后肯定是剩下文本。这种文本有3种情况:注释文本、插值语法、纯文本节点。所以这边会根据文本的不同情况生成不同的代码块,这边我们就不进入细看了,了解一个大概即可,回到genElement
。
之后将生成的子元素代码块插入到事先定义好的模板中,最后再将这个完整的代码块(render
函数)返回出去,到这里我们就粗粗的分析完了generate
的流程。
总结
其实整个generate
的逻辑非常的清晰。由于AST是个树形结构,所以这边也是使用了深度优先的递归方法生成代码块。我这边也只是宏观上的分析了一下整理流程,感兴趣的同学可以自己深入去看。