1. 简介
就是将用户在SFC中写的template编译成选项,这个选项就是一个js对象
2. 执行时机
- webpack下不带编译器,会通过vue-loader进行预编译
- 带运行时,vm.$mount(vue2) vm.mount(vue3)的时刻会执行
注意:运行时的编译会存在性能问题,不仅仅vue变大了,运行时的速度也变慢了
编译器原理
vue3 和 vue2 编译器实现基本相同只不过做了几点有话,后面会说到
vue3中编译器源码实现
// vue包/src/index.ts
// 模版获取
// 编译template微render函数
function compileToFunction(
template: string | HTMLElement,
options?: CompilerOptions
): RenderFunction {
if (!isString(template)) {
if (template.nodeType) {
// 从素宿主元素的innerHTML获取模版
// 总之一定要得到template
template = template.innerHTML
} else {
__DEV__ && warn(`invalid template option: `, template)
return NOOP
}
}
// 获取上次编译的缓存结果
const key = template
const cached = compileCache[key]
if (cached) {
return cached
}
// 用户传递进来的是0选择器(#app)
if (template[0] === '#') {
const el = document.querySelector(template)
if (__DEV__ && !el) {
warn(`Template element not found or is empty: ${template}`)
}
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's rendered
// by the server, the template should not contain any user data.
// 找到el,在拿到innerHTML
template = el ? el.innerHTML : ``
}
// 真正的编译函数
const { code } = compile(
template,
extend(
{
hoistStatic: true,
onError: __DEV__ ? onError : undefined,
onWarn: __DEV__ ? e => onError(e, true) : NOOP
} as CompilerOptions,
options
)
)
function onError(err: CompilerError, asWarning = false) {
const message = asWarning
? err.message
: `Template compilation error: ${err.message}`
const codeFrame =
err.loc &&
generateCodeFrame(
template as string,
err.loc.start.offset,
err.loc.end.offset
)
warn(codeFrame ? `${message}\n${codeFrame}` : message)
}
// The wildcard import results in a huge object with every export
// with keys that cannot be mangled, and can be quite heavy size-wise.
// In the global build we know `Vue` is available globally so we can avoid
// the wildcard object.
// code: `function() { return () => {} }`
// render包装成函数
const render = (
__GLOBAL__ ? new Function(code)() : new Function('Vue', code)(runtimeDom)
) as RenderFunction
// mark the function as runtime compiled
;(render as InternalRenderFunction)._rc = true
return (compileCache[key] = render)
}
// we name it `baseCompile` so that higher order compilers like
// @vue/compiler-dom can export `compile` while re-exporting everything else.
// compileToFunction调用的函数就是它
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module'
/* istanbul ignore if */
if (__BROWSER__) {
if (options.prefixIdentifiers === true) {
onError(createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
} else if (isModuleMode) {
onError(createCompilerError(ErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
}
}
const prefixIdentifiers =
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
if (!prefixIdentifiers && options.cacheHandlers) {
onError(createCompilerError(ErrorCodes.X_CACHE_HANDLER_NOT_SUPPORTED))
}
if (options.scopeId && !isModuleMode) {
onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
}
// 1. 解析(parse):template => ast
const ast = isString(template) ? baseParse(template, options) : template
// 2. 转换(深加工 transform) ast => ast
const [nodeTransforms, directiveTransforms] =
getBaseTransformPreset(prefixIdentifiers)
if (!__BROWSER__ && options.isTS) {
const { expressionPlugins } = options
if (!expressionPlugins || !expressionPlugins.includes('typescript')) {
options.expressionPlugins = [...(expressionPlugins || []), 'typescript']
}
}
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
// 生成:ast =》js function
// 里面是一段递归的遍历,找到一个节点生成一段代码
return generate(
ast,
extend({}, options, {
prefixIdentifiers
})
)
}
4. vue3当中编译器的优化
准备
源码中运行npm run dev-compiler; 打开template-explorer/local.html
1. 静态节点提升
如图,render函数外面定义了静态节点,静态节点会被缓存起来,下次再来的时候就没有必要进行创建和生成(用内存换取事件)
2. 补丁标记和动态属性记录
createElementVNode 第二个参数会把动态属性标记出来(动态属性记录) 第四个参数 8 8 2^3 1000 第四位为1,说明属性是动态的 9 8+1 1001 第1位为1说明内部文本是动态的
这种操作就是patchFlag(布丁标记)
3. 缓存事件处理程序
vue中 @click="onClick" react onClick={(...args) => onClick(...args)}
为啥vue可以直接写函数名呢?因为vue编译器会进行处理,vue3会做一件事,叫回调函数的缓存,内部会处理成成一个像react一样的剪头函数
如果在@click时候写一个箭头函数,那么每次进来都会是一个新的函数,就会导致子树的整棵树的更新,React中有优化useCallback,就是为了解决这个问题;在vue中你只需写一个函数名称,内部会自动对事件程序做一个缓存
4. 块block
一个代码片段会成为一个block,块中有一个数组dynamicChildren,存储需要更新的节点,最后只需要遍历这个数组即可