]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(runtime): support multi-element static vnode in renderer
authorEvan You <yyx990803@gmail.com>
Fri, 15 May 2020 19:12:26 +0000 (15:12 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 15 May 2020 19:12:26 +0000 (15:12 -0400)
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts
packages/runtime-dom/src/nodeOps.ts

index 3050f401b5668645a44bcd2873ce7a9a72b2bf66..a01a1c6633cacc43393d53cb8ca230a2fb43b8c0 100644 (file)
@@ -116,8 +116,7 @@ export interface RendererOptions<
     parent: HostElement,
     anchor: HostNode | null,
     isSVG: boolean
-  ): HostElement
-  setStaticContent?(node: HostElement, content: string): void
+  ): HostElement[]
 }
 
 // Renderer Node can technically be any object in the context of core renderer
@@ -333,8 +332,7 @@ function baseCreateRenderer(
     nextSibling: hostNextSibling,
     setScopeId: hostSetScopeId = NOOP,
     cloneNode: hostCloneNode,
-    insertStaticContent: hostInsertStaticContent,
-    setStaticContent: hostSetStaticContent
+    insertStaticContent: hostInsertStaticContent
   } = options
 
   // Note: functions inside this closure should use `const xxx = () => {}`
@@ -373,11 +371,7 @@ function baseCreateRenderer(
         if (n1 == null) {
           mountStaticNode(n2, container, anchor, isSVG)
         } else if (__DEV__) {
-          // static nodes are only patched during dev for HMR
-          n2.el = n1.el
-          if (n2.children !== n1.children) {
-            hostSetStaticContent!(n2.el!, n2.children as string)
-          }
+          patchStaticNode(n1, n2, container, isSVG)
         }
         break
       case Fragment:
@@ -492,17 +486,83 @@ function baseCreateRenderer(
     isSVG: boolean
   ) => {
     if (n2.el && hostCloneNode !== undefined) {
-      hostInsert(hostCloneNode(n2.el), container, anchor)
+      // static node was already mounted (and reused), or adopted
+      // server-rendered node during hydration (in this case its children can be
+      // stripped by SSR optimizations). Clone the dom nodes instead.
+      let cur: RendererElement | null = n2.el
+      while (cur && cur !== n2.anchor) {
+        hostInsert(hostCloneNode(cur), container, anchor)
+        cur = hostNextSibling(cur)
+      }
+      hostInsert(hostCloneNode(n2.anchor!), container, anchor)
     } else {
       // static nodes are only present when used with compiler-dom/runtime-dom
       // which guarantees presence of hostInsertStaticContent.
-      n2.el = hostInsertStaticContent!(
+      ;[n2.el, n2.anchor] = hostInsertStaticContent!(
+        n2.children as string,
+        container,
+        anchor,
+        isSVG
+      )
+    }
+  }
+
+  /**
+   * Dev / HMR only
+   */
+  const patchStaticNode = (
+    n1: VNode,
+    n2: VNode,
+    container: RendererElement,
+    isSVG: boolean
+  ) => {
+    // static nodes are only patched during dev for HMR
+    if (n2.children !== n1.children) {
+      const anchor = hostNextSibling(n1.anchor!)
+      // remove existing
+      removeStaticNode(n1)
+      // insert new
+      ;[n2.el, n2.anchor] = hostInsertStaticContent!(
         n2.children as string,
         container,
         anchor,
         isSVG
       )
+    } else {
+      n2.el = n1.el
+      n2.anchor = n1.anchor
+    }
+  }
+
+  /**
+   * Dev / HMR only
+   */
+  const moveStaticNode = (
+    vnode: VNode,
+    container: RendererElement,
+    anchor: RendererNode | null
+  ) => {
+    let cur = vnode.el
+    const end = vnode.anchor!
+    while (cur && cur !== end) {
+      const next = hostNextSibling(cur)
+      hostInsert(cur, container, anchor)
+      cur = next
+    }
+    hostInsert(end, container, anchor)
+  }
+
+  /**
+   * Dev / HMR only
+   */
+  const removeStaticNode = (vnode: VNode) => {
+    let cur = vnode.el
+    while (cur && cur !== vnode.anchor) {
+      const next = hostNextSibling(cur)
+      hostRemove(cur)
+      cur = next
     }
+    hostRemove(vnode.anchor!)
   }
 
   const processElement = (
@@ -1456,7 +1516,7 @@ function baseCreateRenderer(
           n1,
           n2,
           container,
-          parentAnchor,
+          null,
           parentComponent,
           parentSuspense,
           isSVG,
@@ -1481,7 +1541,7 @@ function baseCreateRenderer(
           n1,
           n2,
           container,
-          parentAnchor,
+          null,
           parentComponent,
           parentSuspense,
           isSVG,
@@ -1692,6 +1752,12 @@ function baseCreateRenderer(
       return
     }
 
+    // static node move can only happen when force updating HMR
+    if (__DEV__ && type === Static) {
+      moveStaticNode(vnode, container, anchor)
+      return
+    }
+
     // single nodes
     const needTransition =
       moveType !== MoveType.REORDER &&
@@ -1808,6 +1874,11 @@ function baseCreateRenderer(
       return
     }
 
+    if (__DEV__ && type === Static) {
+      removeStaticNode(vnode)
+      return
+    }
+
     const performRemove = () => {
       hostRemove(el!)
       if (transition && !transition.persisted && transition.afterLeave) {
index 108e516a0c6bf87894bd1f2ef73df0a6c42b69f0..7be2a109506e47ca4ceb378ee55054506d989e90 100644 (file)
@@ -127,6 +127,7 @@ export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
   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
 
   // optimization only
   shapeFlag: number
@@ -368,6 +369,7 @@ function _createVNode(
     anchor: null,
     target: null,
     targetAnchor: null,
+    staticCount: 0,
     shapeFlag,
     patchFlag,
     dynamicProps,
@@ -422,6 +424,7 @@ export function cloneVNode<T, U>(
     children: vnode.children,
     target: vnode.target,
     targetAnchor: vnode.targetAnchor,
+    staticCount: vnode.staticCount,
     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 bail out of optimized mode.
@@ -459,8 +462,15 @@ export function createTextVNode(text: string = ' ', flag: number = 0): VNode {
 /**
  * @internal
  */
-export function createStaticVNode(content: string): VNode {
-  return createVNode(Static, null, content)
+export function createStaticVNode(
+  content: string,
+  numberOfNodes: number
+): VNode {
+  // A static vnode can contain multiple stringified elements, and the number
+  // of elements is necessary for hydration.
+  const vnode = createVNode(Static, null, content)
+  vnode.staticCount = numberOfNodes
+  return vnode
 }
 
 /**
index 42e7ee03d25aa37152f393f2aa92cbfd7fda5c91..6d766385ca34dacf852073fb0d3a2f91b208e84c 100644 (file)
@@ -64,17 +64,14 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
         (tempSVGContainer = doc.createElementNS(svgNS, 'svg'))
       : tempContainer || (tempContainer = doc.createElement('div'))
     temp.innerHTML = content
-    const node = temp.children[0]
-    nodeOps.insert(node, parent, anchor)
-    return node
-  }
-}
-
-if (__DEV__) {
-  // __UNSAFE__
-  // Reason: innerHTML.
-  // same as `insertStaticContent`, but this is also dev only (for HMR).
-  nodeOps.setStaticContent = (el, content) => {
-    el.innerHTML = content
+    const first = temp.firstChild as Element
+    let node: Element | null = first
+    let last: Element = node
+    while (node) {
+      last = node
+      nodeOps.insert(node, parent, anchor)
+      node = temp.firstChild as Element
+    }
+    return [first, last]
   }
 }