]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: refactor
authordaiwei <daiwei521@126.com>
Fri, 25 Apr 2025 07:08:19 +0000 (15:08 +0800)
committerdaiwei <daiwei521@126.com>
Fri, 25 Apr 2025 07:30:09 +0000 (15:30 +0800)
25 files changed:
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/compiler-ssr/__tests__/ssrFallthroughAttrs.spec.ts
packages/compiler-ssr/__tests__/ssrInjectCssVars.spec.ts
packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts
packages/compiler-ssr/__tests__/ssrTransitionGroup.spec.ts
packages/compiler-ssr/__tests__/ssrVIf.spec.ts
packages/compiler-ssr/__tests__/ssrVModel.spec.ts
packages/compiler-ssr/src/ssrCodegenTransform.ts
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
packages/compiler-ssr/src/transforms/ssrVIf.ts
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/index.ts
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/apiCreateDynamicComponent.ts
packages/runtime-vapor/src/apiCreateIf.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/dom/hydration.ts
packages/runtime-vapor/src/dom/node.ts
packages/server-renderer/__tests__/ssrAttrFallthrough.spec.ts
packages/server-renderer/__tests__/ssrDynamicComponent.spec.ts
packages/server-renderer/__tests__/ssrSlot.spec.ts
packages/server-renderer/src/render.ts
packages/shared/src/domAnchors.ts [new file with mode: 0644]
packages/shared/src/index.ts

index 7459cb4929f6bea2944026440a69a353d3112b5b..1bae37c0f6569b3002a1f2ece409239d5f82f153 100644 (file)
@@ -39,6 +39,7 @@ describe('ssr: components', () => {
 
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), _mergeProps({ prop: "b" }, _attrs), null), _parent)
+          _push(\`<!--dynamic-component-->\`)
         }"
       `)
 
@@ -49,6 +50,7 @@ describe('ssr: components', () => {
 
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: "b" }, _attrs), null), _parent)
+          _push(\`<!--dynamic-component-->\`)
         }"
       `)
   })
