# Vue 编译器compiler
前面的 vue
初始化已经基本了解,再来了解下 vue
的compiler
,在src/platforms/web/entry-runtime-with-compiler.js
中的$mount
中
//第一个参数就是模板字符串,compileToFunctions 函数会把模板字符串编译为渲染函数
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
//渲染函数 render 就是通过 compileToFunctions 函数生成的
options.render = render
options.staticRenderFns = staticRenderFns
# compileToFunctions
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)//使用 parse 函数将模板解析为 AST
if (options.optimize !== false) {
//调用 optimize 函数优化 ast
optimize(ast, options)
}
//generate 函数将 ast 编译成渲染函数
const code = generate(ast, options)
return {
ast,
render: code.render,//都以字符串的形式存在,因为真正变成函数的过程是在 compileToFunctions 中使用 new Function() 来完成的
staticRenderFns: code.staticRenderFns //静态渲染函数
}
})
//`createCompilerCreator` 函数创建出针对于不同平台的编译器
export function createCompilerCreator (baseCompile: Function): Function {
//返回createCompiler函数
return function createCompiler (baseOptions: CompilerOptions) {
//定义compile
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
//省略
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
//思路 createCompilerCreator=>createCompiler=>render
compileToFunctions
是 createCompileToFunctionFn
的返回值
export function createCompileToFunctionFn (compile: Function): Function {
const cache = Object.create(null)
//传入的模板字符串(template)编译成渲染函数(render)
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
//extend 函数将 options 的属性混合到新的对象中并重新赋值 options
options = extend({}, options)
//检查选项参数中是否包含 warn,如果没有则使用 baseWarn
const warn = options.warn || baseWarn
//delete warn
delete options.warn
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production') {
// detect possible CSP restriction
//new Function() 是否可用
try {
new Function('return 1')
} catch (e) {
//CSP 全称是内容安全策略,如果你的策略比较严格,那么 new Function() 将会受到影响,从而不能够使用
if (e.toString().match(/unsafe-eval|CSP/)) {
warn('something' )
}
}
}
// check cache
const key = options.delimiters
? String(options.delimiters) + template
: template
//判断 cache[key] 是否存在,如果存在直接返回 cache[key]。这么做的目的是缓存字符串模板的编译结果,防止重复编译,提升性能
//cache=Object.create(null) 空对象
if (cache[key]) {
return cache[key]
}
// compile 核心代码 真正的编译工作是依托于 compile 函数
const compiled = compile(template, options)
// check compilation errors/tips 检查编译错误和提示
if (process.env.NODE_ENV !== 'production') {
if (compiled.errors && compiled.errors.length) {
warn(
`Error compiling template:\n\n${template}\n\n` +
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
vm
)
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(msg => tip(msg, vm))
}
}
// turn code into functions
const res = {}
const fnGenErrors = []
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}
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
)
}
}
return (cache[key] = res) //返回结果的同时将结果缓存
}
}
# compile函数
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
//baseOptions 来自于 src/platforms/web/compiler/options.js
const isUnaryTag = makeMap(
'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +
'link,meta,param,source,track,wbr'
)
const canBeLeftOpenTag = makeMap(
'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source'
)
const baseOptions: CompilerOptions = {
expectHTML: true,
modules,//
directives,
isPreTag, //函数检测 签名字检查标签是否是 'pre' 标签
isUnaryTag, //检测给定的标签是否是一元标签
mustUseProp, //检测一个属性在标签中是否要使用 props 进行绑定
canBeLeftOpenTag, //不是一元标签,但却可以自己补全并闭合的标签,如<p>Some content,且浏览器会自动补全
isReservedTag, //是否是保留的标签
getTagNamespace, //获取元素(标签)的命名空间
staticKeys: genStaticKeys(modules) //编译器选项的 modules 选项生成一个静态键字符串
}
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
finalOptions.warn = (msg, tip) => {//tip用来标示 msg 是错误还是提示
(tip ? tips : errors).push(msg)
}
if (options) {
// merge custom modules 将 options 对象混合到 finalOptions
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.directives) {//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] //复制其他属性
}
}
}
// baseCompile 编译模板
const compiled = baseCompile(template, finalOptions)
if (process.env.NODE_ENV !== 'production') {
errors.push.apply(errors, detectErrors(compiled.ast))//通过抽象语法树来检查模板中是否存在错误表达式的
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
// modules
export default [
klass,
style,
model
]
// klass.js 的输出
export default {
staticKeys: ['staticClass'],
transformNode,
genData
}
// style.js 的输出
export default {
staticKeys: ['staticStyle'],
transformNode,
genData
}
// model.js 的输出
export default {
preTransformNode
}
//directives
export default {
model,
text,
html
}
// model.js 的输出
export default function model (
el: ASTElement,
dir: ASTDirective,
_warn: Function
): ?boolean {
// 函数体...
}
// html.js 的输出
export default function html (el: ASTElement, dir: ASTDirective) {
if (dir.value) {
addProp(el, 'innerHTML', `_s(${dir.value})`)
}
}
// text.js 的输出
export default function text (el: ASTElement, dir: ASTDirective) {
if (dir.value) {
addProp(el, 'textContent', `_s(${dir.value})`)
}
}
# Vue 的parser
parser,将字符串模板解析为抽象语法树(AST)
编译器就是将 源代码
转换成 目标代码
的工具,parser 是编译器处理源代码的第一步,大致也分为三个阶段,即:词法分析 -> 句法分析 -> 代码生成,在词法分析阶段 Vue 会把字符串模板解析成一个个的令牌(token),该令牌将用于句法分析阶段,在句法分析阶段会根据令牌生成一棵 AST,最后再根据该 AST 生成最终的渲染函数
← 其他选项的初始化及实现 新华字典库 →