[whatwg/dom] Proposal: Node.DOCUMENT_LIVE_FRAGMENT_NODE (#736)

**edit** I wouldn't mind `Node.DOCUMENT_PERSISTENT_FRAGMENT_NODE` name/kind neither

### TL;DR
The document fragment is a great primitive to wrap together numerous nodes and append these directly as batch, however a fragment loses all its children as soon as appended, making it's one-off usage limited in those cases where a list of nodes, at a certain position, is meant.

This proposal would like to explore the possibility of a live document fragment that:

  * does not lose its childNodes once appended
  * it's still transparent / unaddressable from CSS
  * it's also transparent for any other node so that it's still possible, as example, to append many `<TD>` or `<TR>` through such fragment, and keep a reference for future updates

## Why
Both virtual DOM based libraries, such React, as well as direct DOM based one, such as hyperHTML, or lit-html, have been implementing their own version of a persistent fragment in a way or another.

If there was a primitive to directly reference more than a node, through a fragment with a well known position on the DOM, I am pretty sure all libraries would eventually move to adopt such primitive, so that a tag function could handle both `<p>1</p>` and `<p>1</p><p>2</p>` without needing to re-invent a similar wheel every single time, and making portability between libraries and frameworks easier than ever: it's just a DOM node!

## Example
```js
// either persistent or live
const pf = document.createLiveFragment();

// append zero, one, or any amount of nodes
pf.appendChild(document.createElement('TD'));
pf.appendChild(document.createElement('TD'));

// make it live
const lastTr = document.querySelector('#data tr:last-child');
lastTr.appendChild(pf);

// references are still there
pf.childNodes.length; // 2

// the node is invisible though
lastTr.lastChild === lastTr; // false
lastTr.lastChild === lastTr.childNodes[1]; // true
```

There should be no way to interfere with CSS and/or selectors, the fragment is either referenced somewhere else or it won't exist for the DOM.

## How
The way hyper/lit-html are doing this is by abusing comment nodes as boundaries of these virtual fragments. The itchy part of these libraries is mostly represented by these virtual fragments, 'cause it's obvious if the primitive proposed here would exists, these libraries would've used it instead (happy to be corrected, but at least I would never create my own virtual fragment if I could use something else).

The way this could be implemented, is by weakly referencing nodes to such fragment only if this is held in memory.

```js
// example implementation of the live fragment
const references = new WeakMap;
class LiveFragment extends DocumentFragment {
  #childNodes = [];
  #appendChild = node => {
    this.#removeChild(node);
    this.#childNodes.push(node);
  };
  #removeChild = node => {
    const i = this.#childNodes.indexOf(node);
    if (-1 < i)
      this.#childNodes.splice(i, 1);
  };
  appendChild(node) {
    this.#appendChild(node);
    return super.appendChild(reference.call(this, node));
  }
  append(...nodes) {
    nodes.forEach(this.#appendChild);
    return super.append(...nodes.map(reference, this));
  }
  removeChild(node) {
    this.#removeChild(node);
    references.delete(node);
    return super.removeChild(node);
  }
  // the value of this LiveFragment when moved around
  valueOf() {
    this.append(...this.#childNodes);
    return this;
  }
}
function reference(node) {
  references.set(node, this);
  return node;
}

// amend on the appendChild standard
const {appendChild} = Node.prototype;
Node.prototype.appendChild = function (node) {
  appendChild.call(this, asFragment(node));
};

const {append} = Element.prototype;
Element.prototype.append = function (...nodes) {
  append.apply(this, nodes.map(asFragment));
};

function asFragment(node) {
  return node instanceof LiveFragment ?
    // it could be just node.valueOf() for everything
    // explicit here for explanation sake
    node.valueOf() :
    node;
}
```

### Possible F.A.Q. Answers

  * if a node is manually appended somewhere it's fine. But as soon as the LiveFragment owner will append such fragment again, that node would be moved back (nodes ownership by creator)
  * if the LiveFragment has no references, then nothing changes from a fragment
  * a live fragment always exposes its `childNodes` as immutable, as it is for regular fragments
  * everything is the same, except that live fragments creators, owner of their live fragment content, can use this primitive instead of polluting the DOM with comments
  * it is always possible for DOM engines to know if a node below to a fragment, as long as this is still referenced somewhere. If unnecessary, due forced re-append on `valueOf()`, the `references` part can be ignored

- - -

Thanks in advance for eventually considering this, happy to answer any possible question.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/736

Received on Thursday, 7 March 2019 09:28:53 UTC