• This repository has been archived on 29/Apr/2021
  • Stars
    star
    146
  • Rank 252,769 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 9 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Turn JSX into IncrementalDOM

babel-plugin-transform-incremental-dom Build Status

Turn JSX into Incremental DOM.

Example

In

export default function render(data) {
    var header = data.conditional ? <div /> : null;
    var collection = data.items.map((item) => {
        return <li key={item.id} class={item.className}>{item.name}</li>;
    });

    return <div id="container">
        {header}
        <ul>{collection}</ul>
        <p {...data.props}>Some features</p>
    </div>;
}

Out (default, unoptimized options)

const _li$statics = ["key", ""];
const _li$wrapper = function (_item$id, _item$className, _item$name) {
    _li$statics[1] = _item$id;
    elementOpen("li", _item$id, _li$statics, "class", _item$className);
      _renderArbitrary(_item$name);
    return elementClose("li");
};

const _header$wrapper = function () {
    return elementVoid("div");
};

const _div$statics = ["id", "container"];
function render(data) {
    var header = data.conditional ? _jsxWrapper(_header$wrapper) : null;
    var collection = data.items.map(item => {
        return _jsxWrapper(_li$wrapper, [item.id, item.className, item.name]);
    });

    elementOpen("div", "ab4a1d60-7353-4d98-b1f5-2682f1bc2b3c", _div$statics);

      _renderArbitrary(header);

      elementOpen("ul");
        _renderArbitrary(collection);
      elementClose("ul");

      elementOpenStart("p");
      _spreadAttribute(data.props);
      elementOpenEnd("p");
        text("Some features");
      elementClose("p");

    return elementClose("div");
}


// Helpers
// -------

var _jsxWrapper = function _jsxWrapper(func, args) {
    return {
        __jsxDOMWrapper: true,
        func: func,
        args: args
    };
};

var _flipAttr = function _flipAttr(value, name) {
    attr(name, value);
};

var _spreadAttribute = function _spreadAttribute(spread) {
    _forOwn(spread, _flipAttr);
};

var _hasOwn = Object.prototype.hasOwnProperty;

var _forOwn = function _forOwn(object, iterator) {
    for (var prop in object) if (_hasOwn.call(object, prop)) iterator(object[prop], prop);
};

var _renderArbitrary = function _renderArbitrary(child) {
    var type = typeof child;

    if (type === "number" || type === "string" || type === "object" && child instanceof String) {
        text(child);
    } else if (Array.isArray(child)) {
        child.forEach(_renderArbitrary);
    } else if (type === "object") {
        if (child.__jsxDOMWrapper) {
            var func = child.func,
                args = child.args;

            if (args) {
                func.apply(this, args);
            } else {
                func();
            }
        } else if (String(child) === "[object Object]") {
            _forOwn(child, _renderArbitrary);
        }
    }
};

Installation

$ npm install babel-plugin-transform-incremental-dom

Usage

Via .babelrc (Recommended)

.babelrc

{
  "presets": ["es2015"],
  "plugins": ["transform-incremental-dom"]
}

Any of the configuration options may also be passed.

Via CLI

$ babel --plugins transform-incremental-dom script.js

Via Node API

require("babel-core").transform("code", {
  "presets": ["es2015"],
  "plugins": ["transform-incremental-dom"],
});

Any of the configuration options may also be passed.

Options

Require Statics Key

Incremental DOM recommends only using static attribute arrays when a key is specified. For that reason this plugin will automatically generate a UUID key if there is not one and there are static attributes.

Alternatively, you may disable the automatic generation. In this case, static attributes will be deoptimized into the dynamic attributes list.

// Disabled (default)
var _statics = ["key", "key", "href", "http://key/specified"];
var _statics2 = ["href", "http://example.com"];
var _statics3 = ["href", "http://other.com"];

function render() {
  elementVoid("a", "key", _statics);
  if (condition)
    elementVoid("a", "8ad02822-f391-48fb-a277-8065f7f92a99", _statics2);
  } else {
    elementVoid("a", "adbe4414-e6ad-41c0-aae2-1ca578653119", _statics3);
  }
}
// Enabled
var _statics = ["key", "key", "href", "http://key/specified"];

function render(condition) {
  elementVoid("a", "key", _statics);
  if (condition)
    elementVoid("a", null, null, "href", "http://example.com");
  } else {
    elementVoid("a", null, null, "href", "http://other.com");
  }
}

To do this, simply add the requireStaticsKey option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "requireStaticsKey": true
    }
  ]]
}

UUID Prefix

Together with automatic UUID generation, you can specify a UUID "prefix" that will allow for shorter automatic keys. You must ensure that this prefix will not conflict with any other value you use as a key.

When using the UUID prefix, a simple counter is used instead of creating a true UUID.

// Disabled (default)
var _statics = ["href", "http://example.com"];

function render() {
  elementVoid("a", "8ad02822-f391-48fb-a277-8065f7f92a99", _statics);
}
// Enabled ("uuid-")
var _statics = ["href", "http://example.com"];