@@ -245,7 +247,7 @@ describe('ssr: components', () => {
                     _push(\`<span\${_scopeId}></span>\`)
                   })
                   _push(\`<!--]--></div>\`)
-                  _push(\`<!--$-->\`)
+                  _push(\`<!--if-->\`)
                 } else {
                   _push(\`<!---->\`)
                 }
@@ -269,7 +271,7 @@ describe('ssr: components', () => {
                     _push(\`<span\${_scopeId}></span>\`)
                   })
                   _push(\`<!--]--></div>\`)
-                  _push(\`<!--$-->\`)
+                  _push(\`<!--if-->\`)
                 } else {
                   _push(\`<!---->\`)
                 }
@@ -363,7 +365,7 @@ describe('ssr: components', () => {
                   _push(\`\`)
                   if (false) {
                     _push(\`<div\${_scopeId}></div>\`)
-                    _push(\`<!--$-->\`)
+                    _push(\`<!--if-->\`)
                   } else {
                     _push(\`<!---->\`)
                   }
index a837979003a4e36d40be4c46b812e8052929b51e..712c09d0946041d48abef1709d5e3f1d8591aefa 100644 (file)
@@ -29,7 +29,7 @@ describe('ssr: attrs fallthrough', () => {
         _push(\`<!--[-->\`)
         if (true) {
           _push(\`<div></div>\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<!---->\`)
         }
index 3eef5a7771da4adce31176b6d7991773ef3f7681..0666e8949cc935eda4d60f284e4934b3a9e592da 100644 (file)
@@ -70,7 +70,7 @@ describe('ssr: inject <style vars>', () => {
         const _cssVars = { style: { color: _ctx.color }}
         if (_ctx.ok) {
           _push(\`<div\${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))}></div>\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<!--[--><div\${
             _ssrRenderAttrs(_cssVars)
index 95f48b3b0686fbc2c5d452e94136a0cb9eea3902..4d73dfe082744420a973c8961867aefd6f4f4068 100644 (file)
@@ -153,7 +153,7 @@ describe('ssr: <slot>', () => {
       return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (true) {
           _ssrRenderSlotInner(_ctx.$slots, "default", {}, null, _push, _parent, null, true)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<!---->\`)
         }
index 7111dd5a2717281d6ff777b51c8a4d331d27b011..53295fb03d9191f4f1abda61abd208209bb4cebd 100644 (file)
@@ -54,7 +54,7 @@ describe('transition-group', () => {
         })
         if (false) {
           _push(\`<div></div>\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         }
         _push(\`</ul>\`)
       }"
@@ -124,7 +124,7 @@ describe('transition-group', () => {
         })
         if (_ctx.ok) {
           _push(\`<div>ok</div>\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         }
         _push(\`<!--]-->\`)
       }"
index d9ee698882121aef418619633b8e32d0a4a9ea40..e8909a82842dcc15b2c232ccf08f3e4be865fd97 100644 (file)
@@ -8,7 +8,7 @@ describe('ssr: v-if', () => {
       return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
           _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<!---->\`)
         }
@@ -24,7 +24,7 @@ describe('ssr: v-if', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           if (_ctx.foo) {
             _push(\`<div\${_ssrRenderAttrs(_attrs)}>hello<span>ok</span></div>\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else {
             _push(\`<!---->\`)
           }
@@ -40,7 +40,7 @@ describe('ssr: v-if', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           if (_ctx.foo) {
             _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else {
             _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
           }
@@ -56,10 +56,10 @@ describe('ssr: v-if', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           if (_ctx.foo) {
             _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else if (_ctx.bar) {
             _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else {
             _push(\`<!---->\`)
           }
@@ -75,10 +75,10 @@ describe('ssr: v-if', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           if (_ctx.foo) {
             _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else if (_ctx.bar) {
             _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else {
             _push(\`<p\${_ssrRenderAttrs(_attrs)}></p>\`)
           }
@@ -93,7 +93,7 @@ describe('ssr: v-if', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           if (_ctx.foo) {
             _push(\`<!--[-->hello<!--]-->\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else {
             _push(\`<!---->\`)
           }
@@ -110,7 +110,7 @@ describe('ssr: v-if', () => {
         return function ssrRender(_ctx, _push, _parent, _attrs) {
           if (_ctx.foo) {
             _push(\`<div\${_ssrRenderAttrs(_attrs)}>hi</div>\`)
-            _push(\`<!--$-->\`)
+            _push(\`<!--if-->\`)
           } else {
             _push(\`<!---->\`)
           }
@@ -127,7 +127,7 @@ describe('ssr: v-if', () => {
       return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
           _push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<!---->\`)
         }
@@ -148,7 +148,7 @@ describe('ssr: v-if', () => {
             _push(\`<div></div>\`)
           })
           _push(\`<!--]-->\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<!---->\`)
         }
@@ -167,7 +167,7 @@ describe('ssr: v-if', () => {
       return function ssrRender(_ctx, _push, _parent, _attrs) {
         if (_ctx.foo) {
           _push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
         }
index 906c62df21abfee0342c40c8446b04cc036ed95f..db4af81d01ec9e000ab1a0433b4a06d6ed47d2d8 100644 (file)
@@ -91,7 +91,7 @@ describe('ssr: v-model', () => {
               ? _ssrLooseContain(_ctx.model, _ctx.i)
               : _ssrLooseEqual(_ctx.model, _ctx.i))) ? " selected" : ""
           }></option>\`)
-          _push(\`<!--$-->\`)
+          _push(\`<!--if-->\`)
         } else {
           _push(\`<!---->\`)
         }
index 8a98c2422783ad4e7fd0faacbc630ddf1bdd5e0f..e2324004044a098ec83c179b5376c2e6f39fedf0 100644 (file)
@@ -21,7 +21,12 @@ import {
   isText,
   processExpression,
 } from '@vue/compiler-dom'
-import { escapeHtml, isString } from '@vue/shared'
+import {
+  DYNAMIC_END_ANCHOR_LABEL,
+  DYNAMIC_START_ANCHOR_LABEL,
+  escapeHtml,
+  isString,
+} from '@vue/shared'
 import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers'
 import { ssrProcessIf } from './transforms/ssrVIf'
 import { ssrProcessFor } from './transforms/ssrVFor'
@@ -161,7 +166,7 @@ export function processChildren(
   asDynamic = false,
 ): void {
   if (asDynamic) {
-    context.pushStringPart(`<!--[[-->`)
+    context.pushStringPart(`<!--${DYNAMIC_START_ANCHOR_LABEL}-->`)
   }
   if (asFragment) {
     context.pushStringPart(`<!--[-->`)
@@ -259,7 +264,7 @@ export function processChildren(
     context.pushStringPart(`<!--]-->`)
   }
   if (asDynamic) {
-    context.pushStringPart(`<!--]]-->`)
+    context.pushStringPart(`<!--${DYNAMIC_END_ANCHOR_LABEL}-->`)
   }
 }
 
index cad1ee8102897beab4c8990b99b43366c5d42655..419c2e4e49cab88279712b7ac2136638298841b9 100644 (file)
@@ -55,7 +55,14 @@ import {
   ssrProcessTransitionGroup,
   ssrTransformTransitionGroup,
 } from './ssrTransformTransitionGroup'
-import { extend, isArray, isObject, isPlainObject, isSymbol } from '@vue/shared'
+import {
+  DYNAMIC_COMPONENT_ANCHOR_LABEL,
+  extend,
+  isArray,
+  isObject,
+  isPlainObject,
+  isSymbol,
+} from '@vue/shared'
 import { buildSSRProps } from './ssrTransformElement'
 import {
   ssrProcessTransition,
@@ -264,6 +271,8 @@ export function ssrProcessComponent(
       // dynamic component (`resolveDynamicComponent` call)
       // the codegen node is a `renderVNode` call
       context.pushStatement(node.ssrCodegenNode)
+      // anchor for dynamic component for vapor hydration
+      context.pushStringPart(`<!--${DYNAMIC_COMPONENT_ANCHOR_LABEL}-->`)
     }
   }
 }
index 74ac319f4c338653fd6680d23c8eacc356afa88b..811252c0fab903cb8d432a338845e05c2b94f8cf 100644 (file)
@@ -14,6 +14,7 @@ import {
   type SSRTransformContext,
   processChildrenAsStatement,
 } from '../ssrCodegenTransform'
+import { IF_ANCHOR_LABEL } from '@vue/shared'
 
 // Plugin for the first transform pass, which simply constructs the AST node
 export const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform(
@@ -80,7 +81,10 @@ function processIfBranch(
     needFragmentWrapper,
   )
   if (branch.condition) {
-    statement.body.push(createCallExpression(`_push`, ['`<!--$-->`']))
+    // v-if/v-else-if anchor for vapor hydration
+    statement.body.push(
+      createCallExpression(`_push`, [`\`<!--${IF_ANCHOR_LABEL}-->\``]),
+    )
   }
   return statement
 }
