一、源码解读
以浏览器为例,梳理一下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方法就是经过createCompileToFunctionFn和compile两层包装的baseCompile函数。
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字符串经过createFunction即 new 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