morphdom

repository·master·Indexed 25 days ago

https://github.com/patrick-steele-idem/morphdom

A lightweight module for morphing an existing DOM node tree to match a target DOM node tree without requiring a virtual DOM. It works directly with the real DOM to minimize changes and preserve element state, such as scroll position and input focus. Supports morphing using DOM nodes or HTML strings, and provides a configurable API via the morphdom(fromNode, toNode, options) function. Since version 2.1.0, it also supports diffing real DOM nodes against virtual DOM nodes that implement a minimal DOM API.

Tokens
2.1K
Snippets
8
Records
12
Agent score
84%

What's inside morphdom

  1. Implement a Virtual DOM compatible with morphdom

    master

    Since v2.1.0, morphdom supports diffing real DOM nodes against virtual DOM nodes. To use a custom virtual DOM implementation, your virtual nodes must implement a minimal DOM API.

    Required Properties and Methods

    Your virtual nodes must provide the following:

    • node.firstChild
    • node.nextSibling
    • node.nodeType
    • node.nodeName
    • node.namespaceURI
    • node.nodeValue
    • node.attributes
    • node.value
    • node.selected
    • node.disabled
    • node.hasAttributeNS(namespaceURI, name)
    • node.actualize(document): This method is called when the virtual node needs to be upgraded to a real DOM node to be moved into the real DOM.
    • node.isSameNode(anotherNode): (Optional) Implement this to short-circuit diffing/patching a subtree by treating two nodes as the "same".

    Optimization Hooks

    • node.assignAttributes(targetNode): (Optional) Implement this to optimize copying attributes from the virtual node to the target DOM node. If implemented, you do not need to implement node.attributes.

    For a reference implementation, see marko-vdom.

  2. Optimize performance with HTML strings and DOM/virtual DOM

    master

    For optimal performance in web applications, use a hybrid rendering strategy:

    1. On the Server: Render templates directly to an HTML string. This is faster than rendering virtual DOM nodes that require subsequent serialization.
    2. In the Browser: Compile the same template to render to a DOM or virtual DOM.

    This approach minimizes server CPU usage and provides the best performance for both environments.

  3. Configure morphdom transformation options

    master

    Pass an options object to morphdom to customize the morphing behavior.

    Supported Options:

    • getNodeKey (Function(node)): Returns a unique identifier for a node (defaults to node.id). Used to rearrange elements instead of destroying/recreating them.
    • addChild (Function(parentNode, childNode)): Customizes how new children are added (defaults to parentNode.appendChild(childNode)).
    • onBeforeNodeAdded (Function(node)): Called before a node is added. Return false to cancel addition, or the node to proceed.
    • onNodeAdded (Function(node)): Called after a node is added.
    • onBeforeElUpdated (Function(fromEl, toEl)): Called before an HTMLElement is updated. Return false to cancel, or an HTMLElement to replace the current fromEl branch.
    • onElUpdated (Function(el)): Called after an HTMLElement is updated.
    • onBeforeNodeDiscarded (Function(node)): Called before a node is removed. Return false to cancel removal.
    • onNodeDiscarded (Function(node)): Called after a node is discarded.
    • onBeforeElChildrenUpdated (Function(fromEl, toEl)): Called before children are updated. Return false to cancel.
    • childrenOnly (Boolean): If true, only the children of fromNode and toNode are morphed; the container itself is skipped. Defaults to false.
    • skipFromChildren (Function(fromEl)): If returning true, skips indexing the fromEl tree, keeping current items in place rather than removing them when not found in toEl.
    var morphdom = require('morphdom');
    var morphedNode = morphdom(fromNode, toNode, {
      getNodeKey: function(node) {
        return node.id;
      },
      addChild: function(parentNode, childNode) {
        parentNode.appendChild(childNode);
      },
      onBeforeNodeAdded: function(node) {
        return node;
      },
      onNodeAdded: function(node) {
    
      },
      onBeforeElUpdated: function(fromEl, toEl) {
        return true;
      },
      onElUpdated: function(el) {
    
      },
      onBeforeNodeDiscarded: function(node) {
        return true;
      },
      onNodeDiscarded: function(node) {
    
      },
      onBeforeElChildrenUpdated: function(fromEl, toEl) {
        return true;
      },
      childrenOnly: false,
      skipFromChildren: function(fromEl, toEl) {
        return false;
      }
    });
  4. Morph a DOM node to another DOM node

    master

    Use morphdom to transform an existing DOM element (el1) to match a target DOM element (el2). This minimizes changes to the DOM, preserving internal state like scroll positions and input caret positions.

    var morphdom = require('morphdom');
    
    var el1 = document.createElement('div');
    el1.className = 'foo';
    
    var el2 = document.createElement('div');
    el2.className = 'bar';
    
    morphdom(el1, el2);
    
    // el1.className is now 'bar'
  5. Morph a DOM node using an HTML string

    master

    Instead of a second Node object, you can pass an HTML string as the toNode argument. morphdom will morph the existing element to match the structure defined in the string.

    var morphdom = require('morphdom');
    
    var el1 = document.createElement('div');
    el1.className = 'foo';
    el1.innerHTML = 'Hello John';
    
    morphdom(el1, '<div class="bar">Hello Frank</div>');
    
    // el1.className is now 'bar'
    // el1.innerHTML is now 'Hello Frank'
  6. Optimize morphdom performance with isEqualNode

    master

    You can speed up morphdom by using the onBeforeElUpdated option to skip traversing subtrees when you know two nodes are identical using isEqualNode.

    morphdom(fromNode, toNode, {
        onBeforeElUpdated: function(fromEl, toEl) {
            // spec - https://dom.spec.whatwg.org/#concept-node-equals
            if (fromEl.isEqualNode(toEl)) {
                return false
            }
    
            return true
        }
    })
  7. Use the morphdom(fromNode, toNode, options) API

    master

    The core function morphdom(fromNode, toNode, options) transforms fromNode to match toNode.

    Arguments:

    • fromNode (Node): The node to morph.
    • toNode (Node|String): The target node or an HTML string.
    • options (Object): Configuration for the transformation process.

    Returns:

    • Typically returns the fromNode. If the fromNode is incompatible with the toNode (e.g., different tag names), a different DOM node is returned.
  8. Use morphdom() to transform DOM trees

    master

    The morphdom function is the primary entrypoint for the library. It is used to transform an existing DOM tree (fromNode) into a new DOM tree (toNode) by calculating the minimal set of changes required. This minimizes DOM manipulations and improves performance.

    Signature: morphdom(fromNode, toNode, options)

    Parameters:

    • fromNode: The current DOM node (the source).
    • toNode: The new DOM node (the target).
    • options: (Optional) An object containing configuration for the morphing process.

    Returns:

    • The updated fromNode.