This commit is contained in:
Fernando Fernández 2025-05-05 20:38:28 +00:00 committed by GitHub
commit b1dc06a882
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 60 additions and 4 deletions

View File

@ -2,6 +2,7 @@ import {
Comment,
type VNode,
type VNodeProps,
cloneVNode,
closeBlock,
createVNode,
currentBlock,
@ -10,7 +11,14 @@ import {
normalizeVNode,
openBlock,
} from '../vnode'
import { ShapeFlags, isArray, isFunction, toNumber } from '@vue/shared'
import {
EMPTY_OBJ,
ShapeFlags,
extend,
isArray,
isFunction,
toNumber,
} from '@vue/shared'
import { type ComponentInternalInstance, handleSetupResult } from '../component'
import type { Slots } from '../componentSlots'
import {
@ -31,6 +39,7 @@ import {
} from '../warning'
import { ErrorCodes, handleError } from '../errorHandling'
import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
import type { Writable } from '../../types/util'
export interface SuspenseProps {
onResolve?: () => void
@ -45,6 +54,14 @@ export interface SuspenseProps {
suspensible?: boolean
}
const ComponentProps = [
'onFallback',
'onPending',
'onResolve',
'timeout',
'suspensible',
]
export const isSuspense = (type: any): boolean => type.__isSuspense
// incrementing unique id for every pending branch
@ -820,11 +837,15 @@ function hydrateSuspense(
function normalizeSuspenseChildren(vnode: VNode): void {
const { shapeFlag, children } = vnode
const isSlotChildren = shapeFlag & ShapeFlags.SLOTS_CHILDREN
vnode.ssContent = normalizeSuspenseSlot(
isSlotChildren ? (children as Slots).default : children,
const attrs = getFallthroughAttrs(vnode)
vnode.ssContent = cloneVNode(
normalizeSuspenseSlot(
isSlotChildren ? (children as Slots).default : children,
),
attrs,
)
vnode.ssFallback = isSlotChildren
? normalizeSuspenseSlot((children as Slots).fallback)
? cloneVNode(normalizeSuspenseSlot((children as Slots).fallback), attrs)
: createVNode(Comment)
}
@ -902,3 +923,35 @@ function isVNodeSuspensible(vnode: VNode) {
const suspensible = vnode.props && vnode.props.suspensible
return suspensible != null && suspensible !== false
}
/**
* TODO: The standard renderer should be abstracted to also allow Suspense to work, so
* Suspense can work as a normal component (like Transition and KeepAlive), instead of a "fake" one,
* as it currently is. This is why in some places special handling for it exists. Having it working
* as an standard component means that there's no need to re-implement all the handling of fallthrough attributes.
* Hence, this is just a workaround to normalize the behaviour across built-in components.
* See: https://github.com/vuejs/rfcs/discussions/664
*
* Suspense can receive fallthrough attributes from props and attrs
* - From props: When they are specified directly in the Suspense component (they're filtered here)
* - From attrs: When Suspense is a child of another component
*/
function getFallthroughAttrs(n2: VNode) {
const props = n2.props ?? EMPTY_OBJ
const fallthrough: Writable<typeof props> = {}
for (const key in props) {
if (!ComponentProps.includes(key)) {
fallthrough[key] = props[key]
}
}
if (
n2.suspense &&
n2.suspense.parentComponent &&
n2.suspense.parentComponent.attrs
) {
return extend({}, fallthrough, n2.suspense.parentComponent.attrs)
}
return fallthrough
}

3
packages/runtime-core/types/util.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export type Writable<T> = {
-readonly [P in keyof T]: T[P]
}