wip(runtime): support multi-element static vnode in renderer
This commit is contained in:
		
							parent
							
								
									cb9444807e
								
							
						
					
					
						commit
						dbf627f136
					
				| @ -116,8 +116,7 @@ export interface RendererOptions< | |||||||
|     parent: HostElement, |     parent: HostElement, | ||||||
|     anchor: HostNode | null, |     anchor: HostNode | null, | ||||||
|     isSVG: boolean |     isSVG: boolean | ||||||
|   ): HostElement |   ): HostElement[] | ||||||
|   setStaticContent?(node: HostElement, content: string): void |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Renderer Node can technically be any object in the context of core renderer
 | // Renderer Node can technically be any object in the context of core renderer
 | ||||||
| @ -333,8 +332,7 @@ function baseCreateRenderer( | |||||||
|     nextSibling: hostNextSibling, |     nextSibling: hostNextSibling, | ||||||
|     setScopeId: hostSetScopeId = NOOP, |     setScopeId: hostSetScopeId = NOOP, | ||||||
|     cloneNode: hostCloneNode, |     cloneNode: hostCloneNode, | ||||||
|     insertStaticContent: hostInsertStaticContent, |     insertStaticContent: hostInsertStaticContent | ||||||
|     setStaticContent: hostSetStaticContent |  | ||||||
|   } = options |   } = options | ||||||
| 
 | 
 | ||||||
|   // Note: functions inside this closure should use `const xxx = () => {}`
 |   // Note: functions inside this closure should use `const xxx = () => {}`
 | ||||||
