Vue3 源码解读系列(十五)——编译

编译

web 模式的编译主要做了 3 件事:

  1. 解析 template 生成 AST
  2. AST 转换
  3. 生成代码
/**
 * web 编译
 * @param {string} template - 待编译的模板字符串
 * @param {string} options - 配置对象
 */
function compile(template, options = {}) {
  return baseCompile(template, extend({}, parserOptions, options, {
    nodeTransforms: [...DOMNodeTransforms, ...(options.nodeTransforms || [])],
    directiveTransforms: extend({}, DOMDirectiveTransforms, opotins.directiveTransforms || {}),
    transformHoist: null
  }))
}

/**
 * 基础编译
 * baseCompile 主要做了 3 件事:
 * 1、解析 template 生成 AST
 * 2、AST 转换
 * 3、生成代码
 */
function baseCompile(template, options = {}) {
  const prefixIdentifiers = false

  // 1、解析 template 生成 AST
  const ast = isString(template) ? baseParse(template, options) : template
  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset()

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

  // 3、生成代码
  return generage(ast, extend({}, options, {
    prefixIdentifiers
  }))
}

解析 template 生成 AST

问题:AST 是什么?

答:AST 是抽象语法树,用于描述节点在模板中完整的映射信息。

问题:为什么 AST 的根节点是虚拟节点?

答:因为 Vue3 中支持 语法,即:组件可以有多个根节点,但是树状结构只能有一个根节点,所以需要在外层套一个虚拟节点。

HTML 的嵌套结构的解析过程其实就是一个递归解析元素节点的过程。

/**
 * 解析 template 生成 AST
 * baseParse 主要做了 2 件事:
 * 1、创建解析上下文
 * 2、解析子节点,并创建 AST 根节点
 */
function baseParse(content, options = {}) {
  // 1、创建解析上下文
  const context = createParserContext(content, options)
  const start = getCursor(context)

  // 2、解析子节点,并创建 AST 根节点
  return createRoot(parseChildren(context, 0/* DATA */, []), getSelection(context, start))
}

// 默认解析配置
const defaultParserOptions = {
  delimiters: [`{{`, `}}`],
  getNamespace: () => 0/* HTML */,
  getTextMode: () => 0/* DATA */,
  isVoidTag: NO,
  isPreTag: NO,
  isCustomElement: NO,
  decodeEntities: (rawText) => rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
  onError: defaultOnError
}

/**
 * 创建解析器上下文
 */
function createParserContext(content, options) {
  return {
    options: extend({}, defaultParserOptions, options), // 解析相关配置
    column: 1, // 当前代码的列号
    line: 1, // 当前代码的行号
    offset: 0, // 当前代码相对于原始代码的偏移量
    originalSource: content, // 最初的原始代码
    source: content, // 当前代码
    inPre: false, // 当前代码是否在 pre 标签内
    inVPre: false //  当前代码是否在 VPre 指定的环境下
  }
}

/**
 * 解析子节点
 * 目的:解析并创建 AST 节点数组
 * parseChildren 主要做了 2 件事:
 * 1、自顶向下分析代码,生成 nodes
 * 2、空白字符管理
 */
function parseChildren(context, mode, ancestors) {
  const parent = last(ancestors) // 父节点
  const ns = parent ? parent.ns : 0/* HTML */
  const nodes = []

  // 1、自顶向下分析代码,生成 nodes(AST 节点数组)
  // 自顶向下遍历代码,然后根据不同情况去解析代码
  while (!isEnd(context, mode, ancestors)) {
    const s = context.source
    let node = undefined
    if (mode === 0/* DATA */ || mode === 1/* RCDATA */) {
      // 处理 {{ 插值代码
      if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
        node = parseInterpolation(context, mode)
      }
      // 处理 < 开头的代码
      else if (mode === 0/* DATA */ && s[0] === '<') {
        // s 长度为 1,说明代码结尾是 <,报错
        if (s.length === 1) {
          emitError(context, 5/* EOF_BEFORE_TAG_NAME */, 1)
        }
        // 处理 
        else if (s[1] === '!') {
          // 处理注释节点
          if (startsWith(s, '
                    

你可能感兴趣的:(Vue,vue.js,前端,javascript)