index 9cf6870c479f777717446dc612d7731e479f786c..793c11cede3cab6bbe72bba6f72b76b7aad9d754 100644 (file)
@@ -598,14 +598,14 @@ describe('SSR hydration', () => {
     const ctx: SSRContext = {}
     container.innerHTML = await renderToString(h(App), ctx)
     expect(container.innerHTML).toBe(
-      '<div><!--teleport start--><!--teleport end--><!--$--></div>',
+      '<div><!--teleport start--><!--teleport end--><!--if--></div>',
     )
     teleportContainer.innerHTML = ctx.teleports!['#target']
 
     // hydrate
     createSSRApp(App).mount(container)
     expect(container.innerHTML).toBe(
-      '<div><!--teleport start--><!--teleport end--><!--$--></div>',
+      '<div><!--teleport start--><!--teleport end--><!--if--></div>',
     )
     expect(teleportContainer.innerHTML).toBe(
       '<!--teleport start anchor--><span>Teleported Comp1</span><!--teleport anchor-->',
@@ -614,7 +614,7 @@ describe('SSR hydration', () => {
 
     toggle.value = false
     await nextTick()
-    expect(container.innerHTML).toBe('<div><div>Comp2</div><!--$--></div>')
+    expect(container.innerHTML).toBe('<div><div>Comp2</div><!--if--></div>')
     expect(teleportContainer.innerHTML).toBe('')
   })
 
@@ -657,21 +657,21 @@ describe('SSR hydration', () => {
     // server render
     container.innerHTML = await renderToString(h(App))
     expect(container.innerHTML).toBe(
-      '<div><!--teleport start--><!--teleport end--><!--$--></div>',
+      '<div><!--teleport start--><!--teleport end--><!--if--></div>',
     )
     expect(teleportContainer.innerHTML).toBe('')
 
     // hydrate
     createSSRApp(App).mount(container)
     expect(container.innerHTML).toBe(
-      '<div><!--teleport start--><!--teleport end--><!--$--></div>',
+      '<div><!--teleport start--><!--teleport end--><!--if--></div>',
     )
     expect(teleportContainer.innerHTML).toBe('<span>Teleported Comp1</span>')
     expect(`Hydration children mismatch`).toHaveBeenWarned()
 
     toggle.value = false
     await nextTick()
-    expect(container.innerHTML).toBe('<div><div>Comp2</div><!--$--></div>')
+    expect(container.innerHTML).toBe('<div><div>Comp2</div><!--if--></div>')
     expect(teleportContainer.innerHTML).toBe('')
   })
 
index 876fd877eda872871fdd4d65610e35f2add40eab..7c7fdf3860894fbf9d81abcca16c5e562555fae3 100644 (file)
@@ -25,6 +25,8 @@ import {
   getEscapedCssVarName,
   includeBooleanAttr,
   isBooleanAttr,
+  isDynamicAnchor,
+  isDynamicFragmentEndAnchor,
   isKnownHtmlAttr,
   isKnownSvgAttr,
   isOn,
@@ -84,14 +86,6 @@ const getContainerType = (
   return undefined
 }
 
-export function isDynamicAnchor(node: Node): node is Comment {
-  return isComment(node) && (node.data === '[[' || node.data === ']]')
-}
-
-export function isDynamicFragmentEndAnchor(node: Node): node is Comment {
-  return isComment(node) && node.data === '$'
-}
-
 export const isComment = (node: Node): node is Comment =>
   node.nodeType === DOMNodeTypes.COMMENT
 
@@ -130,8 +124,8 @@ export function createHydrationFunctions(
   function nextSibling(node: Node) {
     let n = next(node)
     // skip if:
-    // - dynamic anchors (`<!--[-->`, `<!--]-->`)
-    // - dynamic fragment end anchors (`<!--$-->`)
+    // - dynamic anchors (`<!--[[-->`, `<!--][-->`)
+    // - dynamic fragment end anchors (e.g. `<!--if-->`, `<!--for-->`)
     if (n && (isDynamicAnchor(n) || isDynamicFragmentEndAnchor(n))) {
       n = next(n)
     }
index b2e2dfcf1807405eeaa9234596dc440b911ce36a..e309554f2f6c3edd517c9b736d06ee1c72594698 100644 (file)
@@ -557,7 +557,3 @@ export { startMeasure, endMeasure } from './profiling'
  * @internal
  */
 export { initFeatureFlags } from './featureFlags'
-/**
- * @internal
- */
-export { isDynamicAnchor, isDynamicFragmentEndAnchor } from './hydration'
index 58898305176d89c8b892d94df3cd247a00357e08..a37076ce160c1ae674682eac65b22dc9001720db 100644 (file)
@@ -4,6 +4,7 @@ import { compileScript, parse } from '@vue/compiler-sfc'
 import * as runtimeVapor from '../src'
 import * as runtimeDom from '@vue/runtime-dom'
 import * as VueServerRenderer from '@vue/server-renderer'
+import { DYNAMIC_COMPONENT_ANCHOR_LABEL, IF_ANCHOR_LABEL } from '@vue/shared'
 
 const Vue = { ...runtimeDom, ...runtimeVapor }
 
@@ -94,65 +95,66 @@ describe('Vapor Mode hydration', () => {
     document.body.innerHTML = ''
   })
 
-  test('root text', async () => {
-    const { data, container } = await testHydration(`
+  describe('element', () => {
+    test('root text', async () => {
+      const { data, container } = await testHydration(`
       <template>{{ data }}</template>
     `)
-    expect(container.innerHTML).toMatchInlineSnapshot(`"foo"`)
+      expect(container.innerHTML).toMatchInlineSnapshot(`"foo"`)
 
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(`"bar"`)
-  })
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(`"bar"`)
+    })
 
-  test('root comment', async () => {
-    const { container } = await testHydration(`
+    test('root comment', async () => {
+      const { container } = await testHydration(`
       <template><!----></template>
     `)
-    expect(container.innerHTML).toBe('<!---->')
-    expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
-  })
+      expect(container.innerHTML).toBe('<!---->')
+      expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
+    })
 
-  test('root with mixed element and text', async () => {
-    const { container, data } = await testHydration(`
+    test('root with mixed element and text', async () => {
+      const { container, data } = await testHydration(`
       <template> A<span>{{ data }}</span>{{ data }}</template>
     `)
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<!--[--> A<span>foo</span>foo<!--]-->"`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<!--[--> A<span>bar</span>bar<!--]-->"`,
-    )
-  })
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<!--[--> A<span>foo</span>foo<!--]-->"`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<!--[--> A<span>bar</span>bar<!--]-->"`,
+      )
+    })
 
-  test('empty element', async () => {
-    const { container } = await testHydration(`
+    test('empty element', async () => {
+      const { container } = await testHydration(`
       <template><div/></template>
     `)
-    expect(container.innerHTML).toBe('<div></div>')
-    expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
-  })
+      expect(container.innerHTML).toBe('<div></div>')
+      expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
+    })
 
-  test('element with binding and text children', async () => {
-    const { container, data } = await testHydration(`
+    test('element with binding and text children', async () => {
+      const { container, data } = await testHydration(`
       <template><div :class="data">{{ data }}</div></template>
     `)
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div class="foo">foo</div>"`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div class="bar">bar</div>"`,
-    )
-  })
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div class="foo">foo</div>"`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div class="bar">bar</div>"`,
+      )
+    })
 
-  test('element with elements children', async () => {
-    const { container } = await testHydration(`
+    test('element with elements children', async () => {
+      const { container } = await testHydration(`
       <template>
         <div>
           <span>{{ data }}</span>
@@ -160,111 +162,113 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
     `)
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span>foo</span><span class="foo"></span></div>"`,
-    )
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span>foo</span><span class="foo"></span></div>"`,
+      )
 
-    // event handler
-    triggerEvent('click', container.querySelector('.foo')!)
+      // event handler
+      triggerEvent('click', container.querySelector('.foo')!)
 
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span>bar</span><span class="bar"></span></div>"`,
-    )
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span>bar</span><span class="bar"></span></div>"`,
+      )
+    })
   })
 
