Size cost of using Facebook’s React javascript framework

Here at finderful.com we are obsessed with page size. We want pages to load fast and render fast. We were excited when we learned about React and how they speed up DOM changes. We were also excited by create-react-app and how it comes with webpack, autoprefixer, and babel out of the box. But what we really wanted to know was: what is the cost of using this framework? We normally don’t use frameworks for client-side pages here at finderful.com because we find that they bloat the size quite a bit for the few features we want to use. For example, all but one of our webpages currently (2017-05-14) are smaller than the jquery library. Probably the most popular javascript library, and we aren’t using it because it’ll blow out our page sizes.

React is a javascript library more than it is a framework. The main functions that would be used are React.createElement() and ReactDOM.render(). There is also the class React.Component which you are expected to subclass and fill in the render() function, returning an HTML/JSX element or React component.

JSX is a core part of React, and the main reason we love React so much. JSX is like components for HTML. You can define your own components, made up of HTML, and then reuse those components just like you would use HTML elements.

create-react-app helps you to bootstrap a React-based project. It sets up a directory structure with some stub files that you can fill in. The main one you would focus on is App.js, though you are free to modify any HTML/CSS/JS file. create-react-app will take care of converting JSX to JS, ES6 to ES5, packing & minifying for production, and even sets up npm start for a quick development cycle (your app updates just by saving changes). No need to deal with grunt/gulp/brunch, etc.

So our goal is to be able to use create-react-app without React. We aim to achieve this by rewriting the library functions above to be smaller, albeit less featureful. You can see our currently supported list of React features in our github repository.

So let’s see how costly (in bytes) it is to use React, and if necessary, we can bring that size down to a size we’re happy with. Being a JS framework, we predict it’s larger than any of our pages, and as we hope to replace handlebars with JSX, we hope we can rewrite it with minimum functionality at a size smaller than handlebars’s runtime.

We start our experiments by using create-react-app and seeing how big the demo app is. create-react-app makes this easy for us when we ‘build’ the app by printing out the sizes for us.

Using create-react-app@1.3.0 and react@15.5.4:

  46.86 KB  build/static/js/main.3c4a7db0.js
  289 B     build/static/css/main.9a0fe4f1.css

Now let’s set up a baseline for what the absolute minimum would be if React was almost 0 bytes. Note that this wouldn’t actually create a usable webpage. We’ve just removed the import statements from the demo app’s .js files and used dummy functions that don’t do anything:

  5.12 KB  build/static/js/main.171258de.js
  289 B    build/static/css/main.9a0fe4f1.css

41.74 KB (46.86 KB – 5.12 KB) for React. Being bigger than jquery 1.9.1 (32714 bytes), this makes React bigger than any of our pages, before adding our content.

People don’t usually use React by itself, though. So let’s see the sizes if we use React and some common libraries.

Adding in react-router-dom@4.1.1 (Router, Route, and Link):

  57.52 KB  build/static/js/main.713de4d6.js
  289 B     build/static/css/main.9a0fe4f1.css

Adding in redux@3.6.0 (createStore):

  49.2 KB  build/static/js/main.fc7fd213.js
  289 B    build/static/css/main.9a0fe4f1.css

Adding in both (without react-router-redux):

  59.68 KB  build/static/js/main.899cc3c0.js
  289 B     build/static/css/main.9a0fe4f1.css

As above, the demo app without React is only 5.12 KB. We’re already using handlebars at 6656 bytes, so if we can use JSX with fewer bytes, that’ll be great. Even if it’s about the same number of bytes, we really like having the JSX inline in our javascript files instead of templates in separate files like we have now. Also we don’t have to create our own process for having the templates in the javascript files, as create-react-app has taken care of that for us.

So our target is 11776 bytes (5.12 KB * 1000 bytes / KB + 6656 bytes). We just have to go forth and rewrite a few React functions, like React.createElement and ReactDOM.render. While we have found a few versions of React.createElement, we usually prefer to write our own. Also, the ones we’ve found only handle basic HTML elements, and not React components.

Below we provide readable versions of the files src/react.js and src/react-dom.js but in our github repo, we keep the version we used for calculating the size below.

src/react.js:

function isString(string) {
    return typeof(string) === 'string' || string instanceof String;
}

function isComponentClass(componentClass) {
    return componentClass.prototype instanceof Component;
}

function createNode(elementOrString) {
    if (isString(elementOrString))
    // if it's a string, we need to create a node
        return document.createTextNode(elementOrString);
    else
    // if it's an element, then it's already a node
        return elementOrString;
}


function createElement(tagName, attributesObject, children) {
    const element = document.createElement(tagName);
    const attributes = Object.entries(attributesObject);

    attributes.forEach(setAttribute);
    children.map(createNode).forEach(element.appendChild.bind(element));

    return element;

    function setAttribute(attribute) {
        element.setAttribute(attribute[0].replace("className", "class"), attribute[1]);
    }
}

class React {
    static createElement(componentClassOrTagName, attributesObject) {
        if (isComponentClass(componentClassOrTagName)) {
            // if it's a component, render the component
            return new componentClassOrTagName().render();
        } else if (isString(componentClassOrTagName)) {
            // if it's a string, then create an element
            const children = [].slice.call(arguments, 2);
            return createElement(componentClassOrTagName, attributesObject, children);
        } else {
            throw new TypeError("Don't know how to create element from " + componentClassOrTagName);
        }
    }
}

class Component {
}

export default React;
export {Component};

src/react-dom.js:

class ReactDOM {
  static render(child, parent) {
    parent.appendChild(child);
  }
}

export default ReactDOM;

So what’s the verdict? How big is the build? How much did it cost us?

  5.53 KB  build/static/js/main.21e4aa97.js
  289 B    build/static/css/main.9a0fe4f1.css

0.41 KB (5.53 KB – 5.12 KB)! That’s much smaller than our target, so we’re pretty happy to start rewriting all our HTML in JSX, and using create-react-app to speed up our development. We’re planning a new design anyway, so now is probably a good time to drop handlebars and our custom frontend build system (based on grunt) and switch to create-react-app. Be on the lookout for a new design on finderful.com. We’ll be updating the github repo as we build the new design, so be sure to watch it for improvements.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s