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

This commit is contained in:
edison 2025-06-18 09:01:59 +08:00 committed by GitHub
parent 99482a4ddf
commit 99a8c6d34b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1536 additions and 1588 deletions

View File

@ -211,38 +211,42 @@ color: red
expect( expect(
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`), compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
".div[data-v-test] { color: red; ".div[data-v-test] { color: red;
} }
.div[data-v-test]:where(:hover) { color: blue; .div[data-v-test]:where(:hover) { color: blue;
}"`) }"
`)
expect( expect(
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`), compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
".div[data-v-test] { color: red; ".div[data-v-test] { color: red;
} }
.div[data-v-test]:is(:hover) { color: blue; .div[data-v-test]:is(:hover) { color: blue;
}"`) }"
`)
expect( expect(
compileScoped( compileScoped(
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`, `.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
), ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
".div[data-v-test] { color: red; ".div[data-v-test] { color: red;
} }
.div[data-v-test]:where(.foo:hover) { color: blue; .div[data-v-test]:where(.foo:hover) { color: blue;
}"`) }"
`)
expect( expect(
compileScoped( compileScoped(
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`, `.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
), ),
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
".div[data-v-test] { color: red; ".div[data-v-test] { color: red;
} }
.div[data-v-test]:is(.foo:hover) { color: blue; .div[data-v-test]:is(.foo:hover) { color: blue;
}"`) }"
`)
}) })
test('media query', () => { test('media query', () => {

View File

@ -44,7 +44,7 @@ export function genOperationWithInsertionState(
): CodeFragment[] { ): CodeFragment[] {
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()
if (isBlockOperation(oper) && oper.parent) { if (isBlockOperation(oper) && oper.parent) {
push(...genInsertionstate(oper, context)) push(...genInsertionState(oper, context))
} }
push(...genOperation(oper, context)) push(...genOperation(oper, context))
return frag return frag
@ -152,7 +152,7 @@ export function genEffect(
return frag return frag
} }
function genInsertionstate( function genInsertionState(
operation: InsertionStateTypes, operation: InsertionStateTypes,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {

View File

@ -23,7 +23,11 @@ import type { DynamicSlot } from './componentSlots'
import { renderEffect } from './renderEffect' import { renderEffect } from './renderEffect'
import { VaporVForFlags } from '../../shared/src/vaporFlags' import { VaporVForFlags } from '../../shared/src/vaporFlags'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
import { insertionAnchor, insertionParent } from './insertionState' import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'
class ForBlock extends VaporFragment { class ForBlock extends VaporFragment {
scope: EffectScope | undefined scope: EffectScope | undefined
@ -72,6 +76,8 @@ export const createFor = (
const _insertionAnchor = insertionAnchor const _insertionAnchor = insertionAnchor
if (isHydrating) { if (isHydrating) {
locateHydrationNode() locateHydrationNode()
} else {
resetInsertionState()
} }
let isMounted = false let isMounted = false

View File

@ -1,6 +1,10 @@
import { type Block, type BlockFn, DynamicFragment, insert } from './block' import { type Block, type BlockFn, DynamicFragment, insert } from './block'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
import { insertionAnchor, insertionParent } from './insertionState' import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'
import { renderEffect } from './renderEffect' import { renderEffect } from './renderEffect'
export function createIf( export function createIf(
@ -13,6 +17,8 @@ export function createIf(
const _insertionAnchor = insertionAnchor const _insertionAnchor = insertionAnchor
if (isHydrating) { if (isHydrating) {
locateHydrationNode() locateHydrationNode()
} else {
resetInsertionState()
} }
let frag: Block let frag: Block

View File

@ -59,7 +59,11 @@ import {
} from './componentSlots' } from './componentSlots'
import { hmrReload, hmrRerender } from './hmr' import { hmrReload, hmrRerender } from './hmr'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
import { insertionAnchor, insertionParent } from './insertionState' import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'
export { currentInstance } from '@vue/runtime-dom' export { currentInstance } from '@vue/runtime-dom'
@ -142,6 +146,8 @@ export function createComponent(
const _insertionAnchor = insertionAnchor const _insertionAnchor = insertionAnchor
if (isHydrating) { if (isHydrating) {
locateHydrationNode() locateHydrationNode()
} else {
resetInsertionState()
} }
// vdom interop enabled and component is not an explicit vapor component // vdom interop enabled and component is not an explicit vapor component

View File

@ -4,7 +4,11 @@ import { rawPropsProxyHandlers } from './componentProps'
import { currentInstance, isRef } from '@vue/runtime-dom' import { currentInstance, isRef } from '@vue/runtime-dom'
import type { LooseRawProps, VaporComponentInstance } from './component' import type { LooseRawProps, VaporComponentInstance } from './component'
import { renderEffect } from './renderEffect' import { renderEffect } from './renderEffect'
import { insertionAnchor, insertionParent } from './insertionState' import {
insertionAnchor,
insertionParent,
resetInsertionState,
} from './insertionState'
import { isHydrating, locateHydrationNode } from './dom/hydration' import { isHydrating, locateHydrationNode } from './dom/hydration'
export type RawSlots = Record<string, VaporSlot> & { export type RawSlots = Record<string, VaporSlot> & {
@ -96,6 +100,8 @@ export function createSlot(
const _insertionAnchor = insertionAnchor const _insertionAnchor = insertionAnchor
if (isHydrating) { if (isHydrating) {
locateHydrationNode() locateHydrationNode()
} else {
resetInsertionState()
} }
const instance = currentInstance as VaporComponentInstance const instance = currentInstance as VaporComponentInstance

View File

@ -49,7 +49,7 @@ test('pipeToWebWritable', async () => {
} }
const { readable, writable } = new TransformStream() const { readable, writable } = new TransformStream()
pipeToWebWritable(createApp(App), {}, writable) pipeToWebWritable(createApp(App), {}, writable as any)
const reader = readable.getReader() const reader = readable.getReader()
const decoder = new TextDecoder() const decoder = new TextDecoder()

File diff suppressed because it is too large Load Diff