From: daiwei Date: Mon, 16 Jun 2025 02:44:54 +0000 (+0800) Subject: chore: Merge branch 'edison/feat/vaporTransition' into edison/testVapor X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ba08c5458b75a7bfa155a9bd0ef3dfaab127a3a8;p=thirdparty%2Fvuejs%2Fcore.git chore: Merge branch 'edison/feat/vaporTransition' into edison/testVapor --- ba08c5458b75a7bfa155a9bd0ef3dfaab127a3a8 diff --cc packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap index 0000000000,2cb5c36e41..8a83143b9b mode 000000,100644..100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap @@@ -1,0 -1,128 +1,128 @@@ + // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + + exports[`compiler: transition > basic 1`] = ` + "import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue'; + const t0 = _template("

foo

") + + export function render(_ctx) { + const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, { + "default": () => { + const n0 = t0() + _applyVShow(n0, () => (_ctx.show)) + return n0 + } + }, true) + return n1 + }" + `; + + exports[`compiler: transition > inject persisted when child has v-show 1`] = ` + "import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue'; + const t0 = _template("
") + + export function render(_ctx) { + const n1 = _createComponent(_VaporTransition, { persisted: () => ("") }, { + "default": () => { + const n0 = t0() + _applyVShow(n0, () => (_ctx.ok)) + return n0 + } + }, true) + return n1 + }" + `; + + exports[`compiler: transition > the v-if/else-if/else branches in Transition should ignore comments 1`] = ` -"import { VaporTransition as _VaporTransition, createIf as _createIf, prepend as _prepend, createComponent as _createComponent, template as _template } from 'vue'; ++"import { VaporTransition as _VaporTransition, setInsertionState as _setInsertionState, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue'; + const t0 = _template("
hey
") + const t1 = _template("

") + const t2 = _template("
") + + export function render(_ctx) { + const n16 = _createComponent(_VaporTransition, null, { + "default": () => { + const n0 = _createIf(() => (_ctx.a), () => { + const n2 = t0() + n2.$key = 2 + return n2 + }, () => _createIf(() => (_ctx.b), () => { + const n5 = t0() + n5.$key = 5 + return n5 + }, () => { + const n14 = t2() ++ _setInsertionState(n14, 0) + const n9 = _createIf(() => (_ctx.c), () => { + const n11 = t1() + return n11 + }, () => { + const n13 = t1() + return n13 + }) - _prepend(n14, n9) + n14.$key = 14 + return n14 + })) + return [n0, n3, n7] + } + }, true) + return n16 + }" + `; + + exports[`compiler: transition > v-show + appear 1`] = ` + "import { VaporTransition as _VaporTransition, applyVShow as _applyVShow, createComponent as _createComponent, template as _template } from 'vue'; + const t0 = _template("

foo

") + + export function render(_ctx) { + const deferredApplyVShows = [] + const n1 = _createComponent(_VaporTransition, { + appear: () => (""), + persisted: () => ("") + }, { + "default": () => { + const n0 = t0() + deferredApplyVShows.push(() => _applyVShow(n0, () => (_ctx.show))) + return n0 + } + }, true) + deferredApplyVShows.forEach(fn => fn()) + return n1 + }" + `; + + exports[`compiler: transition > work with dynamic keyed children 1`] = ` + "import { VaporTransition as _VaporTransition, createKeyedFragment as _createKeyedFragment, createComponent as _createComponent, template as _template } from 'vue'; + const t0 = _template("

foo

") + + export function render(_ctx) { + const n1 = _createComponent(_VaporTransition, null, { + "default": () => { + return _createKeyedFragment(() => _ctx.key, () => { + const n0 = t0() + n0.$key = _ctx.key + return n0 + }) + } + }, true) + return n1 + }" + `; + + exports[`compiler: transition > work with v-if 1`] = ` + "import { VaporTransition as _VaporTransition, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue'; + const t0 = _template("

foo

") + + export function render(_ctx) { + const n3 = _createComponent(_VaporTransition, null, { + "default": () => { + const n0 = _createIf(() => (_ctx.show), () => { + const n2 = t0() + n2.$key = 2 + return n2 + }) + return n0 + } + }, true) + return n3 + }" + `; diff --cc packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap index d823c4ed0a,f2eade4bcd..ced96896b9 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap @@@ -43,24 -43,6 +43,15 @@@ export function render(_ctx) }" `; - exports[`compiler: template ref transform > static ref (PROD) 1`] = ` - " - const _setTemplateRef = _createTemplateRefSetter() - const n0 = t0() - _setTemplateRef(n0, foo) - return n0 - " - `; - +exports[`compiler: template ref transform > static ref (inline mode) 1`] = ` +" + const _setTemplateRef = _createTemplateRefSetter() + const n0 = t0() + _setTemplateRef(n0, foo) + return n0 +" +`; + exports[`compiler: template ref transform > static ref 1`] = ` "import { createTemplateRefSetter as _createTemplateRefSetter, template as _template } from 'vue'; const t0 = _template("
", true) diff --cc packages/compiler-vapor/src/generators/block.ts index ff240dd6ea,944a5ee3af..0811921bd9 --- a/packages/compiler-vapor/src/generators/block.ts +++ b/packages/compiler-vapor/src/generators/block.ts @@@ -11,8 -11,9 +11,9 @@@ import } from './utils' import type { CodegenContext } from '../generate' import { genEffects, genOperations } from './operation' -import { genChildren } from './template' +import { genChildren, genSelf } from './template' import { toValidAssetId } from '@vue/compiler-dom' + import { genExpression } from './expression' export function genBlock( oper: BlockIRNode, @@@ -40,25 -41,15 +41,29 @@@ export function genBlockContent customReturns?: (returns: CodeFragment[]) => CodeFragment[], ): CodeFragment[] { const [frag, push] = buildCodeFragment() - const { dynamic, effect, operation, returns } = block + const { dynamic, effect, operation, returns, key } = block const resetBlock = context.enterBlock(block) + if (block.hasDeferredVShow) { + push(NEWLINE, `const deferredApplyVShows = []`) + } + if (root) { - genResolveAssets('component', 'resolveComponent') + for (let name of context.ir.component) { + const id = toValidAssetId(name, 'component') + const maybeSelfReference = name.endsWith('__self') + if (maybeSelfReference) name = name.slice(0, -6) + push( + NEWLINE, + `const ${id} = `, + ...genCall( + context.helper('resolveComponent'), + JSON.stringify(name), + // pass additional `maybeSelfReference` flag + maybeSelfReference ? 'true' : undefined, + ), + ) + } genResolveAssets('directive', 'resolveDirective') } diff --cc packages/compiler-vapor/src/generators/component.ts index 7c232db754,08d0730a56..d50ac58e77 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@@ -51,9 -53,8 +53,9 @@@ export function genCreateComponent const rawSlots = genRawSlots(slots, context) const [ids, handlers] = processInlineHandlers(props, context) const rawProps = context.withId(() => genRawProps(props, context), ids) + const inlineHandlers: CodeFragment[] = handlers.reduce( - (acc, { name, value }) => { + (acc, { name, value }: InlineHandler) => { const handler = genEventHandler(context, value, undefined, false) return [...acc, `const ${name} = `, ...handler, NEWLINE] }, diff --cc packages/compiler-vapor/src/ir/index.ts index 086f77ca61,b4126863f1..3a00ba68da --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@@ -262,7 -264,7 +265,8 @@@ export interface IRDynamicInfo children: IRDynamicInfo[] template?: number hasDynamicChild?: boolean + operation?: OperationNode + needsKey?: boolean } export interface IREffect { diff --cc packages/compiler-vapor/src/transforms/vSlot.ts index 525fa323d3,9a65e6f1bb..df8fb6a5a6 --- a/packages/compiler-vapor/src/transforms/vSlot.ts +++ b/packages/compiler-vapor/src/transforms/vSlot.ts @@@ -236,15 -253,12 +253,19 @@@ function createSlotBlock ): [SlotBlockIRNode, () => void] { const block: SlotBlockIRNode = newBlock(slotNode) block.props = dir && dir.exp + if (key) { + block.key = key + block.dynamic.needsKey = true + } const exitBlock = context.enterBlock(block) - return [block, exitBlock] + context.inSlot = true + return [ + block, + () => { + context.inSlot = false + exitBlock() + }, + ] } function isNonWhitespaceContent(node: TemplateChildNode): boolean { diff --cc packages/runtime-core/src/apiCreateApp.ts index 097c515b1f,9ea669ca34..7f5450d4ec --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@@ -187,7 -192,10 +186,11 @@@ export interface VaporInteropInterface unmount(vnode: VNode, doRemove?: boolean): void move(vnode: VNode, container: any, anchor: any): void slot(n1: VNode | null, n2: VNode, container: any, anchor: any): void + setTransitionHooks( + component: ComponentInternalInstance, + transition: TransitionHooks, + ): void + hydrate(node: Node, fn: () => void): void vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any vdomUnmount: UnmountComponentFn diff --cc packages/runtime-core/src/index.ts index f921eb0a2c,4eddabcff2..889ab132b6 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@@ -557,7 -562,7 +562,11 @@@ export { startMeasure, endMeasure } fro * @internal */ export { initFeatureFlags } from './featureFlags' + /** + * @internal + */ + export { performTransitionEnter, performTransitionLeave } from './renderer' +/** + * @internal + */ +export { ensureVaporSlotFallback } from './helpers/renderSlot' diff --cc packages/runtime-core/src/renderer.ts index 845e1d5f24,744338b3f4..2c97387b83 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@@ -2116,18 -2116,15 +2117,21 @@@ function baseCreateRenderer transition if (needTransition) { if (moveType === MoveType.ENTER) { - transition!.beforeEnter(el!) - hostInsert(el!, container, anchor) - queuePostRenderEffect(() => transition!.enter(el!), parentSuspense) + performTransitionEnter( + el!, + transition, + () => hostInsert(el!, container, anchor), + parentSuspense, + ) } else { const { leave, delayLeave, afterLeave } = transition! - const remove = () => hostInsert(el!, container, anchor) + const remove = () => { + if (vnode.ctx!.isUnmounted) { + hostRemove(el!) + } else { + hostInsert(el!, container, anchor) + } + } const performLeave = () => { leave(el!, () => { remove() diff --cc packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 7ae689a0ae,c061c8224c..9bd4993f83 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@@ -1,6 -1,6 +1,6 @@@ - import { resolveDynamicComponent } from '@vue/runtime-dom' + import { currentInstance, resolveDynamicComponent } from '@vue/runtime-dom' -import { DynamicFragment, type VaporFragment } from './block' +import { DynamicFragment, type VaporFragment, insert } from './block' - import { createComponentWithFallback } from './component' + import { createComponentWithFallback, emptyContext } from './component' import { renderEffect } from './renderEffect' import type { RawProps } from './componentProps' import type { RawSlots } from './componentSlots' @@@ -18,16 -11,13 +18,18 @@@ export function createDynamicComponent rawSlots?: RawSlots | null, isSingleRoot?: boolean, ): VaporFragment { - const frag = __DEV__ - ? new DynamicFragment('dynamic-component') - : new DynamicFragment() + const _insertionParent = insertionParent + const _insertionAnchor = insertionAnchor + if (!isHydrating) resetInsertionState() + + 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/apiCreateFor.ts index 7138d01a6a,92bfe56258..7bde89fbb1 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@@ -28,17 -22,7 +28,18 @@@ import { currentInstance, isVaporCompon import type { DynamicSlot } from './componentSlots' import { renderEffect } from './renderEffect' import { VaporVForFlags } from '../../shared/src/vaporFlags' + import { applyTransitionHooks } from './components/Transition' +import { + currentHydrationNode, + isHydrating, + locateHydrationNode, + locateVaporFragmentAnchor, +} from './dom/hydration' +import { + insertionAnchor, + insertionParent, + resetInsertionState, +} from './insertionState' class ForBlock extends VaporFragment { scope: EffectScope | undefined diff --cc packages/runtime-vapor/src/block.ts index f0dbe7ab6c,6c882badcd..c788ce8855 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@@ -8,30 -7,53 +8,61 @@@ import } from './component' import { createComment, createTextNode } from './dom/node' import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity' -import { isHydrating } from './dom/hydration' +import { + currentHydrationNode, + isComment, + isHydrating, + locateHydrationNode, + locateVaporFragmentAnchor, +} from './dom/hydration' +import { queuePostFlushCb } from '@vue/runtime-dom' + import { + type TransitionHooks, + type TransitionProps, + type TransitionState, + performTransitionEnter, + performTransitionLeave, + } from '@vue/runtime-dom' + import { + applyTransitionHooks, + applyTransitionLeaveHooks, + } from './components/Transition' + + export interface TransitionOptions { + $key?: any + $transition?: VaporTransitionHooks + } - export type Block = - | Node - | VaporFragment - | DynamicFragment - | VaporComponentInstance - | Block[] + export interface VaporTransitionHooks extends TransitionHooks { + state: TransitionState + props: TransitionProps + instance: VaporComponentInstance + // mark transition hooks as disabled so that it skips during + // inserting + disabled?: boolean + } + + export type TransitionBlock = + | (Node & TransitionOptions) + | (VaporFragment & TransitionOptions) + | (DynamicFragment & TransitionOptions) + + export type Block = TransitionBlock | VaporComponentInstance | Block[] export type BlockFn = (...args: any[]) => Block - export class VaporFragment { + export class VaporFragment implements TransitionOptions { + $key?: any + $transition?: VaporTransitionHooks | undefined nodes: Block anchor?: Node - insert?: (parent: ParentNode, anchor: Node | null) => void - remove?: (parent?: ParentNode) => void + insert?: ( + parent: ParentNode, + anchor: Node | null, + transitionHooks?: TransitionHooks, + ) => void + remove?: (parent?: ParentNode, transitionHooks?: TransitionHooks) => void + fallback?: BlockFn constructor(nodes: Block) { this.nodes = nodes @@@ -155,9 -189,10 +218,9 @@@ export function insert } else { // fragment if (block.insert) { - // TODO handle hydration for vdom interop - block.insert(parent, anchor, (block as TransitionBlock).$transition) + block.insert(parent, anchor) } else { - insert(block.nodes, parent, anchor) + insert(block.nodes, parent, anchor, parentSuspense) } if (block.anchor) insert(block.anchor, parent, anchor) } diff --cc packages/runtime-vapor/src/component.ts index 00b9f26c1b,e1e1feb9e6..55fdcffeb0 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@@ -66,12 -58,7 +66,13 @@@ import getSlot, } from './componentSlots' import { hmrReload, hmrRerender } from './hmr' + import { createElement } from './dom/node' +import { isHydrating, locateHydrationNode } from './dom/hydration' +import { + insertionAnchor, + insertionParent, + resetInsertionState, +} from './insertionState' export { currentInstance } from '@vue/runtime-dom' @@@ -265,16 -234,12 +266,12 @@@ export function createComponent if ( instance.hasFallthrough && component.inheritAttrs !== false && - instance.block instanceof Element && Object.keys(instance.attrs).length ) { - renderEffect(() => - applyFallthroughProps(instance.block as Element, instance.attrs), - ) + const el = getRootElement(instance) + if (el) { - renderEffect(() => { - isApplyingFallthroughProps = true - setDynamicProps(el, [instance.attrs]) - isApplyingFallthroughProps = false - }) ++ applyFallthroughProps(el, instance.attrs) + } } resetTracking() @@@ -296,6 -257,15 +293,17 @@@ export let isApplyingFallthroughProps = false + export function applyFallthroughProps( + block: Block, + attrs: Record, + ): void { - isApplyingFallthroughProps = true - setDynamicProps(block as Element, [attrs]) - isApplyingFallthroughProps = false ++ renderEffect(() => { ++ isApplyingFallthroughProps = true ++ setDynamicProps(block as Element, [attrs]) ++ isApplyingFallthroughProps = false ++ }) + } + /** * dev only */ diff --cc packages/runtime-vapor/src/dom/node.ts index 3f38c477a0,26cb66c462..4c613324e1 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@@ -1,10 -1,8 +1,15 @@@ +import { isComment, isNonHydrationNode, locateEndAnchor } from './hydration' +import { + DYNAMIC_END_ANCHOR_LABEL, + DYNAMIC_START_ANCHOR_LABEL, + isVaporAnchors, +} from '@vue/shared' + + /*! #__NO_SIDE_EFFECTS__ */ + export function createElement(tagName: string): HTMLElement { + return document.createElement(tagName) + } + /*! #__NO_SIDE_EFFECTS__ */ export function createTextNode(value = ''): Text { return document.createTextNode(value) diff --cc packages/runtime-vapor/src/dom/template.ts index b78ca4e52c,14cace63c7..f6cdc3ff48 --- a/packages/runtime-vapor/src/dom/template.ts +++ b/packages/runtime-vapor/src/dom/template.ts @@@ -1,5 -1,9 +1,5 @@@ -import { - adoptHydrationNode, - currentHydrationNode, - isHydrating, -} from './hydration' +import { adoptTemplate, currentHydrationNode, isHydrating } from './hydration' - import { child, createTextNode } from './node' + import { child, createElement, createTextNode } from './node' let t: HTMLTemplateElement diff --cc packages/runtime-vapor/src/vdomInterop.ts index cf67e3e518,3c3ae58e92..33680ba7f8 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@@ -2,25 -2,20 +2,27 @@@ 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, createVNode, currentInstance, + ensureHydrationRenderer, ensureRenderer, + ensureVaporSlotFallback, + isVNode, onScopeDispose, renderSlot, + setTransitionHooks as setVNodeTransitionHooks, + shallowReactive, shallowRef, simpleSetCurrentInstance, } from '@vue/runtime-dom' @@@ -35,24 -30,18 +37,32 @@@ import } from './component' import { type Block, + DynamicFragment, VaporFragment, + type VaporTransitionHooks, insert, + isFragment, remove, } from './block' - import { EMPTY_OBJ, extend, isArray, isFunction } from '@vue/shared' -import { EMPTY_OBJ, extend, isFunction, isReservedProp } from '@vue/shared' ++import { ++ EMPTY_OBJ, ++ extend, ++ isArray, ++ isFunction, ++ isReservedProp, ++} from '@vue/shared' import { type RawProps, rawPropsProxyHandlers } from './componentProps' import type { RawSlots, VaporSlot } from './componentSlots' import { renderEffect } from './renderEffect' import { createTextNode } from './dom/node' import { optimizePropertyLookup } from './dom/prop' + import { setTransitionHooks as setVaporTransitionHooks } from './components/Transition' +import { + currentHydrationNode, + isHydrating, + locateHydrationNode, + hydrateNode as vaporHydrateNode, +} from './dom/hydration' // mounting vapor components and slots in vdom const vaporInteropImpl: Omit< @@@ -80,8 -77,13 +98,14 @@@ )) instance.rawPropsRef = propsRef instance.rawSlotsRef = slotsRef + if (vnode.transition) { + setVaporTransitionHooks( + instance, + vnode.transition as VaporTransitionHooks, + ) + } mountComponent(instance, container, selfAnchor) + vnode.el = instance.block simpleSetCurrentInstance(prev) return instance }, @@@ -150,7 -137,9 +174,11 @@@ insert(vnode.anchor as any, container, anchor) }, + setTransitionHooks(component, hooks) { + setVaporTransitionHooks(component as any, hooks as VaporTransitionHooks) + }, ++ + hydrate: vaporHydrateNode, } const vaporSlotPropsProxyHandler: ProxyHandler< @@@ -223,33 -203,20 +252,35 @@@ function createVDOMComponent internals.umt(vnode.component!, null, !!parentNode) } - frag.insert = (parentNode, anchor, transition) => { + vnode.scopeId = parentInstance.type.__scopeId! + + frag.insert = (parentNode, anchor) => { + const prev = currentInstance + simpleSetCurrentInstance(parentInstance) - if (!isMounted) { - if (transition) setVNodeTransitionHooks(vnode, transition) - internals.mt( - vnode, - parentNode, - anchor, - parentInstance as any, - null, - undefined, - false, - ) + if (!isMounted || isHydrating) { + if (isHydrating) { + ;( + vdomHydrateNode || + (vdomHydrateNode = ensureHydrationRenderer().hydrateNode!) + )( + currentHydrationNode!, + vnode, + parentInstance as any, + null, + null, + false, + ) + } else { + internals.mt( + vnode, + parentNode, + anchor, + parentInstance as any, + null, + undefined, + false, + ) + } onScopeDispose(unmount, true) isMounted = true } else { @@@ -263,8 -230,8 +294,10 @@@ ) } - frag.nodes = [vnode.el as Node] + simpleSetCurrentInstance(prev) ++ + // update the fragment nodes + frag.nodes = vnode.el as Block } frag.remove = unmount diff --cc packages/server-renderer/__tests__/webStream.spec.ts index 700a9a0ae3,700a9a0ae3..de399dbb82 --- a/packages/server-renderer/__tests__/webStream.spec.ts +++ b/packages/server-renderer/__tests__/webStream.spec.ts @@@ -49,7 -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()