|
7 | 7 | type IfStatement,
|
8 | 8 | type JSChildNode,
|
9 | 9 | NodeTypes,
|
| 10 | + type PlainElementNode, |
10 | 11 | type RootNode,
|
11 | 12 | type TemplateChildNode,
|
12 | 13 | type TemplateLiteral,
|
@@ -166,10 +167,14 @@ export function processChildren(
|
166 | 167 | context.pushStringPart(`<!--[-->`)
|
167 | 168 | }
|
168 | 169 |
|
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 | + |
170 | 175 | for (let i = 0; i < children.length; i++) {
|
171 | 176 | const child = children[i]
|
172 |
| - if (shouldProcessAsDynamic(parent, child)) { |
| 177 | + if (inElement && shouldProcessChildAsDynamic(parent, child)) { |
173 | 178 | processChildren(
|
174 | 179 | { children: [child] },
|
175 | 180 | context,
|
@@ -274,87 +279,127 @@ const isStaticChildNode = (c: TemplateChildNode): boolean =>
|
274 | 279 | c.type === NodeTypes.TEXT ||
|
275 | 280 | c.type === NodeTypes.COMMENT
|
276 | 281 |
|
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 | +} |
297 | 288 |
|
298 |
| - const children = parent.children.filter( |
| 289 | +function processChildrenDynamicInfo( |
| 290 | + children: (TemplateChildNode & { _ssrDynamicInfo?: DynamicInfo })[], |
| 291 | +): void { |
| 292 | + const filteredChildren = children.filter( |
299 | 293 | child => !(child.type === NodeTypes.TEXT && !child.content.trim()),
|
300 | 294 | )
|
301 |
| - const len = children.length |
302 |
| - const index = children.indexOf(node) |
303 | 295 |
|
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 |
310 | 316 | break
|
311 | 317 | }
|
| 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++ |
312 | 325 | }
|
313 |
| - } |
314 |
| - if (!hasStaticPreviousSibling) return false |
| 326 | + info.hasStaticPrevious = foundStaticPrev |
| 327 | + info.prevDynamicCount = dynamicCountPrev |
315 | 328 |
|
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 |
322 | 336 | break
|
323 | 337 | }
|
| 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++ |
324 | 345 | }
|
| 346 | + info.hasStaticNext = foundStaticNext |
| 347 | + info.nextDynamicCount = dynamicCountNext |
325 | 348 | }
|
326 |
| - if (!hasStaticNextSibling) return false |
| 349 | +} |
327 | 350 |
|
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 |
332 | 381 |
|
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 |
341 | 385 |
|
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 |
350 | 395 |
|
351 |
| - dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount |
| 396 | + const dynamicNodeCount = 1 + prevDynamicCount + nextDynamicCount |
352 | 397 |
|
353 |
| - // For two consecutive dynamic nodes, mark both as dynamic |
| 398 | + // For two consecutive dynamic nodes, mark the second one as dynamic |
354 | 399 | if (dynamicNodeCount === 2) {
|
355 |
| - return prevDynamicCount > 0 || nextDynamicCount > 0 |
| 400 | + return prevDynamicCount > 0 |
356 | 401 | }
|
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 |
358 | 403 | else if (dynamicNodeCount >= 3) {
|
359 | 404 | return prevDynamicCount > 0 && nextDynamicCount > 0
|
360 | 405 | }
|
|
0 commit comments