From: daiwei Date: Fri, 20 Jun 2025 02:55:50 +0000 (+0800) Subject: chore: Merge branch 'vapor' into edison/feat/vaporTransition X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=58ac955981d327c4f406da26ee8eb8b338704135;p=thirdparty%2Fvuejs%2Fcore.git chore: Merge branch 'vapor' into edison/feat/vaporTransition --- 58ac955981d327c4f406da26ee8eb8b338704135 diff --cc packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap index 2cb5c36e41,0000000000..8a83143b9b mode 100644,000000..100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap @@@ -1,128 -1,0 +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/src/generators/block.ts index 944a5ee3af,ff240dd6ea..0811921bd9 --- a/packages/compiler-vapor/src/generators/block.ts +++ b/packages/compiler-vapor/src/generators/block.ts @@@ -11,9 -11,8 +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, @@@ -41,15 -40,25 +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 08d0730a56,10705a2c79..70fc0c10a6 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@@ -53,8 -51,9 +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 b4126863f1,18f0139ab5..a8130be389 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@@ -54,9 -52,7 +53,8 @@@ export interface BlockIRNode extends Ba tempId: number effect: IREffect[] operation: OperationNode[] - expressions: SimpleExpressionNode[] returns: number[] + hasDeferredVShow: boolean } export interface RootIRNode { @@@ -264,7 -259,7 +262,8 @@@ export interface IRDynamicInfo children: IRDynamicInfo[] template?: number hasDynamicChild?: boolean + needsKey?: boolean + operation?: OperationNode } export interface IREffect { diff --cc packages/compiler-vapor/src/transforms/utils.ts index a42f47eb37,f7d0594fe5..b746999a18 --- a/packages/compiler-vapor/src/transforms/utils.ts +++ b/packages/compiler-vapor/src/transforms/utils.ts @@@ -29,9 -29,7 +29,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 9a65e6f1bb,3e78913a23..05aac4aee3 --- 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,34 @@@ 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 4eddabcff2,1ed6f21df7..ba0cc93ca5 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@@ -562,7 -557,7 +562,11 @@@ export { startMeasure, endMeasure } fro * @internal */ export { initFeatureFlags } from './featureFlags' +/** + * @internal + */ +export { performTransitionEnter, performTransitionLeave } from './renderer' + /** + * @internal + */ + export { createInternalObject } from './internalObject' diff --cc packages/runtime-core/src/renderer.ts index 744338b3f4,3d02c65c16..87d71d7021 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@@ -2116,15 -2115,18 +2116,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-dom/src/components/TransitionGroup.ts index 8e38dbf0f2,72af535d38..abf3e09542 --- a/packages/runtime-dom/src/components/TransitionGroup.ts +++ b/packages/runtime-dom/src/components/TransitionGroup.ts @@@ -96,8 -97,22 +97,9 @@@ const TransitionGroupImpl: ComponentOpt movedChildren.forEach(c => { const el = c.el as ElementWithTransition - const style = el.style - addTransitionClass(el, moveClass) - style.transform = style.webkitTransform = style.transitionDuration = '' - const cb = ((el as any)[moveCbKey] = (e: TransitionEvent) => { - if (e && e.target !== el) { - return - } - if (!e || /transform$/.test(e.propertyName)) { - el.removeEventListener('transitionend', cb) - ;(el as any)[moveCbKey] = null - removeTransitionClass(el, moveClass) - } - }) - el.addEventListener('transitionend', cb) + handleMovedChildren(el, moveClass) }) + prevChildren = [] }) return () => { diff --cc packages/runtime-vapor/src/apiCreateDynamicComponent.ts index c061c8224c,945e0f38d8..8a127c2daf --- 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' @@@ -14,10 -28,9 +28,11 @@@ export function createDynamicComponent const frag = __DEV__ ? new DynamicFragment('dynamic-component') : new DynamicFragment() + renderEffect(() => { const value = getter() + const appContext = + (currentInstance && currentInstance.appContext) || emptyContext frag.update( () => createComponentWithFallback( diff --cc packages/runtime-vapor/src/apiCreateFor.ts index 92bfe56258,62529149ad..f60294f29f --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@@ -22,7 -24,12 +24,13 @@@ import { currentInstance, isVaporCompon import type { DynamicSlot } from './componentSlots' import { renderEffect } from './renderEffect' import { VaporVForFlags } from '../../shared/src/vaporFlags' +import { applyTransitionHooks } from './components/Transition' + import { isHydrating, locateHydrationNode } from './dom/hydration' + import { + insertionAnchor, + insertionParent, + resetInsertionState, + } from './insertionState' class ForBlock extends VaporFragment { scope: EffectScope | undefined diff --cc packages/runtime-vapor/src/component.ts index e1e1feb9e6,af15133dbe..a407268eb5 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@@ -58,7 -58,12 +58,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' @@@ -234,12 -255,16 +256,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 - }) ++ renderEffect(() => applyFallthroughProps(el, instance.attrs)) + } } resetTracking() @@@ -453,13 -473,20 +479,21 @@@ export function createComponentWithFall rawProps?: LooseRawProps | null, rawSlots?: LooseRawSlots | null, isSingleRoot?: boolean, + appContext?: GenericAppContext, ): HTMLElement | VaporComponentInstance { if (!isString(comp)) { - return createComponent(comp, rawProps, rawSlots, isSingleRoot) + return createComponent(comp, rawProps, rawSlots, isSingleRoot, appContext) } + const el = createElement(comp) + const _insertionParent = insertionParent + const _insertionAnchor = insertionAnchor + if (isHydrating) { + locateHydrationNode() + } else { + resetInsertionState() + } + - const el = document.createElement(comp) // mark single root ;(el as any).$root = isSingleRoot diff --cc packages/runtime-vapor/src/dom/template.ts index 14cace63c7,b78ca4e52c..7bfbca4e52 --- a/packages/runtime-vapor/src/dom/template.ts +++ b/packages/runtime-vapor/src/dom/template.ts @@@ -1,9 -1,5 +1,5 @@@ - import { - adoptHydrationNode, - currentHydrationNode, - isHydrating, - } from './hydration' +import { child, createElement, createTextNode } from './node' + import { adoptTemplate, currentHydrationNode, isHydrating } from './hydration' -import { child, createTextNode } from './node' let t: HTMLTemplateElement diff --cc packages/runtime-vapor/src/vdomInterop.ts index 3c3ae58e92,b916a2c8eb..15e595f3ec --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@@ -7,15 -7,15 +7,17 @@@ import type RendererInternals, type ShallowRef, type Slots, + type TransitionHooks, type VNode, type VaporInteropInterface, + createInternalObject, createVNode, currentInstance, ensureRenderer, + isEmitListener, onScopeDispose, renderSlot, + setTransitionHooks as setVNodeTransitionHooks, shallowRef, simpleSetCurrentInstance, } from '@vue/runtime-dom'