| @ -373,11 +371,7 @@ function baseCreateRenderer( | |||||||
|         if (n1 == null) { |         if (n1 == null) { | ||||||
|           mountStaticNode(n2, container, anchor, isSVG) |           mountStaticNode(n2, container, anchor, isSVG) | ||||||
|         } else if (__DEV__) { |         } else if (__DEV__) { | ||||||
|           // static nodes are only patched during dev for HMR
 |           patchStaticNode(n1, n2, container, isSVG) | ||||||
|           n2.el = n1.el |  | ||||||
|           if (n2.children !== n1.children) { |  | ||||||
|             hostSetStaticContent!(n2.el!, n2.children as string) |  | ||||||
|           } |  | ||||||
|         } |         } | ||||||
|         break |         break | ||||||
|       case Fragment: |       case Fragment: | ||||||
| @ -492,11 +486,19 @@ function baseCreateRenderer( | |||||||
|     isSVG: boolean |     isSVG: boolean | ||||||
|   ) => { |   ) => { | ||||||
|     if (n2.el && hostCloneNode !== undefined) { |     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 { |     } else { | ||||||
|       // static nodes are only present when used with compiler-dom/runtime-dom
 |       // static nodes are only present when used with compiler-dom/runtime-dom
 | ||||||
|       // which guarantees presence of hostInsertStaticContent.
 |       // which guarantees presence of hostInsertStaticContent.
 | ||||||
|       n2.el = hostInsertStaticContent!( |       ;[n2.el, n2.anchor] = hostInsertStaticContent!( | ||||||
|         n2.children as string, |         n2.children as string, | ||||||
|         container, |         container, | ||||||
|         anchor, |         anchor, | ||||||
| @ -505,6 +507,64 @@ function baseCreateRenderer( | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * 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 = ( |   const processElement = ( | ||||||
|     n1: VNode | null, |     n1: VNode | null, | ||||||
|     n2: VNode, |     n2: VNode, | ||||||
| @ -1456,7 +1516,7 @@ function baseCreateRenderer( | |||||||
|           n1, |           n1, | ||||||
|           n2, |           n2, | ||||||
|           container, |           container, | ||||||
|           parentAnchor, |           null, | ||||||
|           parentComponent, |           parentComponent, | ||||||
|           parentSuspense, |           parentSuspense, | ||||||
|           isSVG, |           isSVG, | ||||||
| @ -1481,7 +1541,7 @@ function baseCreateRenderer( | |||||||
|           n1, |           n1, | ||||||
|           n2, |           n2, | ||||||
|           container, |           container, | ||||||
|           parentAnchor, |           null, | ||||||
|           parentComponent, |           parentComponent, | ||||||
|           parentSuspense, |           parentSuspense, | ||||||
|           isSVG, |           isSVG, | ||||||
| @ -1692,6 +1752,12 @@ function baseCreateRenderer( | |||||||
|       return |       return | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // static node move can only happen when force updating HMR
 | ||||||
|  |     if (__DEV__ && type === Static) { | ||||||
|  |       moveStaticNode(vnode, container, anchor) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // single nodes
 |     // single nodes
 | ||||||
|     const needTransition = |     const needTransition = | ||||||
|       moveType !== MoveType.REORDER && |       moveType !== MoveType.REORDER && | ||||||
| @ -1808,6 +1874,11 @@ function baseCreateRenderer( | |||||||
|       return |       return | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (__DEV__ && type === Static) { | ||||||
|  |       removeStaticNode(vnode) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const performRemove = () => { |     const performRemove = () => { | ||||||
|       hostRemove(el!) |       hostRemove(el!) | ||||||
|       if (transition && !transition.persisted && transition.afterLeave) { |       if (transition && !transition.persisted && transition.afterLeave) { | ||||||
|  | |||||||
| @ -127,6 +127,7 @@ export interface VNode<HostNode = RendererNode, HostElement = RendererElement> { | |||||||
|   anchor: HostNode | null // fragment anchor
 |   anchor: HostNode | null // fragment anchor
 | ||||||
|   target: HostElement | null // teleport target
 |   target: HostElement | null // teleport target
 | ||||||
|   targetAnchor: HostNode | null // teleport target anchor
 |   targetAnchor: HostNode | null // teleport target anchor
 | ||||||
|  |   staticCount: number // number of elements contained in a static vnode
 | ||||||
| 
 | 
 | ||||||
|   // optimization only
 |   // optimization only
 | ||||||
|   shapeFlag: number |   shapeFlag: number | ||||||
| @ -368,6 +369,7 @@ function _createVNode( | |||||||
|     anchor: null, |     anchor: null, | ||||||
|     target: null, |     target: null, | ||||||
|     targetAnchor: null, |     targetAnchor: null, | ||||||
|  |     staticCount: 0, | ||||||
|     shapeFlag, |     shapeFlag, | ||||||
|     patchFlag, |     patchFlag, | ||||||
|     dynamicProps, |     dynamicProps, | ||||||
| @ -422,6 +424,7 @@ export function cloneVNode<T, U>( | |||||||
|     children: vnode.children, |     children: vnode.children, | ||||||
|     target: vnode.target, |     target: vnode.target, | ||||||
|     targetAnchor: vnode.targetAnchor, |     targetAnchor: vnode.targetAnchor, | ||||||
|  |     staticCount: vnode.staticCount, | ||||||
|     shapeFlag: vnode.shapeFlag, |     shapeFlag: vnode.shapeFlag, | ||||||
|     // if the vnode is cloned with extra props, we can no longer assume its
 |     // 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.
 |     // 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 |  * @internal | ||||||
|  */ |  */ | ||||||
| export function createStaticVNode(content: string): VNode { | export function createStaticVNode( | ||||||
|   return createVNode(Static, null, content) |   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 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | |||||||
| @ -64,17 +64,14 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = { | |||||||
|         (tempSVGContainer = doc.createElementNS(svgNS, 'svg')) |         (tempSVGContainer = doc.createElementNS(svgNS, 'svg')) | ||||||
|       : tempContainer || (tempContainer = doc.createElement('div')) |       : tempContainer || (tempContainer = doc.createElement('div')) | ||||||
|     temp.innerHTML = content |     temp.innerHTML = content | ||||||
|     const node = temp.children[0] |     const first = temp.firstChild as Element | ||||||
|     nodeOps.insert(node, parent, anchor) |     let node: Element | null = first | ||||||
|     return node |     let last: Element = node | ||||||
|   } |     while (node) { | ||||||
| } |       last = node | ||||||
| 
 |       nodeOps.insert(node, parent, anchor) | ||||||
| if (__DEV__) { |       node = temp.firstChild as Element | ||||||
|   // __UNSAFE__
 |     } | ||||||
|   // Reason: innerHTML.
 |     return [first, last] | ||||||
|   // same as `insertStaticContent`, but this is also dev only (for HMR).
 |  | ||||||
|   nodeOps.setStaticContent = (el, content) => { |  | ||||||
|     el.innerHTML = content |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user