Skip to content

fix(runtime-vapor): reset insertion state to avoid duplicate block inserts during non-hydration #13220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 18, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions packages/compiler-sfc/__tests__/compileStyle.spec.ts
Original file line number Diff line number Diff line change
@@ -211,38 +211,42 @@ color: red
expect(
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
).toMatchInlineSnapshot(`
".div[data-v-test] { color: red;
}
.div[data-v-test]:where(:hover) { color: blue;
}"`)
".div[data-v-test] { color: red;
}
.div[data-v-test]:where(:hover) { color: blue;
}"
`)

expect(
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
).toMatchInlineSnapshot(`
".div[data-v-test] { color: red;
}
.div[data-v-test]:is(:hover) { color: blue;
}"`)
".div[data-v-test] { color: red;
}
.div[data-v-test]:is(:hover) { color: blue;
}"
`)

expect(
compileScoped(
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
),
).toMatchInlineSnapshot(`
".div[data-v-test] { color: red;
}
.div[data-v-test]:where(.foo:hover) { color: blue;
}"`)
".div[data-v-test] { color: red;
}
.div[data-v-test]:where(.foo:hover) { color: blue;
}"
`)

expect(
compileScoped(
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
),
).toMatchInlineSnapshot(`
".div[data-v-test] { color: red;
}
.div[data-v-test]:is(.foo:hover) { color: blue;
}"`)
".div[data-v-test] { color: red;
}
.div[data-v-test]:is(.foo:hover) { color: blue;
}"
`)
})

test('media query', () => {
4 changes: 2 additions & 2 deletions packages/compiler-vapor/src/generators/operation.ts
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ export function genOperationWithInsertionState(
): CodeFragment[] {
const [frag, push] = buildCodeFragment()
if (isBlockOperation(oper) && oper.parent) {
push(...genInsertionstate(oper, context))
push(...genInsertionState(oper, context))
}
push(...genOperation(oper, context))
return frag
@@ -152,7 +152,7 @@ export function genEffect(
return frag
}

function genInsertionstate(
function genInsertionState(
operation: InsertionStateTypes,
context: CodegenContext,
): CodeFragment[] {
8 changes: 7 additions & 1 deletion packages/runtime-vapor/src/apiCreateFor.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,11 @@ import type { DynamicSlot } from './componentSlots'
import { renderEffect } from './renderEffect'
import { VaporVForFlags } from '../../shared/src/vaporFlags'
import { isHydrating, locateHydrationNode } from './dom/hydration'
import { insertionAnchor, insertionParent } from './insertionState'
import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'

class ForBlock extends VaporFragment {
scope: EffectScope | undefined
@@ -72,6 +76,8 @@ export const createFor = (
const _insertionAnchor = insertionAnchor
if (isHydrating) {
locateHydrationNode()
} else {
resetInsertionState()
}

let isMounted = false
8 changes: 7 additions & 1 deletion packages/runtime-vapor/src/apiCreateIf.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { type Block, type BlockFn, DynamicFragment, insert } from './block'
import { isHydrating, locateHydrationNode } from './dom/hydration'
import { insertionAnchor, insertionParent } from './insertionState'
import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'
import { renderEffect } from './renderEffect'

export function createIf(
@@ -13,6 +17,8 @@ export function createIf(
const _insertionAnchor = insertionAnchor
if (isHydrating) {
locateHydrationNode()
} else {
resetInsertionState()
}

let frag: Block
8 changes: 7 additions & 1 deletion packages/runtime-vapor/src/component.ts
Original file line number Diff line number Diff line change
@@ -59,7 +59,11 @@ import {
} from './componentSlots'
import { hmrReload, hmrRerender } from './hmr'
import { isHydrating, locateHydrationNode } from './dom/hydration'
import { insertionAnchor, insertionParent } from './insertionState'
import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'

export { currentInstance } from '@vue/runtime-dom'

@@ -142,6 +146,8 @@ export function createComponent(
const _insertionAnchor = insertionAnchor
if (isHydrating) {
locateHydrationNode()
} else {
resetInsertionState()
}

// vdom interop enabled and component is not an explicit vapor component
8 changes: 7 additions & 1 deletion packages/runtime-vapor/src/componentSlots.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,11 @@ import { rawPropsProxyHandlers } from './componentProps'
import { currentInstance, isRef } from '@vue/runtime-dom'
import type { LooseRawProps, VaporComponentInstance } from './component'
import { renderEffect } from './renderEffect'
import { insertionAnchor, insertionParent } from './insertionState'
import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'
import { isHydrating, locateHydrationNode } from './dom/hydration'

export type RawSlots = Record<string, VaporSlot> & {
@@ -96,6 +100,8 @@ export function createSlot(
const _insertionAnchor = insertionAnchor
if (isHydrating) {
locateHydrationNode()
} else {
resetInsertionState()
}

const instance = currentInstance as VaporComponentInstance
2 changes: 1 addition & 1 deletion packages/server-renderer/__tests__/webStream.spec.ts
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ test('pipeToWebWritable', async () => {
}

const { readable, writable } = new TransformStream()
pipeToWebWritable(createApp(App), {}, writable)
pipeToWebWritable(createApp(App), {}, writable as any)

const reader = readable.getReader()
const decoder = new TextDecoder()
Loading
Oops, something went wrong.