--- /dev/null
-"import { VaporTransition as _VaporTransition, createIf as _createIf, prepend as _prepend, createComponent as _createComponent, template as _template } from 'vue';
+ // 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("<h1>foo</h1>")
+
+ 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("<div></div>")
+
+ 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`] = `
- _prepend(n14, n9)
++"import { VaporTransition as _VaporTransition, setInsertionState as _setInsertionState, createIf as _createIf, createComponent as _createComponent, template as _template } from 'vue';
+ const t0 = _template("<div>hey</div>")
+ const t1 = _template("<p></p>")
+ const t2 = _template("<div></div>")
+
+ 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
+ })
+ 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("<h1>foo</h1>")
+
+ 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("<h1>foo</h1>")
+
+ 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("<h1>foo</h1>")
+
+ 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
+ }"
+ `;
}"
`;
- 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("<div></div>", true)
} 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,
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')
}
const rawSlots = genRawSlots(slots, context)
const [ids, handlers] = processInlineHandlers(props, context)
const rawProps = context.withId(() => genRawProps(props, context), ids)
+
const inlineHandlers: CodeFragment[] = handlers.reduce<CodeFragment[]>(
- (acc, { name, value }) => {
+ (acc, { name, value }: InlineHandler) => {
const handler = genEventHandler(context, value, undefined, false)
return [...acc, `const ${name} = `, ...handler, NEWLINE]
},
children: IRDynamicInfo[]
template?: number
hasDynamicChild?: boolean
+ operation?: OperationNode
+ needsKey?: boolean
}
export interface IREffect {
): [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 {
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
* @internal
*/
export { initFeatureFlags } from './featureFlags'
+ /**
+ * @internal
+ */
+ export { performTransitionEnter, performTransitionLeave } from './renderer'
+/**
+ * @internal
+ */
+export { ensureVaporSlotFallback } from './helpers/renderSlot'
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()
- 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'
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(
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
} 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
} 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)
}
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'
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()
export let isApplyingFallthroughProps = false
- isApplyingFallthroughProps = true
- setDynamicProps(block as Element, [attrs])
- isApplyingFallthroughProps = false
+ export function applyFallthroughProps(
+ block: Block,
+ attrs: Record<string, any>,
+ ): void {
++ renderEffect(() => {
++ isApplyingFallthroughProps = true
++ setDynamicProps(block as Element, [attrs])
++ isApplyingFallthroughProps = false
++ })
+ }
+
/**
* dev only
*/
+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)
-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
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'
} 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<
))
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
},
insert(vnode.anchor as any, container, anchor)
},
+ setTransitionHooks(component, hooks) {
+ setVaporTransitionHooks(component as any, hooks as VaporTransitionHooks)
+ },
++
+ hydrate: vaporHydrateNode,
}
const vaporSlotPropsProxyHandler: ProxyHandler<
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 {
)
}
- frag.nodes = [vnode.el as Node]
+ simpleSetCurrentInstance(prev)
++
+ // update the fragment nodes
+ frag.nodes = vnode.el as Block
}
frag.remove = unmount
}
const { readable, writable } = new TransformStream()
-- pipeToWebWritable(createApp(App), {}, writable)
++ pipeToWebWritable(createApp(App), {}, writable as any)
const reader = readable.getReader()
const decoder = new TextDecoder()