Skip to content

RFC: unwrapFragment to flatten nested Fragment nodes #787

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
80 changes: 80 additions & 0 deletions active-rfcs/0045-unwrap-fragment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: unwrapFragment to flatten nested Fragment nodes
start-date: 2025-07-01
type: Feature
status: In Progress
---

# Summary

Introduce an internal utility function `unwrapFragment` to flatten nested `Fragment` nodes inside slot VNode arrays. This helps simplify component logic and improve consistency when working with deeply nested slots.

# Motivation

In Vue 3, when a slot returns multiple root nodes, they are automatically wrapped in a `Fragment`. However, in real-world usage—especially in multi-level component compositions—slots passed from parent to child can accumulate multiple layers of `Fragment` wrappers.

This creates issues for component libraries or advanced slot processing scenarios where:

- Accurate length or type checking of slot children is needed
- Conditional logic based on vnode types fails (e.g. showing expand/collapse buttons)
- Tools like developer inspectors or snapshot serializers receive unnecessarily nested structures

## Real-world Example

In [Tencent's open source component library TDesign](https://github.com/Tencent/tdesign-vue-next), the `Alert` component determines whether to show a toggle button by counting the slot content. When a user wraps `Alert` in a custom component and forwards the slot, it ends up wrapped in a second-level `Fragment`, causing logic to fail and rendering to behave unexpectedly.

This problem has also been observed in internally-developed systems that rely on dynamic slot inspection or vnode traversal for layout logic.

# Detailed Design

```ts
// unwrapFragment.ts
import { Fragment, VNode } from '@vue/runtime-core'

export function unwrapFragment(vnodes: VNode[] | undefined): VNode[] {
if (!vnodes) return []
const result: VNode[] = []
for (const vnode of vnodes) {
if (vnode.type === Fragment) {
result.push(...unwrapFragment(vnode.children as VNode[]))
} else {
result.push(vnode)
}
}
return result
}
```

This utility:

- Accepts a VNode array
- Recursively flattens all `Fragment` nodes
- Returns the "real" vnode list used for actual render logic

This function is **internal-only** and meant to assist renderer and advanced component authors. It is not proposed as a public API.

# Alternatives Considered

- Manually flattening in userland: leads to duplicated logic and error-prone usage
- Using `.flatMap` or `.children`: does not handle nested Fragments recursively
- Opting out of Fragment wrapping: not currently possible and would break existing semantics

# Drawbacks

- Adds a small runtime utility to `@vue/runtime-core` (~0.1kb)
- Slight conceptual overhead (introducing another vnode helper)

# Adoption Strategy

- Used internally by component libraries
- Can be documented as part of advanced authoring guides
- May eventually be moved to `@vue/shared` if needed by other runtimes

# Unresolved Questions

- Should Vue provide a built-in way to unwrap slots by default?
- Should a `.raw()` helper be introduced on `slots.default`?

# Future Possibility

If this proves widely useful, Vue may consider exposing `unwrapFragment` as a public utility or embedding the behavior in slot normalization logic.