if (!(child.flags & DynamicFlag.NON_TEMPLATE)) {
if (prevDynamics.length) {
if (hasStaticTemplate) {
- context.childrenTemplate[index - prevDynamics.length] = `<!>`
- prevDynamics[0].flags -= DynamicFlag.NON_TEMPLATE
- const anchor = (prevDynamics[0].anchor = context.increaseId())
- registerInsertion(prevDynamics, context, anchor)
+ // each dynamic child gets its own placeholder node.
+ // this makes it easier to locate the corresponding node during hydration.
+ for (let i = 0; i < prevDynamics.length; i++) {
+ const idx = index - prevDynamics.length + i
+ context.childrenTemplate[idx] = `<!>`
+ const dynamicChild = prevDynamics[i]
+ dynamicChild.flags -= DynamicFlag.NON_TEMPLATE
+ const anchor = (dynamicChild.anchor = context.increaseId())
+ if (
+ dynamicChild.operation &&
+ isBlockOperation(dynamicChild.operation)
+ ) {
+ // block types
+ dynamicChild.operation.parent = context.reference()
+ dynamicChild.operation.anchor = anchor
+ }
+ }
} else {
registerInsertion(prevDynamics, context, -1 /* prepend */)
}
)
})
- test.todo('mixed component and text with anchor insertion', async () => {
+ test('mixed component and text with anchor insertion', async () => {
const { container, data } = await testHydration(
`<template>
<div>
Child: `<template>{{ data }}</template>`,
},
)
- expect(container.innerHTML).toMatchInlineSnapshot(``)
+ expect(container.innerHTML).toMatchInlineSnapshot(
+ `"<div><span></span><!--[[-->foo<!--]]--><!--[[--> <!--]]--><!--[[--> foo <!--]]--><!--[[--> <!--]]--><!--[[-->foo<!--]]--><span></span></div>"`,
+ )
data.value = 'bar'
await nextTick()
- expect(container.innerHTML).toMatchInlineSnapshot(``)
+ expect(container.innerHTML).toMatchInlineSnapshot(
+ `"<div><span></span><!--[[-->bar<!--]]--><!--[[--> <!--]]--><!--[[--> bar <!--]]--><!--[[--> <!--]]--><!--[[-->bar<!--]]--><span></span></div>"`,
+ )
})
test.todo('if')
export let adoptTemplate: (node: Node, template: string) => Node | null
export let locateHydrationNode: () => void
-const isComment = (node: Node, data: string): node is Anchor =>
+export const isComment = (node: Node, data: string): node is Anchor =>
node.nodeType === 8 && (node as Comment).data === data
/**
if (insertionAnchor === 0) {
node = child(insertionParent!)
} 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)!
- : insertionAnchor)
- node = anchor.nextSibling
- } else {
- node = insertionAnchor
- }
+ // for dynamic children, use insertionAnchor as the node
+ node = insertionAnchor
} else {
node = insertionParent ? insertionParent.lastChild : currentHydrationNode
if (node && isComment(node, ']')) {
resetInsertionState()
currentHydrationNode = node
}
-
-function isDynamicStart(node: Node): node is Anchor {
- return isComment(node, '[[')
-}
-
-function locateNextDynamicStart(anchor: Anchor): Anchor | undefined {
- let cur: Node | null = anchor
- let end = null
- let depth = 0
- while (cur) {
- cur = cur.nextSibling
- if (cur) {
- if (isComment(cur, '[[')) {
- depth++
- } else if (isComment(cur, ']]')) {
- if (!depth) {
- end = cur
- break
- } else {
- depth--
- }
- }
- }
- }
-
- if (end) {
- return end!.nextSibling as Anchor
- }
-}
/*! #__NO_SIDE_EFFECTS__ */
+
+import { isComment, isHydrating } from './hydration'
+
export function createTextNode(value = ''): Text {
return document.createTextNode(value)
}
/*! #__NO_SIDE_EFFECTS__ */
export function next(node: Node): Node {
- return node.nextSibling!
+ let n = node.nextSibling!
+ if (isHydrating) {
+ // skip dynamic anchors and empty text nodes
+ while (n && (isDynamicAnchor(n) || isEmptyText(n))) {
+ n = n.nextSibling!
+ }
+ }
+ return n
+}
+
+function isDynamicAnchor(node: Node): node is Comment {
+ return isComment(node, '[[') || isComment(node, ']]')
+}
+
+function isEmptyText(node: Node): node is Text {
+ return node.nodeType === 3 && !(node as Text).data.trim()
}