})
})
- describe('dynamic child anchor', () => {
- test('with consecutive components', () => {
+ describe('dynamic anchor', () => {
+ test('consecutive components', () => {
expect(
getCompiledString(`
<div>
</div>
`),
).toMatchInlineSnapshot(`
- "\`<div><div></div>\`)
- _push("<!--[[-->")
+ "\`<div><div></div><!--[[-->\`)
_push(_ssrRenderComponent(_component_Comp1, null, null, _parent))
- _push("<!--]]-->")
- _push("<!--[[-->")
+ _push(\`<!--]]--><!--[[-->\`)
_push(_ssrRenderComponent(_component_Comp2, null, null, _parent))
- _push("<!--]]-->")
- _push(\`<div></div></div>\`"
+ _push(\`<!--]]--><div></div></div>\`"
`)
})
})
asFragment = false,
disableNestedFragments = false,
disableComment = false,
+ asDynamic = false,
): void {
+ if (asDynamic) {
+ context.pushStringPart(`<!--[[-->`)
+ }
if (asFragment) {
context.pushStringPart(`<!--[-->`)
}
+
const { children } = parent
for (let i = 0; i < children.length; i++) {
const child = children[i]
+ if (shouldProcessAsDynamic(parent, child)) {
+ processChildren(
+ { children: [child] },
+ context,
+ asFragment,
+ disableNestedFragments,
+ disableComment,
+ true,
+ )
+ continue
+ }
switch (child.type) {
case NodeTypes.ELEMENT:
switch (child.tagType) {
if (asFragment) {
context.pushStringPart(`<!--]-->`)
}
+ if (asDynamic) {
+ context.pushStringPart(`<!--]]-->`)
+ }
}
export function processChildrenAsStatement(
processChildren(parent, childContext, asFragment)
return createBlockStatement(childContext.body)
}
+
+const isStaticElement = (c: TemplateChildNode): boolean =>
+ c.type === NodeTypes.ELEMENT && c.tagType !== ElementTypes.COMPONENT
+
+/**
+ * Check if a node should be processed as dynamic.
+ * This is primarily used in Vapor mode hydration to wrap dynamic parts
+ * with markers (`<!--[[-->` and `<!--]]-->`).
+ *
+ * <element>
+ * <element/> // Static previous sibling
+ * <Comp/> // Dynamic node (current)
+ * <Comp/> // Dynamic next sibling
+ * <element/> // Static next sibling
+ * </element>
+ */
+function shouldProcessAsDynamic(
+ parent: { tag?: string; children: TemplateChildNode[] },
+ node: TemplateChildNode,
+): boolean {
+ // 1. Must be a dynamic node type
+ if (isStaticElement(node)) return false
+ // 2. Must be inside a parent element
+ if (!parent.tag) return false
+
+ const children = parent.children
+ const len = children.length
+ const index = children.indexOf(node)
+
+ // 3. Check for a static previous sibling
+ let hasStaticPreviousSibling = false
+ if (index > 0) {
+ for (let i = index - 1; i >= 0; i--) {
+ if (isStaticElement(children[i])) {
+ hasStaticPreviousSibling = true
+ break
+ }
+ }
+ }
+ if (!hasStaticPreviousSibling) return false
+
+ // 4. Check for a static next sibling
+ let hasStaticNextSibling = false
+ if (index > -1 && index < len - 1) {
+ for (let i = index + 1; i < len; i++) {
+ if (isStaticElement(children[i])) {
+ hasStaticNextSibling = true
+ break
+ }
+ }
+ }
+ if (!hasStaticNextSibling) return false
+
+ // 5. Check for a consecutive dynamic sibling (immediately before or after)
+ let hasConsecutiveDynamicNodes = false
+ if (index > 0 && !isStaticElement(children[index - 1])) {
+ hasConsecutiveDynamicNodes = true
+ }
+ if (
+ !hasConsecutiveDynamicNodes &&
+ index < len - 1 &&
+ !isStaticElement(children[index + 1])
+ ) {
+ hasConsecutiveDynamicNodes = true
+ }
+
+ // Only process as dynamic if all conditions are met
+ return hasConsecutiveDynamicNodes
+}
node.ssrCodegenNode.arguments.push(`_scopeId`)
}
- // `<!--[[-->` marks the start of the dynamic children
- // Only used in Vapor hydration, VDOM hydration
- // skips this marker.
- const needDynamicAnchor = shouldAddDynamicAnchor(parent, node)
- if (needDynamicAnchor) {
- context.pushStatement(createCallExpression(`_push`, [`"<!--[[-->"`]))
- }
if (typeof component === 'string') {
// static component
context.pushStatement(
// the codegen node is a `renderVNode` call
context.pushStatement(node.ssrCodegenNode)
}
- if (needDynamicAnchor) {
- context.pushStatement(createCallExpression(`_push`, [`"<!--]]-->"`]))
- }
}
}
return v
}
}
-
-function shouldAddDynamicAnchor(
- parent: { tag?: string; children: TemplateChildNode[] },
- node: TemplateChildNode,
-): boolean {
- if (!parent.tag) return false
-
- const children = parent.children
- const len = children.length
- const index = children.indexOf(node)
-
- const isStaticElement = (c: TemplateChildNode): boolean =>
- c.type === NodeTypes.ELEMENT && c.tagType !== ElementTypes.COMPONENT
-
- let hasStaticPreviousSibling = false
- if (index > 0) {
- for (let i = index - 1; i >= 0; i--) {
- if (isStaticElement(children[i])) {
- hasStaticPreviousSibling = true
- break
- }
- }
- }
-
- let hasStaticNextSibling = false
- if (hasStaticPreviousSibling && index > -1 && index < len - 1) {
- for (let i = index + 1; i < len; i++) {
- if (isStaticElement(children[i])) {
- hasStaticNextSibling = true
- break
- }
- }
- }
-
- let hasConsecutiveDynamicNodes = false
- if (index > 0 && index < len - 1) {
- if (index > 0 && !isStaticElement(children[index - 1])) {
- hasConsecutiveDynamicNodes = true
- }
-
- if (
- !hasConsecutiveDynamicNodes &&
- index < len - 1 &&
- !isStaticElement(children[index + 1])
- ) {
- hasConsecutiveDynamicNodes = true
- }
- }
-
- return (
- hasStaticPreviousSibling &&
- hasStaticNextSibling &&
- hasConsecutiveDynamicNodes
- )
-}
}
})
- describe('dynamic child anchor', () => {
- test('with consecutive components', () => {
+ describe('dynamic anchor', () => {
+ test('consecutive components', () => {
const Comp = {
render() {
return createTextVNode('foo')
// prepend / firstChild
if (insertionAnchor === 0) {
node = child(insertionParent!)
- } else if (insertionParent && insertionAnchor) {
- // dynamic child anchor `<!--[[-->`
- if (insertionAnchor && isDynamicStart(insertionAnchor)) {
+ } else if (insertionAnchor) {
+ // dynamic anchor `<!--[[-->`
+ if (isDynamicStart(insertionAnchor)) {
const anchor = (insertionParent!.$lds = insertionParent!.$lds
? // continuous dynamic children, the next dynamic start must exist
locateNextDynamicStart(insertionParent!.$lds)!
node = insertionAnchor
}
} else {
- node = insertionAnchor
- ? insertionAnchor.previousSibling
- : insertionParent
- ? insertionParent.lastChild
- : currentHydrationNode
+ node = insertionParent ? insertionParent.lastChild : currentHydrationNode
if (node && isComment(node, ']')) {
// fragment backward search
if (node.$fs) {