源码分析:Vue编译(compile)流程编译入口解析

供稿:hz-xin.com     日期:2025-01-15
Vue编译(Compile)入口

Vue中的数据渲染到页面的整个流程,我们之前介绍过,不清楚可以点击这里,其中的编译流程,是将我们的template模板编译生成render函数。那为什么需要编译呢,因为template模板中有很多指令(比如@click,v-for等),原生js是识别不了的。接下来我们来分析一下Vue编译的过程:

在我们之前分析的mount过程中有这样一段逻辑,compileToFunctions就是执行编译的函数,将我们定义的template作为参数传入,并生成render函数,下面我们来看下compileToFunctions:

if?(template)?{??......??const?{?render,?staticRenderFns?}?=?compileToFunctions(template,?{????shouldDecodeNewlines,????shouldDecodeNewlinesForHref,????delimiters:?options.delimiters,????comments:?options.comments??},?this)??options.render?=?render??options.staticRenderFns?=?staticRenderFns??......}

compileToFunctions

compileToFunctions定义在src/platform/web/compiler/index.js中:

import?{?baseOptions?}?from?'./options'import?{?createCompiler?}?from?'compiler/index'const?{?compile,?compileToFunctions?}?=?createCompiler(baseOptions)export?{?compile,?compileToFunctions?}

可以看到,compileToFunctions是通过调用createCompiler生成的,参数baseOptions是web平台的一些配置项,继续看createCompiler函数,定义在src/compiler/index.js中:

import?{?parse?}?from?'./parser/index'import?{?optimize?}?from?'./optimizer'import?{?generate?}?from?'./codegen/index'import?{?createCompilerCreator?}?from?'./create-compiler'//?`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)??//?优化ast树??if?(options.optimize?!==?false)?{????optimize(ast,?options)??}??//?生成代码??const?code?=?generate(ast,?options)??return?{????ast,????render:?code.render,????staticRenderFns:?code.staticRenderFns??}})

可以看到这里的createCompiler又是通过createCompilerCreator这个函数去生成的,createCompilerCreator这个函数的参数是一个函数baseCompile,这里面的逻辑是编译过程的核心,包含ast树的生成、ast树的优化、代码生成,这三个流程我们之后再去详细的分析。我们继续看createCompilerCreator这个函数,定义在src/compiler/create-compiler.js:

export?function?createCompilerCreator?(baseCompile:?Function):?Function?{??return?function?createCompiler?(baseOptions:?CompilerOptions)?{????//?这个函数的作用是将一些配置项合并一下,再执行传入的核心编译函数baseCompile????function?compile?(??????template:?string,??????options?:?CompilerOptions????):?CompiledResult?{??????//?web平台的基础配置,利用原型继承??????const?finalOptions?=?Object.create(baseOptions)??????const?errors?=?[]??????const?tips?=?[]??????finalOptions.warn?=?(msg,?tip)?=>?{????????(tip???tips?:?errors).push(msg)??????}??????//?配置合并??????if?(options)?{????????//?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]??????????}????????}??????}??????//?核心编译函数??????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????}????return?{??????compile,??????compileToFunctions:?createCompileToFunctionFn(compile)????}??}}

在这里,我们看到,compileToFunctions最终是调用了createCompileToFunctionFn这个函数,我们继续看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)????const?warn?=?options.warn?||?baseWarn????delete?options.warn????/*?istanbul?ignore?if?*/????//?判断是否支持new?Function语法????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????const?key?=?options.delimiters????????String(options.delimiters)?+?template??????:?template????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:

${template}

`?+??????????compiled.errors.map(e?=>?`-?${e}`).join('
')?+?'
',??????????vm????????)??????}??????if?(compiled.tips?&&?compiled.tips.length)?{????????compiled.tips.forEach(msg?=>?tip(msg,?vm))??????}????}????//?turn?code?into?functions????//?这儿将编译生成的字符串代码转换成render函数,通过createFunction函数????const?res?=?{}????const?fnGenErrors?=?[]????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:

`?+??????????fnGenErrors.map(({?err,?code?})?=>?`${err.toString()}?in

${code}
`).join('
'),??????????vm????????)??????}????}????return?(cache[key]?=?res)??}}

function?createFunction?(code,?errors)?{??try?{????return?new?Function(code)??}?catch?(err)?{????errors.push({?err,?code?})????return?noop??}}

可以看到我们绕了很多圈,最终找到了compileToFunctions这个函数,现在我们来捋一下这个执行流程。

1.进入compileToFunctions函数:执行compileToFunctions这个函数,这个函数首先会验证当前环境是否支持newFunction语法,再利用闭包做了一个编译器缓存,然后执行传入的compile函数。

2.进入compile函数compile函数将平台的配置拿到做了一个赋值,再结合当前传入的options参数,做了一些合并配置的操作,然后会执行传入的baseCompile函数,

3.进入baseCompile函数baseCompile函数是编译的核心函数,这个地方会根据传入的template生成ast树、优化ast树、生成字符串代码,完成编译后,

4.完成baseCompile函数的执行,进入compile函数对编译返回结果compiled对象挂载了errors、tips两个属性,

5.完成compile函数,进入compileToFunctions函数进入compileToFunctions这个函数,检查编译过程有没有错误,再将生成的字符串代码利用newFunction语法,转换成匿名函数。

最终的这个匿名函数就是最终生成的render函数。关于模板编译的结果可以参考这个网址https://template-explorer.vuejs.org/。

到此为止,我们清楚了编译的入口了,下一节我们继续去分析编译的核心流程!

点击去往下一节

原文:https://juejin.cn/post/7094837500497100807