function render() {
  elementVoid("a", "uuid-1", _statics);
}

To do this, simply add the uuidPrefix option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "uuidPrefix": "uuid-"
    }
  ]]
}

Namspaced Attributes

Incremental DOM supports a few Attribute Namespaces, but those are foreign to JSX. You can enabled them with the namespaceAttributes option. Note that this does not enable Namespaced Elements.

// Enabled
function render() {
    return elementVoid("a", null, ["xml:static", true], "xlink:href", "https");
}

To do this, simply add the namespaceAttributes option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "namespaceAttributes": true
    }
  ]]
}

Inline JSX Expressions

You may enable the experimental inlineExpressions option to attempt to inline any variables declared outside the root JSX element. This can save you from allocating needless closure wrappers around elements that are only referenced inside the root element.

// Disabled (default)
function render() {
  var header = _jsxWrapper(function () {
    return elementVoid("header");
  });
  elementOpen("body");

  _renderArbitrary(header);

  return elementClose("body");
}
// Enabled
function render() {
  elementOpen("body");
  elementVoid("header");
  return elementClose("body");
}

To do this, simply add the inlineExpressions option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "inlineExpressions": true
    }
  ]]
}

Fast Root

You may enable the experimental fastRoot option so that JSX tags inside the root element are never wrapped inside a closure. For code with array maps, this should significantly decrease memory usage and increase speed.

// Disabled (default)
function render() {
  elementOpen("ul");

  _renderArbitrary(items.map(function (item) {
    return _jsxWrapper(function (_item$name) {
      elementOpen("li");

      _renderArbitrary(_item$name);

      return elementClose("li");
    }, [item.name]);
  }));

  return elementClose("ul");
}
// Enabled
function render() {
  elementOpen("ul");

  items.map(function (item) {
    elementOpen("li");

    _renderArbitrary(item.name);

    return elementClose("li");
  });

  return elementClose("ul");
}

To do this, simply add the fastRoot option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "fastRoot": true
    }
  ]]
}

Alternatively, you may enable and disable this with inline comments:

function render() {
    /**
     * Enable for this tree
     * @incremental-dom enable-fastRoot
     */
    return <div>{
      items.map(function(item) {
        return <li>{item.name}</li>;
      })
    }</div>;
}

/**
 * Enable for everything under this function
 * @incremental-dom enable-fastRoot
 */
function render() {
    /**
     * Disable fastroot for this tree
     * @incremental-dom disable-fastRoot
     */
    return <div>{
      items.map(function(item) {
        return <li>{item.name}</li>;
      })
    }</div>;
}

Components

You may enable the experimental components option so that JSX tags that start with an upper case letter are passed as a reference to incremental DOM calls, instead of as a string. This can be useful when your code implements components through these kind of calls, though that's not done by incremental DOM automatically. Note that this will break unless you have code to handle it.

// Disabled (default)
function render() {
  elementVoid("MyComponent");
}
// Enabled
function render() {
  elementVoid(MyComponent);
}

To do this, simply add the components option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "components": true
    }
  ]]
}

Auto Importing Incremental DOM

By deafult, babel-plugin-transform-incremental-dom expects any necessary Incremental DOM methods to already imported and in-scope:

// Disabled (default)

// Manually managed imports
import { elementOpen, elementClose, text } from "incremental-dom";

function render() {
  elementOpen("div");
  text("Hello World.");
  elementClose("div");
}

This is a hassle if you suddenly use a spread attribute and don't remember to import elementOpenStart, elementOpenEnd, and attr methods. And, attr and text are very generic names, leading to issues where you might redefine the variable in the rendering function, breaking it.

This can be fixed entirely by auto-importing everything necessary from Incremental DOM module:

// Enabled with `incremental-dom`

// Auto generated import
var _incrementalDOM = require("incremental-dom");

function render() {
  (0, _incrementalDOM.elementOpen)("div");
  (0, _incrementalDOM.text)("Hello World.");
  (0, _incrementalDOM.elementClose)("div");
}

To do this, simply add the moduleSource option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "moduleSource": "incremental-dom"
    }
  ]]
}

Additionally, the module source can be an absolute or relative path to the module. If a relative path is used, it will be resolve relative to the process.cwd() of the babel process.

Skip Children

Some templates do not control all the child elements of certain nodes, like components. Incremental DOM supports such nodes using a "skip" child. To support this, we identify a special skip attribute, which will not contain any child nodes and will never clear the later-added children of the element.

function render() {
    // This node will neither create nor clear children.
    return <div __skip>
    </div>;
}

To customize the attribute that signals a skip, simply add the skipAttribute option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "skipAttribute": "skip"
    }
  ]]
}

Runtime

By deafult, babel-plugin-transform-incremental-dom injects several helpers into each file as needed. When transforming multiple files with JSX, you can avoid this helper duplication by specifying a runtime library to use instead.

