码农之家

[vue源码笔记08]vue2.x的模板编译之构建ast

术语和流程

模板:template

抽象语法树:astElement

定向转化后的语法树:transformed-ast

代码字符串:code

渲染函数:render

转化流程:template –> astElement –> transformed-ast –> code –> render

所有源代码默认尽可能剔除开发环境的校验代码,便于关注主干逻辑

源代码

转化入口$mount

以下源代码位于src/platforms/web/entry-runtime-with-compiler.js

// 主要是标准化template模板
// 传入template可以是元素节点,也可以是节点选择器,也可以是模板字符串
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  const options = this.$options
  // 其实是可以传入render函数的,这里就不考虑该分支逻辑了
  if (!options.render) {
    // 获取最终template模板
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        // 如果获取到options中的template为以'#'开头的字符串,则以此为id查找dom对象
        // 并获取其innerHTML作为template
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
        }
      } else if (template.nodeType) { // 如果template为dom对象,则取其innerHTML为template
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) { // 如果options中没有template,则获取el对象的outerHTML为template
      template = getOuterHTML(el)
    }
    
    // 通过template生成render方法
    if (template) {
      // 将template模板字符串转为render函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines, // false
        shouldDecodeNewlinesForHref, // false
        delimiters: options.delimiters, // 自定义插入变量分隔符,默认为"{{"和"}}"
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  // 此处为后续调用render进行渲染、patch以及diff等
  return mount.call(this, el, hydrating)
}
 

function compileToFunctions

compileToFunctionscreateCompileToFunctionFn生成,传入compile函数作为编译函数,闭包变量cache用于缓存编译结果,compileToFunctions并不涉及编译的具体逻辑,只是对options做了一些处理

export function createCompileToFunctionFn (compile: Function): Function {
  // 闭包变量,用于缓存编译结果
  const cache: {
    [key: string]: CompiledFunctionResult;
  } = Object.create(null)

  return function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    options = extend({}, options)
    const warn = options.warn || baseWarn
    delete options.warn
    // 检查是否命中缓存
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
      return cache[key]
    }

    // 调用compile进行编译
    const compiled = compile(template, options)

    // 将编译得到的code转化为render-function
    const res = {}
    const fnGenErrors = []
    // 将render字符串生成render函数
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
    })
    return (cache[key] = res)
  }
}
 

function compile

该函数位于src/compiler/create-compiler.js,作用是合并传入options和baseOptions生成最终的options:finalOptions作为参数传给baseCompile

// 传入baseCompile,为编译函数
function createCompilerCreator (baseCompile: Function): Function {
  // baseOptions为基础option和传入option做一次合并生成最终options
  return function createCompiler (baseOptions: CompilerOptions) {
    return function compile (
      template: string,
      options?: CompilerOptions // 传入options
    ): CompiledResult {
      const finalOptions = Object.create(baseOptions)
      const errors = []
      const tips = []
      finalOptions.warn = (msg, tip) => {
        (tip ? tips : errors).push(msg)
      }
      if (options) {
        // 合并options.modules
        if (options.modules) {
          finalOptions.modules =
            (baseOptions.modules || []).concat(options.modules)
        }
        // 合并options.directives
        if (options.directives) {
          finalOptions.directives = extend(
            Object.create(baseOptions.directives),
            options.directives
          )
        }
        // 拷贝其他options项目
        for (const key in options) {
          if (key !== 'modules' && key !== 'directives') {
            finalOptions[key] = options[key]
          }
        }
      }
      const compiled = baseCompile(template, finalOptions)
      compiled.errors = errors
      compiled.tips = tips
      return compiled
    }
  }
}
 

baseOptions

位于src/platforms/web/compiler/options.js

const modules = [
  klass,
  style,
  model
]
const directives = {
  model,
  text,
  html
}
const baseOptions: CompilerOptions = {
  expectHTML: true,
  modules, // 处理class类、style、v-model
  directives, // 处理指令
  isPreTag, // (tag: ?string): boolean => tag === 'pre'
  isUnaryTag, // 和上一个属性类似,返回一些一元tag的函数
  mustUseProp, // 一些需要绑定属性的标签,如<input checked /> 中的checked属性,是和数据绑定相关的
  canBeLeftOpenTag, // 和上一个属性类似,返回一些自闭合的tag
  isReservedTag, // 保留的html标签
  getTagNamespace, // 针对svg、math标签
  staticKeys: genStaticKeys(modules)
}
 

