1.complie简述

complie阶段主要是将Vue代码通过一系列转换,最终生成对应的render字符串,然后Vue通过render字符串里面的关键词或者说定义,对页面内容进行增删改,Vue对象上有一个全局函数compile

import { compileToFunctions } from './compiler/index'
......
Vue.compile = compileToFunctions

可以看出,他是引用的./compiler/index模块导出的compileToFunctions

import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }

baseOptions,对解析模板的一些选项进行基本的定义,规定如果解析模板中的各种标签或代码

export const baseOptions: CompilerOptions = {
  expectHTML: true,
  modules,
  directives,
  isPreTag,
  isUnaryTag,
  mustUseProp,
  canBeLeftOpenTag,
  isReservedTag,
  getTagNamespace,
  staticKeys: genStaticKeys(modules)
}

expectHTML:为true时,解析HTML的结束标签

modules:包括klassstyle,对模板中类和样式的解析。

directives:这里包括modelv-model)、htmlv-html)、text(v-text)三个指令

isPreTag:是否是pre标签

isUnaryTag:是否是单标签,比如imginputiframe

mustUseProp:需要使用props绑定的属性,比如valueselected

canBeLeftOpenTag:可以不闭合的标签,比如trtd

isReservedTag:是否是保留标签,html标签和SVG标签

getTagNamespace:获取命名空间,svgmath

staticKeys:静态关键词,包括staticClass,staticStyle

然后是createCompiler

export function createCompilerCreator (baseCompile: Function): Function {
  // 接受一个基本的CompilerOptions
  return function createCompiler (baseOptions: CompilerOptions) {
    function compile (
      template: string,
      options?: CompilerOptions
    ): CompiledResult {
      // finalOptions继承自baseOptions
      const finalOptions = Object.create(baseOptions)
      const errors = []
      const tips = []

      let warn = (msg, range, tip) => {
        (tip ? tips : errors).push(msg)
      }

      if (options) {
        if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
          // $flow-disable-line
          const leadingSpaceLength = template.match(/^\s*/)[0].length

          warn = (msg, range, tip) => {
            const data: WarningMessage = { msg }
            if (range) {
              if (range.start != null) {
                data.start = range.start + leadingSpaceLength
              }
              if (range.end != null) {
                data.end = range.end + leadingSpaceLength
              }
            }
            (tip ? tips : errors).push(data)
          }
        }
        // 合并modules
        if (options.modules) {
          finalOptions.modules =
            (baseOptions.modules || []).concat(options.modules)
        }
        // 合并directives
        if (options.directives) {
          finalOptions.directives = extend(
            Object.create(baseOptions.directives || null),
            options.directives
          )
        }
        
        // 额外传入的options,合并
        for (const key in options) {
          if (key !== 'modules' && key !== 'directives') {
            finalOptions[key] = options[key]
          }
        }
      }
	
      // 收集到的错误日志
      finalOptions.warn = warn

      const compiled = baseCompile(template.trim(), finalOptions)
      if (process.env.NODE_ENV !== 'production') {
        detectErrors(compiled.ast, warn)
      }
      compiled.errors = errors
      compiled.tips = tips
      // 返回render字符串
      return compiled
    }

    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}

导出的模块compileToFunctionscreateCompileToFunctionFn执行返回的结果

createCompileToFunctionFn代码

export function createCompileToFunctionFn (compile: Function): Function {
  const cache = Object.create(null)

  return function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    // 拿到options,并且删除错误日志等数据
    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]
    }

    // 编译的结果
    const compiled = compile(template, options)

 		......

    const res = {}
    const fnGenErrors = []
    // render字符串
    res.render = createFunction(compiled.render, fnGenErrors)
    // 将render字符串中的标识符转为函数,如_c、_m
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
    })

		......

    return (cache[key] = res)
  }
}

2.生成ast

src/compiler/parser/index.js中,首先定义了各种

// 匹配@或v-on开头的属性
export const onRE = /^@|^v-on:/
// 是匹配v-或@或:开头的属性
export const dirRE = /^v-|^@|^:|^\./ 
// 匹配v-for中的属性值,比如item in items、(item, index) of items
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ 
// 是对forAliasRE中捕获的第一部分内容,进行拆解,v-for中in|of前最后可以有三个逗号分隔的参数
export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
// 去掉字符串 '(item, index)' 中的左右括号
const stripParensRE = /^\(|\)$/g
// 用来匹配指令编写中的参数
const argRE = /:(.*)$/
// 匹配以字符:或字符串 v-bind: 开头的字符串
export const bindRE = /^:|^\.|^v-bind:/

