vue template render模板编译
qb 人气:0正文
在vue的渲染过程中,渲染核心逻辑是vm._update(vm._render(), hydrating),通过vm._render的执行获取到vNode,再通过vm._update的执行来将vNode渲染成真实视图。
其中,render函数的来源是:
(1)用户手写;
(2)通过vue-loader引入的时候进行转换;
(3)通过compileToFunctions将template进行处理产生。
开发过程中主要以template的方式进行代码的编写,这里主要梳理compileToFunctions的方法。
1、template:模板获取
在src/platforms/web/entry-runtime-with-compiler.js中在vue的原型上定义了$mount:
const idToTemplate = cached(id => { const el = query(id) return el && el.innerHTML }) const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) } /** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */ function getOuterHTML (el: Element): string { if (el.outerHTML) { return el.outerHTML } else { const container = document.createElement('div') container.appendChild(el.cloneNode(true)) return container.innerHTML } }
在没有编写render时候才会去获取template并进行编译,获取方式有
- 在传入的参数中获取options.template
- 如果是真实节点则获取其innerHTML
- 以上都不满足则通过或者el.outerhTML的方式获取
获取到template后通过compileToFunctions的方式进行编译,这里是编译的入口。
2、createCompiler:核心参数
在src/platforms/web/compile/index.js中调用了createCompiler:
import { baseOptions } from './options' import { createCompiler } from 'compiler/index' const { compile, compileToFunctions } = createCompiler(baseOptions) export { compile, compileToFunctions }
这里将baseOptions作为基础参数传入,在src/complile/index.js中定义了createCompiler:
import { parse } from './parser/index' import { optimize } from './optimizer' import { generate } from './codegen/index' import { createCompilerCreator } from './create-compiler' export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })
这里定义了baseCompile核心参数,主要目的是进行ast的获取、ast的优化和code的拼接。并将baseCompile作为参数传入执行了createCompilerCreator:
export function createCompilerCreator (baseCompile: Function): Function { return function createCompiler (baseOptions: CompilerOptions) { function compile ( template: string, options?: CompilerOptions ): CompiledResult { // ... } return { compile, compileToFunctions: createCompileToFunctionFn(compile) } } }
createCompiler就是createCompilerCreator的返回函数,createCompiler中返回了compile和compileToFunctions,这里的compileToFunctions就是入口获取render的函数,由createCompileToFunctionFn(compile)执行获得。
再看createCompileToFunctionFn(compile):
3、createCompileToFunctionFn:缓存处理
function createFunction (code, errors) { try { return new Function(code) } catch (err) { errors.push({ err, code }) return noop } } 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 */ 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 const compiled = compile(template, options) // ... // turn code into functions 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:\n\n` + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n$[code]\n`).join('\n'), vm ) } } return (cache[key] = res) } }
这里通过const cache = Object.create(null)的方式定义了缓存,返回的compileToFunctions函数中执行return (cache[key] = res),通过闭包的方式进行了计算的重复利用。
如果当前环境支持new Function('return 1')则调用了createFunction将compiled.render通过new Function(code)进行可执行代码的转换,否则进行提示(放宽环境执行或预编译当前模板)。
再看const compiled = compile(template, options):
4、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 const compiled = baseCompile(template.trim(), finalOptions) if (process.env.NODE_ENV !== 'production') { detectErrors(compiled.ast, warn) } compiled.errors = errors compiled.tips = tips return compiled }
这里的参数就是入口处获取到的template,options就是入口处传入的用户参数
{ outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }
然后进行平台参数和用户参数的合并。最后将合并后的参数传入并执行
const compiled = baseCompile(template.trim(), finalOptions)
小结
vue模板编译的真实入口是baseCompile,但是从Vue.prototype.$mount中的compileToFunctions方法开始进行了大量函数的嵌套,主要目的是通过闭包的方式进行缓存处理和平台参数与用户参数的合并。
加载全部内容