Skip to content

Proposal: Bulk mutation methods for working with ranges of nodes #1369

@justinfagnani

Description

@justinfagnani

What problem are you trying to solve?

It's very common in modern framework code to have to work with ranges of nodes as a unit, where a range is a sequence of sibling nodes. The operations include:

  • Clear a range of nodes
  • Move a range of nodes
  • Extract a range of nodes into a fragment

Two of the APIs are available on Range as deleteContents(), extractContents(), but using Range, even StaticRange, is slower than implementing these operations in JS. The move operation doesn't exist in any form, since moveBefore() is brand new.

While the JS code for implementing these operations is relatively simple, native methods would still reduce the amount of required utility code needed in some libraries and frameworks. Higher-level methods would also help reduce the number of wrapper JS objects created during traversal, and the total number of calls like .remove() and .nextSibling. The operations are often on the very hottest paths of rendering code, so any perf improvement is beneficial.

What solutions exist today?

Today frameworks implement these using traversal and individual node removal and insertion.

How would you solve it?

Add these methods to ParentNode:

/**
 * Removes the nodes between startNode and endNode exclusively.
 * - startNode and endNode must be children of the receiver, or nullish.
 * - If startNode is nullish, then start removing nodes at the first child of the receiver.
 * - If endNode is nullsih, then remove nodes until the last child of the receiver.
 */
deleteRange(startNode?: ChildNode, endNode?: ChildNode): void;

/**
 * Moves the nodes between startNode and endNode exclusively to a new DocumentFragment.
 */
extractRange(startNode?: ChildNode, endNode?: ChildNode): DocumentFragment;

/**
 * Atomically moves the nodes between startNode and endNode exclusively to the receiver, inserted
 * before refNode or the end of the container.
 */
moveRangeBefore(fromParent: ParentNode, startNode?: ChildNode, endNode?: ChildNode, refNode?: ChildNode): void

/**
 * Moves the nodes between startNode and endNode exclusively to the receiver, inserted
 * before refNode or the end of the container.
 * 
 * This is the same as moveRangeBefore() without the atomic operation guarantees and without
 * throwing in cases where moveRangeBefore() throws (the same as in moveBefore()).
 */
insertRangeBefore(fromParent: ParentNode, startNode?: ChildNode, endNode?: ChildNode, refNode?: ChildNode): void

Anything else?

This is an alternative to #736 that is smaller in scope. It helps with the bulk mutation operations, but doesn't deal with DOM serialization and how start and end nodes are represented.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions