前言
compiler是Vue中三大模块之一,负责模板的编译编译。我们知道,当模板经过编译解析之后就会生成一个render函数,然后在runtime时会被调用以创建实际的dom树。vue3模板编译在这个网站中,我们就能看到,字符串模板经过compiler之后实际生成的render函数。
模板经过compiler之后会生成render函数,那其内部的工作原理又是怎么样的呢?在vue3中新提出的patchFlag、hoist、cacheHandlers...又是怎么做到的呢?
在这里我们先简单的概述一下,在之后的文章里,我们将会详细展开。在compiler中一共会经历三个过程:
parse:将字符串模板转化为ASTtransform:对AST进行优化,添加一些属性值,为后续的步骤做准备codegen:将AST转化生成为一个render函数
其中在vue2时是没有transform这个步骤的,而是optimize 。两者都是对AST的优化,但transform多了许多新的职责,如上面提到的patchFLag等
模块解耦
和runtime一样,为了提高可拓展性,compiler也分为comoiler-core和在针对各个平台的compiler,如:
compiler-dom、compiler-sfc、compiler-ssr,其中各个平台的compiler内部还是调用到compiler-core方法。
compiler-dom:浏览器compiler-sfc:单文件组件compiler-ssr:服务端渲染
compiler-dom
现在就从compiler-dom开始。
export function compile(
template: string,
options: CompilerOptions = {}
): CodegenResult {
return baseCompile(
template,
extend({}, parserOptions, options, {
nodeTransforms: [
ignoreSideEffectTags,
...DOMNodeTransforms,
...(options.nodeTransforms || [])
],
directiveTransforms: extend(
{},
DOMDirectiveTransforms,
options.directiveTransforms || {}
),
transformHoist: __BROWSER__ ? null : stringifyStatic // 是否优化hoist
})
)
}
可以看到,在compile函数内部调用了baseCompile。baseCompiler就是来源于compiler-core。
在看看调用时传入的参数,传入了template和由许多对象混入而成options。其中parseOptions则是由compiler-dom提供的针对dom平台的option。
让我们继续看看compiler-dom提供的parseOptions
export const parserOptions: ParserOptions = {
isVoidTag, // 是否自闭和
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag), // 是否为平台元素的标签
isPreTag: tag => tag === 'pre', // 是否为pre标签
decodeEntities: __BROWSER__ ? decodeHtmlBrowser : decodeHtml, // 对内容进行解码
// 判断是否为内部自带的标签
isBuiltInComponent: (tag: string): symbol | undefined => {},
// 获取命名空间
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {},
// 获取标签的textMode,会影响parse
getTextMode({ tag, ns }: ElementNode): TextModes {}
}
其中包含这些属性和方法:
isVoidTag:是否自闭合isNativeTag:是否为平台提供的原生标签isPreTag:是否为pre标签decodeEntities:对内容进行解码isBuiltInComponent:判断是否为内部自带的标签getNamespace:获取命名空间getTextMode:获取标签的textMode
其中textMode十分重要,它影响了prase的解析,不同的textMode在解析时判断结束以及具体的解析都不相同。
baseCompile
在上面我们看到compiler-dom的compile函数的核心在于内部调用了compiler-core提供的baseCompiler函数。现在我们就来看看baseCompile函数到底做了些什么
export function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
// 参数校验
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module' // dom平台为false
/* 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.将模板字符串转为ast
const ast = isString(template) ? baseParse(template, options) : template
// 根据prefixIdentifiers获取默认的transform方法
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
prefixIdentifiers
)
// 2.优化ast
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
// 3.将ast转为render函数
return generate(
ast,
extend({}, options, {
prefixIdentifiers
})
)
}
- 进行参数检验,标记代码的生成模式
prefixIdentifiers - 调用
baseParse将模板字符串转为ast - 根据
prefixIdentifiers来获取默认的nodeTransforms和directiveTransforms - 调用
transform对ast进行优化 - 调用
generate根据ast生成代码
可以看到我们上面提到的三个过程都在baseCompile中被调用。在接下来的文章中,我们就会逐个细细解读。