From ed9eb62e5992bd575d999c4197330d8bad622cfb Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 16 Jan 2022 20:39:55 +0800 Subject: [PATCH] perf: improve memory usage for static vnodes Use the already mounted nodes as cache instead of separate caching via template. This reduces memory usage by 30%+ in VitePress. --- packages/runtime-core/src/renderer.ts | 8 +++++-- .../runtime-dom/__tests__/nodeOps.spec.ts | 23 +++++++++++++++++++ packages/runtime-dom/src/nodeOps.ts | 22 ++++++++++-------- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 04bebdb1f0e..0a215a14b76 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -124,7 +124,9 @@ export interface RendererOptions< content: string, parent: HostElement, anchor: HostNode | null, - isSVG: boolean + isSVG: boolean, + start?: HostNode | null, + end?: HostNode | null ): [HostNode, HostNode] } @@ -511,7 +513,9 @@ function baseCreateRenderer( n2.children as string, container, anchor, - isSVG + isSVG, + n2.el, + n2.anchor ) } diff --git a/packages/runtime-dom/__tests__/nodeOps.spec.ts b/packages/runtime-dom/__tests__/nodeOps.spec.ts index 1944bf0e4f4..dcab9a0a6b9 100644 --- a/packages/runtime-dom/__tests__/nodeOps.spec.ts +++ b/packages/runtime-dom/__tests__/nodeOps.spec.ts @@ -82,5 +82,28 @@ describe('runtime-dom: node-ops', () => { expect((first as Element).namespaceURI).toMatch('svg') expect((last as Element).namespaceURI).toMatch('svg') }) + + test('cached insertion', () => { + const content = `
one
two
three` + const existing = `
existing
` + const parent = document.createElement('div') + parent.innerHTML = existing + const anchor = parent.firstChild + + const cached = document.createElement('div') + cached.innerHTML = content + + const nodes = nodeOps.insertStaticContent!( + content, + parent, + anchor, + false, + cached.firstChild, + cached.lastChild + ) + expect(parent.innerHTML).toBe(content + existing) + expect(nodes[0]).toBe(parent.firstChild) + expect(nodes[1]).toBe(parent.childNodes[parent.childNodes.length - 2]) + }) }) }) diff --git a/packages/runtime-dom/src/nodeOps.ts b/packages/runtime-dom/src/nodeOps.ts index de13d8f19d0..c3a4f4ba642 100644 --- a/packages/runtime-dom/src/nodeOps.ts +++ b/packages/runtime-dom/src/nodeOps.ts @@ -4,7 +4,7 @@ export const svgNS = 'http://www.w3.org/2000/svg' const doc = (typeof document !== 'undefined' ? document : null) as Document -const staticTemplateCache = new Map() +const templateContainer = doc && doc.createElement('template') export const nodeOps: Omit, 'patchProp'> = { insert: (child, parent, anchor) => { @@ -73,14 +73,19 @@ export const nodeOps: Omit, 'patchProp'> = { // Reason: innerHTML. // Static content here can only come from compiled templates. // As long as the user only uses trusted templates, this is safe. - insertStaticContent(content, parent, anchor, isSVG) { + insertStaticContent(content, parent, anchor, isSVG, start, end) { // before | first ... last | anchor const before = anchor ? anchor.previousSibling : parent.lastChild - let template = staticTemplateCache.get(content) - if (!template) { - const t = doc.createElement('template') - t.innerHTML = isSVG ? `${content}` : content - template = t.content + if (start && end) { + // cached + while (true) { + parent.insertBefore(start!.cloneNode(true), anchor) + if (start === end || !(start = start!.nextSibling)) break + } + } else { + // fresh insert + templateContainer.innerHTML = isSVG ? `${content}` : content + const template = templateContainer.content if (isSVG) { // remove outer svg wrapper const wrapper = template.firstChild! @@ -89,9 +94,8 @@ export const nodeOps: Omit, 'patchProp'> = { } template.removeChild(wrapper) } - staticTemplateCache.set(content, template) + parent.insertBefore(template, anchor) } - parent.insertBefore(template.cloneNode(true), anchor) return [ // first before ? before.nextSibling! : parent.firstChild!,