Skip to content

Commit

Permalink
perf: improve memory usage for static vnodes
Browse files Browse the repository at this point in the history
Use the already mounted nodes as cache instead of separate caching via
template. This reduces memory usage by 30%+ in VitePress.
  • Loading branch information
yyx990803 committed Jan 16, 2022
1 parent f4f0966 commit ed9eb62
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 11 deletions.
8 changes: 6 additions & 2 deletions packages/runtime-core/src/renderer.ts
Expand Up @@ -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]
}

Expand Down Expand Up @@ -511,7 +513,9 @@ function baseCreateRenderer(
n2.children as string,
container,
anchor,
isSVG
isSVG,
n2.el,
n2.anchor
)
}

Expand Down
23 changes: 23 additions & 0 deletions packages/runtime-dom/__tests__/nodeOps.spec.ts
Expand Up @@ -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 = `<div>one</div><div>two</div>three`
const existing = `<div>existing</div>`
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])
})
})
})
22 changes: 13 additions & 9 deletions packages/runtime-dom/src/nodeOps.ts
Expand Up @@ -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<string, DocumentFragment>()
const templateContainer = doc && doc.createElement('template')

export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
insert: (child, parent, anchor) => {
Expand Down Expand Up @@ -73,14 +73,19 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, '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) {
// <parent> before | first ... last | anchor </parent>
const before = anchor ? anchor.previousSibling : parent.lastChild
let template = staticTemplateCache.get(content)
if (!template) {
const t = doc.createElement('template')
t.innerHTML = isSVG ? `<svg>${content}</svg>` : 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 ? `<svg>${content}</svg>` : content
const template = templateContainer.content
if (isSVG) {
// remove outer svg wrapper
const wrapper = template.firstChild!
Expand All @@ -89,9 +94,8 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, '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!,
Expand Down

0 comments on commit ed9eb62

Please sign in to comment.