|
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