vue2從template到render模板編譯入口詳解
正文
在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方法開始進行瞭大量函數的嵌套,主要目的是通過閉包的方式進行緩存處理和平臺參數與用戶參數的合並。
以上就是vue2從template到render模板編譯入口詳解的詳細內容,更多關於vue template render模板編譯的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- vue原理Compile從新建實例到結束流程源碼
- Vue源碼學習記錄之手寫vm.$mount方法
- Vue編譯器AST抽象語法樹源碼分析
- Vue Computed底層原理深入探究
- Vue3初始化如何調用函數