baseCompile:
编译器的总入口,是编译器的一个基础骨架(概念上可以理解为基类),然后不同平台的编译系统都是基于baseCompile来进行扩展的,如dom编译、服务端渲染编译、sfc,都是基于baseCompile进行了对应平台下处理场景的扩展。
function baseCompile(
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
// 省略无关代码...
// 生成AST节点树
const ast = isString(template) ? baseParse(template, options) : template
// 获取节点转换工具集、指令转换工具集
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
prefixIdentifiers
)
// 遍历AST节点树,对上面生成的AST进行指令转换,生成可用节点,同时根据compiler
// 传入的配置(如是否做静态节点提升等)对AST节点树进行优化处理,为rootNode及
// 下属每个节点挂载codegenNode
transform(
ast,
extend({}, options, {
prefixIdentifiers,
nodeTransforms: [
...nodeTransforms,
...(options.nodeTransforms || []) // user transforms
],
directiveTransforms: extend(
{},
directiveTransforms,
options.directiveTransforms || {} // user transforms
)
})
)
// 对转换及优化后的AST进行代码生成
return generate(
ast,
extend({}, options, {
prefixIdentifiers
})
)
}
parser的作用就是将我们传入的template string转化为AST节点树,供后面的transform使用。
rootNode:
根节点是一个临时容器,真正在运行时映射成具体内容的是rootNode下的children,说白了rootNode只是个用来存放实际节点的空壳子,假如parse AST节点时template string中是多根节点,那么没有一个抽象出来的根节点就无法表述完整的树结构,这也是为什么vue3.0能够允许多根模版的原因所在。
Position:
包含offset、line、column三个属性,offset记录parser解析到的位置相对原始template string开头的位置,line记录parser解析到的行数,column为列数,因为parse过程中会遇到\n\t\f之类的转义字符。
baseParse:
将template string解析成AST,AST是vue对节点的一种表述形式,和平时JS生成的抽象语法树是两码事。
export function baseParse(
content: string, // 原始的模版字符串
options: ParserOptions = {}
): RootNode {
// 获得parser上下文,相当于class实例化的产物,用来存储parser的一些信息
const context = createParserContext(content, options)
// 获取parser开始位置
const start = getCursor(context)
// 生成AST节点
return createRoot(
parseChildren(context, TextModes.DATA, []), // 生成AST子节点
getSelection(context, start) // 获取根节点位置信息、对应string信息:loc
)
}
createParserContext创建出上下文的结构:
{
options: extend({}, defaultParserOptions, options), // parser配置项
// column、line、offset均是相对template string的全局位置信息
column: 1, // parser解析到的列数
line: 1, // 解析到的行数
offset: 0, // 解析到相对于template string开始的位置
originalSource: content, // 初始template string,即用户定义的完整模版字符串
source: content, // parser处理后的最新template string
inPre: false,
inVPre: false
}
createRoot:
创建一个虚拟根节点容器,根据模版解析出children和对应的位置信息,透传给根节点对象
function createRoot(
children: TemplateChildNode[],
loc = locStub
): RootNode {
// 生成AST根节点的结构
return {
type: NodeTypes.ROOT, // 节点类型
children, // 子节点
helpers: [],
components: [], // 组件节点
directives: [], // 指令节点
hoists: [],
imports: [],
cached: 0,
temps: 0,
codegenNode: undefined, // 用于后续generate阶段生成vnode创建物料
// 节点在template string中所处的位置,结构{ source, start, end }
// source对应节点在模版中对应的string部分,start、end对应节点起始标签
// 对应template string中的位置
loc
}
}
createChildren: test ...
创建AST根节点容器的所属子节点,即模版中实际的节点。该方法为parser核心处理逻辑,用于解析一段“完整”的模版串,比如
,
和
都是“完整”的,因此会递归的执行parseChildren解析子节点,将解析出的子节点插入父节点中。
过程中两个比较重要的解析方法是parseInterpolation(处理插值)、parseElement(解析dom节点),将重点介绍。
function parseChildren(
context: ParserContext,
mode: TextModes,
// 祖先节点,是一个栈结构,用于维护节点嵌套关系,越靠后的节点在dom树中的层级越深
ancestors: ElementNode[]
): TemplateChildNode[] {
// 父节点
const parent = last(ancestors)
const ns = parent ? parent.ns : Namespaces.HTML
// 存储解析出来的AST子节点
const nodes: TemplateChildNode[] = []
// 遇到闭合标签结束解析
while (!isEnd(context, mode, ancestors)) {
const s = context.source
let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined
if (mode === TextModes.DATA || mode === TextModes.RCDATA) {
if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
// '{{'
// 解析以‘{{’开头的模版,parseInterpolation为核心方法,下面重点讲解
node = parseInterpolation(context, mode)
} else if (mode === TextModes.DATA && s[0] === '<') {
// https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
if (s.length === 1) {
// 错误处理
emitError(context, ErrorCodes.EOF_BEFORE_TAG_NAME, 1)
} else if (s[1] === '!') {
if (startsWith(s, '