]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-dom): fix static node content caching edge cases
authorEvan You <yyx990803@gmail.com>
Thu, 1 Jul 2021 23:17:07 +0000 (19:17 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 1 Jul 2021 23:19:53 +0000 (19:19 -0400)
reverts fded1e8

fix #4023, #4031, #4037

packages/runtime-core/src/hydration.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts
packages/runtime-dom/__tests__/nodeOps.spec.ts
packages/runtime-dom/src/nodeOps.ts

index 5f1ed828b2cedfaaa4b8a1877e8cac96fd35e421..50274fcb489b5ffd80aec037b4596a9174134522 100644 (file)
@@ -134,10 +134,10 @@ export function createHydrationFunctions(
           // if the static vnode has its content stripped during build,
           // adopt it from the server-rendered HTML.
           const needToAdoptContent = !(vnode.children as string).length
-          for (let i = 0; i < vnode.staticCount; i++) {
+          for (let i = 0; i < vnode.staticCount!; i++) {
             if (needToAdoptContent)
               vnode.children += (nextNode as Element).outerHTML
-            if (i === vnode.staticCount - 1) {
+            if (i === vnode.staticCount! - 1) {
               vnode.anchor = nextNode
             }
             nextNode = nextSibling(nextNode)!
index d869cadd68e0b3e850c108c9db1fbee5bb8e696d..dd2661ead7193e767878f7312af7fd0b7b71dbce 100644 (file)
@@ -143,7 +143,7 @@ export interface RendererOptions<
     parent: HostElement,
     anchor: HostNode | null,
     isSVG: boolean,
-    cached?: [HostNode, HostNode | null] | null
+    cached?: HostNode[] | null
   ): HostElement[]
 }
 
@@ -633,7 +633,7 @@ function baseCreateRenderer(
   ) => {
     // static nodes are only present when used with compiler-dom/runtime-dom
     // which guarantees presence of hostInsertStaticContent.
-    ;[n2.el, n2.anchor] = hostInsertStaticContent!(
+    const nodes = hostInsertStaticContent!(
       n2.children as string,
       container,
       anchor,
@@ -641,8 +641,14 @@ function baseCreateRenderer(
       // pass cached nodes if the static node is being mounted multiple times
       // so that runtime-dom can simply cloneNode() instead of inserting new
       // HTML
-      n2.el && [n2.el, n2.anchor]
+      n2.staticCache
     )
+    // first mount - this is the orignal hoisted vnode. cache nodes.
+    if (!n2.el) {
+      n2.staticCache = nodes
+    }
+    n2.el = nodes[0]
+    n2.anchor = nodes[nodes.length - 1]
   }
 
   /**
@@ -686,16 +692,14 @@ function baseCreateRenderer(
     hostInsert(anchor!, container, nextSibling)
   }
 
-  const removeStaticNode = (vnode: VNode) => {
+  const removeStaticNode = ({ el, anchor }: VNode) => {
     let next
-    let { el, anchor } = vnode
     while (el && el !== anchor) {
       next = hostNextSibling(el)
       hostRemove(el)
       el = next
     }
     hostRemove(anchor!)
-    vnode.el = vnode.anchor = null
   }
 
   const processElement = (
index ec5a8f5dc61a17a487cf150a35aa19d6a1eeaa39..366340d99763249740539a5e7e1d1dce0e408369 100644 (file)
@@ -167,7 +167,8 @@ export interface VNode<
   anchor: HostNode | null // fragment anchor
   target: HostElement | null // teleport target
   targetAnchor: HostNode | null // teleport target anchor
-  staticCount: number // number of elements contained in a static vnode
+  staticCount?: number // number of elements contained in a static vnode
+  staticCache?: HostNode[] // cache of parsed static nodes for faster repeated insertions
 
   // suspense
   suspense: SuspenseBoundary | null
@@ -439,7 +440,6 @@ function _createVNode(
     anchor: null,
     target: null,
     targetAnchor: null,
-    staticCount: 0,
     shapeFlag,
     patchFlag,
     dynamicProps,
@@ -521,6 +521,7 @@ export function cloneVNode<T, U>(
     target: vnode.target,
     targetAnchor: vnode.targetAnchor,
     staticCount: vnode.staticCount,
+    staticCache: vnode.staticCache,
     shapeFlag: vnode.shapeFlag,
     // if the vnode is cloned with extra props, we can no longer assume its
     // existing patch flag to be reliable and need to add the FULL_PROPS flag.
index 5e625ff53c4f95a2b2fb4ba5a4bcdf742b3ab71a..aef6564ad8bd61b57457a5e6ca0dbccf82802af7 100644 (file)
@@ -30,15 +30,11 @@ describe('runtime-dom: node-ops', () => {
     test('fresh insertion', () => {
       const content = `<div>one</div><div>two</div>three`
       const parent = document.createElement('div')
-      const [first, last] = nodeOps.insertStaticContent!(
-        content,
-        parent,
-        null,
-        false
-      )
+      const nodes = nodeOps.insertStaticContent!(content, parent, null, false)
       expect(parent.innerHTML).toBe(content)
-      expect(first).toBe(parent.firstChild)
-      expect(last).toBe(parent.lastChild)
+      expect(nodes.length).toBe(3)
+      expect(nodes[0]).toBe(parent.firstChild)
+      expect(nodes[nodes.length - 1]).toBe(parent.lastChild)
     })
 
     test('fresh insertion with anchor', () => {
@@ -47,15 +43,13 @@ describe('runtime-dom: node-ops', () => {
       const parent = document.createElement('div')
       parent.innerHTML = existing
       const anchor = parent.firstChild
-      const [first, last] = nodeOps.insertStaticContent!(
-        content,
-        parent,
-        anchor,
-        false
-      )
+      const nodes = nodeOps.insertStaticContent!(content, parent, anchor, false)
       expect(parent.innerHTML).toBe(content + existing)
-      expect(first).toBe(parent.firstChild)
-      expect(last).toBe(parent.childNodes[parent.childNodes.length - 2])
+      expect(nodes.length).toBe(3)
+      expect(nodes[0]).toBe(parent.firstChild)
+      expect(nodes[nodes.length - 1]).toBe(
+        parent.childNodes[parent.childNodes.length - 2]
+      )
     })
 
     test('fresh insertion as svg', () => {
@@ -97,7 +91,7 @@ describe('runtime-dom: node-ops', () => {
       const content = `<div>one</div><div>two</div>three`
 
       const cacheParent = document.createElement('div')
-      const [cachedFirst, cachedLast] = nodeOps.insertStaticContent!(
+      const nodes = nodeOps.insertStaticContent!(
         content,
         cacheParent,
         null,
@@ -106,20 +100,18 @@ describe('runtime-dom: node-ops', () => {
 
       const parent = document.createElement('div')
 
-      const [first, last] = nodeOps.insertStaticContent!(
+      const clonedNodes = nodeOps.insertStaticContent!(
         ``,
         parent,
         null,
         false,
-        [cachedFirst, cachedLast]
+        nodes
       )
 
       expect(parent.innerHTML).toBe(content)
-      expect(first).toBe(parent.firstChild)
-      expect(last).toBe(parent.lastChild)
-
-      expect(first).not.toBe(cachedFirst)
-      expect(last).not.toBe(cachedLast)
+      expect(clonedNodes[0]).toBe(parent.firstChild)
+      expect(clonedNodes[clonedNodes.length - 1]).toBe(parent.lastChild)
+      expect(clonedNodes[0]).not.toBe(nodes[0])
     })
   })
 })
index 087725aae3a48b62cda81a988b2c198400e34a49..ceccf13b8b6522746f0e71de14c01d3e9ee6f8d7 100644 (file)
@@ -73,17 +73,15 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
   // As long as the user only uses trusted templates, this is safe.
   insertStaticContent(content, parent, anchor, isSVG, cached) {
     if (cached) {
-      let [cachedFirst, cachedLast] = cached
-      let first, last
-      while (true) {
-        let node = cachedFirst.cloneNode(true)
-        if (!first) first = node
+      let first
+      let last
+      let i = 0
+      let l = cached.length
+      for (; i < l; i++) {
+        const node = cached[i].cloneNode(true)
+        if (i === 0) first = node
+        if (i === l - 1) last = node
         parent.insertBefore(node, anchor)
-        if (cachedFirst === cachedLast) {
-          last = node
-          break
-        }
-        cachedFirst = cachedFirst.nextSibling!
       }
       return [first, last] as any
     }
@@ -111,11 +109,14 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
     } else {
       parent.insertAdjacentHTML('beforeend', content)
     }
-    return [
-      // first
-      before ? before.nextSibling : parent.firstChild,
-      // last
-      anchor ? anchor.previousSibling : parent.lastChild
-    ]
+    let first = before ? before.nextSibling : parent.firstChild
+    const last = anchor ? anchor.previousSibling : parent.lastChild
+    const ret = []
+    while (first) {
+      ret.push(first)
+      if (first === last) break
+      first = first.nextSibling
+    }
+    return ret
   }
 }