vue-compile概述

vue

来源 刘涛

Vue的核心可以分为三个大块:数据处理和双向绑定、模板编译、虚拟dom。

前面我们对第一部分的主要内容双向绑定做了一个分析讲解,接下来我们说一说模板编译。

这一部分的内容比较多,也比较复杂。由于所涉及的情况太多了,我也不可能把每一种情况都覆盖到。

尽量做到既不啰嗦,又能分享更多的内容。前面我们也提到过,模板编译分为三个阶段:

生成ast、优化静态内容、生成render

官方文档中提到rendertemplate更加底层,许多人对render函数都不是很明白,

相信通过这一部分的分析,你可以对render基本做到了如指掌。

如果你在创建对象时直接传入render函数,模板编译这一步就可以直接跳过,这样效率肯定更高,

(react就是不用模板编译的, 如果vue比react效率高,那就是因为需要编译jsx?)

但同时我们编写代码的难度会增加很多。实际开发过程中,根据需要,恰当选择。

Vue对象上有一个全局函数compile,在src/entries/web-runtime-with-compiler.js中。

import { compileToFunctions } from 'web/compiler/index'

...

...

...

Vue.compile = compileToFunctions

该方法来自src/platforms/web/compiler/index文件。

import { isUnaryTag, canBeLeftOpenTag } from './util'

import { genStaticKeys } from 'shared/util'

import { createCompiler } from 'compiler/index'

import modules from './modules/index'

import directives from './directives/index'

import {

isPreTag,

mustUseProp,

isReservedTag,

getTagNamespace

} from '../util/index'

export const baseOptions: CompilerOptions = {

expectHTML: true,

modules,

directives,

isPreTag,

isUnaryTag,

mustUseProp,

canBeLeftOpenTag,

isReservedTag,

getTagNamespace,

staticKeys: genStaticKeys(modules)

}

const { compile, compileToFunctions } = createCompiler(baseOptions)

export { compile, compileToFunctions }

该文件中主要定义了一个baseOptions,它主要保存了解析模板时和平台相关的一些配置。

对应的src/platforms/weex/compiler/index中也有一份名称一样的配置。

这里我们简单说说web相关的这些配置都是什么意思。

  • expectHTML。目前具体还不是很明白,weex中没有改项,从字面意思来看,应该是是否期望HTML

  • modules。包括klassstyle,对模板中类和样式的解析。

  • directives。这里包括modelv-model)、htmlv-html)、text(v-text)三个指令。

  • isPreTag。是否是pre标签。

  • isUnaryTag。是否是单标签,比如imginputiframe等。

  • mustUseProp。需要使用props绑定的属性,比如valueselected等。

  • canBeLeftOpenTag。可以不闭合的标签,比如trtd等。

  • isReservedTag。是否是保留标签,html标签和SVG标签。

  • getTagNamespace。获取命名空间,svgmath

  • staticKeys。静态关键词,包括staticClass,staticStyle

上面这些方法或者属性,在编译模板时会用到。

这里知识简单的列出来它们的用途,方便看一眼。

我们来看createCompiler函数的实现。

export function createCompiler (baseOptions: CompilerOptions) {

const functionCompileCache: {

[key: string]: CompiledFunctionResult;

} = Object.create(null)

// compile 函数的实现

// compileToFunctions 函数的实现

return {

compile,

compileToFunctions

}

}

该函数只是compilecompileToFunctions的简单封装,开始定义了functionCompileCache

它用来缓存编译之后的模板,方便之后复用。

因为compileToFunctions里面调用了compile,所以我们先看一下compile

  function compile (

template: string,

options?: CompilerOptions

): CompiledResult {

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),

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

}

我们从上往下,依次看看它都做了哪些事儿。

它接收两个参数templateoptionstemplate不用过多解释,

options在内部主要是用户自己定义的delimiters

finalOptions继承自我们上面提到的baseOptions

并添加了一个搜集错误的warn方法,然后合并了options传入的各种配置选项。

modulesdirectives合并方法不同是因为modules是数组,而directives是一个对象。

baseCompile中执行的就是模板编译的三个重要步骤,后面我们会详细讲解。

最终,返回编译之后的对象。

function compileToFunctions (

template: string,

options?: CompilerOptions,

vm?: Component

): CompiledFunctionResult {

options = options || {}

...

// check cache

const key = options.delimiters

? String(options.delimiters) + template

: template

if (functionCompileCache[key]) {

return functionCompileCache[key]

}

// 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 = []

res.render = makeFunction(compiled.render, fnGenErrors)

const l = compiled.staticRenderFns.length

res.staticRenderFns = new Array(l)
for (let i = 0; i < l; i++) {

res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)

}

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 (functionCompileCache[key] = res)

}

compileToFunctions函数中,首先从缓存中获取编译结果,没有则调用compile函数来编译。

在开发环境,我们在这里会抛出编译过程中产生的错误,

最终返回一个含有render函数,和staticRenderFns数组的对象,并把它放在缓存中。

这里我们使用makeFunction来创建函数。

function makeFunction (code, errors) {

try {

return new Function(code)

} catch (err) {

errors.push({ err, code })

return noop

}

}

很简单,就是利用了我们new Function,并搜集了错误。

compilecompileToFunctions两个方法的不同之处有以下几点。

1、 compile返回的结果中render是字符串,staticRenderFns是字符串组成的数组,而compileToFunctions中把它们变成了函数。

2、 compile返回的结果中,有模板生成的ast和搜集到的错误。而compileToFunctions对其结果进行了一些处理。

以上是 vue-compile概述 的全部内容, 来源链接: utcz.com/z/380188.html

回到顶部