[whatwg/dom] Suggestion: `Node.insertAfter(newNode, referenceNode)` (#986)

In frameworks, it's *far* more common to want to insert after the current node than before the next node, and it's significantly easier to implement. Problem is, for performance reasons, some of us employ various hacks [like this](https://github.com/MithrilJS/mithril.js/blob/08051e9d33d84ffac0c4eab8d25273322f3c0349/render/render.js#L559-L564) ([example call](https://github.com/MithrilJS/mithril.js/blob/08051e9d33d84ffac0c4eab8d25273322f3c0349/render/render.js#L299)) to avoid having to pay the cost of a C++ round trip just to get the next sibling of a given DOM node.

I also see a gaping hole in this matrix for child-relative operations:

| Operation | `Node` method | `ChildNode` method | jQuery method |
|:-:|:-:|:-:|:-:|
| Insert before | `node.insertBefore(child, ref)` | `child.before(...nodes)` | `$(parent).before(child)`, `$(child).insertBefore(parent)` |
| Insert after | | `child.after(...nodes)` | `$(parent).before(child)` | `$(parent).after(child)`, `$(child).insertAfter(parent)` |
| Remove | `node.removeChild(child)` | `child.remove()` | `$(child).remove()` |
| Replace | `node.replaceChild(child, prev)` | `child.replaceWith(...nodes)` | `$(child).replaceWith(newChild)`, `$(newChild).replaceAll(child)` |

I propose we fill that with the following method:

```webidl
partial interface Node {
    [CEReactions] Node insertAfter(Node node, Node? child);
};
```

The semantics are pretty obvious:

- If `child` is `null`, this prepends `node` as the first child.
- If `child` is not `null`, this inserts `node` after `child` similar to how `insertBefore` inserts it before `child`.

With this primitive, a tree walk could then just do this (high-level pseudocode, omits most the irrelevant crap) on create:

```
global parent = null
global lastUpdated = null

render(root, tree):
    local prev = {parent, lastUpdated}
    parent = root
    lastUpdated = null
    updateTree(treeForRoot)
    {parent, lastUpdated} = prev

createElement(virtualNode):
    local elem = document.createElement(virtualNode.tagName)
    local prev = parent
    setAttributes(elem, virtualNode.attributes)
    parent = elem // Save parent node to build to
    for child of virtualNode.children:
        createChild(child)
    parent = prev
    parent.insertAfter(elem, lastUpdated)
    lastUpdated = elem
    return
```

We currently can't do that without a *lot* of branching and/or multiple C++ round trips - that was our mechanism prior to our current system, actually, and it proved to be an eventual bottleneck. And of course, this isn't unique to virtual DOM - it could apply to most frameworks overall. In general, it means us framework writers can get away with a lot less bookkeeping when incrementally building the DOM, so I would really love this.

> As for why I'm not using `child.after`? We need the parent node because we won't have the child node until we visit one that already exists, and when we're creating a node after a fragment, we won't know it's the first child until we visit the entire fragment, and when we do, we might very well not have a child there to work from. And it's a lot more awkward to branch between `parent.prepend(newChild)` and `child.after(newChild)`, and I suspect it'd also provide a performance cliff due to new CPU cache misses and a more complicated branch profile. Ideally, that conditional belongs in C++ land - it's already there for `insertBefore`.

-- 
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/986

Received on Wednesday, 9 June 2021 07:02:58 UTC