function baseCompile

位于src/compiler/index.js

function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
    // template --> ast
    const ast = parse(template.trim(), options)
    // transform ast
  	optimize(ast, options)
    // ast --> code
  	const code = generate(ast, options)
  	return {
    	ast,
    	render: code.render,
    	staticRenderFns: code.staticRenderFns
  	}
}
 

function parse

位于src/compiler/parser/index.js,作用是对options做进一步解析,为编译做最后的准备,以及为parseHTML提供上下文环境

function parse(template: string, options: CompilerOptions): ASTElement | void {
  warn = options.warn || baseWarn
  platformIsPreTag = options.isPreTag || no // no: (a, b, c) => false
  platformMustUseProp = options.mustUseProp || no
  platformGetTagNamespace = options.getTagNamespace || no
  // transforms、preTransforms、postTransforms在对属性的转化中将被调用
  // 从options.modules列表中提取出键名为'transformNode'的值,组成新的列表
  transforms = pluckModuleFunction(options.modules, 'transformNode')
  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
  // 插值分隔符
  delimiters = options.delimiters
  // 编译栈 等编译上下文需要的变量
  const stack = []
  const preserveWhitespace = options.preserveWhitespace !== false
  let root
  let currentParent
  let inVPre = false
  let inPre = false
  let warned = false

  function warnOnce(msg) {
    if (!warned) {
      warned = true
      warn(msg)
    }
  }
  // 处理开始标签
  function start(tag, attrs, unary) {
    // code
  }
  function end() {
    // code
  }
  function chars() {
    // code
  }
  function comment(text: string) {
    currentParent.children.push({
      type: 3,
      text,
      isComment: true
    })
  }
  parseHTML(template, {
    warn,
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    canBeLeftOpenTag: options.canBeLeftOpenTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
    shouldKeepComment: options.comments,
    start,
    end,
    chars,
    comment
  })
  return root
}
 

function start

