Skip to content

Commit dbf627f

Browse files
committed
wip(runtime): support multi-element static vnode in renderer
1 parent cb94448 commit dbf627f

File tree

3 files changed

+105
-27
lines changed

3 files changed

+105
-27
lines changed

packages/runtime-core/src/renderer.ts

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ export interface RendererOptions<
116116
parent: HostElement,
117117
anchor: HostNode | null,
118118
isSVG: boolean
119-
): HostElement
120-
setStaticContent?(node: HostElement, content: string): void
119+
): HostElement[]
121120
}
122121

123122
// Renderer Node can technically be any object in the context of core renderer
@@ -333,8 +332,7 @@ function baseCreateRenderer(
333332
nextSibling: hostNextSibling,
334333
setScopeId: hostSetScopeId = NOOP,
335334
cloneNode: hostCloneNode,
336-
insertStaticContent: hostInsertStaticContent,
337-
setStaticContent: hostSetStaticContent
335+
insertStaticContent: hostInsertStaticContent
338336
} = options
339337

340338
// Note: functions inside this closure should use `const xxx = () => {}`
@@ -373,11 +371,7 @@ function baseCreateRenderer(
373371
if (n1 == null) {
374372
mountStaticNode(n2, container, anchor, isSVG)
375373
} else if (__DEV__) {
376-
// static nodes are only patched during dev for HMR
377-
n2.el = n1.el
378-
if (n2.children !== n1.children) {
379-
hostSetStaticContent!(n2.el!, n2.children as string)
380-
}
374+
patchStaticNode(n1, n2, container, isSVG)
381375
}
382376
break
383377
case Fragment:
@@ -492,17 +486,83 @@ function baseCreateRenderer(
492486
isSVG: boolean
493487
) => {
494488
if (n2.el && hostCloneNode !== undefined) {
495-
hostInsert(hostCloneNode(n2.el), container, anchor)
489+
// static node was already mounted (and reused), or adopted
490+
// server-rendered node during hydration (in this case its children can be
491+
// stripped by SSR optimizations). Clone the dom nodes instead.
492+
let cur: RendererElement | null = n2.el
493+
while (cur && cur !== n2.anchor) {
494+
hostInsert(hostCloneNode(cur), container, anchor)
495+
cur = hostNextSibling(cur)
496+
}
497+
hostInsert(hostCloneNode(n2.anchor!), container, anchor)
496498
} else {
497499
// static nodes are only present when used with compiler-dom/runtime-dom
498500
// which guarantees presence of hostInsertStaticContent.
499-
n2.el = hostInsertStaticContent!(
501+
;[n2.el, n2.anchor] = hostInsertStaticContent!(
502+
n2.children as string,
503+
container,
504+
anchor,
505+
isSVG
506+
)
507+
}
508+
}
509+
510+
/**
511+
* Dev / HMR only
512+
*/
513+
const patchStaticNode = (
514+
n1: VNode,
515+
n2: VNode,
516+
container: RendererElement,
517+
isSVG: boolean
518+
) => {
519+
// static nodes are only patched during dev for HMR
520+
if (n2.children !== n1.children) {
521+
const anchor = hostNextSibling(n1.anchor!)
522+
// remove existing
523+
removeStaticNode(n1)
524+
// insert new
525+
;[n2.el, n2.anchor] = hostInsertStaticContent!(
500526
n2.children as string,
501527
container,
502528
anchor,
503529
isSVG
504530
)
531+
} else {
532+
n2.el = n1.el
533+
n2.anchor = n1.anchor
534+
}
535+
}
536+
537+
/**
538+
* Dev / HMR only
539+
*/
540+
const moveStaticNode = (
541+
vnode: VNode,
542+
container: RendererElement,
543+
anchor: RendererNode | null
544+
) => {
545+
let cur = vnode.el
546+
const end = vnode.anchor!
547+
while (cur && cur !== end) {
548+
const next = hostNextSibling(cur)
549+
hostInsert(cur, container, anchor)
550+
cur = next
551+
}
552+
hostInsert(end, container, anchor)
553+
}
554+
555+
/**
556+
* Dev / HMR only
557+
*/
558+
const removeStaticNode = (vnode: VNode) => {
559+
let cur = vnode.el
560+
while (cur && cur !== vnode.anchor) {
561+
const next = hostNextSibling(cur)
562+
hostRemove(cur)
563+
cur = next
505564
}
565+
hostRemove(vnode.anchor!)
506566
}
507567

508568
const processElement = (
@@ -1456,7 +1516,7 @@ function baseCreateRenderer(
14561516
n1,
14571517
n2,
14581518
container,
1459-
parentAnchor,
1519+
null,
14601520
parentComponent,
14611521
parentSuspense,
14621522
isSVG,
@@ -1481,7 +1541,7 @@ function baseCreateRenderer(
14811541
n1,
14821542
n2,
14831543
container,
1484-
parentAnchor,
1544+
null,
14851545
parentComponent,
14861546
parentSuspense,
14871547
isSVG,
@@ -1692,6 +1752,12 @@ function baseCreateRenderer(
16921752
return
16931753
}
16941754

1755+
// static node move can only happen when force updating HMR
1756+
if (__DEV__ && type === Static) {
1757+
moveStaticNode(vnode, container, anchor)
1758+
return
1759+
}
1760+
16951761
// single nodes
16961762
const needTransition =
16971763
moveType !== MoveType.REORDER &&
@@ -1808,6 +1874,11 @@ function baseCreateRenderer(
18081874
return
18091875
}
18101876

1877+
if (__DEV__ && type === Static) {
1878+
removeStaticNode(vnode)
1879+
return
1880+
}
1881+
18111882
const performRemove = () => {
18121883
hostRemove(el!)
18131884
if (transition && !transition.persisted && transition.afterLeave) {

packages/runtime-core/src/vnode.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
127127
anchor: HostNode | null // fragment anchor
128128
target: HostElement | null // teleport target
129129
targetAnchor: HostNode | null // teleport target anchor
130+
staticCount: number // number of elements contained in a static vnode
130131

131132
// optimization only
132133
shapeFlag: number
@@ -368,6 +369,7 @@ function _createVNode(
368369
anchor: null,
369370
target: null,
370371
targetAnchor: null,
372+
staticCount: 0,
371373
shapeFlag,
372374
patchFlag,
373375
dynamicProps,
@@ -422,6 +424,7 @@ export function cloneVNode<T, U>(
422424
children: vnode.children,
423425
target: vnode.target,
424426
targetAnchor: vnode.targetAnchor,
427+
staticCount: vnode.staticCount,
425428
shapeFlag: vnode.shapeFlag,
426429
// if the vnode is cloned with extra props, we can no longer assume its
427430
// 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 {
459462
/**
460463
* @internal
461464
*/
462-
export function createStaticVNode(content: string): VNode {
463-
return createVNode(Static, null, content)
465+
export function createStaticVNode(
466+
content: string,
467+
numberOfNodes: number
468+
): VNode {
469+
// A static vnode can contain multiple stringified elements, and the number
470+
// of elements is necessary for hydration.
471+
const vnode = createVNode(Static, null, content)
472+
vnode.staticCount = numberOfNodes
473+
return vnode
464474
}
465475

466476
/**

packages/runtime-dom/src/nodeOps.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,14 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
6464
(tempSVGContainer = doc.createElementNS(svgNS, 'svg'))
6565
: tempContainer || (tempContainer = doc.createElement('div'))
6666
temp.innerHTML = content
67-
const node = temp.children[0]
68-
nodeOps.insert(node, parent, anchor)
69-
return node
70-
}
71-
}
72-
73-
if (__DEV__) {
74-
// __UNSAFE__
75-
// Reason: innerHTML.
76-
// same as `insertStaticContent`, but this is also dev only (for HMR).
77-
nodeOps.setStaticContent = (el, content) => {
78-
el.innerHTML = content
67+
const first = temp.firstChild as Element
68+
let node: Element | null = first
69+
let last: Element = node
70+
while (node) {
71+
last = node
72+
nodeOps.insert(node, parent, anchor)
73+
node = temp.firstChild as Element
74+
}
75+
return [first, last]
7976
}
8077
}

0 commit comments

Comments
 (0)