From: daiwei Date: Fri, 20 Jun 2025 01:35:45 +0000 (+0800) Subject: chore: Merge branch 'vapor' into edison/testVapor X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a253aa6935d542f2b19e8822a6a0eea73d91af71;p=thirdparty%2Fvuejs%2Fcore.git chore: Merge branch 'vapor' into edison/testVapor --- a253aa6935d542f2b19e8822a6a0eea73d91af71 diff --cc packages/compiler-vapor/src/ir/index.ts index 64d9abf1b8,18f0139ab5..a1a6e5085d --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@@ -54,9 -52,7 +54,8 @@@ export interface BlockIRNode extends Ba tempId: number effect: IREffect[] operation: OperationNode[] - expressions: SimpleExpressionNode[] returns: number[] + hasDeferredVShow: boolean } export interface RootIRNode { diff --cc packages/compiler-vapor/src/transforms/utils.ts index 41a65b9dd3,f7d0594fe5..30fdcbc414 --- a/packages/compiler-vapor/src/transforms/utils.ts +++ b/packages/compiler-vapor/src/transforms/utils.ts @@@ -30,9 -29,7 +30,8 @@@ export const newBlock = (node: BlockIRN effect: [], operation: [], returns: [], - expressions: [], tempId: 0, + hasDeferredVShow: false, }) export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode { diff --cc packages/compiler-vapor/src/transforms/vSlot.ts index df8fb6a5a6,3e78913a23..05790825a2 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@@ -23,12 -23,8 +23,13 @@@ import type SlotBlockIRNode, type VaporDirectiveNode, } from '../ir' -import { findDir, resolveExpression } from '../utils' +import { + findDir, + findProp, + isTransitionNode, + resolveExpression, +} from '../utils' + import { markNonTemplate } from './transformText' export const transformVSlot: NodeTransform = (node, context) => { if (node.type !== NodeTypes.ELEMENT) return @@@ -71,24 -67,23 +72,33 @@@ function transformComponentSlot ) { const { children } = node const arg = dir && dir.arg - const nonSlotTemplateChildren = children.filter( - n => - isNonWhitespaceContent(node) && - !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)), - ) - + // whitespace: 'preserve' + const emptyTextNodes: TemplateChildNode[] = [] + const nonSlotTemplateChildren = children.filter(n => { + if (isNonWhitespaceContent(n)) { + return !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)) + } else { + emptyTextNodes.push(n) + } + }) + if (!nonSlotTemplateChildren.length) { + emptyTextNodes.forEach(n => { + markNonTemplate(n, context) + }) + } - const [block, onExit] = createSlotBlock(node, dir, context) + let slotKey - if (isTransitionNode(node)) { ++ if (isTransitionNode(node) && nonSlotTemplateChildren.length) { + const keyProp = findProp( + nonSlotTemplateChildren[0] as ElementNode, + 'key', + ) as VaporDirectiveNode + if (keyProp) { + slotKey = keyProp.exp + } + } + + const [block, onExit] = createSlotBlock(node, dir, context, slotKey) const { slots } = context diff --cc packages/runtime-core/src/index.ts index 889ab132b6,1ed6f21df7..cbbe04772c --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@@ -562,11 -557,7 +562,15 @@@ export { startMeasure, endMeasure } fro * @internal */ export { initFeatureFlags } from './featureFlags' +/** + * @internal + */ +export { performTransitionEnter, performTransitionLeave } from './renderer' +/** + * @internal + */ +export { ensureVaporSlotFallback } from './helpers/renderSlot' + /** + * @internal + */ + export { createInternalObject } from './internalObject' diff --cc packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts index 87dc922ddf,e912af2851..99de869e21 --- a/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts +++ b/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts @@@ -79,4 -80,34 +80,52 @@@ describe('api: createDynamicComponent' mount() expect(html()).toBe('
') }) + + test('switch dynamic component children', async () => { + const CompA = defineVaporComponent({ + setup() { + return template('
A
')() + }, + }) + const CompB = defineVaporComponent({ + setup() { + return template('
B
')() + }, + }) + + const current = shallowRef(CompA) + const { html } = define({ + setup() { + const t1 = template('
') + const n2 = t1() as any + setInsertionState(n2) + createDynamicComponent(() => current.value) + return n2 + }, + }).render() + + expect(html()).toBe('
A
') + + current.value = CompB + await nextTick() + expect(html()).toBe('
B
') + }) ++ ++ test('render fallback with insertionState', async () => { ++ const { html, mount } = define({ ++ setup() { ++ const html = ref('hi') ++ const n1 = template('
', true)() as any ++ setInsertionState(n1) ++ const n0 = createComponentWithFallback( ++ resolveDynamicComponent('button') as any, ++ ) as any ++ renderEffect(() => setHtml(n0, html.value)) ++ return n1 ++ }, ++ }).create() ++ ++ mount() ++ expect(html()).toBe('
') ++ }) }) diff --cc packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 9bd4993f83,945e0f38d8..522fd43c23 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@@ -9,8 -9,7 +9,8 @@@ import insertionParent, resetInsertionState, } from './insertionState' - import { isHydrating } from './dom/hydration' +import { DYNAMIC_COMPONENT_ANCHOR_LABEL } from '@vue/shared' + import { isHydrating, locateHydrationNode } from './dom/hydration' export function createDynamicComponent( getter: () => any, @@@ -20,16 -19,18 +20,21 @@@ ): VaporFragment { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor - if (!isHydrating) resetInsertionState() + if (isHydrating) { + locateHydrationNode() + } else { + resetInsertionState() + } - const frag = __DEV__ - ? new DynamicFragment('dynamic-component') - : new DynamicFragment() + const frag = + isHydrating || __DEV__ + ? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL) + : new DynamicFragment() + renderEffect(() => { const value = getter() + const appContext = + (currentInstance && currentInstance.appContext) || emptyContext frag.update( () => createComponentWithFallback( diff --cc packages/runtime-vapor/src/vdomInterop.ts index 236836b4c9,b916a2c8eb..e80e359427 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@@ -2,27 -2,20 +2,29 @@@ import type App, type ComponentInternalInstance, type ConcreteComponent, + type HydrationRenderer, MoveType, type Plugin, + type RendererElement, type RendererInternals, + type RendererNode, type ShallowRef, type Slots, + type TransitionHooks, type VNode, type VaporInteropInterface, + createInternalObject, createVNode, currentInstance, + ensureHydrationRenderer, ensureRenderer, + ensureVaporSlotFallback, + isEmitListener, + isVNode, onScopeDispose, renderSlot, + setTransitionHooks as setVNodeTransitionHooks, + shallowReactive, shallowRef, simpleSetCurrentInstance, } from '@vue/runtime-dom' @@@ -229,16 -163,15 +231,23 @@@ function createVDOMComponent // overwrite how the vdom instance handles props vnode.vi = (instance: ComponentInternalInstance) => { - instance.props = wrapper.props + // Ensure props are shallow reactive to align with VDOM behavior. + // This enables direct watching of props and prevents DEV warnings. + // + // Example: + // const props = defineProps(...) + // watch(props, () => { ... }) // props must be reactive for this to work + // + // Without reactivity, Vue will warn in DEV about non-reactive watch sources + instance.props = shallowReactive(wrapper.props) - instance.attrs = wrapper.attrs + + const attrs = (instance.attrs = createInternalObject()) + for (const key in wrapper.attrs) { + if (!isEmitListener(instance.emitsOptions, key)) { + attrs[key] = wrapper.attrs[key] + } + } + instance.slots = wrapper.slots === EMPTY_OBJ ? EMPTY_OBJ