Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/runtime-core/src/apiCreateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { warn } from './warning'
import type { VNode } from './vnode'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
import { NO, extend, hasOwn, isFunction, isObject } from '@vue/shared'
import { version } from '.'
import { type SuspenseBoundary, version } from '.'
import { installAppCompatProperties } from './compat/global'
import type { NormalizedPropsOptions } from './componentProps'
import type { ObjectEmitsOptions } from './componentEmits'
Expand Down Expand Up @@ -183,6 +183,7 @@ export interface VaporInteropInterface {
container: any,
anchor: any,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
): GenericComponentInstance // VaporComponentInstance
update(n1: VNode, n2: VNode, shouldUpdate: boolean): void
unmount(vnode: VNode, doRemove?: boolean): void
Expand Down
15 changes: 14 additions & 1 deletion packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,19 @@ export interface GenericComponentInstance {
* @internal
*/
suspense: SuspenseBoundary | null
/**
* suspense pending batch id
* @internal
*/
suspenseId: number
/**
* @internal
*/
asyncDep: Promise<any> | null
/**
* @internal
*/
asyncResolved: boolean

// lifecycle
/**
Expand Down Expand Up @@ -927,7 +940,7 @@ function setupStatefulComponent(
// bail here and wait for re-entry.
instance.asyncDep = setupResult
if (__DEV__ && !instance.suspense) {
const name = Component.name ?? 'Anonymous'
const name = getComponentName(Component) ?? 'Anonymous'
warn(
`Component <${name}>: setup function returned a promise, but no ` +
`<Suspense> boundary was found in the parent component tree. ` +
Expand Down
71 changes: 40 additions & 31 deletions packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ function createSuspenseBoundary(
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)
Expand All @@ -709,37 +709,46 @@ function createSuspenseBoundary(
}
// 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)

// vapor component
if (instance.vapor) {
// @ts-expect-error
setupRenderEffect(asyncSetupResult)
}
updateHOCHostEl(instance, vnode.el)
if (__DEV__) {
popWarningContext()
// vdom component
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) {
Expand Down
4 changes: 4 additions & 0 deletions packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,10 @@ export { startMeasure, endMeasure } from './profiling'
* @internal
*/
export { initFeatureFlags } from './featureFlags'
/**
* @internal
*/
export { getComponentName } from './component'
/**
* @internal
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,7 @@ function baseCreateRenderer(
container,
anchor,
parentComponent,
parentSuspense,
)
} else {
getVaporInterface(parentComponent, n2).update(
Expand Down Expand Up @@ -2523,7 +2524,7 @@ function baseCreateRenderer(
const getNextHostNode: NextFn = vnode => {
if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
if ((vnode.type as ConcreteComponent).__vapor) {
return hostNextSibling((vnode.component! as any).block)
return hostNextSibling(vnode.anchor!)
}
return getNextHostNode(vnode.component!.subTree)
}
Expand Down
51 changes: 51 additions & 0 deletions packages/runtime-vapor/__tests__/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { createVaporApp, vaporInteropPlugin } from '../src'
import { type App, type Component, createApp } from '@vue/runtime-dom'
import type { VaporComponent, VaporComponentInstance } from '../src/component'
import type { RawProps } from '../src/componentProps'
import { compileScript, parse } from '@vue/compiler-sfc'
import * as runtimeVapor from '../src'
import * as runtimeDom from '@vue/runtime-dom'
import * as VueServerRenderer from '@vue/server-renderer'

export interface RenderContext {
component: VaporComponent
Expand Down Expand Up @@ -83,6 +87,53 @@ export function makeRender<C = VaporComponent>(
return define
}

export { runtimeDom, runtimeVapor, VueServerRenderer }
export function compile(
sfc: string,
data: runtimeDom.Ref<any>,
components: Record<string, any> = {},
{
vapor = true,
ssr = false,
}: {
vapor?: boolean | undefined
ssr?: boolean | undefined
} = {},
): any {
if (!sfc.includes(`<script`)) {
sfc =
`<script vapor>const data = _data; const components = _components;</script>` +
sfc
}
const descriptor = parse(sfc).descriptor

const script = compileScript(descriptor, {
id: 'x',
isProd: true,
inlineTemplate: true,
genDefaultAs: '__sfc__',
vapor,
templateOptions: {
ssr,
},
})

const code =
script.content
.replace(/\bimport {/g, 'const {')
.replace(/ as _/g, ': _')
.replace(/} from ['"]vue['"]/g, `} = Vue`)
.replace(/} from "vue\/server-renderer"/g, '} = VueServerRenderer') +
'\nreturn __sfc__'

return new Function('Vue', 'VueServerRenderer', '_data', '_components', code)(
{ ...runtimeDom, ...runtimeVapor },
VueServerRenderer,
data,
components,
)
}

export interface InteropRenderContext {
mount: (container?: string | ParentNode) => InteropRenderContext
render: (
Expand Down
Loading