From d3d4fe84cdc3f93838acb5d657d60672182a2f18 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 11 Dec 2019 09:46:42 -0500 Subject: [PATCH] fix(vue): properly cache runtime compilation --- packages/compiler-sfc/src/index.ts | 7 ++++- packages/runtime-core/src/apiOptions.ts | 2 +- packages/vue/__tests__/index.spec.ts | 39 ++++++++++++++++++++++- packages/vue/src/index.ts | 42 ++++++++++++++----------- 4 files changed, 68 insertions(+), 22 deletions(-) diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index 7c8918be..984fe76a 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -12,5 +12,10 @@ export { SFCScriptBlock, SFCStyleBlock } from './parse' - +export { + TemplateCompiler, + TemplateCompileOptions, + TemplateCompileResults +} from './compileTemplate' export { StyleCompileOptions, StyleCompileResults } from './compileStyle' +export { CompilerOptions } from '@vue/compiler-core' diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/apiOptions.ts index a3aad5df..80e15884 100644 --- a/packages/runtime-core/src/apiOptions.ts +++ b/packages/runtime-core/src/apiOptions.ts @@ -55,7 +55,7 @@ export interface ComponentOptionsBase< ctx: SetupContext ) => RawBindings | RenderFunction | void name?: string - template?: string + template?: string | object // can be a direct DOM node // Note: we are intentionally using the signature-less `Function` type here // since any type with signature will cause the whole inference to fail when // the return expression contains reference to `this`. diff --git a/packages/vue/__tests__/index.spec.ts b/packages/vue/__tests__/index.spec.ts index 58fad597..a8d57b38 100644 --- a/packages/vue/__tests__/index.spec.ts +++ b/packages/vue/__tests__/index.spec.ts @@ -4,7 +4,7 @@ import { mockWarn } from '@vue/runtime-test' describe('compiler + runtime integration', () => { mockWarn() - it('should support on-the-fly template compilation', () => { + it('should support runtime template compilation', () => { const container = document.createElement('div') const App = { template: `{{ count }}`, @@ -18,6 +18,43 @@ describe('compiler + runtime integration', () => { expect(container.innerHTML).toBe(`0`) }) + it('should support runtime template via CSS ID selector', () => { + const container = document.createElement('div') + const template = document.createElement('div') + template.id = 'template' + template.innerHTML = '{{ count }}' + document.body.appendChild(template) + + const App = { + template: `#template`, + data() { + return { + count: 0 + } + } + } + createApp().mount(App, container) + expect(container.innerHTML).toBe(`0`) + }) + + it('should support runtime template via direct DOM node', () => { + const container = document.createElement('div') + const template = document.createElement('div') + template.id = 'template' + template.innerHTML = '{{ count }}' + + const App = { + template, + data() { + return { + count: 0 + } + } + } + createApp().mount(App, container) + expect(container.innerHTML).toBe(`0`) + }) + it('should warn template compilation errors with codeframe', () => { const container = document.createElement('div') const App = { diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 1bdf58c2..17b77dfd 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -5,32 +5,36 @@ import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom' import * as runtimeDom from '@vue/runtime-dom' import { isString, NOOP } from '@vue/shared' -const idToTemplateCache = Object.create(null) +const compileCache: Record = Object.create(null) function compileToFunction( template: string | HTMLElement, options?: CompilerOptions ): RenderFunction { - if (isString(template)) { - if (template[0] === '#') { - if (template in idToTemplateCache) { - template = idToTemplateCache[template] - } else { - const el = document.querySelector(template) - if (__DEV__ && !el) { - warn(`Template element not found or is empty: ${template}`) - } - template = idToTemplateCache[template] = el ? el.innerHTML : `` - } + if (!isString(template)) { + if (template.nodeType) { + template = template.innerHTML + } else { + __DEV__ && warn(`invalid template option: `, template) + return NOOP } - } else if (template.nodeType) { - template = template.innerHTML - } else { - __DEV__ && warn(`invalid template option: `, template) - return NOOP } - const { code } = compile(template as string, { + const key = template + const cached = compileCache[key] + if (cached) { + return cached + } + + if (template[0] === '#') { + const el = document.querySelector(template) + if (__DEV__ && !el) { + warn(`Template element not found or is empty: ${template}`) + } + template = el ? el.innerHTML : `` + } + + const { code } = compile(template, { hoistStatic: true, cacheHandlers: true, ...options @@ -38,7 +42,7 @@ function compileToFunction( const render = new Function('Vue', code)(runtimeDom) as RenderFunction render.isRuntimeCompiled = true - return render + return (compileCache[key] = render) } registerRuntimeCompiler(compileToFunction)