Skip to content

Commit e26120f

Browse files
committed
wip: refactor
1 parent 3e7f093 commit e26120f

File tree

5 files changed

+160
-74
lines changed

5 files changed

+160
-74
lines changed

packages/compiler-ssr/__tests__/ssrElement.spec.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ describe('ssr: element', () => {
398398
})
399399

400400
describe('dynamic anchor', () => {
401-
test('consecutive components', () => {
401+
test('two consecutive components', () => {
402402
expect(
403403
getCompiledString(`
404404
<div>
@@ -409,12 +409,37 @@ describe('ssr: element', () => {
409409
</div>
410410
`),
411411
).toMatchInlineSnapshot(`
412-
"\`<div><div></div><!--[[-->\`)
412+
"\`<div><div></div>\`)
413413
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
414-
_push(\`<!--]]--><!--[[-->\`)
414+
_push(\`<!--[[-->\`)
415415
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
416416
_push(\`<!--]]--><div></div></div>\`"
417417
`)
418418
})
419+
420+
test('multiple consecutive components', () => {
421+
expect(
422+
getCompiledString(`
423+
<div>
424+
<div/>
425+
<Comp1/>
426+
<Comp2/>
427+
<Comp3/>
428+
<Comp4/>
429+
<div/>
430+
</div>
431+
`),
432+
).toMatchInlineSnapshot(`
433+
"\`<div><div></div>\`)
434+
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
435+
_push(\`<!--[[-->\`)
436+
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
437+
_push(\`<!--]]--><!--[[-->\`)
438+
_push(_ssrRenderComponent(_component_Comp3, null, null, _parent))
439+
_push(\`<!--]]-->\`)
440+
_push(_ssrRenderComponent(_component_Comp4, null, null, _parent))
441+
_push(\`<div></div></div>\`"
442+
`)
443+
})
419444
})
420445
})

packages/compiler-ssr/src/ssrCodegenTransform.ts

Lines changed: 105 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,11 @@ export function processChildren(
167167
}
168168