// 对handleStartTag处理过的节点再做处理,基于匹配结果对象创建astElement
// 对开始节点的属性进行处理
function start(tag, attrs, unary) { // unary表示是否为一元自闭合标签
    // 获取父节点的命名空间,或者如果tag === 'svg' || 'math'返回对应命名空间
    const ns =
      (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
	// 创建astElement
    /*
    {
    	type: 1,
    	tag,
    	attrsList: attrs,
    	attrsMap: makeAttrsMap(attrs),
    	parent,
    	children: []
  	}
    */
    let element: ASTElement = createASTElement(tag, attrs, currentParent)
    if (ns) {
      element.ns = ns
    }
	// <style> 和 <script>标签
    if (isForbiddenTag(element) && !isServerRendering()) {
      element.forbidden = true
    }

    // 执行 pre-transforms,来自传入合并的baseOptions,处理input标签
    for (let i = 0; i < preTransforms.length; i++) {
      element = preTransforms[i](element, options) || element
    }

    if (!inVPre) {
      processPre(element) // 判断是否包含'v-pre'属性,如果包含则将astElement.pre标记为true
      if (element.pre) {
        inVPre = true // 当前处于pre的编译环境
      }
    }
    // (tag) => tag === 'pre'
    if (platformIsPreTag(element.tag)) {
      inPre = true
    }
    // 如果v-pre,则进行对应编译
    if (inVPre) {
      processRawAttrs(element)
    } else if (!element.processed) { // element.processed标记当前astElement有没有经过处理
      processFor(element) // 处理v-for
      processIf(element) // 处理v-if、v-else 等
      processOnce(element) // 处理v-once
      processElement(element, options) // 处理其他属性
    }

    // 如果根节点还没有出现,则当前节点置为根节点
    if (!root) {
      root = element
    } else if (!stack.length) {
      // 允许根节点出现v-if v-else 等情况
      if (root.if && (element.elseif || element.else)) {
        // 添加if表达式
        addIfCondition(root, {
          exp: element.elseif,
          block: element
        })
      }
    }
    if (currentParent && !element.forbidden) {
      // 处理v-else
      if (element.elseif || element.else) {
        processIfConditions(element, currentParent)
      } else if (element.slotScope) {
        currentParent.plain = false
        const name = element.slotTarget || '"default"'
        ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[
          name
        ] = element
      } else {
        currentParent.children.push(element)
        element.parent = currentParent
      }
    }
    // 非一元自闭合标签
    if (!unary) {
      currentParent = element // 标记之后的标签都是当前标签的子节点
      stack.push(element) // 将当前标签入栈
    } else {
      endPre(element)
    }
}
 

functions preTransforms

// 处理<input :type="type" v-model="txt" /> 这样的情况
// 对于input标签动态设置'type'属性同时包含'v-model'的情况
// 由ifCondition来最终决定type='checkbox' || 'radio' || 'other'
function preTransformNode (el: ASTElement, options: CompilerOptions) {
  if (el.tag === 'input') {
    const map = el.attrsMap
    if (map['v-model'] && (map['v-bind:type'] || map[':type'])) {
      const typeBinding: any = getBindingAttr(el, 'type')
      const ifCondition = getAndRemoveAttr(el, 'v-if', true)
      const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : ``
      const hasElse = getAndRemoveAttr(el, 'v-else', true) != null // 是否存在'v-else'属性
      const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true)
      // 1. checkbox
      const branch0 = cloneASTElement(el) // 克隆节点
      // process for on the main node
      processFor(branch0)
      addRawAttr(branch0, 'type', 'checkbox') // 添加type=checkbox属性
      processElement(branch0, options)
      branch0.processed = true // prevent it from double-processed
      branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra
      addIfCondition(branch0, {
        exp: branch0.if,
        block: branch0
      })
      // 2. 添加 radio else-if 语句
      const branch1 = cloneASTElement(el)
      getAndRemoveAttr(branch1, 'v-for', true)
      addRawAttr(branch1, 'type', 'radio')
      processElement(branch1, options)
      addIfCondition(branch0, {
        exp: `(${typeBinding})==='radio'` + ifConditionExtra,
        block: branch1
      })
      // 3. other
      const branch2 = cloneASTElement(el)
      getAndRemoveAttr(branch2, 'v-for', true)
      addRawAttr(branch2, ':type', typeBinding)
      processElement(branch2, options)
      addIfCondition(branch0, {
        exp: ifCondition,
        block: branch2
      })

      if (hasElse) {
        branch0.else = true
      } else if (elseIfCondition) {
        branch0.elseif = elseIfCondition
      }

      return branch0
    }
  }
}
 

function processPre

// 判断当前astElement的attrsMap中是否包含'v-pre'属性
function processPre (el) {
  if (getAndRemoveAttr(el, 'v-pre') != null) {
    el.pre = true
  }
}
 

processRawAttrs

// 直接将所有属性当作静态属性做处理
function processRawAttrs (el) {
  const l = el.attrsList.length
  if (l) {
    const attrs = el.attrs = new Array(l)
    for (let i = 0; i < l; i++) {
      attrs[i] = {
        name: el.attrsList[i].name,
        value: JSON.stringify(el.attrsList[i].value)
      }
    }
  } else if (!el.pre) {
    el.plain = true
  }
}
 

processFor

// v-for表达式: 'item in list' --> el.for = 'list', el.alias = 'item', el.iterator1 = undefined
// v-for表达式: '(item, index) in list' --> el.for = 'list', el.alias = 'item', el.iterator1 = 'index'
// v-for表达式: '(val, key, index) of obj' --> el.for = 'obj', el.alias = 'val', el.iterator1 = 'key', el.iterator2 = 'index'
function processFor (el: ASTElement) {
  let exp
  if ((exp = getAndRemoveAttr(el, 'v-for'))) { // 获取v-for属性
    // /(.*?)\s+(?:in|of)\s+(.*)/
    // 匹配'item in list' 或者'item of list'表达式
    const inMatch = exp.match(forAliasRE)
    
    el.for = inMatch[2].trim() // 表达式的'list'
    // '(item, index)' --> 'item, index'
    const alias = inMatch[1].trim().replace(stripParensRE, '')
    // 匹配出遍历器'item, index' --> 'index'
    const iteratorMatch = alias.match(forIteratorRE)
    if (iteratorMatch) {
      // 从'item, index'中提取出'item'
      el.alias = alias.replace(forIteratorRE, '')
      // 从'item, index'中提取出'index'
      el.iterator1 = iteratorMatch[1].trim()
      if (iteratorMatch[2]) {
        el.iterator2 = iteratorMatch[2].trim()
      }
    } else {
      el.alias = alias
    }
  }
}
 