-  test('basic component', async () => {
-    const { container, data } = await testHydration(
-      `
+  describe('component', () => {
+    test('basic component', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><span></span><components.Child/></div></template>
       `,
-      { Child: `<template>{{ data }}</template>` },
-    )
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span>foo</div>"`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span>bar</div>"`,
-    )
-  })
+        { Child: `<template>{{ data }}</template>` },
+      )
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span></span>foo</div>"`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span></span>bar</div>"`,
+      )
+    })
 
-  test('fragment component', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('fragment component', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><span></span><components.Child/></div></template>
       `,
-      { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
-    )
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span><!--[--><div>foo</div>-foo-<!--]--></div>"`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span><!--[--><div>bar</div>-bar-<!--]--></div>"`,
-    )
-  })
+        { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
+      )
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span></span><!--[--><div>foo</div>-foo-<!--]--></div>"`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span></span><!--[--><div>bar</div>-bar-<!--]--></div>"`,
+      )
+    })
 
-  test('fragment component with prepend', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('fragment component with prepend', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><components.Child/><span></span></div></template>
       `,
-      { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
-    )
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><!--[--><div>foo</div>-foo-<!--]--><span></span></div>"`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><!--[--><div>bar</div>-bar-<!--]--><span></span></div>"`,
-    )
-  })
+        { Child: `<template><div>{{ data }}</div>-{{ data }}-</template>` },
+      )
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><!--[--><div>foo</div>-foo-<!--]--><span></span></div>"`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><!--[--><div>bar</div>-bar-<!--]--><span></span></div>"`,
+      )
+    })
 
-  test('nested fragment components', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested fragment components', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><components.Parent/><span></span></div></template>
       `,
-      {
-        Parent: `<template><div/><components.Child/><div/></template>`,
-        Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<!--[-->` +
-        `<div></div>` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<div></div>` +
-        `<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<!--[-->` +
-        `<div></div>` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<div></div>` +
-        `<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Parent: `<template><div/><components.Child/><div/></template>`,
+          Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<!--[-->` +
+          `<div></div>` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<div></div>` +
+          `<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<!--[-->` +
+          `<div></div>` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<div></div>` +
+          `<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('component with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('component with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -272,82 +276,82 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template>{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span>foo<span></span></div>"`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toMatchInlineSnapshot(
-      `"<div><span></span>bar<span></span></div>"`,
-    )
-  })
+        {
+          Child: `<template>{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span></span>foo<span></span></div>"`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toMatchInlineSnapshot(
+        `"<div><span></span>bar<span></span></div>"`,
+      )
+    })
 
-  test('nested components with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested components with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><components.Parent/></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div></template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div><span></span><div>foo</div><span></span></div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div><span></span><div>bar</div><span></span></div>`,
-    )
-  })
+        {
+          Parent: `<template><div><span/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div></template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div><span></span><div>foo</div><span></span></div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div><span></span><div>bar</div><span></span></div>`,
+      )
+    })
 
