简述编译流程
总的来说,在beforeMount之前
执行编译过程,第一步通过html-parser将template解析成ast抽象语法树
,第二步通过optimize优化
ast并标记静态节点和静态根节点,第三步通过generate将ast抽象语法树编译成render字符串
并将静态部分放到staticRenderFns中,最后通过new Function(render)生成render函数。在beforeMount和mounted之间执行render函数
生成VNode,然后通过patch(VNode)生成dom树并挂载,调用mounted。
编译入口
//entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function ( //挂载前先做一些预处理
el?: string | Element,
hydrating?: boolean
): Component {
const {
render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
return mount.call(this, el, hydrating)
}
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
callHook(vm, 'beforeMount')
let updateComponent = () => {
vm._update(vm._render(), hydrating)//调用render生成VNode,然后patch渲染到页面
}
vm._watcher = new Watcher(vm, updateComponent, noop)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
由此可知,在beforeMount之前执行编译过程,然后调用beforeMount,然后执行render函数,然后调用mounted
编译过程
//src/compiler/index.js
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options) //生成抽象语法树
optimize(ast, options) //优化抽象语法树,标记静态节点和静态根节点
const code = generate(ast, options) //生成render函数字符串
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns //静态无需重新渲染的部分
}
})
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)
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
export function createCompileToFunctionFn (compile: Function): Function {
const cache: {
[key: string]: CompiledFunctionResult;
} = Object.create(null)
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
options = extend({
}, options)
const warn = options.warn || baseWarn
delete options.warn
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
const compiled = compile(template, options)
const res = {
}
const fnGenErrors = []
res.render = createFunction(compiled.render, fnGenErrors) // 将render字符串转换成函数
res.staticRenderFns = compiled.staticRenderFns.map(code => {
return createFunction(code, fnGenErrors) //将静态render字符串转换成函数
})
return (cache[key] = res) //缓存结果,当template不变时直接返回缓存
}
}
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({
err, code })
return noop
}
}
由此可知,编译过程为:第一步通过html-parser将template解析成ast抽象语法树
,第二步通过optimize优化
ast并标记静态节点和静态根节点,第三步通过generate将ast抽象语法树编译成render字符串
并将静态部分放到staticRenderFns中,最后通过new Function(render)生成render函数。
将template解析成ast抽象语法树
src/compiler/index.js
src/compiler/parser/html-parser.js
export function parse (
template: string,
options: CompilerOptions
): ASTElement | void {
const stack = []
const preserveWhitespace = options.preserveWhitespace !== false
let root
let currentParent
let inVPre = false
let inPre = false
let warned = false
parseHTML(template, {
warn,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldKeepComment: options.comments,
start (tag, attrs, unary) {
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs)
}
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true
process.env.NODE_ENV !== 'production' && warn(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
`<${
tag}>` + ', as they will not be parsed.'
)
}
// apply pre-transforms
for (let i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element
}
if (!inVPre) {
processPre(element)
if (element.pre) {
inVPre = true
}
}
if (platformIsPreTag(element.tag