Proposal: Live fragments


#1

Document fragments are great for templates, because you can just clone them and append them. They make great generic containers for reusable nodes that way. They also make for convenient batch append operations (although the perf argument isn’t as much as it used to be).

Now, while they make for great management of nodes out of the main tree, they don’t make for easy management within the tree - notably, they just empty themselves.

Something I’d like to see is maybe some sort of live fragment support natively. Here’s a few examples of where people have already implemented this in library code:

  • React, Mithril, and friends. It’s very common for more advanced virtual DOM-based libraries and frameworks to support fragments, since they already more or less abstract over the DOM to begin with, and since all they’re doing is patching, they don’t need to worry about much beyond just iterating a bunch of nodes.
  • Ember/Glimmer. They have to know how to add, track, and remove slices in user components, since they support components with multiple children.
  • Angular. They use a virtual DOM-like system under the hood to implement a similar thing themselves.

However, without the assistance of a framework, it gets almost prohibitive to manage subtrees in any significant capacity, and also, libraries have to choose to either wrap one whole element, or return one whole element. This affects CSS layout, and is consequently not that great of a thing to work with.

Live fragment support should be able to cover the common case of just wanting to render a collection of elements without resorting to creating a new element, and that’s pretty much the crux of what I’m proposing.

As for a concrete example, consider a list of dt + dd pairs within a definition list. The HTML spec mandates that definition lists only contain such pairs as direct children. However, if you could use live fragments, you could not only generate them from a list; you could isolate those fragments into a separate component without having to roll your own framework or heavy library to do it.

Also, as a concrete example of how hard it is to work around this kind of scenario, consider the fact that relatively few frameworks implement support for such fragments without having a very significant layer of indirection (virtual DOM, Glimmer’s VM, Closure templates via incremental-dom) over the DOM that basically shifts it to a whole different paradigm. Also, Elm itself does not support nested fragments, Etch implicitly flattens nested arrays and requires that components render a node, and the original virtual-dom library has no support for fragments, either.

So, I feel we should instead have a built-in browser primitive, basically a node proxy, that allows live fragments to be created in a way that elements can still deal with just the unit they expect, and CSS can work as if it’s not even there, but something that browsers could actually use to better optimize the case of groups of contiguous nodes manipulated via proxy rather than directly on the instance.


#2

So, thoughts?

Oh, and to clarify, what I mean by “isolating those fragments into a separate component without having to roll your own framework” is that you could do something like this:**

// Define a component
class ChildComponent {
    constructor() {
        this._visible = true
        this._internal = frag([
            n("li", t("child 3")),
            n("li", t("child 4")),
        ])
        this.body = frag(this._internal)
    }

    update(attrs) {
        if (!!attrs.show === this.visible) return
        if (this._visible) this.body.removeChild(this._internal)
        else this.body.appendChild(this._internal)
        this._visible = !this._visible
    }
}

class ParentComponent {
    constructor(attrs) {
        this._child = new ChildComponent()
        this.body = n("ul", [
            n("li", t("child 1")),
            n("li", t("child 2")),
            this._child.body,
        ])
        // ...
    }

    update(attrs) {
        // ...
        this._child.update({show: attrs.showChild})
    }
}

* Note: the following helpers are used above, to simplify the examples.

function t(str) { return document.createTextNode(str) }

function append(elem, child) {
    if (Array.isArray(child)) child.forEach(c => append(elem, e))
    else if (child != null) this.appendChild(child)
    return elem
}

function frag(children) { return append(new LiveFragment(), children) }

function n(tag, attrs, children) {
    const elem = document.createElement(tag)
    if (children == null && attrs != null &&
            (typeof attrs !== "object" || Array.isArray(attrs)) {
        return append(elem, attrs)
    } else {
        for (const key of Object.keys(attrs || {})) elem.setAttribute(key, attrs[key])
        return append(elem, children)
    }
}

** For what it’s worth, you could already define the parent component today - you just couldn’t define the child component. Also, I know it’s a contrived example - you could factor the child component back into the parent pretty easily, even without using live fragments.


[Proposal] new <ignore> element HTML
#3

Is this something worth considering?

I know it’s almost a year old, but I’d rather resurrect an old post over re-proposing it with basically the same information.


#4

After quickly looking over this (I don’t remember seeing it previously), this is basically a simplistic virtual DOM?

Just the other day, I was trying to find a way to reorder elements dynamically (using IDs stored in an array), and quickly came to the conclusion the only sane way to approach it was to use a virtual DOM. Is that the type of use case you’re envisioning?


#5

That’s one of the imagined use cases, but this is really a primitive. You could use it for more than virtual DOM, like:

  • Making userland libraries that just return live fragments instead of DOM nodes.
  • A Backbone-like component pattern almost like web components where every component subclasses LiveDocumentFragment (maybe indirectly), and you invoke methods on each instance to update them. This would make for very efficient DOM patching.

These would be very similar conceptually to existing DocumentFragments, but instead of emptying their contents into the new node, they turn into a subtree that can change the nodes in question.

Reordering elements dynamically based on IDs is something I’d prefer to be implemented natively (it requires an efficient, adaptive implementation of this algorithm). This would require a way to tie elements to IDs, but I don’t believe it’d require a virtual DOM as its core primitive. It’d just need a way to allow patching without sacrificing performance (which complicates API design here).

I’ve been currently working on narrowing down the minimum basic primitives for natively assisting frameworks’ DOM management, and so far, I’ve come down to this:

  • Basic elements
  • Basic text nodes
  • Live unkeyed fragments (this proposal)
  • Live keyed fragments

We’ve already got the first two, but the last two is where most of the wins can be gained. I’m not sure what the optimal API for the last one is, though.


#6

I like this idea but is creating a whole new live fragments necessary? Cant we just add a new reorder method (or similar) to the HTMLCollection api? perhaps make it constructable in cases where the collection needs to contain unrelated elements?

UPDATE:

Whoa–I must be really old school because I’m a little surprised to see that the latest spec states:

HTMLCollection is a historical artifact we cannot rid the web of. While developers are of course welcome to keep using it, new API standard designers ought not to use it

But why? I’ve been using HTMLCollections for forever. :smile:


#7

@mkay581 That’s part of why I didn’t suggest extending it. Only part, though - I need whatever ends up used to be a node, too.


#8

Related: [Proposal] Fragments

A Fragments node would be similar to a node with display: contents; but with the added feature of being able to use the node to hold onto multiple elements that are symbolically linked i.e an array of table rows.