generate
2023年07月19日
一、src/compiler/codegen/index.ts generate()
/**
* 从 AST 生成渲染函数
* @returns {
* render: `with(this){return _c(tag, data, children)}`,
* staticRenderFns: state.staticRenderFns
* }
*/
export function generate(
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
// 实例化 CodegenState 对象,生成代码的时候需要用到其中的一些东西
const state = new CodegenState(options)
// 生成字符串格式的代码,比如:'_c(tag, data, children, normalizationType)'
// data 为节点上的属性组成 JSON 字符串,比如 '{ key: xx, ref: xx, ... }'
// children 为所有子节点的字符串格式的代码组成的字符串数组,格式:
// `['_c(tag, data, children)', ...],normalizationType`,
// 最后的 normalization 是 _c 的第四个参数,
// 表示节点的规范化类型,不是重点,不需要关注
// 当然 code 并不一定就是 _c,也有可能是其它的,比如整个组件都是静态的,则结果就为 _m(0)
const code = ast
? ast.tag === 'script'
? 'null'
: genElement(ast, state)
: '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
二、src/compiler/codegen/index.ts genElement()
export function genElement(el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
/**
* 处理静态根节点,生成节点的渲染函数
* 1、将当前静态节点的渲染函数放到 staticRenderFns 数组中
* 2、返回一个可执行函数 _m(idx, true or '')
*/
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
/**
* 处理带有 v-once 指令的节点,结果会有三种:
* 1、当前节点存在 v-if 指令,得到一个三元表达式,condition ? render1 : render2
* 2、当前节点是一个包含在 v-for 指令内部的静态节点,得到 `_o(_c(tag, data, children), number, key)`
* 3、当前节点就是一个单纯的 v-once 节点,得到 `_m(idx, true of '')`
*/
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
/**
* 处理节点上的 v-for 指令
* 得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`
*/
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
/**
* 处理带有 v-if 指令的节点,最终得到一个三元表达式:condition ? render1 : render2
*/
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
/**
* 当前节点不是 template 标签也不是插槽和带有 v-pre 指令的节点时走这里
* 生成所有子节点的渲染函数,返回一个数组,格式如:
* [_c(tag, data, children, normalizationType), ...]
*/
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
/**
* 生成插槽的渲染函数,得到
* _t(slotName, children, attrs, bind)
*/
return genSlot(el, state)
} else {
// component or element
// 处理动态组件和普通元素(自定义组件、原生标签)
let code
/**
* 处理动态组件,生成动态组件的渲染函数
* 得到 `_c(compName, data, children)`
*/
if (el.component) {
code = genComponent(el.component, el, state)
} else {
// 自定义组件和原生标签走这里
let data
const maybeComponent = state.maybeComponent(el)
if (!el.plain || (el.pre && maybeComponent)) {
// 非普通元素或者带有 v-pre 指令的组件走这里,处理节点的所有属性,返回一个 JSON 字符串,
// 比如 '{ key: xx, ref: xx, ... }'
data = genData(el, state)
}
let tag: string | undefined
// check if this is a component in <script setup>
const bindings = state.options.bindings
if (maybeComponent && bindings && bindings.__isScriptSetup !== false) {
tag = checkBindingType(bindings, el.tag)
}
if (!tag) tag = `'${el.tag}'`
// 处理子节点,得到所有子节点字符串格式的代码组成的数组,格式:
// `['_c(tag, data, children)', ...],normalizationType`,
// 最后的 normalization 表示节点的规范化类型,不是重点,不需要关注
const children = el.inlineTemplate ? null : genChildren(el, state, true)
// 得到最终的字符串格式的代码,格式:
// '_c(tag, data, children, normalizationType)'
code = `_c(${tag}${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// 如果提供了 transformCode 方法,
// 则最终的 code 会经过各个模块(module)的该方法处理,
// 不过框架没提供这个方法,不过即使处理了,最终的格式也是 _c(tag, data, children)
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
三、src/compiler/codegen/index.ts genChildren()
/**
* 生成所有子节点的渲染函数,返回一个数组,格式如:
* [_c(tag, data, children, normalizationType), ...]
*/
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'
) {
// 优化,只有一个子节点 && 子节点的上有 v-for 指令 && 子节点的标签不为 template 或者 slot
// 优化的方式是直接调用 genElement 生成该节点的渲染函数,不需要走下面的循环然后调用 genCode 最后得到渲染函数
const normalizationType = checkSkip
? state.maybeComponent(el)
? `,1`
: `,0`
: ``
return `${(altGenElement || genElement)(el, state)}${normalizationType}`
}
// 获取节点规范化类型,返回一个 number 0、1、2,不是重点, 不重要
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
// 函数,生成代码的一个函数
const gen = altGenNode || genNode
// 返回一个数组,数组的每个元素都是一个子节点的渲染函数,
// 格式:['_c(tag, data, children, normalizationType)', ...]
return `[${children.map(c => gen(c, state)).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
}
四、src/compiler/codegen/index.ts 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)
}
}