本文为原创文章,未获授权禁止转载,侵权必究!
本篇是 Vue 源码解析系列第 2 篇,关注专栏
前言
Vue.js 提供了 2 个版本,一个是 Runtime + Compiler,一个是 Runtime only,前者包含编译代码,可以把编译过程放在运行时做,后者不包含编译代码,需要借助 webpack 的 vue-loader
事先把模板编译成 render
函数。本文基于 Runtime + Compiler 的 Vue.js,它的入口是src/platforms/web/entry-runtime-with-compiler.js
,下面我们来一探究竟。
从入口开始
// 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
if (template) {
// 省略
} else if (el) {
template = getOuterHTML(el) // 返回 "<div id=\"app\">\n {{ msg }}\n </div>"
}
if (template) {
// 省略
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
// 省略
}
}
return mount.call(this, el, hydrating)
}
可以看出这段逻辑,编译的入口就在这里:
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
一探究竟
上文中 compileToFunctions
方法被定义在 src/platforms/web/compiler/index.js
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
compileToFunctions
实际上是 createCompiler
返回值,它被定义在 src/compiler/index.js
export const createCompiler = createCompilerCreator(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
}
})
而真正的编译都是在 baseCompile
方法中执行的
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
}
}
createCompilerCreator
方法被定义在 src/compiler/create-compiler.js
,该方法返回的是一个 createCompiler
函数,最终返回一个对象,包含 compile
和 compileToFunctions
属性。而 compileToFunctions
实际是入口 $mount
函数中调用的 compileToFunctions
方法。
export function createCompilerCreator (baseCompile: Function): Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
const finalOptions = Object.create(baseOptions)
// 省略
const compiled = baseCompile(template, finalOptions) // baseCompile 方法传入
// 省略
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile) // compile 方法传入
}
}
}
之后我们再来查看 createCompileToFunctionFn
方法,它被定义在 src/compiler/to-function.js
export function createCompileToFunctionFn (compile: Function): Function {
const cache = Object.create(null)
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
options = extend({}, options)
// 省略
// 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
的最终定义,它接收三个参数:编译模板 template
,编译配置 options
和 Vue 实例 vm
。核心的编译过程就一行代码:
const compiled = compile(template, options)
compile
函数实际执行 createCompileToFunctionFn
时作为参数传入,它被定义在 createCompiler
函数中
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
const finalOptions = Object.create(baseOptions)
// 省略
const compiled = baseCompile(template, finalOptions)
// 省略
return compiled
}
而 compile
函数真正执行编译的是 baseCompile
方法,它在 createCompilerCreator
函数执行时作为参数传入
export const createCompiler = createCompilerCreator(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
}
})
最后,我们要找的编译入口就是执行了 baseCompile
函数,它主要做了三件事:
- 解析模板字符串生成AST语法树,
parse
函数目的是将template
模板字符串转换成AST
语法树,整个解析过程是利用正则表达式对模板的解析。
const ast = parse(template.trim(), options)
- 优化语法树,
optimize
过程是深度遍历AST
树,去检测它的每一个子树是否为静态节点,如果是,则它们生成DOM
永远不需要改变。
optimize(ast, options)
- 生成代码,
generate
过程是将AST
树转换为code
const code = generate(ast, options)
AST语法树结构
// template 模板
<div id="app">{{ msg }}</div>
// template 转换 AST
{
attrs: [
{
name: "id",
value: "\"app\""
}
],
attrsList: [
{
name: 'id',
value: 'app'
}
],
attrsMap: { id: 'app' },
children: [
{
expression: "\"\\n \"+_s(msg)+\"\\n \"",
static: false,
text: "\n {{ msg }}\n ",
tokens: [
"\n ",
{@binding: 'msg'},
"\n "
],
type: 2
}
],
parent: undefined,
plain: false,
static: false,
staticRoot: false,
tag: 'div',
type: 1
}
总结
Vue 编译实际是执行了 baseCompile
函数,它主要做了三件事:解析模板字符串生成 AST
树、优化语法树、生成代码。而核心的 parse
解析过程是通过一系列的正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造 AST
树的目的。