Vue2 Compile

1,399 阅读2分钟

一、源码解读

以浏览器为例,梳理一下compile的过程。

创建Vue实例后调用实例的$mount方法,浏览器端的$mount方法在src\platforms\web\entry-runtime-with-compiler.js定义。

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component {
  el = el && query(el)

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
        let template = options.template
        // 省略代码……
        const { render, staticRenderFns } = compileToFunctions(template, {
            outputSourceRange: process.env.NODE_ENV !== 'production',
            shouldDecodeNewlines,
            shouldDecodeNewlinesForHref,
            delimiters: options.delimiters,
            comments: options.comments
        }, this)
        options.render = render
        options.staticRenderFns = staticRenderFns
    }
  return mount.call(this, el, hydrating)
}

可以看到,不提供render函数的情况下,会调用compileToFunctions通过template生成render函数。下面看下compileToFunctions的调用过程:

compileToFunctions

《-- createCompiler( baseOptions ) src\platforms\web\compiler\index.js

《-- createCompilerCreator( function beasCompile( ){ } ) src\compiler\create-compiler.js

function createCompilerCreator (baseCompile: Function): Function {
  return function createCompiler (baseOptions: CompilerOptions) {

    function compile ( template: string, options?: CompilerOptions
    ): CompiledResult {
      const finalOptions = Object.create(baseOptions)
	// 省略代码(对finalOptions的属性赋值)……
      const compiled = baseCompile(template.trim(), finalOptions)
      return compiled
    }

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

调用createCompiler(baseOptions)生成compileToFunctions方法时,baseCompile形成闭包。实际上compileToFunctions方法就是经过createCompileToFunctionFncompile两层包装的baseCompile函数。 compile

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

  return function compileToFunctions ( template: string, options?: CompilerOptions,
    vm?: Component ): CompiledFunctionResult {
    options = extend({}, options)
    // check cache
    const key = options.delimiters ? String(options.delimiters) + template : template
    if (cache[key]) {
      return cache[key]
    }

    // compile
    const compiled = compile(template, options)
    // turn code into functions
    const res = {}
    const fnGenErrors = []
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
})

    return (cache[key] = res)
  }
}

compileToFunctions返回的render函数是baseCompile返回的render字符串经过createFunctionnew Function生成的方法。 这里还做了render方法的缓存,在cache对象中,键是template,值是最终的render方法。 这里就引出了为什么要对baseCompile进行包装。

  • compile包装 是将baseOptions与传入的options通过柯里化合并成finalOptions,这样不同目标平台的baseOptions就保留下来,只需要调用compile就好,不必每次编译时都传入了;
  • createCompileToFunctionFn包装 是对render方法进行缓存。当某个渲染过的组件再次渲染时(比如路由再次跳转回来),就可以避免parse,optimize,generate的一系列计算,直接得到渲染render函数。
function baseCompile ( template: string, options: CompilerOptions): CompiledResult {
  const ast = parse(template.trim(), options)
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}

二、parse,optimize,generate

这部分会专门拿出一片文章来讲解,也可以参考mini-vue2,明白过程即可,也可以参考Vue 源码深入解析之 编译、编译入口、parse、optimize 和 codegen