-  test('nested components with multi level anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested components with multi level anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><span></span><components.Parent/><span></span></div></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div></template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+        {
+          Parent: `<template><div><span/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div></template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<div>foo</div>` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<div>foo</div>` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<div>bar</div>` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<div>bar</div>` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('consecutive components with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('consecutive components with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -356,104 +360,104 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template>{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `foo` +
-        `<!--[[-->foo<!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `bar` +
-        `<!--[[-->bar<!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Child: `<template>{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `foo` +
+          `<!--[[-->foo<!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `bar` +
+          `<!--[[-->bar<!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('nested consecutive components with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested consecutive components with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><components.Parent/></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div></template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<div>foo</div>` +
-        `<!--[[--><div>foo</div><!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<div>bar</div>` +
-        `<!--[[--><div>bar</div><!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div></template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<div>foo</div>` +
+          `<!--[[--><div>foo</div><!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<div>bar</div>` +
+          `<!--[[--><div>bar</div><!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('nested consecutive components with multi level anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested consecutive components with multi level anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><span></span><components.Parent/><span></span></div></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div></template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+        {
+          Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div></template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<div>foo</div>` +
-        `<!--[[--><div>foo</div><!--]]-->` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<div>foo</div>` +
+          `<!--[[--><div>foo</div><!--]]-->` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<div>bar</div>` +
-        `<!--[[--><div>bar</div><!--]]-->` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<div>bar</div>` +
+          `<!--[[--><div>bar</div><!--]]-->` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('mixed component and element with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('mixed component and element with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -463,36 +467,36 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template>{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `foo` +
-        `<span></span>` +
-        `foo` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `bar` +
-        `<span></span>` +
-        `bar` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Child: `<template>{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `foo` +
+          `<span></span>` +
+          `foo` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `bar` +
+          `<span></span>` +
+          `bar` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('mixed component and text with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('mixed component and text with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -502,36 +506,36 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template>{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `foo` +
-        `<!--[[--> foo <!--]]-->` +
-        `foo` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `bar` +
-        `<!--[[--> bar <!--]]-->` +
-        `bar` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Child: `<template>{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `foo` +
+          `<!--[[--> foo <!--]]-->` +
+          `foo` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `bar` +
+          `<!--[[--> bar <!--]]-->` +
+          `bar` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('fragment component with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('fragment component with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -539,98 +543,98 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('nested fragment component with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested fragment component with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><components.Parent/></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Parent: `<template><div><span/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('nested fragment component with multi level anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested fragment component with multi level anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><span/><components.Parent/><span/></div></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+        {
+          Parent: `<template><div><span/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('consecutive fragment components with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('consecutive fragment components with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -639,151 +643,151 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo<!--]-->` +
-        `<!--[[-->` +
-        `<!--[--><div>foo</div>-foo<!--]-->` +
-        `<!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar<!--]-->` +
-        `<!--[[-->` +
-        `<!--[--><div>bar</div>-bar<!--]-->` +
-        `<!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo<!--]-->` +
+          `<!--[[-->` +
+          `<!--[--><div>foo</div>-foo<!--]-->` +
+          `<!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar<!--]-->` +
+          `<!--[[-->` +
+          `<!--[--><div>bar</div>-bar<!--]-->` +
+          `<!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('nested consecutive fragment components with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested consecutive fragment components with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><components.Parent/></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<!--[[-->` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<!--[[-->` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<!--]]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<!--[[-->` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<!--[[-->` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<!--]]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('nested consecutive fragment components with multi level anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested consecutive fragment components with multi level anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><span></span><components.Parent/><span></span></div></template>
       `,
-      {
-        Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
-        Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+        {
+          Parent: `<template><div><span/><components.Child/><components.Child/><span/></div></template>`,
+          Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<!--[[-->` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<!--]]-->` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<!--[[-->` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<!--]]-->` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
         `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<!--[[-->` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<!--]]-->` +
-        `<span></span>` +
-        `</div>` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+          `<span></span>` +
+          `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<!--[[-->` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<!--]]-->` +
+          `<span></span>` +
+          `</div>` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('nested consecutive fragment components with root level anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `
+    test('nested consecutive fragment components with root level anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `
       <template><div><span></span><components.Parent/><span></span></div></template>
       `,
-      {
-        Parent: `<template><components.Child/><components.Child/></template>`,
-        Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[-->` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<!--[--><div>foo</div>-foo-<!--]-->` +
-        `<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[-->` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<!--[--><div>bar</div>-bar-<!--]-->` +
-        `<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Parent: `<template><components.Child/><components.Child/></template>`,
+          Child: `<template><div>{{ data }}</div>-{{ data }}-</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[-->` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<!--[--><div>foo</div>-foo-<!--]-->` +
+          `<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[-->` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<!--[--><div>bar</div>-bar-<!--]-->` +
+          `<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('mixed fragment component and element with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('mixed fragment component and element with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -793,36 +797,36 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo<!--]-->` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar<!--]-->` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-  })
+        {
+          Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo<!--]-->` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar<!--]-->` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
 
-  test('mixed fragment component and text with anchor insertion', async () => {
-    const { container, data } = await testHydration(
-      `<template>
+    test('mixed fragment component and text with anchor insertion', async () => {
+      const { container, data } = await testHydration(
+        `<template>
         <div>
           <span/>
           <components.Child/>
@@ -832,31 +836,32 @@ describe('Vapor Mode hydration', () => {
         </div>
       </template>
       `,
-      {
-        Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
-      },
-    )
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>foo</div>-foo<!--]-->` +
-        ` <!--[[--> foo <!--]]--> ` +
-        `<!--[--><div>foo</div>-foo<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
-
-    data.value = 'bar'
-    await nextTick()
-    expect(container.innerHTML).toBe(
-      `<div>` +
-        `<span></span>` +
-        `<!--[--><div>bar</div>-bar<!--]-->` +
-        ` <!--[[--> bar <!--]]--> ` +
-        `<!--[--><div>bar</div>-bar<!--]-->` +
-        `<span></span>` +
-        `</div>`,
-    )
+        {
+          Child: `<template><div>{{ data }}</div>-{{ data }}</template>`,
+        },
+      )
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>foo</div>-foo<!--]-->` +
+          ` <!--[[--> foo <!--]]--> ` +
+          `<!--[--><div>foo</div>-foo<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+
+      data.value = 'bar'
+      await nextTick()
+      expect(container.innerHTML).toBe(
+        `<div>` +
+          `<span></span>` +
+          `<!--[--><div>bar</div>-bar<!--]-->` +
+          ` <!--[[--> bar <!--]]--> ` +
+          `<!--[--><div>bar</div>-bar<!--]-->` +
+          `<span></span>` +
+          `</div>`,
+      )
+    })
   })
 
   describe('if', () => {
@@ -869,7 +874,7 @@ describe('Vapor Mode hydration', () => {
     })
 
     function runTests(isProd: boolean = false) {
-      const anchorLabel = isProd ? '$' : 'if'
+      const anchorLabel = IF_ANCHOR_LABEL
 
       test('basic toggle - true -> false', async () => {
         runWithEnv(isProd, async () => {
@@ -893,9 +898,6 @@ describe('Vapor Mode hydration', () => {
 
       test('basic toggle - false -> true', async () => {
         runWithEnv(isProd, async () => {
-          // v-if="false" is rendered as <!----> in the server-rendered HTML
-          // it reused as anchor, so the anchor label is empty in PROD
-          let anchorLabel = isProd ? '' : 'if'
           if (isProd) __DEV__ = false
           const data = ref(false)
           const { container } = await testHydration(
@@ -905,13 +907,13 @@ describe('Vapor Mode hydration', () => {
             undefined,
             data,
           )
-          expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
+          // v-if="false" is rendered as <!----> in the server-rendered HTML
+          // it reused as anchor, so the anchor label is empty
+          expect(container.innerHTML).toBe(`<!---->`)
 
           data.value = true
           await nextTick()
-          expect(container.innerHTML).toBe(
-            `<div>foo</div><!--${anchorLabel}-->`,
-          )
+          expect(container.innerHTML).toBe(`<div>foo</div><!---->`)
         })
       })
 
@@ -1160,10 +1162,9 @@ describe('Vapor Mode hydration', () => {
         })
       })
 
-      // problem is there is a continuous `<!--$-->`
-      test.todo('on dynamic component with anchor insertion', async () => {
+      test('on dynamic component with anchor insertion', async () => {
         runWithEnv(isProd, async () => {
-          const dynamicComponentAnchorLabel = isProd ? '$' : 'dynamic-component'
+          const dynamicComponentAnchorLabel = DYNAMIC_COMPONENT_ANCHOR_LABEL
           const data = ref(true)
           const { container } = await testHydration(
             `<template>
@@ -1207,7 +1208,7 @@ describe('Vapor Mode hydration', () => {
     })
 
     function runTests(isProd: boolean = false) {
-      const anchorLabel = isProd ? '$' : 'dynamic-component'
+      const anchorLabel = DYNAMIC_COMPONENT_ANCHOR_LABEL
 
       test('basic dynamic component', async () => {
         runWithEnv(isProd, async () => {
index db690184292e06e5665d52cfed106c0d34a362de..e272f0d225a26230eb168239417a476a8644edef 100644 (file)
@@ -9,7 +9,8 @@ import {
   insertionParent,
   resetInsertionState,
 } from './insertionState'
-import { isHydrating, locateHydrationNode } from './dom/hydration'
+import { isHydrating } from './dom/hydration'
+import { DYNAMIC_COMPONENT_ANCHOR_LABEL } from '@vue/shared'
 
 export function createDynamicComponent(
   getter: () => any,
@@ -19,15 +20,9 @@ export function createDynamicComponent(
 ): VaporFragment {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  if (isHydrating) {
-    locateHydrationNode(true)
-  } else {
-    resetInsertionState()
-  }
+  if (!isHydrating) resetInsertionState()
 
-  const frag = __DEV__
-    ? new DynamicFragment('dynamic-component')
-    : new DynamicFragment()
+  const frag = new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
   renderEffect(() => {
     const value = getter()
     frag.update(
index 1e81d3745a743917d38e335f3f29aaedadce3577..bec4a3504df51fddba144dbb7490dd7024cb12f9 100644 (file)
@@ -1,6 +1,11 @@
+import { IF_ANCHOR_LABEL } from '@vue/shared'
 import { type Block, type BlockFn, DynamicFragment, insert } from './block'
-import { isHydrating, locateHydrationNode } from './dom/hydration'
-import { insertionAnchor, insertionParent } from './insertionState'
+import { isHydrating } from './dom/hydration'
+import {
+  insertionAnchor,
+  insertionParent,
+  resetInsertionState,
+} from './insertionState'
 import { renderEffect } from './renderEffect'
 
 export function createIf(
@@ -11,15 +16,13 @@ export function createIf(
 ): Block {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  if (isHydrating) {
-    locateHydrationNode(true)
-  }
+  if (!isHydrating) resetInsertionState()
 
   let frag: Block
   if (once) {
     frag = condition() ? b1() : b2 ? b2() : []
   } else {
-    frag = __DEV__ ? new DynamicFragment('if') : new DynamicFragment()
+    frag = new DynamicFragment(IF_ANCHOR_LABEL)
     renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
   }
 
index 254779c6457806eeafa10335cd38649224fee3be..c146bb10e8f020bfe4369777c368c439bacd84f0 100644 (file)
@@ -1,4 +1,4 @@
-import { isArray } from '@vue/shared'
+import { isArray, isDynamicFragmentEndAnchor } from '@vue/shared'
 import {
   type VaporComponentInstance,
   isVaporComponent,
@@ -7,8 +7,13 @@ import {
 } from './component'
 import { createComment, createTextNode, nextSiblingAnchor } from './dom/node'
 import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
-import { currentHydrationNode, isComment, isHydrating } from './dom/hydration'
-import { isDynamicFragmentEndAnchor, warn } from '@vue/runtime-dom'
+import {
+  currentHydrationNode,
+  isComment,
+  isHydrating,
+  locateHydrationNode,
+} from './dom/hydration'
+import { warn } from '@vue/runtime-dom'
 
 export type Block =
   | Node
@@ -39,7 +44,8 @@ export class DynamicFragment extends VaporFragment {
   constructor(anchorLabel?: string) {
     super([])
     if (isHydrating) {
-      this.hydrate(anchorLabel)
+      locateHydrationNode(true)
+      this.hydrate(anchorLabel!)
     } else {
       this.anchor =
         __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
@@ -81,15 +87,15 @@ export class DynamicFragment extends VaporFragment {
     resetTracking()
   }
 
-  hydrate(label?: string): void {
+  hydrate(label: string): void {
     // for v-if="false" the hydrationNode will be a empty comment node
     // use it as anchor.
     // otherwise, use the next sibling comment node as anchor
     if (isComment(currentHydrationNode!, '')) {
       this.anchor = currentHydrationNode
     } else {
-      // find next sibling `<!--$-->` as anchor
-      const anchor = nextSiblingAnchor(currentHydrationNode!, '$')!
+      // find next sibling dynamic fragment end anchor
+      const anchor = nextSiblingAnchor(currentHydrationNode!, label)!
       if (anchor && isDynamicFragmentEndAnchor(anchor)) {
         this.anchor = anchor
       } else if (__DEV__) {
@@ -97,7 +103,6 @@ export class DynamicFragment extends VaporFragment {
         warn(`DynamicFragment anchor not found...`)
       }
     }
-    if (__DEV__ && label && this.anchor) (this.anchor as Comment).data = label
   }
 }
 
index 4d4ba85c4c616f54083169703ccc0f3236f92dae..b0e8e7d5283d64979ecf3d4184f80924edf246b4 100644 (file)
@@ -1,4 +1,4 @@
-import { isDynamicFragmentEndAnchor, warn } from '@vue/runtime-dom'
+import { warn } from '@vue/runtime-dom'
 import {
   insertionAnchor,
   insertionParent,
@@ -12,6 +12,7 @@ import {
   next,
   prev,
 } from './node'
+import { isDynamicFragmentEndAnchor } from '@vue/shared'
 
 export let isHydrating = false
 export let currentHydrationNode: Node | null = null
index 1ea4831f3634e74d3bce89f77f28248b54224e57..2a546c69e2facc098b764fcc03ae0e312350e1b3 100644 (file)
@@ -1,10 +1,15 @@
-import { isDynamicAnchor } from '@vue/runtime-dom'
 import {
   isComment,
   isEmptyText,
   locateEndAnchor,
   locateStartAnchor,
 } from './hydration'
+import {
+  DYNAMIC_END_ANCHOR_LABEL,
+  DYNAMIC_START_ANCHOR_LABEL,
+  isDynamicAnchor,
+  isDynamicFragmentEndAnchor,
+} from '@vue/shared'
 
 /*! #__NO_SIDE_EFFECTS__ */
 export function createTextNode(value = ''): Text {
@@ -102,8 +107,12 @@ export function disableHydrationNodeLookup(): void {
 /*! #__NO_SIDE_EFFECTS__ */
 export function prev(node: Node): Node | null {
   // process dynamic node (<!--[[-->...<!--]]-->) as a single one
-  if (isComment(node, ']]')) {
-    node = locateStartAnchor(node, '[[', ']]')!
+  if (isComment(node, DYNAMIC_END_ANCHOR_LABEL)) {
+    node = locateStartAnchor(
+      node,
+      DYNAMIC_START_ANCHOR_LABEL,
+      DYNAMIC_END_ANCHOR_LABEL,
+    )!
   }
 
   // process fragment node (<!--[-->...<!--]-->) as a single one
@@ -122,21 +131,12 @@ function isNonHydrationNode(node: Node) {
   return (
     // empty text nodes, no need to hydrate
     isEmptyText(node) ||
-    // dynamic anchors (<!--[[-->, <!--]]-->)
+    // dynamic node anchors (<!--[[-->, <!--]]-->)
     isDynamicAnchor(node) ||
     // fragment end anchor (`<!--]-->`)
     isComment(node, ']') ||
-    // dynamic fragment anchors
-    (__DEV__
-      ? // v-if anchor (`<!--if-->`)
-        isComment(node, 'if') ||
-        // v-for anchor (`<!--for-->`)
-        isComment(node, 'for') ||
-        // v-slot anchor (`<!--slot-->`)
-        isComment(node, 'slot') ||
-        // dynamic-component anchor (`<!--dynamic-component-->`)
-        isComment(node, 'dynamic-component')
-      : isComment(node, '$'))
+    // dynamic fragment end anchors
+    isDynamicFragmentEndAnchor(node)
   )
 }
 
@@ -157,8 +157,12 @@ export function nextSiblingAnchor(
 
 function handleWrappedNode(node: Node): Node {
   // process dynamic node (<!--[[-->...<!--]]-->) as a single one
-  if (isComment(node, '[[')) {
-    return locateEndAnchor(node, '[[', ']]')!
+  if (isComment(node, DYNAMIC_START_ANCHOR_LABEL)) {
+    return locateEndAnchor(
+      node,
+      DYNAMIC_START_ANCHOR_LABEL,
+      DYNAMIC_END_ANCHOR_LABEL,
+    )!
   }
 
   // process fragment (<!--[-->...<!--]-->) as a single one
index 471a48edbdd0eba59dee91c71a51fe4a1bc8cbb8..cbf13db254ee31e6c30b498b8edcada84ddfb2df 100644 (file)
@@ -25,7 +25,7 @@ describe('ssr: attr fallthrough', () => {
       template: `<child :ok="ok" class="bar"/>`,
     }
     expect(await renderToString(createApp(Parent, { ok: true }))).toBe(
-      `<div class="foo bar"></div><!--$-->`,
+      `<div class="foo bar"></div><!--if-->`,
     )
     expect(await renderToString(createApp(Parent, { ok: false }))).toBe(
       `<span class="bar"></span>`,
index 0679c82168bea003edaee2a15bb3383afdf95168..b685fbfe1abe113acc8b0151871deefa5c3a7f7d 100644 (file)
@@ -14,7 +14,9 @@ describe('ssr: dynamic component', () => {
           template: `<component :is="'one'"><span>slot</span></component>`,
         }),
       ),
-    ).toBe(`<div><!--[--><span>slot</span><!--]--></div>`)
+    ).toBe(
+      `<div><!--[--><span>slot</span><!--]--></div><!--dynamic-component-->`,
+    )
   })
 
   test('resolved to component with v-show', async () => {
@@ -30,7 +32,7 @@ describe('ssr: dynamic component', () => {
         }),
       ),
     ).toBe(
-      `<div><!--[--><div style=\"display:none;\"><!--[-->hi<!--]--></div><!--]--></div>`,
+      `<div><!--[--><div style="display:none;"><!--[-->hi<!--]--></div><!--dynamic-component--><!--]--></div><!--dynamic-component-->`,
     )
   })
 
@@ -41,7 +43,7 @@ describe('ssr: dynamic component', () => {
           template: `<component :is="'p'"><span>slot</span></component>`,
         }),
       ),
-    ).toBe(`<p><span>slot</span></p>`)
+    ).toBe(`<p><span>slot</span></p><!--dynamic-component-->`)
   })
 
   test('resolve to component vnode', async () => {
@@ -60,7 +62,9 @@ describe('ssr: dynamic component', () => {
           template: `<component :is="vnode"><span>slot</span></component>`,
         }),
       ),
-    ).toBe(`<div>test<!--[--><span>slot</span><!--]--></div>`)
+    ).toBe(
+      `<div>test<!--[--><span>slot</span><!--]--></div><!--dynamic-component-->`,
+    )
   })
 
   test('resolve to element vnode', async () => {
@@ -75,6 +79,6 @@ describe('ssr: dynamic component', () => {
           template: `<component :is="vnode"><span>slot</span></component>`,
         }),
       ),
-    ).toBe(`<div id="test"><span>slot</span></div>`)
+    ).toBe(`<div id="test"><span>slot</span></div><!--dynamic-component-->`)
   })
 })
index 7f104e4246a3c1bb5585048bbfc5ae2d23fcc961..0e3e3535641d73fb28627a5bc7cb28ec1b45bac6 100644 (file)
@@ -94,7 +94,7 @@ describe('ssr: slot', () => {
           template: `<one><template v-if="true">hello</template></one>`,
         }),
       ),
-    ).toBe(`<div><!--[--><!--[-->hello<!--]--><!--$--><!--]--></div>`)
+    ).toBe(`<div><!--[--><!--[-->hello<!--]--><!--if--><!--]--></div>`)
   })
 
   test('fragment slot (template v-if + multiple elements)', async () => {
@@ -106,7 +106,7 @@ describe('ssr: slot', () => {
         }),
       ),
     ).toBe(
-      `<div><!--[--><!--[--><div>one</div><div>two</div><!--]--><!--$--><!--]--></div>`,
+      `<div><!--[--><!--[--><div>one</div><div>two</div><!--]--><!--if--><!--]--></div>`,
     )
   })
 
@@ -135,7 +135,7 @@ describe('ssr: slot', () => {
           template: `<one><div v-if="true">foo</div></one>`,
         }),
       ),
-    ).toBe(`<div>foo</div><!--$-->`)
+    ).toBe(`<div>foo</div><!--if-->`)
   })
 
   // #9933
@@ -170,7 +170,9 @@ describe('ssr: slot', () => {
           template: `<ButtonComp><Wrap><div v-if="false">hello</div></Wrap></ButtonComp>`,
         }),
       ),
-    ).toBe(`<button><!--[--><div><!--[--><!--]--></div><!--]--></button>`)
+    ).toBe(
+      `<button><!--[--><div><!--[--><!--]--></div><!--]--></button><!--dynamic-component-->`,
+    )
 
     expect(
       await renderToString(
@@ -187,7 +189,7 @@ describe('ssr: slot', () => {
         }),
       ),
     ).toBe(
-      `<button><!--[--><div><!--[--><div>hello</div><!--]--></div><!--]--></button>`,
+      `<button><!--[--><div><!--[--><div>hello</div><!--]--></div><!--]--></button><!--dynamic-component-->`,
     )
 
     expect(
@@ -201,6 +203,6 @@ describe('ssr: slot', () => {
           template: `<ButtonComp><template v-if="false">hello</template></ButtonComp>`,
         }),
       ),
-    ).toBe(`<button><!--[--><!--]--></button>`)
+    ).toBe(`<button><!--[--><!--]--></button><!--dynamic-component-->`)
   })
 })
index f4775bb9d505c9a84cc30d0e2a5288f55773e5aa..221d3895e2288f80699d918f333d3631498825f2 100644 (file)
@@ -264,7 +264,6 @@ export function renderVNode(
         renderElementVNode(push, vnode, parentComponent, slotScopeId)
       } else if (shapeFlag & ShapeFlags.COMPONENT) {
         push(renderComponentVNode(vnode, parentComponent, slotScopeId))
-        push(`<!--$-->`) // anchor for vapor hydration
       } else if (shapeFlag & ShapeFlags.TELEPORT) {
         renderTeleportVNode(push, vnode, parentComponent, slotScopeId)
       } else if (shapeFlag & ShapeFlags.SUSPENSE) {
diff --git a/packages/shared/src/domAnchors.ts b/packages/shared/src/domAnchors.ts
new file mode 100644 (file)
index 0000000..ae75a97
--- /dev/null
@@ -0,0 +1,30 @@
+export const DYNAMIC_START_ANCHOR_LABEL = '[['
+export const DYNAMIC_END_ANCHOR_LABEL = ']]'
+
+export const IF_ANCHOR_LABEL: string = __DEV__ ? 'if' : '$'
+export const DYNAMIC_COMPONENT_ANCHOR_LABEL: string = __DEV__
+  ? 'dynamic-component'
+  : '$2'
+export const FOR_ANCHOR_LABEL: string = __DEV__ ? 'for' : '$3'
+export const SLOT_ANCHOR_LABEL: string = __DEV__ ? 'slot' : '$4'
+
+export function isDynamicAnchor(node: Node): node is Comment {
+  if (node.nodeType !== 8) return false
+
+  const data = (node as Comment).data
+  return (
+    data === DYNAMIC_START_ANCHOR_LABEL || data === DYNAMIC_END_ANCHOR_LABEL
+  )
+}
+
+export function isDynamicFragmentEndAnchor(node: Node): node is Comment {
+  if (node.nodeType !== 8) return false
+
+  const data = (node as Comment).data
+  return (
+    data === IF_ANCHOR_LABEL ||
+    data === FOR_ANCHOR_LABEL ||
+    data === SLOT_ANCHOR_LABEL ||
+    data === DYNAMIC_COMPONENT_ANCHOR_LABEL
+  )
+}
index dc56faf8cf582f6c206e5eab895cb1fcd77c4ea0..674bcdf96cd70277532ff858ff7d281af70ced5a 100644 (file)
@@ -13,3 +13,4 @@ export * from './looseEqual'
 export * from './toDisplayString'
 export * from './typeUtils'
 export * from './subSequence'
+export * from './domAnchors'