parse,里面解析的方法主要是parseHTML

export function parse (
	template: string,
	options: CompilerOptions
  ): ASTElement | void {
	warn = options.warn || baseWarn
  
	......
	parseHTML()
  return root
}

// src/compiler/parser/html-parser.js

export function parseHTML (html, options) {
  const stack = []
  const expectHTML = options.expectHTML
  const isUnaryTag = options.isUnaryTag || no
  const canBeLeftOpenTag = options.canBeLeftOpenTag || no
  let index = 0
  let last, lastTag
  while (html) {}

  function advance (n) {
    ...
  }

  function parseStartTag () {
    ...
  }

  function handleStartTag (match) {
    ...
  }

	function parseEndTag (tagName, start, end) {
  	...
  }
}

parseHTMLwhile循环里

// advance这个方法,用于截取传入的字符串
// 过滤掉注释,doctype等
if (comment.test(html)) {
  const commentEnd = html.indexOf('-->')

  if (commentEnd >= 0) {
    advance(commentEnd + 3)
    continue
  }
}

// 过滤文档类型标示的字符串
const doctypeMatch = html.match(doctype)
if (doctypeMatch) {
  advance(doctypeMatch[0].length)
  continue
}

// 匹配结束的标签
const endTagMatch = html.match(endTag)
if (endTagMatch) {
  const curIndex = index
  advance(endTagMatch[0].length)
  parseEndTag(endTagMatch[1], curIndex, index)
  continue
}

// 匹配开始标签
// 传入handleStartTag处理完后,会生成一个包含标签各种属性的对象
const startTagMatch = parseStartTag()
if (startTagMatch) {
  handleStartTag(startTagMatch)
  continue
}

后续会调用parse里调用parseHTML传入的start的方法

start () {
  // 定义基本的ast结构
  createASTElement()
  // 解析v-pre、v-if、v-for、v-once、slot、key、ref等指令
  processPre()
  processFor()
  ......
}

处理完后会调用parse里调用parseHTML传入的end的方法

1、取出stack中的最后一个元素。

2、取出该元素的最后一个子元素。

3、如果最后一个子元素是纯文本' '则删除,这是因为我们的模板一般都会缩进,都会有换行,所以这里是清除换行等添加的内容。

4、stack长度减一

5、currentParent变为栈中最后一个元素

6、 处理v-prepre的结束标签

end (tag, start, end) {
  const element = stack[stack.length - 1]
  if (!inPre) {
    const lastNode = element.children[element.children.length - 1]
    if (lastNode && lastNode.type === 3 && lastNode.text === ' ') {
      element.children.pop()
    }
  }
  stack.length -= 1
  currentParent = stack[stack.length - 1]
  if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    element.end = end
  }
  closeElement(element)
},

最后,得到一个包含名称、类型、属性、父子节点信息的一个ast语法树,如

const element = {
  type: 1,
  tag: '',
  attrsList: [],
  attrsMap: {},
  parent: {},
  children: []
};

3.优化静态内容,打开src/compiler/index,看到调用const ast = parse(template.trim(), options)生成ast后,调用optimize,传入ast ,打开optimisze源码

// 获取基本静态key的一个集合
const genStaticKeysCached = cached(genStaticKeys)
export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  // 如果ast上有staticKeys选项
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  // 是不是平台保留tag,如HTML 标签
  isPlatformReservedTag = options.isReservedTag || no
  // 标记所有静态和非静态节点
  markStatic(root)
  // 标记静态根节点
  markStaticRoots(root, false)
}

function genStaticKeys (keys: string): Function {
  return makeMap(
    'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap,has$Slot' +
    (keys ? ',' + keys : '')
  )
}

markStatic标记所有静态和非静态节点

