Vue 3 编译流程 — 解析 template 生成 AST

897 阅读2分钟

解析 template 生成 AST

项目地址:github.com/vuejs/core

packages/compiler-core/src/compile.ts

packages/compiler-core/src/parse.ts

又是又臭又长,自己记录用的

compile (Web编译入口)

文件目录:packages\compiler-core\src\compile.ts

baseCompile (解析 template 生成 AST、AST 转换、生成代码)

function baseCompile(template,options){
  
  // 解析template,生成AST
  const ast = isString(template) ? baseParse(template, options) : template
  const [nodeTransforms, directiveTransforms] =
    getBaseTransformPreset(prefixIdentifiers)

  // AST转换
  transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []) // user transforms
      ],
      directiveTransforms: extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      )
    })
  )

  // 生成代码
  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers
    })
  )
}

baseCompile:解析 template 生成 ASTAST 转换生成代码

baseParse (解析template,生成AST)

function baseParse(content,options) {
  // 创建解析上下文
  const context = createParserContext(content, options)
  const start = getCursor(context)
  
  // 解析子节点,生产AST
  return createRoot(
    parseChildren(context, TextModes.DATA, []),
    getSelection(context, start)
  )
}
createParserContext (初始化默认解析配置)
function createParserContext(content,rawOptions) {
  // extend 就是 Object.assign
  const options = extend({}, defaultParserOptions)

  // 未配置就去默认解析配置
  let key
  for (key in rawOptions) {
    options[key] =
      rawOptions[key] === undefined
        ? defaultParserOptions[key]
        : rawOptions[key]
  }
  return {
    options,
    column: 1,
    line: 1,
    offset: 0,
    originalSource: content,
    source: content,
    inPre: false,
    inVPre: false,
    onWarn: options.onWarn
  }
}
getCursor
function getCursor(context) {
  const { column, line, offset } = context
  return { column, line, offset }
}
创建解析上下文
context = {
    options, // 表示解析相关配置
    column: 1, // 表示当前代码的列号
    line: 1, // 表示当前代码的行号
    offset: 0, // 表示当前代码相对于原始代码的偏移量
    originalSource: content, // 表示最初的原始代码
    source: content, // 表示当前代码
    inPre: false, // 表示当前代码是否在 pre 标签内
    inVPre: false, // 表示当前代码是否在 v-pre 指令的环境下
    onWarn: options.onWarn
}

start = {
    column: 1, // 表示当前代码的列号
    line: 1, // 表示当前代码的行号
    offset: 0 // 表示当前代码相对于原始代码的偏移量
}

pre 标签内 或 v-pre 指令下,跳过这个元素和它的子元素的编译过程。

parseChildren (解析子节点,也是重点)

  // 初始化进入的三个参数  context , mode = 1, ancestors = []
function parseChildren(context,mode,ancestors){
  // last = xs:[] => xs[xs.length - 1]; 数组最后一个参数
  const parent = last(ancestors)  
  // parent = ''
  const ns = parent ? parent.ns : Namespaces.HTML
  // ns = Namespaces.HTML
  const nodes = []
  
  开始遍历所有文本或节点,编译为nodes
  // isEnd : 判断是否有还有 < 结束标签或 /> 自封闭标签
  while (!isEnd(context, mode, ancestors)) {
  ... 
  }
  
}
遍历处理所有文本或节点

开始遍历所有文本或节点,AST 节点数组

'{{' 或 '}}'
'{{' 或 '}}'
if不为 Pre 标签或 v-pre,开始为'{{' 或 '}}'
Render声明式渲染
Notespre 不进行渲染作为文本直接输出
'<'

流程文档 html.spec.whatwg.org/multipage/p…

<
if只有一个字符串 '<'
处理流程控制台提示,结束标记解析阶段错误 1.png
注释只有一个字符串。控制台提示,以文本输出。
<!<!--<!DOCTYPE<![CDATA[<!**
if起始字符串为'<!--'起始字符串为'<!DOCTYPE'起始字符串为'<![CDATA['<!开头,后面的没匹配上
注释处理为注释节点处理为<!DOCTYPE节点CDATA 节状态,而且它不是 HTML 命名空间中的元素,那么切换到 CDATA 节状态。否则,创建一个注释节点,其数据为"[CDATA["字符串。这是一个不正确打开注释的解析错误。创建一个注释节点,其数据为空字符串。 2.png
  1. <![CDATA[ 展示的数据取决与后面的数据
  2. <!** 后的数据没有或未匹配上 <!--<!DOCTYPE<![CDATA[,那么创建注释节点并且数据是空字符串
</<//[a-z]/i.test(s[2])</**
if只有两个字符 '</'第三个字符是 '>'正则匹配,第三个字符a-z之间未匹配上
处理流程控制台提示,结束标记解析阶段错误控制台提示,结束标记解析阶段错误无效的标记
注释数据是'</'这是一个缺少结束标记名的解析错误。数据是'</>'。多余的结束标签,不展示无效的标记。创建一个注释节点,其数据为空字符串。
无效标记,数据为空字符串
app.component('my-component', {
  template:"</**"
})
</[a-z]/i.test(s[1])?
if第二个字符在 a-z 之间第二个字符是 '?'
处理流程创建元素节点。vue2.x兼容处理template元素节点 node = node.children
注释vue2.x兼容处理template元素节点,使用子节点替换当前节点无效的标记。创建一个注释节点,其数据为空字符串。
文本节点

开头未匹配上 '<'、'{{'、'}}' ,创建文本节点

后续还有 AST 转换生成代码