大致过程
首先,compile 方法会通过 baseCompile 函数将模板字符串编译为 AST(抽象语法树)
接下来,编译器会根据 AST 进行优化处理,包括静态节点提升、静态属性提升、事件侦听器缓存等。这一步骤主要是调用 optimize 函数。
然后,编译器会将优化后的 AST 生成渲染函数所需要的代码,这一步骤主要是调用 generate 函数生成渲染函数代码。
最后,编译器会封装生成的渲染函数、静态节点等信息,返回最终的编译结果对象。
AST(抽象语法树)
在 baseParse 将template转成ast 抽象语法树
开始前template的样子
转的结果
在compiler-core中的parse方法中parseChildren 循环处理 template
静态提升
静态提升是Vue3编译优化中的其中一个优化点。所谓的静态提升,就是指在编译器编译的过程中,将一些静态的节点或属性提升到渲染函数之外。类似于 baseCompile方法中调用transform 方法 将ast 丰满起来 (codegenNode )
if (options.hoistStatic) {
hoistStatic(root, context)
}
<p>static text</p> 会被提出去,不用每次都去重新渲染
翻译成渲染函数
通过其中 genFunctionPreamble 在context 的code 参数 从ast 编译成 code, 同时在这里将hoist 翻译成字符串
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
const {
ssr,
prefixIdentifiers,
push,
newline,
runtimeModuleName,
runtimeGlobalName,
ssrRuntimeModuleName
} = context
const VueBinding =
!__BROWSER__ && ssr
? `require(${JSON.stringify(runtimeModuleName)})`
: runtimeGlobalName
// Generate const declaration for helpers
// In prefix mode, we place the const declaration at top so it's done
// only once; But if we not prefixing, we place the declaration inside the
// with block so it doesn't incur the `in` check cost for every helper access.
const helpers = Array.from(ast.helpers)
if (helpers.length > 0) {
if (!__BROWSER__ && prefixIdentifiers) {
push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`)
} else {
// "with" mode.
// save Vue in a separate variable to avoid collision
push(`const _Vue = ${VueBinding}\n`)
// in "with" mode, helpers are declared inside the with block to avoid
// has check cost, but hoists are lifted out of the function - we need
// to provide the helper here.
if (ast.hoists.length) {
const staticHelpers = [
CREATE_VNODE,
CREATE_ELEMENT_VNODE,
CREATE_COMMENT,
CREATE_TEXT,
CREATE_STATIC
]
.filter(helper => helpers.includes(helper))
.map(aliasHelper)
.join(', ')
push(`const { ${staticHelpers} } = _Vue\n`)
}
}
}
// generate variables for ssr helpers
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
// ssr guarantees prefixIdentifier: true
push(
`const { ${ast.ssrHelpers
.map(aliasHelper)
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`
)
}
genHoists(ast.hoists, context)
newline()
push(`return `)
}
"const _Vue = Vue const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode } = _Vue const _hoisted_1 = { class: "todoapp" } const _hoisted_2 = { class: "header" } const _hoisted_3 = /#PURE/_createElementVNode("h1", null, "todos", -1 /* HOISTED /) const _hoisted_4 = ["onUpdate:modelValue", "onKeyup"] const _hoisted_5 = { class: "main" } const _hoisted_6 = ["onUpdate:modelValue"] const _hoisted_7 = /#PURE*/_createElementVNode("label", { for: "toggle-all" }, "Mark all as complete", -1 /* HOISTED */) const _hoisted_8 = { class: "todo-list" } const _hoisted_9 = { class: "view" } const _hoisted_10 = ["onUpdate:modelValue"] const _hoisted_11 = ["onDblclick"] const _hoisted_12 = ["onClick"] const _hoisted_13 = ["onUpdate:modelValue", "onBlur", "onKeyup"] const _hoisted_14 = { class: "footer" } const _hoisted_15 = { class: "todo-count" } const _hoisted_16 = { class: "filters" } const _hoisted_17 = ["onClick"] return function render(_ctx, _cache) {" 但是这个时候,并没有完成翻译成code码,继续~
generate 方法中
const functionName = ssr ? `ssrRender` : `render`
const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
// binding optimization args
args.push('$props', '$setup', '$data', '$options')
}
const signature =
!__BROWSER__ && options.isTS
? args.map(arg => `${arg}: any`).join(',')
: args.join(', ')
if (isSetupInlined) {
push(`(${signature}) => {`)
} else {
push(`function ${functionName}(${signature}) {`)
}
indent()
if (useWithBlock) {
push(`with (_ctx) {`)
indent()
// function mode const declarations should be inside with block
// also they should be renamed to avoid collision with user properties
if (hasHelpers) {
push(`const { ${helpers.map(aliasHelper).join(', ')} } = _Vue`)
push(`\n`)
newline()
}
}
// generate asset resolution statements
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 (__COMPAT__ && ast.filters && ast.filters.length) {
newline()
genAssets(ast.filters, 'filter', context)
newline()
}
if (ast.temps > 0) {
push(`let `)
for (let i = 0; i < ast.temps; i++) {
push(`${i > 0 ? `, ` : ``}_temp${i}`)
}
}
if (ast.components.length || ast.directives.length || ast.temps) {
push(`\n`)
newline()
}
// generate the VNode tree expression
if (!ssr) {
push(`return `)
}
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
push(`null`)
}
if (useWithBlock) {
deindent()
push(`}`)
}
deindent()
push(`}`)
执行这部分代码后 code 加了下面部分字符串
return function render(_ctx, _cache) { with (_ctx) { const { createElementVNode: _createElementVNode, vModelText: _vModelText, withKeys: _withKeys, withDirectives: _withDirectives, vModelCheckbox: _vModelCheckbox, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, toDisplayString: _toDisplayString, resolveDirective: _resolveDirective, normalizeClass: _normalizeClass, vShow: _vShow, createTextVNode: _createTextVNode } = _Vue"