CSS in JS React vs Stylesheet (v2)

Revision 2 of this benchmark created on


Description

1000 node version of http://jsperf.com/js-inline-css-vs-stylesheet-one-at-a-time

Testing insertion speed for long deeply nested DOM structures.

Rendered using React in the following ways:

  1. Inline: inline style attributes from JS (see https://speakerdeck.com/vjeux/react-css-in-js)
  2. Stylesheet: JS created stylesheet + class names.

Example taken from actual usage on irccloud.com

More tests could be added using innerHTML, DOM, jQuery.

Preparation HTML

<!-- React -->
<script src="http://fb.me/react-0.12.1.min.js"></script>
<!-- Undercore -->
<script src="http://underscorejs.org/underscore-min.js"></script>

<div id="inlineMountNode"></div>
<div id="stylesheetMountNode"></div>

Setup

var STYLESHEET = (function() {
        // Create the <style> tag
        var style = document.createElement("style");
        // WebKit hack :(
        style.appendChild(document.createTextNode(""));
        // Add the <style> element to the page
        document.head.appendChild(style);
        return style.sheet;
    })();
    function addStyleRule(props, selector) {
        // we insert an empty rule just to create a new CSSStyleRule object
        // the second param is the index to insert at
        // using the length property we effectively "append" the rule to the end of the sheet
        var ruleIndex = STYLESHEET.insertRule(selector + " {}", STYLESHEET.cssRules.length)
    
        // the index is the position the rule was inserted at, so we can now get a handle on it
        var rule = STYLESHEET.cssRules.item(ruleIndex);
    
        // now we have it, modify it's styles!
        // applied to the document immediately
        _.each(props, function (v, k) {
            rule.style[k] = v;
        });
    }
    
    var styleData = {
        '.row': {
            backgroundColor: "rgb(226, 237, 255)",
            borderCollapse: "separate",
            color: "rgb(51, 51, 51)",
            display: "block",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "14px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "19px",
            lineHeight: "17px",
            overflowX: "hidden",
            overflowY: "hidden",
            paddingBottom: "1px",
            paddingLeft: "50px",
            paddingRight: "15px",
            paddingTop: "1px",
            whiteSpace: "pre-wrap",
            width: "796px",
            wordWrap: "break-word"
        },
        '.date': {
            borderCollapse: "separate",
            color: "rgb(170, 170, 170)",
            display: "inline-block",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "11px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "17px",
            lineHeight: "17px",
            marginLeft: "-50px",
            marginRight: "5px",
            marginTop: "2px",
            textAlign: "center",
            verticalAlign: "top",
            whiteSpace: "pre-wrap",
            width: "45px",
            wordWrap: "break-word"
        },
        '.title': {
            borderCollapse: "separate",
            color: "rgb(170, 170, 170)",
            display: "inline",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "11px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "auto",
            lineHeight: "17px",
            textAlign: "center",
            whiteSpace: "pre-wrap",
            width: "auto",
            wordWrap: "break-word"
        },
        '.g': {
            borderCollapse: "separate",
            color: "rgb(51, 51, 51)",
            display: "block",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "14px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "17px",
            left: "-999px",
            lineHeight: "17px",
            position: "absolute",
            top: "-999px",
            whiteSpace: "pre-wrap",
            width: "3.90625px",
            wordWrap: "break-word"
        },
        '.message': {
            borderCollapse: "separate",
            color: "rgb(51, 51, 51)",
            display: "inline",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "14px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "auto",
            lineHeight: "17px",
            whiteSpace: "pre-wrap",
            width: "auto",
            wordWrap: "break-word"
        },
        '.authorWrap': {
            borderCollapse: "separate",
            color: "rgb(51, 51, 51)",
            display: "inline",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "14px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "auto",
            lineHeight: "17px",
            whiteSpace: "pre-wrap",
            width: "auto",
            wordWrap: "break-word"
        },
        '.mode_symbol': {
            borderCollapse: "separate",
            color: "rgb(186, 23, 25)",
            display: "none",
            fontFamily: "Inconsolata, 'Ubuntu Mono', Menlo, Consolas, Courier, monospace",
            fontSize: "15px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "auto",
            lineHeight: "17px",
            marginBottom: "0px",
            marginBeft: "0px",
            marginBight: "3px",
            marginBop: "0px",
            whiteSpace: "pre-wrap",
            width: "auto",
            wordWrap: "break-word"
        },
        '.mode_pill': {
            borderCollapse: "separate",
            color: "rgb(186, 23, 25)",
            display: "inline",
            fontFamily: "Inconsolata, 'Ubuntu Mono', Menlo, Consolas, Courier, monospace",
            fontSize: "15px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "bold",
            height: "auto",
            lineHeight: "17px",
            marginBottom: "0px",
            marginBeft: "0px",
            marginBight: "3px",
            marginBop: "0px",
            whiteSpace: "pre-wrap",
            width: "auto",
            wordWrap: "break-word"
        },
        '.bufferLink': {
            borderCollapse: "separate",
            color: "rgb(34, 34, 34)",
            cursor: "auto",
            display: "inline",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "14px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "bold",
            height: "auto",
            lineHeight: "17px",
            textDecoration: "none",
            whiteSpace: "nowrap",
            width: "auto",
            wordWrap: "break-word"
        },
        '.content': {
            borderCollapse: "separate",
            color: "rgb(51, 51, 51)",
            display: "inline",
            fontFamily: "'Helvetica Neue', Helvetica, Arial, sans-serif",
            fontSize: "14px",
            fontStyle: "normal",
            fontVariant: "normal",
            fontWeight: "normal",
            height: "auto",
            lineHeight: "17px",
            whiteSpace: "pre-wrap",
            width: "auto",
            wordWrap: "break-word"
        }
    };
    
    _.each(styleData, addStyleRule);
    var LogLines = React.createClass({
        styled: function (className, elProps) {
            elProps = elProps || {};
            if (this.props.inline) {
                elProps.style = styleData['.' + className];
            } else {
                elProps.className = className;
            }
            return elProps;
        },
        render: function () {
            var lines = {};
            _.each(_.range(1000), function (i) {
                var div = React.DOM.div(this.styled('row'),
                    React.DOM.span(this.styled('date'),
                        React.DOM.span(this.styled('title'), '17:06')
                    ),
                    React.DOM.span(this.styled('g'), '\u00A0'),
                    React.DOM.span(this.styled('message'),
                        React.DOM.span(this.styled('authorWrap'),
                            React.DOM.span(this.styled('g'), '<'),
                            React.DOM.span(this.styled('mode_symbol'), '@'),
                            React.DOM.span(this.styled('mode_pill'), '•'),
                            React.DOM.a(this.styled('bufferLink', {
                                'href': "#"
                            }), 'foo'),
                            React.DOM.span(this.styled('g'), '>'),
                            '\u00A0'
                        ),
                        React.DOM.span(this.styled('content'), 'bar')
                    )
                );
                lines['line-' + i] = div;
            }, this);
            return React.DOM.div(null, lines);
        }
    });
    var logs = React.createFactory(LogLines);

Test runner

Ready to run.

Testing in
TestOps/sec
Stylesheet

React.render(logs({inline: false}), document.getElementById('stylesheetMountNode'));
ready
Inline
React.render(logs({inline: true}), document.getElementById('inlineMountNode'));
 
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.