模板编译
- 如果是在html中引入的vue,他会将
template
编译成render
渲染,所以直接引入vue,体积是比较大的, 除非自己手写render。 - 在使用
cli
的时候,会在打包的时候将template
编译成render
函数,这样在打包完成后vue是无编译器的版本
将下面代码的编译生成后的代码输出查看
<div id="app">
<h1>Vue<span>模板编译过程</span></h1>
<p>{{ msg }}</p>
<comp @myclick="handler"></comp>
</div>
<script src="../../dist/vue.js"></script>
<script>
Vue.component('comp', {
template: '<div>I am a comp</div>'
})
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello compiler'
},
methods: {
handler () {
console.log('test')
}
}
})
console.log(vm.$options.render)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
得到如下编译后的代码
(function anonymous() {
with (this) {
return _c(
"div",
{ attrs: { id: "app" } },
[
_m(0),
// 换行空白节点
_v(" "),
_c("p", [_v(_s(msg))]),
_v(" "),
_c("comp", { on: { myclick: handler } }),
],
1
);
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这些调用的函数,core/instance/render.js
中有_c
,前面我们已经知道_c
其实就是_h
。 在core/instance/render-helpers/index.js
中,声明了其他方法
入口
template
生成render
函数的入口在,platform/web/entry-runtime-with-compiler
中的$mount
方法。 在compileToFunctions
方法中生成了render
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
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
往上找,能看到createCompiler
返回了compileToFunctions
const { compile, compileToFunctions } = createCompiler(baseOptions)
1
在compiler/index.js
中createCompilerCreator
的传入函数baseCompile
返回了ast
、render
、staticRenderFns
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 把模板转换成 ast 抽象语法书
// 抽象语法树,用来以树的方式描述代码结构
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
// 优化抽象语法树
optimize(ast, options)
}
// 把抽象语法树生成字符串形式的 js 代码
const code = generate(ast, options)
return {
ast,
// 渲染函数 字符串形式
render: code.render,
// 静态渲染函数,生成静态 VNode 树
staticRenderFns: code.staticRenderFns
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在generate
函数中生成render
和staticRenderFns
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
// fix #11483, Root level <script> tags should not be rendered.
const code = ast ? (ast.tag === 'script' ? 'null' : genElement(ast, state)) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
在createCompilerCreator
中我们可以看到,它是把用户的options和baseOptions
合并后 通过baseCompile
生成了compiled
,返回的compiled
其实就是compile
export function createCompilerCreator (baseCompile: Function): Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
// h合并options
const finalOptions = Object.create(baseOptions)
finalOptions.warn = warn
// 返回了compiled, 其中包含render,和staticRenderFns
const compiled = baseCompile(template.trim(), finalOptions)
if (process.env.NODE_ENV !== 'production') {
detectErrors(compiled.ast, warn)
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
编译的过程
接上面,我们来看createCompileToFunctionFn
方法,这个方法其实就是运行的核心,整个流程分为了4步,源码中用了很多高阶 函数非常绕,但是只要debugger
放到runtime-with-compiler
中的$mount
下的compileToFunctions
方法, 就很清晰,运行到下面的函数的时候,跟着注释的1234步骤打上断点,就能看的明白
TIP
看的时候可以略去 AST相关生成和优化
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
// check cache
// 1. 读取缓存中的CompiledFunctionResult 对象,如果有直接返回
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// compile
// 2. 把模板编译为编译对象(render, staticReenderFns),字符串形式的js代码
const compiled = compile(template, options)
// turn code into functions
const res = {}
const fnGenErrors = []
// 3. 把字符串代码 转为函数 new Function(code)
res.render = createFunction(compiled.render, fnGenErrors)
res.staticRenderFns = compiled.staticRenderFns.map(code => {
return createFunction(code, fnGenErrors)
})
// 4. 缓存并返回res对象(render, staticRenderFns方法)
return (cache[key] = res)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41