After struggling several months spent brainstorming how I could implement this without DOM patching, something which seemed to be impossible turned out to be pretty easy.
Not everything, but essential parts like "remote attaching" of nodes and "PersistentFragment" was achieved purely in conventional Vanilla JS.
Here's the tests & playground:
https://stackblitz.com/edit/node-group
The One Big Reveal
Traditional DocumentFragment
is a one‑shot batch—append it once, its children move into the host, and the fragment empties itself. If you need the same nodes elsewhere, you must clone or rebuild them.
Group
changes that. It holds onto its children as single instances, and lets you attach the very same nodes to multiple hosts - not by duplicating or sequentially reappending, but by moving the live nodes between parents while they remain discoverable via childNodes
.
const group = new Group
group.append(span, img, div)
// mount the same nodes in hostA
hostA.append(group)
console.log(group.childNodes) // [span, img, div]
// later, move them to hostB, without losing discovery:
hostB.append(group)
console.log(group.childNodes); // still [span, img, div]
Unlike DocumentFragment
, Group
does not clear its childNodes
on append
. It simply reassigns their parentage - so your live nodes persist as a coherent set across hosts.
Why It Actually Matters
Shared Ownership: Borrowing React compositional mindset - sharing a single node tree instead of duplicating or rebuilding it. Now multiple containers can host the exact same elements, without side-effects.
Wrapper-free Transparency: No extra <div>
or invisible wrapper. Under the hood, Group
is a Custom Element shim that preserves CSS selector behavior, event targeting, and box‑model semantics.
Live, Incremental Updates: Every append, remove, or move inside a Group fires connectedCallback
/disconnectedCallback
, streaming exactly the DOM mutations you expect - no innerHTML
rewrites, no virtual DOM.
Alignment with WHATWG PersistentFragment
Proposal
Group
mirrors WHATWG Issue #736: "A DocumentFragment
whose nodes do not get removed once inserted."
- Persistent fragment semantics: children survive mounts.
- No artificial wrappers: CSS and DOM behave as if the fragment never existed.
- Mutation tracking via Custom Elements: no heavy diffing, just native callbacks.
Perfect Fit With Proton Rootless Architecture
Proton struggled to express null or an array of nodes from components without fake containers. Group solves this by exposing a mountable footprint that:
- Represents null but retains identity.
- Integrates with inflators to plug into the DOM later.
- Moves dynamically without re-rendering.
Use Cases That Spark Imagination
- Shared UI fragments: One tree, live across multiple panes.
- Drag‑and‑drop reparenting: Move a node group seamlessly between containers.
- Nullable Proton components: Maintain identity even when nothing renders.
- Plugin UIs without hacks: Inject UI anywhere without hijacking a root.
But Let's Be Skeptical: What Doesn't Work (Yet)
-
parentNode
illusions: Children report their real parent, not theGroup
- maybe contrary to some expectations. - Serialization gaps: No native markup for named fragments - will be implemented if enough attraction is gained.
- Callback latency: Custom‑element lifecycle timing can introduce microtask delays - but in reality I haven't noticed even once.
- Browser support: Custom Elements V1 only; legacy browsers need polyfills.
How to Try It Now
npm install node-group
import { Group } from "node-group"
const group = new Group
group.append("Text1", "Text2")
document.body.append(group)
setTimeout(() => group.append("Note3"), 500);
Roadmap & Ambitions
- Named fragment registry (e.g.
Group.groups
). - Configurable parent-reporting behavior for
.parentNode
. - SSR‑friendly anchors for direct serialization.
- Collaboration with WHATWG on native support.
Closing Thought
I built a DOM primitive that feels like the future-persistent, transparent, live UI chunks that remain discoverable and mutable. No virtual DOM, no wrappers, no re-renders - just native behavior in plain JavaScript.
Call to Action
Let's see what it hides behind and where it can evolve to - this can't happen without you.
I would gladly appreciate if you star the repo and support me with a coffee.
Top comments (0)