Vue3.0 - beta源码解读 - 编译系统

一、baseCompile

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模版解析器

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:
创建AST根节点容器的所属子节点,即模版中实际的节点。该方法为parser核心处理逻辑,用于解析一段“完整”的模版串,比如

test

...

...

都是“完整”的,因此会递归的执行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, '
                    

你可能感兴趣的:(VUE3.0源码全系列解读)