Skip to content

Commit 04eadd8

Browse files
committed
wip: refactor
1 parent 3e7f093 commit 04eadd8

File tree

5 files changed

+164
-75
lines changed

5 files changed

+164
-75
lines changed

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

+28-3
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

+109-64
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type IfStatement,
88
type JSChildNode,
99
NodeTypes,
10+
type PlainElementNode,
1011
type RootNode,
1112
type TemplateChildNode,
1213
type TemplateLiteral,
@@ -166,10 +167,14 @@ export function processChildren(
166167
context.pushStringPart(`<!--[-->`)
167168
}
168169

169-
const { children } = parent
170+
const { children, type, tagType } = parent as PlainElementNode
171+
const inElement =
172+
type === NodeTypes.ELEMENT && tagType === ElementTypes.ELEMENT
173+
if (inElement) processChildrenDynamicInfo(children)
174+
170175
for (let i = 0; i < children.length; i++) {
171176
const child = children[i]
172-
if (shouldProcessAsDynamic(parent, child)) {
177+
if (inElement && shouldProcessChildAsDynamic(parent, child)) {
173178
processChildren(
174179
{ children: [child] },
175180
context,
@@ -274,87 +279,127 @@ const isStaticChildNode = (c: TemplateChildNode): boolean =>
274279
c.type === NodeTypes.TEXT ||
275280
c.type === NodeTypes.COMMENT
276281

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
282+
interface DynamicInfo {
283+
hasStaticPrevious: boolean
284+
hasStaticNext: boolean
285+
prevDynamicCount: number
286+
nextDynamicCount: number
287+
}
297288

298-
const children = parent.children.filter(
289+
function processChildrenDynamicInfo(
290+
children: (TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo })[],
291+
): void {
292+
const filteredChildren = children.filter(
299293
child => !(child.type === NodeTypes.TEXT && !child.content.trim()),
300294
)
301-
const len = children.length
302-
const index = children.indexOf(node)
303295

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
296+
for (let i = 0; i < filteredChildren.length; i++) {
297+
const child = filteredChildren[i]
298+
if (isStaticChildNode(child)) continue
299+
300+
child._ssrDynamicInfo = {
301+
hasStaticPrevious: false,
302+
hasStaticNext: false,
303+
prevDynamicCount: 0,
304+
nextDynamicCount: 0,
305+
}
306+
307+
const info = child._ssrDynamicInfo
308+
309+
// Calculate the previous static and dynamic node counts
310+
let foundStaticPrev = false
311+
let dynamicCountPrev = 0
312+
for (let j = i - 1; j >= 0; j--) {
313+
const prevChild = filteredChildren[j]
314+
if (isStaticChildNode(prevChild)) {
315+
foundStaticPrev = true
310316
break
311317
}
318+
// if the previous child has dynamic info, use it
319+
else if (prevChild._ssrDynamicInfo) {
320+
foundStaticPrev = prevChild._ssrDynamicInfo.hasStaticPrevious
321+
dynamicCountPrev = prevChild._ssrDynamicInfo.prevDynamicCount + 1
322+
break
323+
}
324+
dynamicCountPrev++
312325
}
313-
}
314-
if (!hasStaticPreviousSibling) return false
326+
info.hasStaticPrevious = foundStaticPrev
327+
info.prevDynamicCount = dynamicCountPrev
315328

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
329+
// Calculate the number of static and dynamic nodes afterwards
330+
let foundStaticNext = false
331+
let dynamicCountNext = 0
332+
for (let j = i + 1; j < filteredChildren.length; j++) {
333+
const nextChild = filteredChildren[j]
334+
if (isStaticChildNode(nextChild)) {
335+
foundStaticNext = true
322336
break
323337
}
338+
// if the next child has dynamic info, use it
339+
else if (nextChild._ssrDynamicInfo) {
340+
foundStaticNext = nextChild._ssrDynamicInfo.hasStaticNext
341+
dynamicCountNext = nextChild._ssrDynamicInfo.nextDynamicCount + 1
342+
break
343+
}
344+
dynamicCountNext++
324345
}
346+
info.hasStaticNext = foundStaticNext
347+
info.nextDynamicCount = dynamicCountNext
325348
}
326-
if (!hasStaticNextSibling) return false
349+
}
327350

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
351+
/**
352+
* Check if a node should be processed as dynamic.
353+
* This is primarily used in Vapor mode hydration to wrap dynamic parts
354+
* with markers (`<!--[[-->` and `<!--]]-->`).
355+
* The purpose is to distinguish the boundaries of nodes during hydration
356+
*
357+
* 1. two consecutive dynamic nodes should only wrap the second one
358+
* <element>
359+
* <element/> // Static node
360+
* <Comp/> // Dynamic node -> should NOT be wrapped
361+
* <Comp/> // Dynamic node -> should be wrapped
362+
* <element/> // Static node
363+
* </element>
364+
*
365+
* 2. three or more consecutive dynamic nodes should only wrap the
366+
* middle nodes, leaving the first and last static.
367+
* <element>
368+
* <element/> // Static node
369+
* <Comp/> // Dynamic node -> should NOT be wrapped
370+
* <Comp/> // Dynamic node -> should be wrapped
371+
* <Comp/> // Dynamic node -> should be wrapped
372+
* <Comp/> // Dynamic node -> should NOT be wrapped
373+
* <element/> // Static node
374+
*/
375+
function shouldProcessChildAsDynamic(
376+
parent: { tag?: string; children: TemplateChildNode[] },
377+
node: TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo },
378+
): boolean {
379+
// must be inside a parent element
380+
if (!parent.tag) return false
332381

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-
}
382+
// must has dynamic info
383+
const { _ssrDynamicInfo: info } = node
384+
if (!info) return false
341385

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-
}
386+
const {
387+
hasStaticPrevious,
388+
hasStaticNext,
389+
prevDynamicCount,
390+
nextDynamicCount,
391+
} = info
392+
393+
// must have static nodes on both sides
394+
if (!hasStaticPrevious || !hasStaticNext) return false
350395

351-
dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount
396+
const dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount
352397

353-
// For two consecutive dynamic nodes, mark both as dynamic
398+
// For two consecutive dynamic nodes, mark the second one as dynamic
354399
if (dynamicNodeCount === 2) {
355-
return prevDynamicCount > 0 || nextDynamicCount > 0
400+
return prevDynamicCount > 0
356401
}
357-
// For three or more dynamic nodes, only mark the intermediate nodes as dynamic
402+
// For three or more dynamic nodes, mark the intermediate node as dynamic
358403
else if (dynamicNodeCount >= 3) {
359404
return prevDynamicCount > 0 && nextDynamicCount > 0
360405
}

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

+16-2
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

+4-4
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

+7-2
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)