169169
const { children } = parent
170+
processChildrenDynamicInfo(children)
171+
170172
for (let i = 0; i < children.length; i++) {
171173
const child = children[i]
172-
if (shouldProcessAsDynamic(parent, child)) {
174+
if (shouldProcessChildAsDynamic(parent, child)) {
173175
processChildren(
174176
{ children: [child] },
175177
context,
@@ -274,87 +276,127 @@ const isStaticChildNode = (c: TemplateChildNode): boolean =>
274276
c.type === NodeTypes.TEXT ||
275277
c.type === NodeTypes.COMMENT
276278

277-
/**
278-
* Check if a node should be processed as dynamic.
279-
* This is primarily used in Vapor mode hydration to wrap dynamic parts
280-
* with markers (`<!--[[-->` and `<!--]]-->`).
281-
*
282-
* <element>
283-
* <element/> // Static previous sibling
284-
* <Comp/> // Dynamic node (current)
285-
* <Comp/> // Dynamic next sibling
286-
* <element/> // Static next sibling
287-
* </element>
288-
*/
289-
function shouldProcessAsDynamic(
290-
parent: { tag?: string; children: TemplateChildNode[] },
291-
node: TemplateChildNode,
292-
): boolean {
293-
// 1. Must be a dynamic node type
294-
if (isStaticChildNode(node)) return false
295-
// 2. Must be inside a parent element
296-
if (!parent.tag) return false
279+
interface DynamicInfo {
280+
hasStaticPrevious: boolean
281+
hasStaticNext: boolean
282+
prevDynamicCount: number
283+
nextDynamicCount: number
284+
}
297285

298-
const children = parent.children.filter(
286+
function processChildrenDynamicInfo(
287+
children: (TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo })[],
288+
): void {
289+
const filteredChildren = children.filter(
299290
child => !(child.type === NodeTypes.TEXT && !child.content.trim()),
300291
)
301-
const len = children.length
302-
const index = children.indexOf(node)
303292

304-
// 3. Check for a static previous sibling
305-
let hasStaticPreviousSibling = false
306-
if (index > 0) {
307-
for (let i = index - 1; i >= 0; i--) {
308-
if (isStaticChildNode(children[i])) {
309-
hasStaticPreviousSibling = true
293+
for (let i = 0; i < filteredChildren.length; i++) {
294+
const child = filteredChildren[i]
295+
if (isStaticChildNode(child)) continue
296+
297+
child._ssrDynamicInfo = {
298+
hasStaticPrevious: false,
299+
hasStaticNext: false,
300+
prevDynamicCount: 0,
301+
nextDynamicCount: 0,
302+
}
303+
304+
const info = child._ssrDynamicInfo
305+
306+
// Calculate the previous static and dynamic node counts
307+
let foundStaticPrev = false
308+
let dynamicCountPrev = 0
309+
for (let j = i - 1; j >= 0; j--) {
310+
const prevChild = filteredChildren[j]
311+
if (isStaticChildNode(prevChild)) {
312+
foundStaticPrev = true
310313
break
311314
}
315+
// if the previous child has dynamic info, use it
316+
else if (prevChild._ssrDynamicInfo) {
317+
foundStaticPrev = prevChild._ssrDynamicInfo.hasStaticPrevious
318+
dynamicCountPrev = prevChild._ssrDynamicInfo.prevDynamicCount + 1
319+
break
320+
}
321+
dynamicCountPrev++
312322
}
313-
}
314-
if (!hasStaticPreviousSibling) return false
323+
info.hasStaticPrevious = foundStaticPrev
324+
info.prevDynamicCount = dynamicCountPrev
315325

316-
// 4. Check for a static next sibling
317-
let hasStaticNextSibling = false
318-
if (index > -1 && index < len - 1) {
319-
for (let i = index + 1; i < len; i++) {
320-
if (isStaticChildNode(children[i])) {
321-
hasStaticNextSibling = true
326+
// Calculate the number of static and dynamic nodes afterwards
327+
let foundStaticNext = false
328+
let dynamicCountNext = 0
329+
for (let j = i + 1; j < filteredChildren.length; j++) {
330+
const nextChild = filteredChildren[j]
331+
if (isStaticChildNode(nextChild)) {
332+
foundStaticNext = true
322333
break
323334
}
335+
// if the next child has dynamic info, use it
336+
else if (nextChild._ssrDynamicInfo) {
337+
foundStaticNext = nextChild._ssrDynamicInfo.hasStaticNext
338+
dynamicCountNext = nextChild._ssrDynamicInfo.nextDynamicCount + 1
339+
break
340+
}
341+
dynamicCountNext++
324342
}
343+
info.hasStaticNext = foundStaticNext
344+
info.nextDynamicCount = dynamicCountNext
325345
}
326-
if (!hasStaticNextSibling) return false
346+
}
327347

328-
// 5. Calculate the number and location of continuous dynamic nodes
329-
let dynamicNodeCount = 1 // The current node is counted as one
330-
let prevDynamicCount = 0
331-
let nextDynamicCount = 0
348+
/**
349+
* Check if a node should be processed as dynamic.
350+
* This is primarily used in Vapor mode hydration to wrap dynamic parts
351+
* with markers (`<!--[[-->` and `<!--]]-->`).
352+
* The purpose is to distinguish the boundaries of nodes during hydration
353+
*
354+
* 1. two consecutive dynamic nodes should only wrap the second one
355+
* <element>
356+
* <element/> // Static node
357+
* <Comp/> // Dynamic node -> should NOT be wrapped
358+
* <Comp/> // Dynamic node -> should be wrapped
359+
* <element/> // Static node
360+
* </element>
361+
*
362+
* 2. three or more consecutive dynamic nodes should only wrap the
363+
* middle nodes, leaving the first and last static.
364+
* <element>
365+
* <element/> // Static node
366+
* <Comp/> // Dynamic node -> should NOT be wrapped
367+
* <Comp/> // Dynamic node -> should be wrapped
368+
* <Comp/> // Dynamic node -> should be wrapped
369+
* <Comp/> // Dynamic node -> should NOT be wrapped
370+
* <element/> // Static node
371+
*/
372+
function shouldProcessChildAsDynamic(
373+
parent: { tag?: string; children: TemplateChildNode[] },
374+
node: TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo },
375+
): boolean {
376+
// must be inside a parent element
377+
if (!parent.tag) return false
332378

333-
// Count consecutive dynamic nodes forward
334-
for (let i = index - 1; i >= 0; i--) {
335-
if (!isStaticChildNode(children[i])) {
336-
prevDynamicCount++
337-
} else {
338-
break
339-
}
340-
}
379+
// must has dynamic info
380+
const { _ssrDynamicInfo: info } = node
381+
if (!info) return false
341382

342-
// Count consecutive dynamic nodes backwards
343-
for (let i = index + 1; i < len; i++) {
344-
if (!isStaticChildNode(children[i])) {
345-
nextDynamicCount++
346-
} else {
347-
break
348-
}
349-
}
383+
const {
384+
hasStaticPrevious,
385+
hasStaticNext,
386+
prevDynamicCount,
387+
nextDynamicCount,
388+
} = info
389+
390+
// must have static nodes on both sides
391+
if (!hasStaticPrevious || !hasStaticNext) return false
350392

351-
dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount
393+
const dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount
352394

353-
// For two consecutive dynamic nodes, mark both as dynamic
395+
// For two consecutive dynamic nodes, mark the second one as dynamic
354396
if (dynamicNodeCount === 2) {
355-
return prevDynamicCount > 0 || nextDynamicCount > 0
397+
return prevDynamicCount > 0
356398
}
357-
// For three or more dynamic nodes, only mark the intermediate nodes as dynamic
399+
// For three or more dynamic nodes, mark the intermediate node as dynamic
358400
else if (dynamicNodeCount >= 3) {
359401
return prevDynamicCount > 0 && nextDynamicCount > 0
360402
}

packages/runtime-core/__tests__/hydration.spec.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,19 +1844,33 @@ describe('SSR hydration', () => {
18441844
})
18451845

18461846
describe('dynamic anchor', () => {
1847-
test('consecutive components', () => {
1847+
test('two consecutive components', () => {
18481848
const Comp = {
18491849
render() {
18501850
return createTextVNode('foo')
18511851
},
18521852
}
18531853
const { vnode, container } = mountWithHydration(
1854-
`<div><span></span><!--[[-->foo<!--]]--><!--[[-->foo<!--]]--><span></span></div>`,
1854+
`<div><span></span>foo<!--[[-->foo<!--]]--><span></span></div>`,
18551855
() => h('div', null, [h('span'), h(Comp), h(Comp), h('span')]),
18561856
)
18571857
expect(vnode.el).toBe(container.firstChild)
18581858
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
18591859
})
1860+
1861+
test('multiple consecutive components', () => {
1862+
const Comp = {
1863+
render() {
1864+
return createTextVNode('foo')
1865+
},
1866+
}
1867+
const { vnode, container } = mountWithHydration(
1868+
`<div><span></span>foo<!--[[-->foo<!--]]-->foo<span></span></div>`,
1869+
() => h('div', null, [h('span'), h(Comp), h(Comp), h(Comp), h('span')]),
1870+
)
1871+
expect(vnode.el).toBe(container.firstChild)
1872+
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
1873+
})
18601874
})
18611875

18621876
describe('mismatch handling', () => {

packages/runtime-vapor/__tests__/hydration.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,13 +280,13 @@ describe('Vapor Mode hydration', () => {
280280
},
281281
)
282282
expect(container.innerHTML).toMatchInlineSnapshot(
283-
`"<div><span></span><!--[[-->foo<!--]]--><!--[[-->foo<!--]]--><span></span></div>"`,
283+
`"<div><span></span>foo<!--[[-->foo<!--]]--><span></span></div>"`,
284284
)
285285

286286
data.value = 'bar'
287287
await nextTick()
288288
expect(container.innerHTML).toMatchInlineSnapshot(
289-
`"<div><span></span><!--[[-->bar<!--]]--><!--[[-->bar<!--]]--><span></span></div>"`,
289+
`"<div><span></span>bar<!--[[-->bar<!--]]--><span></span></div>"`,
290290
)
291291
})
292292

@@ -385,13 +385,13 @@ describe('Vapor Mode hydration', () => {
385385
},
386386
)
387387
expect(container.innerHTML).toMatchInlineSnapshot(
388-
`"<div><span></span><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><span></span></div>"`,
388+
`"<div><span></span><!--[--><div>foo</div>-foo<!--]--><!--[[--><!--[--><div>foo</div>-foo<!--]--><!--]]--><span></span></div>"`,
389389
)
390390

391391
data.value = 'bar'
392392
await nextTick()
393393
expect(container.innerHTML).toMatchInlineSnapshot(
394-
`"<div><span></span><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><span></span></div>"`,
394+
`"<div><span></span><!--[--><div>bar</div>-bar<!--]--><!--[[--><!--[--><div>bar</div>-bar<!--]--><!--]]--><span></span></div>"`,
395395
)
396396
})
397397

packages/runtime-vapor/src/dom/node.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ function _next(node: Node): Node {
4242

4343
/*! #__NO_SIDE_EFFECTS__ */
4444
function __next(node: Node): Node {
45-
// process fragment as a single node
46-
if (node && isComment(node, '[')) {
45+
// treat dynamic node (<!--[[-->...<!--]]-->) as a single node
46+
if (node && isComment(node, '[[')) {
47+
node = locateEndAnchor(node, '[[', ']]')!
48+
}
49+
50+
// treat dynamic node (<!--[-->...<!--]-->) as a single node
51+
else if (node && isComment(node, '[')) {
4752
node = locateEndAnchor(node)!
4853
}
4954

0 commit comments

Comments
 (0)