]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
perf: improve memory usage for static vnodes
authorEvan You <yyx990803@gmail.com>
Sun, 16 Jan 2022 12:39:55 +0000 (20:39 +0800)
committerEvan You <yyx990803@gmail.com>
Sun, 16 Jan 2022 13:10:26 +0000 (21:10 +0800)
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
packages/runtime-dom/__tests__/nodeOps.spec.ts
packages/runtime-dom/src/nodeOps.ts

index 04bebdb1f0e76b216cc471791c945a04aa1385f6..0a215a14b762b6d3bfe218b137ef9f25b6fa60fb 100644 (file)
@@ -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
     )
   }
 
index 1944bf0e4f443d9bae3a77c8d198ead67f570209..dcab9a0a6b9c03b9438d4c4de528378567f8b1ed 100644 (file)
@@ -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])
+    })
   })
 })
index de13d8f19d0c87df831e33b166bb27d2779cb61a..c3a4f4ba64247cb5c0848527a70eb83afc76cabb 100644 (file)
@@ -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!,