processIf

// 处理v-if、v-else-if、v-else指令
// v-if:el.ifConditions = [] 添加if表达式
// v-else-if: el.elseif = elseif
// v-else: el.else = true
function processIf (el) {
  const exp = getAndRemoveAttr(el, 'v-if')
  if (exp) {
    el.if = exp
    addIfCondition(el, {
      exp: exp,
      block: el
    })
  } else {
    if (getAndRemoveAttr(el, 'v-else') != null) {
      el.else = true
    }
    const elseif = getAndRemoveAttr(el, 'v-else-if')
    if (elseif) {
      el.elseif = elseif
    }
  }
}

function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
  if (!el.ifConditions) {
    el.ifConditions = []
  }
  el.ifConditions.push(condition)
}
 

processOnce

function processOnce (el) {
  const once = getAndRemoveAttr(el, 'v-once')
  if (once != null) {
    el.once = true
  }
}
 

function processElement

function processElement (element: ASTElement, options: CompilerOptions) {
  processKey(element) // 处理:key 属性
  // 如果astElement不包含:key和其它属性,则标记element.plain = true
  element.plain = !element.key && !element.attrsList.length
  processRef(element) // 处理ref、v-bind:ref属性
  processSlot(element) // 处理slot相关属性
  processComponent(element) // 处理is、:is、inline-template这样的属性
  // 执行transforms函数,来自于parse函数的baseOptions,
  // 这两个函数分别用来处理class、:class和style、:style属性
  for (let i = 0; i < transforms.length; i++) {
    element = transforms[i](element, options) || element
  }
  processAttrs(element) // 处理属性列表
}
 
processKey
function processKey (el) {
  const exp = getBindingAttr(el, 'key') // 获取属性v-bind:key或:key
  if (exp) {
    el.key = exp
  }
}
 
processRef
function processRef (el) {
  const ref = getBindingAttr(el, 'ref')
  if (ref) {
    el.ref = ref
    el.refInFor = checkInFor(el) // 检查是否ref属性同时出现在v-for的节点,这种情况需要特殊处理
  }
}
function checkInFor (el: ASTElement): boolean {
  let parent = el
  while (parent) {
    if (parent.for !== undefined) {
      return true
    }
    parent = parent.parent
  }
  return false
}
 
processSlot
function processSlot (el) {
  if (el.tag === 'slot') {
    el.slotName = getBindingAttr(el, 'name')
  } else {
    let slotScope
    if (el.tag === 'template') {
      slotScope = getAndRemoveAttr(el, 'scope')
      el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
    } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
      el.slotScope = slotScope
    }
    const slotTarget = getBindingAttr(el, 'slot')
    if (slotTarget) {
      el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
      if (el.tag !== 'template' && !el.slotScope) {
        addAttr(el, 'slot', slotTarget)
      }
    }
  }
}
 
processComponent
// 处理<template :is="com">的情况
function processComponent (el) {
  let binding
  if ((binding = getBindingAttr(el, 'is'))) {
    el.component = binding
  }
  if (getAndRemoveAttr(el, 'inline-template') != null) {
    el.inlineTemplate = true
  }
}
 
processAttrs
function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, rawName, value, modifiers, isProp
  for (i = 0, l = list.length; i < l; i++) {
    name = rawName = list[i].name
    value = list[i].value
    if (dirRE.test(name)) { // 匹配以“v-、@、:”开头的属性名,即动态属性
      // 如果匹配到上述,则标记当前astElement为动态
      el.hasBindings = true
      // 修饰符:'@click.a.b' --> {a: true, b: true}
      // v-a:b.c.d --> {c: true, d: true} v-a为指令,b为参数,c、d为修饰符
      modifiers = parseModifiers(name) // 处理类似 @click.capture\@click.once之类的修饰符capture、once之类的
      if (modifiers) {
        name = name.replace(modifierRE, '') // 将属性名去除修饰符
      }
      if (bindRE.test(name)) { // 匹配以“:、v-bind:”开头的属性名
        name = name.replace(bindRE, '') // 属性名去除':'、'v-bind'
        value = value
        isProp = false
        if (modifiers) {
          if (modifiers.prop) {
            isProp = true
            name = camelize(name) // 'a-b' --> 'aB'
            if (name === 'innerHtml') name = 'innerHTML'
          }
          if (modifiers.camel) {
            name = camelize(name)
          }
          // 处理sync修饰符
          if (modifiers.sync) {
            addHandler(
              el,
              `update:${camelize(name)}`,
              genAssignmentCode(value, `$event`)
            )
          }
        }
        if (isProp || (
          !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
        )) {
          addProp(el, name, value)
        } else {
          addAttr(el, name, value)
        }
      } else if (onRE.test(name)) { // 匹配以“@、v-on”开头的属性名,处理事件绑定
        name = name.replace(onRE, '')
        addHandler(el, name, value, modifiers, false, warn) // 绑定事件
      } else { // 处理普通指令、自定义指令
        // 去除'v-': 'v-a:b' ---> 'a:b'
        name = name.replace(dirRE, '')
        // 匹配指令参数:'a:b' ---> [":b", "b", index: 1, input: "a:b", groups: undefined]
        const argMatch = name.match(argRE)
        // 分离出指令参数
        const arg = argMatch && argMatch[1]
        if (arg) {
          name = name.slice(0, -(arg.length + 1)) // 截取指令name
        }
        addDirective(el, name, rawName, value, arg, modifiers)
      }
    } else {
      addAttr(el, name, JSON.stringify(value)) // 将{name: 'id', value: 'demo'} push加入attrs属性数组
      // #6887 firefox doesn't update muted state if set via attribute
      // even immediately after element creation
      if (!el.component &&
          name === 'muted' &&
          platformMustUseProp(el.tag, el.attrsMap.type, name)) {
        addProp(el, name, 'true')
      }
    }
  }
}
 
addAttr
function addAttr (el: ASTElement, name: string, value: string) {
  (el.attrs || (el.attrs = [])).push({ name, value })
}
 
addProp
function addProp (el: ASTElement, name: string, value: string) {
  (el.props || (el.props = [])).push({ name, value })
}
 
addHandler
// 处理事件,将事件保存到el.events = {click: {value: 'handleClick'}} handleClick为事件回调
// 带native修饰符:el.nativeEvents = {click: {value: 'handleClick'}}
function addHandler (
  el: ASTElement,
  name: string,
  value: string,
  modifiers: ?ASTModifiers,
  important?: boolean,
  warn?: Function
) {
  modifiers = modifiers || emptyObject

  // capture修饰符最终转化为'!name'
  if (modifiers.capture) {
    delete modifiers.capture
    name = '!' + name
  }
  // once修饰符最终转化为'~name'
  if (modifiers.once) {
    delete modifiers.once
    name = '~' + name
  }
  // passive修饰符最终转化为'&name'
  if (modifiers.passive) {
    delete modifiers.passive
    name = '&' + name
  }

  if (name === 'click') {
    if (modifiers.right) { // 右键事件
      name = 'contextmenu'
      delete modifiers.right
    } else if (modifiers.middle) { // 中键事件
      name = 'mouseup'
    }
  }

  let events
  if (modifiers.native) {
    delete modifiers.native
    events = el.nativeEvents || (el.nativeEvents = {})
  } else {
    events = el.events || (el.events = {})
  }

  const newHandler: any = { value }
  if (modifiers !== emptyObject) {
    newHandler.modifiers = modifiers
  }

  const handlers = events[name]

  if (Array.isArray(handlers)) {
    important ? handlers.unshift(newHandler) : handlers.push(newHandler)
  } else if (handlers) {
    events[name] = important ? [newHandler, handlers] : [handlers, newHandler]
  } else {
    events[name] = newHandler
  }
}
 