function markStatic (node: ASTNode) {
  // 判断当前节点是否为静态节点
  // 静态节点包括普通元素、纯文本元素、v-pre、v-once
  node.static = isStatic(node)
 	// 如果type为1,代表当前节点为元素节点
  if (node.type === 1) {
    if (
      // 如果不是保留标签,即自定义标签时
      !isPlatformReservedTag(node.tag) &&
      // 如果不是component标签
      !node.component &&
      // 如果标签不是slot
      node.tag !== 'slot' &&
      // 不是一个内联模板容器??
      node.attrsMap['inline-template'] == null
    ) {
      // 直接return,不对子节点做处理,反之,递归对子节点做标记
      return
    }
  	......
  }
}
  
function isStatic (node: ASTNode): boolean {
  // 表达式
  if (node.type === 2) {
    return false
  }
  // text
  if (node.type === 3) { 
    return true
  }
  return !!(node.pre || (
    // 没有任何指令或事件绑定
    !node.hasBindings && 
    // 没有if 和 for 指令
    !node.if && !node.for && 
    // 不是内置标签,如slot
    !isBuiltInTag(node.tag) &&
    // 平台保留标签,如HTML
    isPlatformReservedTag(node.tag) && 
    // 不是template标签的直接子元素且没有包含在for循环中
    !isDirectChildOfTemplateFor(node) &&
    // 结点包含的属性只能有isStaticKey中指定的几个
    Object.keys(node).every(isStaticKey)
  ))
}

markStaticRoots标记静态根节点

function markStaticRoots (node: ASTNode, isInFor: boolean) {
  // 只处理元素节点类型
  if (node.type === 1) {
    if (node.static || node.once) {
      node.staticInFor = isInFor
    }
    // 标记静态根节点的条件为本身static标记为true
    // 并且该结点不是只有一个静态文本子节点
    if (node.static && node.children.length && !(
      node.children.length === 1 &&
      node.children[0].type === 3
    )) {
      node.staticRoot = true
      return
    } else {
      node.staticRoot = false
    }
		......
  }
}

4.生成render字符串

在标记完静态节点之后,会调用generate方法来生成render字符串,生成的字符串中

_c 该方法对应的是createElement方法,顾名思义,它的含义是创建一个元素,它的第一个参数是要定义的元素标签名、第二个参数是元素上添加的属性,第三个参数是子元素数组,第四个参数是子元素数组进行归一化处理的级别。

_v 该方法是创建一个文本结点。

_s 是把一个值转换为字符串。

_m 是渲染静态内容,它接收的第一个参数是一个索引值,指向最终生成的staticRenderFns数组中对应的内容,第二个参数是标识元素是否包裹在for循环内。

export function generate (
  ast: ASTElement | void,
  options: CompilerOptions
): CodegenResult {
  const state = new CodegenState(options)
  // 如果ast为空,直接返回一个空的div节点
  // 有过有值,调用genElement
  const code = ast ? genElement(ast, state) : '_c("div")'
  // 返回render字符串
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}

genElement会通过节点上的标志来判断使用什么方法来生成render字符串,这里说一下genStatic

export function genElement (el: ASTElement, state: CodegenState): string {
  // pre的状态用来判断是否跳过当前解析
  if (el.parent) {
    el.pre = el.pre || el.parent.pre
  }
	
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } 
  ...... 
		else {
      let data
      // 如果节点没其他属性或者跳过解析,直接genData
      // genData这个方法会给节点添加各种属性
      if (!el.plain || (el.pre && state.maybeComponent(el))) {
        data = genData(el, state)
      }
			
      // 如果不是内部模板,调用genChildren,返回对应的字符串
      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      code = `_c('${el.tag}'${
        data ? `,${data}` : '' 
      }${
        children ? `,${children}` : '' 
      })`
    }
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}

function genStatic (el: ASTElement, state: CodegenState): string {
  el.staticProcessed = true
  // 暂存pre
  const originalPreState = state.pre
  // 如果当前节点pre为true,更改pre的状态
  if (el.pre) {
    state.pre = el.pre
  }
  // 通过递归处理静态根节点及其子内容,添加到staticRenderFns
  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
  // 恢复pre状态
  state.pre = originalPreState
  return `_m(${
    state.staticRenderFns.length - 1
  }${
    el.staticInFor ? ',true' : ''
  })`
}

最后返回的结果就包含render字符串以及staticRenderFns静态节点