Building a rootless framework is difficult, but still Proton was built with a very intentional design choice: its components are allowed to return anything - from DOM Nodes, to primitives, and null
/undefined
. This flexibility is not a bug, but a feature. However, this freedom comes with sharp edges: when null
is returned, no anchor is created, and the view replacement logic has nothing to latch onto.
One of core goals of Proton is to eliminate the concept of "Root Components" entirely and embrace fully "Atomic Components" - meaning self-contained, relocatable, and behaviorally independent.
For example:
function Component() {
setTimeout(() => this.view.set(<div>Another layout</div>), 1_000)
return <div>Some layout</div>
}
const componentView1 = inflator.inflate(<Component />)
const componentView2 = inflator.inflate(<Component />)
// These should be two independent component nodes with identical reactive behavior.
document.body.append(componentView1, componentView2)
setTimeout(() => {
// And they should be moveable across the DOM freely.
anotherElement.append(componentView2, componentView1)
}, 2_000)
This is where complications begin to surface. Without an anchor or predictable rendering footprint, null
becomes a dead-end.
These problems (dynamic mounts, node independence, movable view layers) are elegantly solved by node-group
library.
Introducing Group
node
The design of Group
is a practical implementation of the DocumentPersistentFragment
proposal, aiming to cover its core aspects in current browsers. The proposal envisions a fragment that can persist beyond a single parent and avoid surrogate wrapper elements. node-group
approximates these goals today by leveraging custom elements and live synchronization.
Alignment with the Proposal
Persistent Fragment Semantics
The proposal defines a DocumentPersistentFragment
that its children remains even when its was attached to a parent. In node-group
, it follows this precisely.
A Group
instance can be reparented at any time. When appended to a new parent, its associated nodes removed from the previous one.
Zero-Wrapper Behavior
The proposal discussion emphasizes avoiding synthetic wrapper elements. node-group
uses a non-rendering custom element that emits no visual footprint. To engines, the Group
nodes appear as true direct children, preserving CSS, Box Model and Layout consistency.
Live Mutation Tracking
While the spec would rely on low-level fragment mutation observers, node-group
hooks into the Custom Elements API (connectedCallback
/disconnectedCallback
) to detect when a relay enters or leaves the DOM. It then streams append
, remove
, and move
operations to the targeted parent.
Group Usage Example
import { Group } from "node-group"
const group = new Group
group.append(
document.createElement("header"),
document.createElement("main"),
document.createElement("footer")
)
const content = document.getElementById("root")
content.append(group)
setTimeout(() => {
const note = document.createElement("aside")
note.textContent = "Persistent Fragment in action"
group.append(note)
}, 500)
Group
not only solves Proton dynamic mounting challenges, but also opens up door for future enhancements.
Top comments (0)