addDirective
function addDirective (
  el: ASTElement,
  name: string,
  rawName: string,
  value: string,
  arg: ?string,
  modifiers: ?ASTModifiers
) {
  (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })
}
 
functions transforms
function transformNode (el: ASTElement, options: CompilerOptions) {
  const warn = options.warn || baseWarn
  const staticClass = getAndRemoveAttr(el, 'class') // 获取astElement的静态class属性
  if (staticClass) {
    el.staticClass = JSON.stringify(staticClass)
  }
  const classBinding = getBindingAttr(el, 'class', false)// 获取:class属性
  if (classBinding) {
    el.classBinding = classBinding
  }
}

function transformNode (el: ASTElement, options: CompilerOptions) {
  const warn = options.warn || baseWarn
  const staticStyle = getAndRemoveAttr(el, 'style') // 获取astElement的静态style属性

  const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) // 获取:style属性
  if (styleBinding) {
    el.styleBinding = styleBinding
  }
}
 

function chars

// 处理文本节点或者插值变量
function chars(text: string) {
    const children = currentParent.children // 当前父节点的children
    text =
      inPre || text.trim()
        ? isTextTag(currentParent)
          ? text
          : decodeHTMLCached(text) // 转移html字符串,如'文本&lt' --> '文本<'
        : // only preserve whitespace if its not right after a starting tag
        preserveWhitespace && children.length
        ? ' '
        : ''
    if (text) {
      let expression
      if (
        // 非pre模式
        !inVPre &&
        text !== ' ' &&
        // 以分隔符delimiters(默认'{{}}'匹配是否包含插值变量)
        // 将插值字符转化为插值表达式,'{{count}}' --> '_s(count)'
        // '_s'为Vue中预定义的转化插值变量的方法,后面再介绍
        (expression = parseText(text, delimiters))
      ) {
        // 包含插值表达式的文本节点
        children.push({
          type: 2,
          expression,
          text
        })
      } else if (
        text !== ' ' ||
        !children.length ||
        children[children.length - 1].text !== ' '
      ) {
        // 静态文本节点
        children.push({
          type: 3,
          text
        })
      }
    }
}
 

function parseText

// 处理插值变量
function parseText (
  text: string,
  delimiters?: [string, string]
): string | void {
  // 根据传入分隔符创建正则,或者使用默认正则
  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
  if (!tagRE.test(text)) {
    return
  }
  const tokens = []
  let lastIndex = tagRE.lastIndex = 0
  let match, index
  // 使用分隔符正则匹配文本字符串,从中匹配出插值变量并转化为插值表达式字符串
  while ((match = tagRE.exec(text))) {
    index = match.index
    // 静态文本节点
    if (index > lastIndex) {
      tokens.push(JSON.stringify(text.slice(lastIndex, index)))
    }
    // 变量表达式
    const exp = match[1].trim()
    tokens.push(`_s(${exp})`)
    lastIndex = index + match[0].length
  }
  if (lastIndex < text.length) {
    tokens.push(JSON.stringify(text.slice(lastIndex)))
  }
  return tokens.join('+')
}
 

function end

function end() {
    // 取出栈顶节点
    const element = stack[stack.length - 1]
    // 获取栈顶节点最后一个子节点
    const lastNode = element.children[element.children.length - 1]
    // 如果最后一个子节点是一个文本节点,且其值为' ',同时不是处于pre环境则丢弃它
    if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
      element.children.pop()
    }
    // 弹出栈顶元素,表示该元素编译已完成
    // 将currentParent置为下一个元素,表示接下来编译是在其内部
    stack.length -= 1
    currentParent = stack[stack.length - 1]
    endPre(element) // 结束pre状态
}
function endPre(element) {
    if (element.pre) {
      inVPre = false
    }
    if (platformIsPreTag(element.tag)) {
      inPre = false
    }
}
 

工具方法

pluckModuleFunction

// modules中的每一项提取出键值为key的属性,同时该项非空
// 例如:[{a: 'a', b: 'b'}, {a: 'aa', b: 'bb'}] --> ['a', 'aa']
function pluckModuleFunction<F: Function> (
  modules: ?Array<Object>,
  key: string
): Array<F> {
  return modules
    ? modules.map(m => m[key]).filter(_ => _)
    : []
}
 

