Vue 模板编译-过程
编译的入口文件是 src/compiler/create-compiler.js 中的 createCompilerCreator 生成的方法 complieToFunctions 。
下面就简单介绍一下编译的过程吧。

用于生成 render 函数的方法 compileToFunctions 是通过调用方法 createCompileToFunctionFn 生成的。
export function createCompileToFunctionFn (compile: Function): Function {
// 定义 cache 通过闭包缓存编译后的结果
const cache = Object.create(null)
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
// 防止污染 Vue 中的 options clone 一份
options = extend({}, options)
const warn = options.warn || baseWarn
delete options.warn
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production') {
// detect possible CSP restriction
try {
new Function('return 1')
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
)
}
}
}
// check cache
// 1. 读取缓存中的 CompiledFunctionResult 对象,如果有直接返回
// options.delimiters 是插值表达式使用的符号,默认是花括号,可以自定义
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// compile
// 2. 把模板编译成编译对象(render, staticRenderFns),字符串形式的 js 代码
const compiled = compile(template, options)
// check compilation errors/tips
if (process.env.NODE_ENV !== 'production') {
if (compiled.errors && compiled.errors.length) {
if (options.outputSourceRange) {
compiled.errors.forEach(e => {
warn(
`Error compiling template:\n\n${e.msg}\n\n` +
generateCodeFrame(template, e.start, e.end),
vm
)
})
} else {
warn(
`Error compiling template:\n\n${template}\n\n` +
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
vm
)
}
}
if (compiled.tips && compiled.tips.length) {
if (options.outputSourceRange) {
compiled.tips.forEach(e => tip(e.msg, vm))
} else {
compiled.tips.forEach(msg => tip(msg, vm))
}
}
}
// turn code into functions
const res = {}
const fnGenErrors = []
// 3. 把字符串形式的js代码转换成js方法(通过 new Function)
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
)
}
}
// 4. 缓存并返回res对象(render,staticRenderFns方法)
return (cache[key] = res)
}
}
这个函数的核心就是先去找缓存中编译的结果,如果有的话直接返回,没有开始编译,并且把字符串形式的js代码转换成函数的形式,最后缓存并且返回。
字符串格式的js代码是由 createFunction 通过 new Function 生成的。
这里的核心就是字符串格式的js代码的生成过程。它是由 compile 方法生成的。
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
let warn = (msg, range, tip) => {
(tip ? tips : errors).push(msg)
}
if (options) {
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
// $flow-disable-line
const leadingSpaceLength = template.match(/^\s*/)[0].length
warn = (msg, range, tip) => {
const data: WarningMessage = { msg }
if (range) {
if (range.start != null) {
data.start = range.start + leadingSpaceLength
}
if (range.end != null) {
data.end = range.end + leadingSpaceLength
}
}
(tip ? tips : errors).push(data)
}
}
// merge custom modules
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.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]
}
}
}
finalOptions.warn = warn
// 通过 baseCompile 把模板编译成 render 函数
// 返回的是一个对象,对象中有两个成员 render 和 staticRenderFns
// render 中存放的是字符串形式的 js 代码
const compiled = baseCompile(template.trim(), finalOptions)
if (process.env.NODE_ENV !== 'production') {
detectErrors(compiled.ast, warn)
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
compie 的核心作用就是合并选项调用 baseCompile 进行编译,最后记录错误并返回编译好的对象。
接着介绍 baseCompile 中编译模板的过程,它是模板编译的核心函数。
baseCompile 里面就只有三个步骤
- 生成抽象语法树
- 优化抽象语法树
- 把优化好的AST对象转换成字符串形式的js代码
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
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
}
})
-
什么是抽象语法树(AST)
- 抽象语法树简称AST(Abstract Syntax Tree)
- 使用对象的形式描述树形的代码结构
- 此处的抽象语法树是用来描述树形结构的HTML字符串
-
为什么要使用抽象语法树
- 模板字符串转换成AST后,可以通过AST对模板做优化处理
- 标记模板中的静态内容,在 patch 的时候直接跳过静态内容
- 在 patch 的过程中静态内容不需要对比和重新渲染
使用 AST explorer 可以生成各种模板生成的 AST ,非常方便了解 AST 。
https://astexplorer.net/
在 baseCompile 中生成 AST 是由 parse 函数生成的。
parse 函数接受两个参数一个是模板字符串,一个是合并后的选项。返回 AST。
- 首先去解析 options 中的成员
- parseHTML 解析 HTML 模板,生成 AST(这个函数 Vue 借鉴了一个开源库 simplehtmlparser.js)
- start(解析开始标签)
- end(解析结束标签)
- chars(解析文本内容)
- comment(解析注释标签)
生成 AST 后会调用 optimize 方法对 AST 进行优化。
优化的目的是为了标记抽象语法树中的静态节点,这些节点不会重新渲染,在 patch 中直接跳过这些节点。
optimize 将标记静态节点的过程分为了两步
- 标记静态节点(调用 markStatic)
- 标记静态根节点(调用 markStaticRoots)
AST对象优化完成之后,接下来是 baseCompile 的最后一步,将优化好的 AST 对象通过 generate 转换成字符串形式的js代码。
generate 接收两个参数一个是优化好的 AST 对象,另一个是选项对象,但会字符串js代码。
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
1696

被折叠的 条评论
为什么被折叠?



