wip: refactor

This commit is contained in:
daiwei 2025-04-25 15:08:19 +08:00
parent 34b9a4b5cd
commit aad75fd7c4
25 changed files with 760 additions and 705 deletions

View 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(\`<!---->\`)
}

View File

@ -29,7 +29,7 @@ describe('ssr: attrs fallthrough', () => {
_push(\`<!--[-->\`)
if (true) {
_push(\`<div></div>\`)
_push(\`<!--$-->\`)
_push(\`<!--if-->\`)
} else {
_push(\`<!---->\`)
}

View 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)

View 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(\`<!---->\`)
}

View 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(\`<!--]-->\`)
}"

View 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>\`)
}

View 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(\`<!---->\`)
}

View 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}-->`)
}
}

View 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}-->`)
}
}
}

View 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
}

View 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('')
})

View 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)
}

View File

@ -557,7 +557,3 @@ export { startMeasure, endMeasure } from './profiling'
* @internal
*/
export { initFeatureFlags } from './featureFlags'
/**
* @internal
*/
export { isDynamicAnchor, isDynamicFragmentEndAnchor } from './hydration'

File diff suppressed because it is too large Load Diff

View 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(

View 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))
}

View 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
}
}

View 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

View 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

View 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>`,

View 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-->`)
})
})

View 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-->`)
})
})

View 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) {

View File

@ -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
)
}

View File

@ -13,3 +13,4 @@ export * from './looseEqual'
export * from './toDisplayString'
export * from './typeUtils'
export * from './subSequence'
export * from './domAnchors'