compile
2023年07月19日
一、src/compiler/create-compiler.ts compile()
import { extend } from 'shared/util'
import { CompilerOptions, CompiledResult, WarningMessage } from 'types/compiler'
import { detectErrors } from './error-detector'
import { createCompileToFunctionFn } from './to-function'
export function createCompilerCreator(baseCompile: Function): Function {
return function createCompiler(baseOptions: CompilerOptions) {
/**
* 编译函数,做了两件事:
* 1、选项合并,将 options 配置项 合并到 finalOptions(baseOptions) 中,得到最终的编译配置对象
* 2、调用核心编译器 baseCompile 得到编译结果
* 3、将编译期间产生的 error 和 tip 挂载到编译结果上,返回编译结果
* @param {*} template 模版
* @param {*} options 配置项
* @returns
*/
function compile(
template: string,
options?: CompilerOptions
): CompiledResult {
// 以平台特有的编译配置为原型创建编译选项对象
const finalOptions = Object.create(baseOptions)
const errors: WarningMessage[] = []
const tips: WarningMessage[] = []
// 日志,负责记录将 error 和 tip
let warn = (
msg: WarningMessage,
range: { start: number; end: number },
tip: string
) => {
;(tip ? tips : errors).push(msg)
}
// 如果存在编译选项,合并 options 和 baseOptions
if (options) {
// 开发环境走
if (__DEV__ && options.outputSourceRange) {
// $flow-disable-line
const leadingSpaceLength = template.match(/^\s*/)![0].length
// 增强 日志 方法
warn = (
msg: WarningMessage | string,
range: { start: number; end: number },
tip: string
) => {
const data: WarningMessage = typeof msg === 'string' ? { msg } : msg
if (range) {
if (range.start != null) {
data.start = range.start + leadingSpaceLength
}
if (range.end != null) {
data.end = range.end + leadingSpaceLength
}
}
;(tip ? tips : errors).push(data)
}
}
/**
* 将 options 中的配置项合并到 finalOptions
*/
// 合并自定义 module
if (options.modules) {
finalOptions.modules = (baseOptions.modules || []).concat(
options.modules
)
}
// 合并自定义指令
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
)
}
// 拷贝其它配置项
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key as keyof CompilerOptions]
}
}
}
// 日志
finalOptions.warn = warn
// 到这里为止终于到重点了,调用核心编译函数,传递模版字符串和最终的编译选项,得到编译结果
// 前面做的所有事情都是为了构建平台最终的编译选项
const compiled = baseCompile(template.trim(), finalOptions)
if (__DEV__) {
detectErrors(compiled.ast, warn)
}
// 将编译期间产生的错误和提示挂载到编译结果上
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
二、src/platforms/web/compiler/options.ts baseOptions
import {
isPreTag,
mustUseProp,
isReservedTag,
getTagNamespace
} from '../util/index'
import modules from './modules/index'
import directives from './directives/index'
import { genStaticKeys } from 'shared/util'
import { isUnaryTag, canBeLeftOpenTag } from './util'
import { CompilerOptions } from 'types/compiler'
export const baseOptions: CompilerOptions = {
expectHTML: true,
modules,
directives,
isPreTag,
isUnaryTag,
mustUseProp,
canBeLeftOpenTag,
isReservedTag,
getTagNamespace,
staticKeys: genStaticKeys(modules)
}
三、src/compiler/index.ts baseCompile()
import { parse } from './parser/index'
import { optimize } from './optimizer'
import { generate } from './codegen/index'
import { createCompilerCreator } from './create-compiler'
import { CompilerOptions, CompiledResult } from 'types/compiler'
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile(
template: string,
options: CompilerOptions
): CompiledResult {
/**
* 在这之前做的所有的事情,只有一个目的,就是为了构建平台特有的编译选项(options),比如 web 平台
*
* 1、将 html 模版解析成 ast
* 2、对 ast 树进行静态标记
* 3、将 ast 生成渲染函数
* 静态渲染函数放到 code.staticRenderFns 数组中
* code.render 为动态渲染函数
* 在将来渲染时执行渲染函数得到 vnode
*/
// 将模版解析为 AST,每个节点的 ast 对象上都设置了元素的所有信息,比如,标签信息、属性信息、插槽信息、父节点、子节点等。
// 具体有那些属性,查看 start 和 end 这两个处理开始和结束标签的方法
const ast = parse(template.trim(), options)
// 优化,遍历 AST,为每个节点做静态标记
// 标记每个节点是否为静态节点,然后进一步标记出静态根节点
// 这样在后续更新中就可以跳过这些静态节点了
// 标记静态根,用于生成渲染函数阶段,生成静态根节点的渲染函数
if (options.optimize !== false) {
optimize(ast, options)
}
// 从 AST 生成渲染函数,生成像这样的代码,比如:code.render = "_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return _c('div',{key:item},[_v(_s(item))])}),0)"
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})