Skip to content

Commit ed9eb62

Browse files
committed
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.
1 parent f4f0966 commit ed9eb62

File tree

3 files changed

+42
-11
lines changed

3 files changed

+42
-11
lines changed

packages/runtime-core/src/renderer.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ export interface RendererOptions<
124124
content: string,
125125
parent: HostElement,
126126
anchor: HostNode | null,
127-
isSVG: boolean
127+
isSVG: boolean,
128+
start?: HostNode | null,
129+
end?: HostNode | null
128130
): [HostNode, HostNode]
129131
}
130132

@@ -511,7 +513,9 @@ function baseCreateRenderer(
511513
n2.children as string,
512514
container,
513515
anchor,
514-
isSVG
516+
isSVG,
517+
n2.el,
518+
n2.anchor
515519
)
516520
}
517521

packages/runtime-dom/__tests__/nodeOps.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,28 @@ describe('runtime-dom: node-ops', () => {
8282
expect((first as Element).namespaceURI).toMatch('svg')
8383
expect((last as Element).namespaceURI).toMatch('svg')
8484
})
85+
86+
test('cached insertion', () => {
87+
const content = `<div>one</div><div>two</div>three`
88+
const existing = `<div>existing</div>`
89+
const parent = document.createElement('div')
90+
parent.innerHTML = existing
91+
const anchor = parent.firstChild
92+
93+
const cached = document.createElement('div')
94+
cached.innerHTML = content
95+
96+
const nodes = nodeOps.insertStaticContent!(
97+
content,
98+
parent,
99+
anchor,
100+
false,
101+
cached.firstChild,
102+
cached.lastChild
103+
)
104+
expect(parent.innerHTML).toBe(content + existing)
105+
expect(nodes[0]).toBe(parent.firstChild)
106+
expect(nodes[1]).toBe(parent.childNodes[parent.childNodes.length - 2])
107+
})
85108
})
86109
})

packages/runtime-dom/src/nodeOps.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export const svgNS = 'http://www.w3.org/2000/svg'
44

55
const doc = (typeof document !== 'undefined' ? document : null) as Document
66

7-
const staticTemplateCache = new Map<string, DocumentFragment>()
7+
const templateContainer = doc && doc.createElement('template')
88

99
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
1010
insert: (child, parent, anchor) => {
@@ -73,14 +73,19 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
7373
// Reason: innerHTML.
7474
// Static content here can only come from compiled templates.
7575
// As long as the user only uses trusted templates, this is safe.
76-
insertStaticContent(content, parent, anchor, isSVG) {
76+
insertStaticContent(content, parent, anchor, isSVG, start, end) {
7777
// <parent> before | first ... last | anchor </parent>
7878
const before = anchor ? anchor.previousSibling : parent.lastChild
79-
let template = staticTemplateCache.get(content)
80-
if (!template) {
81-
const t = doc.createElement('template')
82-
t.innerHTML = isSVG ? `<svg>${content}</svg>` : content
83-
template = t.content
79+
if (start && end) {
80+
// cached
81+
while (true) {
82+
parent.insertBefore(start!.cloneNode(true), anchor)
83+
if (start === end || !(start = start!.nextSibling)) break
84+
}
85+
} else {
86+
// fresh insert
87+
templateContainer.innerHTML = isSVG ? `<svg>${content}</svg>` : content
88+
const template = templateContainer.content
8489
if (isSVG) {
8590
// remove outer svg wrapper
8691
const wrapper = template.firstChild!
@@ -89,9 +94,8 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
8994
}
9095
template.removeChild(wrapper)
9196
}
92-
staticTemplateCache.set(content, template)
97+
parent.insertBefore(template, anchor)
9398
}
94-
parent.insertBefore(template.cloneNode(true), anchor)
9599
return [
96100
// first
97101
before ? before.nextSibling! : parent.firstChild!,

0 commit comments

Comments
 (0)