getAndRemoveAttr

// 获取astElement 的attrsMap中的某个属性
// 同时通过传入removeFromMap参数标记是否移除它
function getAndRemoveAttr (
  el: ASTElement,
  name: string,
  removeFromMap?: boolean // 是否移除当前属性
): ?string {
  let val
  if ((val = el.attrsMap[name]) != null) {
    const list = el.attrsList
    for (let i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1)
        break
      }
    }
  }
  if (removeFromMap) {
    delete el.attrsMap[name]
  }
  return val
}
 

getBindingAttr

// 获取动态绑定的属性
// 如果v-bind:key、:key
// 也可以通过参数getStatic参数标记是否降级获取静态属性
function getBindingAttr (
  el: ASTElement,
  name: string,
  getStatic?: boolean
): ?string {
  const dynamicValue =
    getAndRemoveAttr(el, ':' + name) ||
    getAndRemoveAttr(el, 'v-bind:' + name)
  if (dynamicValue != null) {
    return dynamicValue
  } else if (getStatic !== false) {
    const staticValue = getAndRemoveAttr(el, name)
    if (staticValue != null) {
      return JSON.stringify(staticValue)
    }
  }
}
 

function parseHTML

作用是将template转化为astElement

工具方法advance

function parseStartTag

function handleStartTag

function parseEndTag

流程回顾

上面对template–> ast过程中设计的方法都粗浅得进行了注释,但是缺乏整体性,下面尝试从整体流程的角度进行一次分析

流程图:

编译环境

模板编译过程中有两个编译执行环境:parseparseHTML

parse提供了startendchars方法分别用来处理开始标签、结束标签和文本节点,同时其有内部变量stack(后面称parse-stack)、currentParentinPre分别是节点astElement栈、当前节点的父节点、pre环境。

parseHTML提供了handleStartTaghandleEndTag方法分别用来匹配开始标签和结束标签,同时其内部变量stack(后面成parseHTML-stack)、indexlastTag分别是开始节点栈、当前匹配结束点、上一个匹配的标签。

编译流程

匹配到开始标签

依次经过handleStartTagstart进行处理

  1. parseHTML环境下handleStartTag进行处理
    1. 匹配出tag
    2. 匹配出属性attrs
    3. 标签描述对象推入parseHTML-stacklastTag置为当前匹配的tagName
  2. parse环境下start进行处理
    1. 基于tagattrscurrentParent创建astElement
    2. 如果标签包含’v-pre’属性或者为pre标签则将inPre置为true
    3. 调用属性处理方法处理属性
    4. 将当前astElement推入currentParent.children同时astElement.parent = currentParent
    5. currentParent置为当前astElement并且将当前astElement推入parse-stack

匹配到文本节点

经过chars进行处理,parse环境下

  1. 将插值变量转化为插值表达式推入currentParent.children
  2. 将普通文本节点转化为描述对象推入currentParent.children

匹配到结束标签

依次经过handleEndTagend进行处理:

  1. parseHTML环境下handleEndTag
    1. 从上向下遍历parseHTML-stack找出第一个和当前结束标签具有相同标签名的开始标签的位置pos
    2. 如果pos的位置不是parseHTML-stack的顶部位置,则表示在pos上方的开始标签都缺失结束标签,则给予warn,同时依次调用end结束未闭合的标签
    3. 如果pos位置等于parseHTML-stack.length - 1,则表示开始标签刚好匹配结束标签,调用end结束
    4. 如果未找到对应的开始标签,即pos === -1,如果tag为br或者p则当作开始标签处理
    5. parseHTML-stack中移除已经匹配到结束标签的开始标签:parseHTML-stack.length = poslastTag赋值为最上面的开始标签
  2. parse环境下调用end
    1. 找到parse-stack栈顶astElement:element
    2. 找到element的最后一个child:lastNode
    3. 如果lastNode.text === ' '且非pre且为文本节点,则将其移除
    4. 弹出parse-stack栈顶元素,currentParent置为新的栈顶元素