The runtime's required functions are:

  • jsxWrapper

    To prevent iDOM's incremental nature from screwing up our beautiful JSX syntax, certain elements rendering functions must be wrapped evaluated at a later time. The element will be passed into jsxWrapper, along with an array of any (if any) arguments needed to render the contained JSX element.

    Note it is not jsxWrapper's responsibility to create the JSX rendering function, merely to mark the passed in function as a lazy evaluation. Here, we return a special __jsxDOMWrapper struct with the needed information.

    runtime.jsxWrapper = function(elementFn, args) {
      return {
        __jsxDOMWrapper: true,
        func: elementFn,
        args: args
      };
    }
  • renderArbitrary

    To render child elements correctly, we'll need to be able to identify them. renderArbitrary receives a child, and must call the appropriate action. For string and numbers, that's to call IncrementalDOM#text. For lazy evaluation JSX functions, that's to invoke the closure. For arrays, that's to render every element. And for objects, that's to render every property.

    Note that we identify lazy JSX functions by the __jsxDOMWrapper struct we created inside the jsxWrapper runtime function.

    runtime.renderArbitrary = function _renderArbitrary(child) {
      var type = typeof child;
      if (type === "number" || (type === string || type === 'object' && child instanceof String)) {
        iDOM.text(child);
      } else if (Array.isArray(child)) {
        child.forEach(_renderArbitrary);
      } else if (type === "object") {
        if (child.__jsxDOMWrapper) {
          var func = child.func, args = child.args;
          if (args) {
            func.apply(this, args);
          } else {
            func();
          }
        } else if (String(child) === "[object Object]") {
          _forOwn(child, _renderArbitrary);
        }
      }
    }
  • spreadAttribute

    To set every attribute inside a SpreadAttribute, we'll need to iterate over every property and determine if it's an own property. If so, call IncrementalDOM#attr to set it.

    runtime.spreadAttribute = function _spreadAttribute(spread) {
      for (var prop in spread) {
        if (Object.prototype.hasOwn.call(spread, prop)) {
          iDOM.attr(prop, spread[prop]);
        }
      }
    }

To do this, simply add the runtimeModuleSource option to the Incremental DOM plugin:

{
  "plugins": [[
    "transform-incremental-dom", {
      "runtimeModuleSource": "iDOMHelpers"
    }
  ]]
}

Additionally, the runtime module source can be an absolute or relative path to the module. If a relative path is used, it will be resolve relative to the process.cwd() of the babel process.

More Repositories

1

Unlock

Unlock multiple Core Storage encrypted volumes at boot.
Objective-C
195
star
2

trace-mapping

Trace the original position through a source map
TypeScript
76
star
3

jsx2

Just your normal (JSX) syntax, with faster performance and less memory
TypeScript
25
star
4

minionette

A mini Marionette for Backbone.js
JavaScript
23
star
5

PJs

A Promises A/+ implementation
JavaScript
18
star
6

ns2

C++
16
star
7

turbo-tailwind

An example repo that demonstrates a basic turbo-engine enabled program.
Rust
14
star
8

babel-plugin-transform-for-of-as-array

Transform all for-of loops into the equivalent array for loop
JavaScript
14
star
9

source-map

JavaScript
13
star
10

string-dedent

De-indents (dedents) passed in strings
TypeScript
8
star
11

async-context-native

A dead simple native implementation of AsyncContext
JavaScript
7
star
12

gen-mapping

JavaScript
7
star
13

proposal-regexp-atomic-and-possessive

A proposal to add Atomic Groups and Possessive Quantifiers to RegExps
HTML
6
star
14

safe-decode-uri-component

Safely decode URI components, leaving invalid sequences as they are
JavaScript
6
star
15

resolve-uri

Resolve a URI relative to an optional base URI
JavaScript
6
star
16

rw

Like sponge, but without the moreutils kitchen sink
Rust
4
star
17

path-sorter

Sort paths, giving precedence to sibling, descendant, then parent directories
TypeScript
3
star
18

set-array

JavaScript
3
star
19

minx

A completely static site generator powered by JS+JSX
JavaScript
3
star
20

typescript-import-type-without-execution

Demonstrates that TypeScript supports importing a type from an impure file without executing it
TypeScript
2
star
21

dotfiles

My homesick dotfiles
Shell
2
star
22

InstapaperActivity

Instapaper UIActivity in the share menu!
Objective-C
2
star
23

ActivityLoader

A MobileSubstrate library for adding custom UIActions (iOS 6+ share actions) to all apps.
Objective-C
2
star
24

babel-plugin-bundler

A concatenated module bundler, without redundant UMD boilerplate.
JavaScript
1
star
25

jquery-remove-backbone

Have jQuery remove your Backbone Views for you
JavaScript
1
star
26

dotvim

My precious .vim files
Vim Script
1
star
27

trimmest

Trims leading and trailing whitespace
JavaScript
1
star
28

build-mapping

Build source and source maps
TypeScript
1
star