From 58d97cd78a588f93ca08739829b745afd145261a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Fern=C3=A1ndez?= Date: Fri, 14 Jun 2024 15:29:22 +0000 Subject: [PATCH] feat(Suspense): handle fallthrough attributes Signed-off-by: GitHub --- .../runtime-core/src/components/Suspense.ts | 61 +++++++++++++++++-- packages/runtime-core/types/util.d.ts | 3 + 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 packages/runtime-core/types/util.d.ts diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index 7c3c75a6f..d85ee46ab 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -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) { 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 = {} + 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 +} diff --git a/packages/runtime-core/types/util.d.ts b/packages/runtime-core/types/util.d.ts new file mode 100644 index 000000000..7040f9158 --- /dev/null +++ b/packages/runtime-core/types/util.d.ts @@ -0,0 +1,3 @@ +export type Writable = { + -readonly [P in keyof T]: T[P] +}