marko-vdom

    1.0.1 • Public • Published

    marko-vdom

    This module provides an optimized virtual DOM implementation where each virtual DOM node is API compatible with real DOM nodes for the minimal subset that is required to support DOM diffing/patching using morphdom.

    Build Status Coverage Status NPM

    Overview

    Each virtual DOM node supports the following properties and methods required by morphdom:

    • node.firstChild
    • node.nextSibling
    • node.nodeType
    • node.nodeName
    • node.namespaceURI
    • node.nodeValue
    • node.attributes [1]
    • node.value
    • node.selected
    • node.disabled
    • node.actualize(document) [2]
    • node.hasAttributeNS(namespaceURI, name)
    • node.isSameNode(anotherNode) [3]
    • node.assignAttributes(targetNode) [4]

    NOTES:

    1. Unlike with real DOM nodes, node.attributes can either be an Array of Attr objects or an Object (where each property represents an attribute. e.g., { "class": "foo", "id": "bar" })
    2. In addition to the standard DOM node methods and properties, a virtual DOM node must also provide a node.actualize(document) method. The node.actualize(document) will be called when the virtual DOM node needs to be upgraded to a real DOM node so that it can be moved into the real DOM.
    3. A virtual DOM node may choose to implement isSameNode(anotherNode) to short-circuit diffing/patching a particular DOM subtree by treating two nodes as the "same"
    4. A virtual DOM node may choose to implement the non-standard assignAttributes(targetNode) to optimize copying the attributes from the virtual DOM node to the target DOM node

    marko-vdom is namespace aware and will work correctly with SVG and MathML elements.

    While marko-vdom exposes an API that can be used directly, the terse API is designed to be used with a compiler that generates JavaScript code.

    Usage

    Create an element with a fixed number of attributes and a fixed number of children

    var createElement = require('marko-vdom').createElement;
     
    createElement('div', { class: 'foo', onclick: 'doSomething()' }, 2 /* childCount */)
        .e('span', null, 1)
            .e('b', null, 1)
                .t('Hello World!')
        .e('a', { href: 'http://ebay.com' }, 1)
            .t('eBay')

    The above code will generate a virtual DOM tree that mirrors the following:

    <div class="foo" onclick="doSomething()">
        <span>
            <b>Hello World!</b>
        </span>
        <a href="http://ebay.com">eBay</a>
    </div>

    Dynamic HTML with a unknown number of children

    var createElement = require('marko-vdom').createElement;
     
    var el = createElement('div', { class: 'foo' });
    el.appendChild(createElement('span', { class: 'bar' }));

    The above code will generate a virtual DOM tree that mirrors the following:

    <div class="foo">
        <span class="bar"></span>
    </div>

    Static subtree

    var createElement = require('marko-vdom').createElement;
     
    var staticLink = createElement('a', { href: 'http://ebay.com' }, 1 /* childCount */, 'abc123' /* key */)
        .t('eBay')
     
    function render() {
        createElement('div', null, 1 /* childCount */)
            .n(staticLink);
    }

    The above code will generate a virtual DOM tree that, when converted to a real DOM, will be the following:

    <div>
        <a href="http://ebay.com" data-marko-same-id="abc123">
            eBay
        </a>
    </div>

    For the static link, both the virtual DOM node and the real DOM node will be marked with an "id" that identifies the two nodes as the "same" node in order to short-circuit DOM/diffing patching. That is:

    var realStaticLink = staticLink.actualize(document);
    console.log(staticLink.isSameNode(realStaticLink)); //Output: true

    Document fragments

    Document fragments are containers for child nodes that can be appended as children nodes, but the actual DocumentFragment node is never directly visited when walking the DOM using node.firstChild and node.nextSibling. Instead, the children (if any) of a DocumentFragment node are treated as direct children of the parent of the DocumentFragment node. A DocumentFragment node can be modified with new children even after it has been inserted into the DOM.

    var createElement = require('marko-vdom').createElement;
     
    var div = createElement('div');
    documentFragment.appendChild(createElement('div'));
     
    var documentFragment = div.appendDocumentFragment();
    documentFragment.appendChild(createElement('span', { class: 'foo' }));
    documentFragment.appendChild(createElement('span', { class: 'bar' }));
     
    /*
     
    Output DOM:
     
    <div>
        <span class="foo"></span>
        <span class="bar"></span>
    </div>
    */
     

    Benchmarks

    This library includes some benchmarks to compare performance with the real DOM (and React). To run the benchmarks:

    npm run benchmark
    

    This will open a web page in your browser that you can use to run a variety of benchmarks.

    We are interested in the following performance characteristics:

    • Creation time - the time it takes to construct a [virtual] DOM tree
    • Walk time - the time it takes to walk a [virtual] DOM tree using firstChild and nextSibilng

    We encourage you to run the benchmarks on your machine and in various browsers. If you see any problems with the benchmarks, or if you would like clarifying information please open a Github issue.

    Please see Benchmark Results for more detailed numbers.

    API

    marko-vdom

    Methods

    createElement(tagName, attrCount, childCount, key)

    Returns a new HTMLElement.

    createText(value)

    Returns a new Text.

    createComment(value)

    Returns a new Comment.

    createAttributes(attrCount)

    Returns a new AttributeCollection.

    createDocumentFragment()

    Returns a new DocumentFragment


    AttributeCollection

    Methods

    a(name, value)

    as(attributes)


    Comment


    DocumentFragment


    HTMLElement

    Constructors

    HTMLElement(tagName, attrCount, childCount, key)

    Parameters:

    • tagName - The tag name for the new HTML element (String)
    • attrCount - The number of attributes (if known) (an integer, null or undefined)
    • childCount - The number of child nodes (if known) (an integer, null or undefined)
    • key - A key for static nodes to use for isSameNode() checks

    HTMLElement(htmlElement)

    Used to do a shallow clone of another HTMLElement

    Properties

    nodeType

    Always set to 1

    Methods

    a(name, value) : Node

    See AttributeCollection#a

    actualize(document) : HTMLElement

    Converts the virtual HTMLElement tree to a real HTMLElement tree using the provided document.

    as(name, value) : Node

    See AttributeCollection#a

    appendDocumentFragment() : DocumentFragment

    See Node#appendDocumentFragment

    c(value) : Node

    Shorthand method for creating a Comment node and appending it as a child.

    cloneNode() : HTMLElement

    Performs a shallow clone of the node (nextSibling and parentNode will be undefined since a cloned node will always start out as detached)

    e(tagName, attrCount, childCount, key) : Node

    Shorthand method for creating an HTMLElement node and appending it as a child.

    hasAttributeNS(namespaceURI, name) : boolean

    isSameNode(otherNode) : boolean

    Called by morphdom to determine if the target HTMLElement (either virtual or real) node is the same as the current node. The key passed in to the constructor is used to do determine if the other node is the "same" node. If the other node is a real DOM node then the key is pulled from the data-markokey attribute.

    n(node) : Node

    Shorthand method for appending a node as a child. The provided is automatically cloned (using a shallow clone) since it is assumed that this method will be called to append a static/memoized DOM node and the original DOM node should not be modified.

    t(value) : Node

    Shorthand method for creating a Text node and appending it as a child.


    Node

    Properties

    firstChild : Node

    Returns the first child node

    nextSibling : Node

    Returns the next sibling node

    Methods

    appendDocumentFragment() : DocumentFragment

    Creates and appends a new DocumentFragment node and appends it as a child and the newly created DocumentFragment node is returned.

    removeChildren()

    Clears out all child nodes


    Text

    Install

    npm i marko-vdom

    DownloadsWeekly Downloads

    12

    Version

    1.0.1

    License

    MIT

    Last publish

    Collaborators

    • avatar
    • avatar
    • avatar
    • avatar