if (isInPendingSuspense) {
suspense.deps++
}
- const hydratedEl = instance.vnode.el
+ const hydratedEl = instance.vapor ? null : instance.vnode.el
instance
.asyncDep!.catch(err => {
handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
}
// retry from this component
instance.asyncResolved = true
- const { vnode } = instance
- if (__DEV__) {
- pushWarningContext(vnode)
- }
- handleSetupResult(instance, asyncSetupResult, false)
- if (hydratedEl) {
- // vnode may have been replaced if an update happened before the
- // async dep is resolved.
- vnode.el = hydratedEl
- }
- const placeholder = !hydratedEl && instance.subTree.el
- setupRenderEffect(
- instance,
- vnode,
- // component may have been moved before resolve.
- // if this is not a hydration, instance.subTree will be the comment
- // placeholder.
- parentNode(hydratedEl || instance.subTree.el!)!,
- // anchor will not be used if this is hydration, so only need to
- // consider the comment placeholder case.
- hydratedEl ? null : next(instance.subTree),
- suspense,
- namespace,
- optimized,
- )
- if (placeholder) {
- remove(placeholder)
- }
- updateHOCHostEl(instance, vnode.el)
- if (__DEV__) {
- popWarningContext()
+
+ // vapor component
+ if (instance.vapor) {
+ // @ts-expect-error
+ setupRenderEffect(asyncSetupResult)
+ } else {
+ const { vnode } = instance
+ if (__DEV__) {
+ pushWarningContext(vnode)
+ }
+ handleSetupResult(instance, asyncSetupResult, false)
+ if (hydratedEl) {
+ // vnode may have been replaced if an update happened before the
+ // async dep is resolved.
+ vnode.el = hydratedEl
+ }
+ const placeholder = !hydratedEl && instance.subTree.el
+ setupRenderEffect(
+ instance,
+ vnode,
+ // component may have been moved before resolve.
+ // if this is not a hydration, instance.subTree will be the comment
+ // placeholder.
+ parentNode(hydratedEl || instance.subTree.el!)!,
+ // anchor will not be used if this is hydration, so only need to
+ // consider the comment placeholder case.
+ hydratedEl ? null : next(instance.subTree),
+ suspense,
+ namespace,
+ optimized,
+ )
+ if (placeholder) {
+ remove(placeholder)
+ }
+ updateHOCHostEl(instance, vnode.el)
+ if (__DEV__) {
+ popWarningContext()
+ }
}
// only decrease deps count if suspense is not already resolved
if (isInPendingSuspense && --suspense.deps === 0) {
currentInstance,
endMeasure,
expose,
+ getComponentName,
nextUid,
popWarningContext,
pushWarningContext,
resetTracking,
unref,
} from '@vue/reactivity'
-import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared'
+import {
+ EMPTY_OBJ,
+ invokeArrayFns,
+ isFunction,
+ isPromise,
+ isString,
+} from '@vue/shared'
import {
type DynamicPropsSource,
type RawProps,
appContext: GenericAppContext = (currentInstance &&
currentInstance.appContext) ||
emptyContext,
+ parentSuspense?: SuspenseBoundary | null,
): VaporComponentInstance {
const _insertionParent = insertionParent
const _insertionAnchor = insertionAnchor
rawProps as RawProps,
rawSlots as RawSlots,
appContext,
+ parentSuspense,
)
if (__DEV__) {
]) || EMPTY_OBJ
: EMPTY_OBJ
- if (__DEV__ && !isBlock(setupResult)) {
- if (isFunction(component)) {
- warn(`Functional vapor component must return a block directly.`)
- instance.block = []
- } else if (!component.render) {
+ const isAsyncSetup = isPromise(setupResult)
+ if (__FEATURE_SUSPENSE__ && isAsyncSetup) {
+ // async setup returned Promise.
+ // bail here and wait for re-entry.
+ instance.asyncDep = setupResult
+ if (__DEV__ && !instance.suspense) {
+ const name = getComponentName(component, true) ?? 'Anonymous'
warn(
- `Vapor component setup() returned non-block value, and has no render function.`,
+ `Component <${name}>: setup function returned a promise, but no ` +
+ `<Suspense> boundary was found in the parent component tree. ` +
+ `A component with async setup() must be nested in a <Suspense> ` +
+ `in order to be rendered.`,
)
- instance.block = []
- } else {
- instance.devtoolsRawSetupState = setupResult
- // TODO make the proxy warn non-existent property access during dev
- instance.setupState = proxyRefs(setupResult)
- devRender(instance)
-
- // HMR
- if (component.__hmrId) {
- registerHMR(instance)
- instance.isSingleRoot = isSingleRoot
- instance.hmrRerender = hmrRerender.bind(null, instance)
- instance.hmrReload = hmrReload.bind(null, instance)
- }
- }
- } else {
- // component has a render function but no setup function
- // (typically components with only a template and no state)
- if (!setupFn && component.render) {
- instance.block = callWithErrorHandling(
- component.render,
- instance,
- ErrorCodes.RENDER_FUNCTION,
- )
- } else {
- // in prod result can only be block
- instance.block = setupResult as Block
}
}
- // single root, inherit attrs
- if (
- instance.hasFallthrough &&
- component.inheritAttrs !== false &&
- instance.block instanceof Element &&
- Object.keys(instance.attrs).length
- ) {
- renderEffect(() => {
- isApplyingFallthroughProps = true
- setDynamicProps(instance.block as Element, [instance.attrs])
- isApplyingFallthroughProps = false
- })
+ if (!isAsyncSetup) {
+ handleSetupResult(setupResult, component, instance, isSingleRoot, setupFn)
}
resetTracking()
onScopeDispose(() => unmountComponent(instance), true)
- if (!isHydrating && _insertionParent) {
+ if (!isHydrating && _insertionParent && !isAsyncSetup) {
insert(instance.block, _insertionParent, _insertionAnchor)
}
ids: [string, number, number]
// for suspense
suspense: SuspenseBoundary | null
+ suspenseId: number
+ asyncDep: Promise<any> | null
+ asyncResolved: boolean
hasFallthrough: boolean
rawProps?: RawProps | null,
rawSlots?: RawSlots | null,
appContext?: GenericAppContext,
+ suspense?: SuspenseBoundary | null,
) {
this.vapor = true
this.uid = nextUid()
this.emit = emit.bind(null, this)
this.expose = expose.bind(null, this)
this.refs = EMPTY_OBJ
- this.emitted =
- this.exposed =
- this.exposeProxy =
- this.propsDefaults =
- this.suspense =
- null
+ this.emitted = this.exposed = this.exposeProxy = this.propsDefaults = null
+
+ // suspense related
+ this.suspense = suspense!
+ this.suspenseId = suspense ? suspense.pendingId : 0
+ this.asyncDep = null
+ this.asyncResolved = false
this.isMounted =
this.isUnmounted =
)
}
}
+
+export function handleSetupResult(
+ setupResult: any,
+ component: VaporComponent,
+ instance: VaporComponentInstance,
+ isSingleRoot: boolean | undefined,
+ setupFn: VaporSetupFn | undefined,
+): void {
+ if (__DEV__) {
+ pushWarningContext(instance)
+ }
+ if (__DEV__ && !isBlock(setupResult)) {
+ if (isFunction(component)) {
+ warn(`Functional vapor component must return a block directly.`)
+ instance.block = []
+ } else if (!component.render) {
+ warn(
+ `Vapor component setup() returned non-block value, and has no render function.`,
+ )
+ instance.block = []
+ } else {
+ instance.devtoolsRawSetupState = setupResult
+ // TODO make the proxy warn non-existent property access during dev
+ instance.setupState = proxyRefs(setupResult)
+ devRender(instance)
+
+ // HMR
+ if (component.__hmrId) {
+ registerHMR(instance)
+ instance.isSingleRoot = isSingleRoot
+ instance.hmrRerender = hmrRerender.bind(null, instance)
+ instance.hmrReload = hmrReload.bind(null, instance)
+ }
+ }
+ } else {
+ // component has a render function but no setup function
+ // (typically components with only a template and no state)
+ if (!setupFn && component.render) {
+ instance.block = callWithErrorHandling(
+ component.render,
+ instance,
+ ErrorCodes.RENDER_FUNCTION,
+ )
+ } else {
+ // in prod result can only be block
+ instance.block = setupResult as Block
+ }
+ }
+
+ // single root, inherit attrs
+ if (
+ instance.hasFallthrough &&
+ component.inheritAttrs !== false &&
+ instance.block instanceof Element &&
+ Object.keys(instance.attrs).length
+ ) {
+ renderEffect(() => {
+ isApplyingFallthroughProps = true
+ setDynamicProps(instance.block as Element, [instance.attrs])
+ isApplyingFallthroughProps = false
+ })
+ }
+
+ if (__DEV__) {
+ popWarningContext()
+ }
+}
type VaporComponent,
VaporComponentInstance,
createComponent,
+ handleSetupResult,
mountComponent,
unmountComponent,
} from './component'
VaporInteropInterface,
'vdomMount' | 'vdomUnmount' | 'vdomSlot'
> = {
- mount(vnode, container, anchor, parentComponent) {
+ mount(
+ vnode,
+ container,
+ anchor,
+ parentComponent,
+ parentSuspense,
+ isSingleRoot,
+ ) {
const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
container.insertBefore(selfAnchor, anchor)
const prev = currentInstance
const propsRef = shallowRef(vnode.props)
const slotsRef = shallowRef(vnode.children)
+ const component = vnode.type as any as VaporComponent
// @ts-expect-error
const instance = (vnode.component = createComponent(
- vnode.type as any as VaporComponent,
+ component,
{
$: [() => propsRef.value],
} as RawProps,
{
_: slotsRef, // pass the slots ref
} as any as RawSlots,
+ isSingleRoot,
+ undefined,
+ parentSuspense,
))
instance.rawPropsRef = propsRef
instance.rawSlotsRef = slotsRef
- mountComponent(instance, container, selfAnchor)
+ if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
+ parentSuspense &&
+ parentSuspense.registerDep(
+ instance as any,
+ setupResult => {
+ handleSetupResult(
+ setupResult,
+ component,
+ instance,
+ isSingleRoot,
+ isFunction(component) ? component : component.setup,
+ )
+ mountComponent(instance, container, selfAnchor)
+ },
+ false,
+ )
+ } else {
+ mountComponent(instance, container, selfAnchor)
+ }
simpleSetCurrentInstance(prev)
return instance
},