# Vue 编译器compiler

前面的 vue 初始化已经基本了解,再来了解下 vuecompiler,在src/platforms/web/entry-runtime-with-compiler.js中的$mount

//第一个参数就是模板字符串,compileToFunctions 函数会把模板字符串编译为渲染函数
const { render, staticRenderFns } = compileToFunctions(template, {
  shouldDecodeNewlines,
  shouldDecodeNewlinesForHref,
  delimiters: options.delimiters,
  comments: options.comments
}, this)
//渲染函数 render 就是通过 compileToFunctions 函数生成的
options.render = render
options.staticRenderFns = staticRenderFns

# compileToFunctions

const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }

export const createCompiler = createCompilerCreator(function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options)//使用 parse 函数将模板解析为 AST
  if (options.optimize !== false) {
      //调用 optimize 函数优化 ast
    optimize(ast, options)
  }
  //generate 函数将 ast 编译成渲染函数
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,//都以字符串的形式存在,因为真正变成函数的过程是在 compileToFunctions 中使用 new Function() 来完成的
    staticRenderFns: code.staticRenderFns //静态渲染函数
  }
})
 //`createCompilerCreator` 函数创建出针对于不同平台的编译器
export function createCompilerCreator (baseCompile: Function): Function {
    //返回createCompiler函数
  return function createCompiler (baseOptions: CompilerOptions) {
      //定义compile
    function compile (
      template: string,
      options?: CompilerOptions
    ): CompiledResult {
      //省略
    }

    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
//思路 createCompilerCreator=>createCompiler=>render

compileToFunctionscreateCompileToFunctionFn的返回值

export function createCompileToFunctionFn (compile: Function): Function {
  const cache = Object.create(null)
//传入的模板字符串(template)编译成渲染函数(render)
  return function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
      //extend 函数将 options 的属性混合到新的对象中并重新赋值 options
    options = extend({}, options)
    //检查选项参数中是否包含 warn,如果没有则使用 baseWarn
    const warn = options.warn || baseWarn
    //delete warn
    delete options.warn

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production') {
      // detect possible CSP restriction
      //new Function() 是否可用
      try {
        new Function('return 1')
      } catch (e) {
          //CSP 全称是内容安全策略,如果你的策略比较严格,那么 new Function() 将会受到影响,从而不能够使用
        if (e.toString().match(/unsafe-eval|CSP/)) {
          warn('something' )
        }
      }
    }

    // check cache
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
      //判断 cache[key] 是否存在,如果存在直接返回 cache[key]。这么做的目的是缓存字符串模板的编译结果,防止重复编译,提升性能
      //cache=Object.create(null) 空对象
    if (cache[key]) {
      return cache[key]
    }

    // compile 核心代码 真正的编译工作是依托于 compile 函数
    const compiled = compile(template, options)

    // check compilation errors/tips 检查编译错误和提示
    if (process.env.NODE_ENV !== 'production') {
      if (compiled.errors && compiled.errors.length) {
        warn(
          `Error compiling template:\n\n${template}\n\n` +
          compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
          vm
        )
      }
      if (compiled.tips && compiled.tips.length) {
        compiled.tips.forEach(msg => tip(msg, vm))
      }
    }

    // turn code into functions
    const res = {}
    const fnGenErrors = []
    function createFunction (code, errors) {
        try {
            return new Function(code)
        } catch (err) {
            errors.push({ err, code })
            return noop
        }
    }
    res.render = createFunction(compiled.render, fnGenErrors) //最终生成的渲染函数
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
    })

    // check function generation errors.
    // this should only happen if there is a bug in the compiler itself.
    // mostly for codegen development use
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production') {//用来打印在生成渲染函数过程中的错误
      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
        warn(
          `Failed to generate render function:\n\n` +
          fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
          vm
        )
      }
    }

    return (cache[key] = res) //返回结果的同时将结果缓存
  }
}

# compile函数

function compile (
      template: string,
      options?: CompilerOptions
    ): CompiledResult {
        //baseOptions 来自于 src/platforms/web/compiler/options.js
         const isUnaryTag = makeMap(
            'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
            'link,meta,param,source,track,wbr'
            )
        const canBeLeftOpenTag = makeMap(
        'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
        )
        const baseOptions: CompilerOptions = {
        expectHTML: true,
        modules,//
        directives,
        isPreTag, //函数检测 签名字检查标签是否是 'pre' 标签
        isUnaryTag, //检测给定的标签是否是一元标签
        mustUseProp, //检测一个属性在标签中是否要使用 props 进行绑定
        canBeLeftOpenTag, //不是一元标签,但却可以自己补全并闭合的标签,如<p>Some content,且浏览器会自动补全
        isReservedTag, //是否是保留的标签
        getTagNamespace, //获取元素(标签)的命名空间
        staticKeys: genStaticKeys(modules) //编译器选项的 modules 选项生成一个静态键字符串
        }
      const finalOptions = Object.create(baseOptions)
      const errors = []
      const tips = []
      finalOptions.warn = (msg, tip) => {//tip用来标示 msg 是错误还是提示
        (tip ? tips : errors).push(msg)
      }

      if (options) {
        // merge custom modules 将 options 对象混合到 finalOptions
        if (options.modules) {
          finalOptions.modules =
            (baseOptions.modules || []).concat(options.modules)
        }
        // merge custom directives
        if (options.directives) {//directives 是对象
          finalOptions.directives = extend(
            Object.create(baseOptions.directives || null),
            options.directives
          )
        }
        // copy other options
        for (const key in options) {
          if (key !== 'modules' && key !== 'directives') {
            finalOptions[key] = options[key] //复制其他属性
          }
        }
      }
// baseCompile 编译模板
      const compiled = baseCompile(template, finalOptions)
      if (process.env.NODE_ENV !== 'production') {
        errors.push.apply(errors, detectErrors(compiled.ast))//通过抽象语法树来检查模板中是否存在错误表达式的
      }
      compiled.errors = errors
      compiled.tips = tips
      return compiled
    }
// modules
export default [
  klass,
  style,
  model
]
// klass.js 的输出
export default {
  staticKeys: ['staticClass'],
  transformNode,
  genData
}
// style.js 的输出
export default {
  staticKeys: ['staticStyle'],
  transformNode,
  genData
}
// model.js 的输出
export default {
  preTransformNode
}
//directives
export default {
  model,
  text,
  html
}
// model.js 的输出
export default function model (
  el: ASTElement,
  dir: ASTDirective,
  _warn: Function
): ?boolean {
  // 函数体...
}
// html.js 的输出
export default function html (el: ASTElement, dir: ASTDirective) {
  if (dir.value) {
    addProp(el, 'innerHTML', `_s(${dir.value})`)
  }
}
// text.js 的输出
export default function text (el: ASTElement, dir: ASTDirective) {
  if (dir.value) {
    addProp(el, 'textContent', `_s(${dir.value})`)
  }
}

# Vue 的parser

parser,将字符串模板解析为抽象语法树(AST)
编译器就是将 源代码 转换成 目标代码 的工具,parser 是编译器处理源代码的第一步,大致也分为三个阶段,即:词法分析 -> 句法分析 -> 代码生成,在词法分析阶段 Vue 会把字符串模板解析成一个个的令牌(token),该令牌将用于句法分析阶段,在句法分析阶段会根据令牌生成一棵 AST,最后再根据该 AST 生成最终的渲染函数