Skip to content

Commit ba89ca9

Browse files
committed
fix(runtime-dom): fix static node content caching edge cases
reverts fded1e8 fix #4023, #4031, #4037
1 parent 347d901 commit ba89ca9

File tree

5 files changed

+48
-50
lines changed

5 files changed

+48
-50
lines changed

packages/runtime-core/src/hydration.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ export function createHydrationFunctions(
134134
// if the static vnode has its content stripped during build,
135135
// adopt it from the server-rendered HTML.
136136
const needToAdoptContent = !(vnode.children as string).length
137-
for (let i = 0; i < vnode.staticCount; i++) {
137+
for (let i = 0; i < vnode.staticCount!; i++) {
138138
if (needToAdoptContent)
139139
vnode.children += (nextNode as Element).outerHTML
140-
if (i === vnode.staticCount - 1) {
140+
if (i === vnode.staticCount! - 1) {
141141
vnode.anchor = nextNode
142142
}
143143
nextNode = nextSibling(nextNode)!

packages/runtime-core/src/renderer.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export interface RendererOptions<
143143
parent: HostElement,
144144
anchor: HostNode | null,
145145
isSVG: boolean,
146-
cached?: [HostNode, HostNode | null] | null
146+
cached?: HostNode[] | null
147147
): HostElement[]
148148
}
149149

@@ -633,16 +633,22 @@ function baseCreateRenderer(
633633
) => {
634634
// static nodes are only present when used with compiler-dom/runtime-dom
635635
// which guarantees presence of hostInsertStaticContent.
636-
;[n2.el, n2.anchor] = hostInsertStaticContent!(
636+
const nodes = hostInsertStaticContent!(
637637
n2.children as string,
638638
container,
639639
anchor,
640640
isSVG,
641641
// pass cached nodes if the static node is being mounted multiple times
642642
// so that runtime-dom can simply cloneNode() instead of inserting new
643643
// HTML
644-
n2.el && [n2.el, n2.anchor]
644+
n2.staticCache
645645
)
646+
// first mount - this is the orignal hoisted vnode. cache nodes.
647+
if (!n2.el) {
648+
n2.staticCache = nodes
649+
}
650+
n2.el = nodes[0]
651+
n2.anchor = nodes[nodes.length - 1]
646652
}
647653

648654
/**
@@ -686,16 +692,14 @@ function baseCreateRenderer(
686692
hostInsert(anchor!, container, nextSibling)
687693
}
688694

689-
const removeStaticNode = (vnode: VNode) => {
695+
const removeStaticNode = ({ el, anchor }: VNode) => {
690696
let next
691-
let { el, anchor } = vnode
692697
while (el && el !== anchor) {
693698
next = hostNextSibling(el)
694699
hostRemove(el)
695700
el = next
696701
}
697702
hostRemove(anchor!)
698-
vnode.el = vnode.anchor = null
699703
}
700704

701705
const processElement = (

packages/runtime-core/src/vnode.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ export interface VNode<
167167
anchor: HostNode | null // fragment anchor
168168
target: HostElement | null // teleport target
169169
targetAnchor: HostNode | null // teleport target anchor
170-
staticCount: number // number of elements contained in a static vnode
170+
staticCount?: number // number of elements contained in a static vnode
171+
staticCache?: HostNode[] // cache of parsed static nodes for faster repeated insertions
171172

172173
// suspense
173174
suspense: SuspenseBoundary | null
@@ -439,7 +440,6 @@ function _createVNode(
439440
anchor: null,
440441
target: null,
441442
targetAnchor: null,
442-
staticCount: 0,
443443
shapeFlag,
444444
patchFlag,
445445
dynamicProps,
@@ -521,6 +521,7 @@ export function cloneVNode<T, U>(
521521
target: vnode.target,
522522
targetAnchor: vnode.targetAnchor,
523523
staticCount: vnode.staticCount,
524+
staticCache: vnode.staticCache,
524525
shapeFlag: vnode.shapeFlag,
525526
// if the vnode is cloned with extra props, we can no longer assume its
526527
// existing patch flag to be reliable and need to add the FULL_PROPS flag.

packages/runtime-dom/__tests__/nodeOps.spec.ts

+16-24
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,11 @@ describe('runtime-dom: node-ops', () => {
3030
test('fresh insertion', () => {
3131
const content = `<div>one</div><div>two</div>three`
3232
const parent = document.createElement('div')
33-
const [first, last] = nodeOps.insertStaticContent!(
34-
content,
35-
parent,
36-
null,
37-
false
38-
)
33+
const nodes = nodeOps.insertStaticContent!(content, parent, null, false)
3934
expect(parent.innerHTML).toBe(content)
40-
expect(first).toBe(parent.firstChild)
41-
expect(last).toBe(parent.lastChild)
35+
expect(nodes.length).toBe(3)
36+
expect(nodes[0]).toBe(parent.firstChild)
37+
expect(nodes[nodes.length - 1]).toBe(parent.lastChild)
4238
})
4339

4440
test('fresh insertion with anchor', () => {
@@ -47,15 +43,13 @@ describe('runtime-dom: node-ops', () => {
4743
const parent = document.createElement('div')
4844
parent.innerHTML = existing
4945
const anchor = parent.firstChild
50-
const [first, last] = nodeOps.insertStaticContent!(
51-
content,
52-
parent,
53-
anchor,
54-
false
55-
)
46+
const nodes = nodeOps.insertStaticContent!(content, parent, anchor, false)
5647
expect(parent.innerHTML).toBe(content + existing)
57-
expect(first).toBe(parent.firstChild)
58-
expect(last).toBe(parent.childNodes[parent.childNodes.length - 2])
48+
expect(nodes.length).toBe(3)
49+
expect(nodes[0]).toBe(parent.firstChild)
50+
expect(nodes[nodes.length - 1]).toBe(
51+
parent.childNodes[parent.childNodes.length - 2]
52+
)
5953
})
6054

6155
test('fresh insertion as svg', () => {
@@ -97,7 +91,7 @@ describe('runtime-dom: node-ops', () => {
9791
const content = `<div>one</div><div>two</div>three`
9892

9993
const cacheParent = document.createElement('div')
100-
const [cachedFirst, cachedLast] = nodeOps.insertStaticContent!(
94+
const nodes = nodeOps.insertStaticContent!(
10195
content,
10296
cacheParent,
10397
null,
@@ -106,20 +100,18 @@ describe('runtime-dom: node-ops', () => {
106100

107101
const parent = document.createElement('div')
108102

109-
const [first, last] = nodeOps.insertStaticContent!(
103+
const clonedNodes = nodeOps.insertStaticContent!(
110104
``,
111105
parent,
112106
null,
113107
false,
114-
[cachedFirst, cachedLast]
108+
nodes
115109
)
116110

117111
expect(parent.innerHTML).toBe(content)
118-
expect(first).toBe(parent.firstChild)
119-
expect(last).toBe(parent.lastChild)
120-
121-
expect(first).not.toBe(cachedFirst)
122-
expect(last).not.toBe(cachedLast)
112+
expect(clonedNodes[0]).toBe(parent.firstChild)
113+
expect(clonedNodes[clonedNodes.length - 1]).toBe(parent.lastChild)
114+
expect(clonedNodes[0]).not.toBe(nodes[0])
123115
})
124116
})
125117
})

packages/runtime-dom/src/nodeOps.ts

+17-16
Original file line numberDiff line numberDiff line change
@@ -73,17 +73,15 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
7373
// As long as the user only uses trusted templates, this is safe.
7474
insertStaticContent(content, parent, anchor, isSVG, cached) {
7575
if (cached) {
76-
let [cachedFirst, cachedLast] = cached
77-
let first, last
78-
while (true) {
79-
let node = cachedFirst.cloneNode(true)
80-
if (!first) first = node
76+
let first
77+
let last
78+
let i = 0
79+
let l = cached.length
80+
for (; i < l; i++) {
81+
const node = cached[i].cloneNode(true)
82+
if (i === 0) first = node
83+
if (i === l - 1) last = node
8184
parent.insertBefore(node, anchor)
82-
if (cachedFirst === cachedLast) {
83-
last = node
84-
break
85-
}
86-
cachedFirst = cachedFirst.nextSibling!
8785
}
8886
return [first, last] as any
8987
}
@@ -111,11 +109,14 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
111109
} else {
112110
parent.insertAdjacentHTML('beforeend', content)
113111
}
114-
return [
115-
// first
116-
before ? before.nextSibling : parent.firstChild,
117-
// last
118-
anchor ? anchor.previousSibling : parent.lastChild
119-
]
112+
let first = before ? before.nextSibling : parent.firstChild
113+
const last = anchor ? anchor.previousSibling : parent.lastChild
114+
const ret = []
115+
while (first) {
116+
ret.push(first)
117+
if (first === last) break
118+
first = first.nextSibling
119+
}
120+
return ret
120121
}
121122
}

0 commit comments

Comments
 (0)