Skip to content

feat(vapor): vdom Suspense render Vapor components #13355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: vapor
Choose a base branch
from
Draft
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, 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 @@ -182,6 +182,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 @@ -438,6 +438,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 @@ -926,7 +939,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 @@ -557,3 +557,7 @@ export { startMeasure, endMeasure } from './profiling'
* @internal
*/
export { initFeatureFlags } from './featureFlags'
/**
* @internal
*/
export { getComponentName } from './component'
3 changes: 2 additions & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ function baseCreateRenderer(
container,
anchor,
parentComponent,
parentSuspense,
)
} else {
getVaporInterface(parentComponent, n2).update(
Expand Down Expand Up @@ -2425,7 +2426,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 } from '../src'
import type { App } 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 @@ -82,3 +86,50 @@ 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,
)
}
Loading