shallowRef,
toReactive,
toReadonly,
+ watch,
} from '@vue/reactivity'
- import { getSequence, isArray, isObject, isString } from '@vue/shared'
+ import { isArray, isObject, isString } from '@vue/shared'
import { createComment, createTextNode } from './dom/node'
-import {
- type Block,
- VaporFragment,
- insert,
- remove as removeBlock,
-} from './block'
+import { type Block, insert, remove as removeBlock } from './block'
import { warn } from '@vue/runtime-dom'
import { currentInstance, isVaporComponent } from './component'
import type { DynamicSlot } from './componentSlots'
--- /dev/null
- import { EffectScope, pauseTracking, resetTracking } from '@vue/reactivity'
++import { EffectScope, setActiveSub } from '@vue/reactivity'
+import { createComment, createTextNode } from './dom/node'
+import { type Block, type BlockFn, insert, isValidBlock, remove } from './block'
+
+export class VaporFragment {
+ nodes: Block
+ target?: ParentNode | null
+ targetAnchor?: Node | null
+ anchor?: Node
+ insert?: (parent: ParentNode, anchor: Node | null) => void
+ remove?: (parent?: ParentNode) => void
+ getNodes?: () => Block
+
+ constructor(nodes: Block) {
+ this.nodes = nodes
+ }
+}
+
+export class DynamicFragment extends VaporFragment {
+ anchor: Node
+ scope: EffectScope | undefined
+ current?: BlockFn
+ fallback?: BlockFn
+
+ constructor(anchorLabel?: string) {
+ super([])
+ this.anchor =
+ __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
+ }
+
+ update(render?: BlockFn, key: any = render): void {
+ if (key === this.current) {
+ return
+ }
+ this.current = key
+
- pauseTracking()
++ const prevSub = setActiveSub()
+ const parent = this.anchor.parentNode
+
+ // teardown previous branch
+ if (this.scope) {
+ this.scope.stop()
+ parent && remove(this.nodes, parent)
+ }
+
+ if (render) {
+ this.scope = new EffectScope()
+ this.nodes = this.scope.run(render) || []
+ if (parent) insert(this.nodes, parent, this.anchor)
+ } else {
+ this.scope = undefined
+ this.nodes = []
+ }
+
+ if (this.fallback && !isValidBlock(this.nodes)) {
+ parent && remove(this.nodes, parent)
+ this.nodes =
+ (this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
+ []
+ parent && insert(this.nodes, parent, this.anchor)
+ }
+
- resetTracking()
++ setActiveSub(prevSub)
+ }
+}
+
+export function isFragment(val: NonNullable<unknown>): val is VaporFragment {
+ return val instanceof VaporFragment
+}
const parent = normalized[0].parentNode!
const anchor = normalized[normalized.length - 1].nextSibling
remove(instance.block, parent)
- const prev = currentInstance
- simpleSetCurrentInstance(instance)
+ if (instance.hmrRerenderEffects) {
+ instance.hmrRerenderEffects.forEach(e => e())
+ instance.hmrRerenderEffects.length = 0
+ }
+ const prev = setCurrentInstance(instance)
pushWarningContext(instance)
devRender(instance)
popWarningContext()
instance.rawSlots,
instance.isSingleRoot,
)
- simpleSetCurrentInstance(prev, instance.parent)
+ setCurrentInstance(...prev)
mountComponent(newInstance, parent, anchor)
+ handleTeleportRootComponentHmrReload(instance, newInstance)
}
export { defineVaporComponent } from './apiDefineComponent'
export { vaporInteropPlugin } from './vdomInterop'
export type { VaporDirective } from './directives/custom'
+export { VaporTeleportImpl as VaporTeleport } from './components/Teleport'
// compiler-use only
-export { insert, prepend, remove, isFragment, VaporFragment } from './block'
+export { insert, prepend, remove } from './block'
export { setInsertionState } from './insertionState'
- export { createComponent, createComponentWithFallback } from './component'
+ export {
+ createComponent,
+ createComponentWithFallback,
+ isVaporComponent,
+ } from './component'
export { renderEffect } from './renderEffect'
export { createSlot } from './componentSlots'
export { template } from './dom/template'
import { type VaporComponentInstance, isVaporComponent } from './component'
import { invokeArrayFns } from '@vue/shared'
- export function renderEffect(
- fn: () => void,
- noLifecycle = false,
- ): ReactiveEffect<void> {
- const instance = currentInstance as VaporComponentInstance | null
- const scope = getCurrentScope()
- if (__DEV__ && !__TEST__ && !scope && !isVaporComponent(instance)) {
- warn('renderEffect called without active EffectScope or Vapor instance.')
- }
+ class RenderEffect extends ReactiveEffect {
+ i: VaporComponentInstance | null
+ job: SchedulerJob
+ updateJob: SchedulerJob
+
+ constructor(public render: () => void) {
+ super()
+ const instance = currentInstance as VaporComponentInstance | null
+ if (__DEV__ && !__TEST__ && !this.subs && !isVaporComponent(instance)) {
+ warn('renderEffect called without active EffectScope or Vapor instance.')
+ }
- // renderEffect is always called after user has registered all hooks
- const hasUpdateHooks = instance && (instance.bu || instance.u)
- const renderEffectFn = noLifecycle
- ? fn
- : () => {
- if (__DEV__ && instance) {
- startMeasure(instance, `renderEffect`)
- }
- const prev = currentInstance
- simpleSetCurrentInstance(instance)
- if (scope) scope.on()
- if (hasUpdateHooks && instance.isMounted && !instance.isUpdating) {
- instance.isUpdating = true
- instance.bu && invokeArrayFns(instance.bu)
- fn()
- queuePostFlushCb(() => {
- instance.isUpdating = false
- instance.u && invokeArrayFns(instance.u)
- })
- } else {
- fn()
- }
- if (scope) scope.off()
- simpleSetCurrentInstance(prev, instance)
- if (__DEV__ && instance) {
- startMeasure(instance, `renderEffect`)
- }
+ const job: SchedulerJob = () => {
+ if (this.dirty) {
+ this.run()
}
+ }
+ this.updateJob = () => {
+ instance!.isUpdating = false
+ instance!.u && invokeArrayFns(instance!.u)
+ }
+
+ if (instance) {
+ if (__DEV__) {
+ this.onTrack = instance.rtc
+ ? e => invokeArrayFns(instance.rtc!, e)
+ : void 0
+ this.onTrigger = instance.rtg
+ ? e => invokeArrayFns(instance.rtg!, e)
+ : void 0
+ }
+ job.i = instance
+ }
- const effect = new ReactiveEffect(renderEffectFn)
- const job: SchedulerJob = () => effect.dirty && effect.run()
+ this.job = job
+ this.i = instance
- if (instance) {
- if (__DEV__) {
- effect.onTrack = instance.rtc
- ? e => invokeArrayFns(instance.rtc!, e)
- : void 0
- effect.onTrigger = instance.rtg
- ? e => invokeArrayFns(instance.rtg!, e)
- : void 0
+ // TODO recurse handling
+ }
+
+ fn(): void {
+ const instance = this.i
+ const scope = this.subs ? (this.subs.sub as EffectScope) : undefined
+ // renderEffect is always called after user has registered all hooks
+ const hasUpdateHooks = instance && (instance.bu || instance.u)
+ if (__DEV__ && instance) {
+ startMeasure(instance, `renderEffect`)
+ }
+ const prev = setCurrentInstance(instance, scope)
+ if (hasUpdateHooks && instance.isMounted && !instance.isUpdating) {
+ instance.isUpdating = true
+ instance.bu && invokeArrayFns(instance.bu)
+ this.render()
+ queuePostFlushCb(this.updateJob)
+ } else {
+ this.render()
+ }
+ setCurrentInstance(...prev)
+ if (__DEV__ && instance) {
+ startMeasure(instance, `renderEffect`)
}
- job.i = instance
- job.id = instance.uid
}
- effect.scheduler = () => queueJob(job)
+ notify(): void {
+ const flags = this.flags
+ if (!(flags & EffectFlags.PAUSED)) {
+ queueJob(this.job, this.i ? this.i.uid : undefined)
+ }
+ }
+ }
+
-export function renderEffect(fn: () => void, noLifecycle = false): void {
++export function renderEffect(
++ fn: () => void,
++ noLifecycle = false,
++): RenderEffect {
+ const effect = new RenderEffect(fn)
+ if (noLifecycle) {
+ effect.fn = fn
+ }
effect.run()
- // TODO recurse handling
+
+ return effect
}
import { renderEffect } from './renderEffect'
import { createTextNode } from './dom/node'
import { optimizePropertyLookup } from './dom/prop'
+import { VaporFragment } from './fragment'
+ export const interopKey: unique symbol = Symbol(`interop`)
+
// mounting vapor components and slots in vdom
const vaporInteropImpl: Omit<
VaporInteropInterface,