Ver Fonte

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.
Evan You há 4 anos atrás
pai
commit
ed9eb62e59

+ 6 - 2
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
     )
   }
 

+ 23 - 0
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 = `<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])
+    })
   })
 })

+ 13 - 9
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<string, DocumentFragment>()
+const templateContainer = doc && doc.createElement('template')
 
 export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
   insert: (child, parent, anchor) => {
@@ -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!
@@ -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!,