ponyfoo.com

Universal React with Babel, Browserify

Fix

I finally got around to trying out React, and this blog post will detail my initial impressions as well as a tutorial on how to make your React apps universal from the get-go. As usual, I have a long-standing need to refactor bevacqua.io, as well as a long-standing desire to finally try out ES6 through Babel, React, and JSX. I’ve passively read a lot on the subject but hadn’t actually dipped my toes on any of it, so I figured I’d put my fingertips where my eyeballs were at – hoping not to poke myself. I came across a bit of free time and decided this was the perfect moment to put Babel and ES6 in practice.

My website is static enough that it doesn’t actually need any of this, but it’s nevertheless a great excuse to get started with React and Babel. After all, this blog is the excuse that got me into Node.js, client-side MVC, browserify, and so on. You get the idea – learning through experimentation works really well for me, and I guess it’s like that for most people.

Here’s what this article will cover.

  • Using universal ES6 (server-side and client-side) through Babel and Browserify
  • Using npm run for the whole build process
  • Building an Express server-side application that serves our React app
  • Building a simple server-rendered React app
  • Booting that app on the client-side

If all goes well, ES6 and React may become the driving topics on Pony Foo. Let’s see where this article takes us, first.

The Plan

The first thing I did was installing some dependencies. I decided I would use browserify because that way I can easily leverage any of the many CommonJS modules on npm. I’d use the babelify transform to turn ES6 code into something the browser understands, and babel-node (for now – it’s not meant for use in production) to run that code on the server-side. I’ll be using nodemon and watchify to speed up my development cycle, and the latest version of io.js (3.0.0 at the time of this writing).

io.js and nvm

First off, if you don’t use io.js or nvm, it’s time. Installing nvm let’s you easily switch around different versions of Node.js (and io.js – whatever) without any friction. Installing nvm is easy, and it makes installing different versions of node just as easy. Here’s how to install nvm:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.26.0/install.sh | bash

Once you have nvm, you can easily install any version of io.js. We’ll install 3.0.0.

nvm install iojs-v3.0.0

When you want to switch to that version of the node binary, just run the following.

nvm use iojs-v3.0.0

Getting Started

Next up let’s create a directory for our application and install some dependencies.

mkdir my-app
cd my-app
npm init
npm i react -S
npm i babel babelify browserify watchify nodemon -D

Setting up Babel

The next order of business is to set up Babel, so that we can leverage ES6 everywhere. When it comes to the server-side, we can use babel-node as a drop-in replacement for node. There’s a big warning sign against using this in production flows, but it works very well during development.

First off, we’ll need a scripts entry for babel-node in our package.json. This is necessary because npm run understands how to find the babel-node executable, so now you can do npm run babel-node app.js and run things using babel-node without installing it globally and messing up versioning.

{
  "scripts": {
    "babel-node": "babel-node --stage 0"
  }
}

When it comes to client-side ES6, we were already going to compile our code into a bundle via browserify, so it makes a lot of sense to throw in the babelify transform in there. Babelify leverages Babel to automatically transform our ES6 code into ES5 when bundling the code in our package. We can easily set babelify up by adding the following entry to our package.json.

{
  "browserify": {
    "transform": [
      ["babelify", { "stage": [0] }]
    ]
  }
}

You also definitely want a continuous development script. These save you precious time during development. Whenever your source code changes, the Browserify bundle will be rebuilt. Whenever the server-side code changes, the application should be restarted. For now, we can stick to just those two things. You’ll need a small change to package.json and a build script.

{
  "scripts": {
    "start": "build/build-development"
  }
}
#!/bin/bash

watchify client/main.js -o public/bundle.js -dv &
nodemon --exec npm run babel-node -- app.js

Don’t forget to run chmod +x build/build-development so that you can execute that script!

The -dv flags on watchify mean debug mode (source maps) and verbose output (a single line written to the terminal whenever the bundle gets recompiled). We pass --exec npm run babel-node to nodemon so that it runs our app through babel-node instead of using the regular node executable.

Using JSX? Built-into Babel

Given that Babel now has built-in support for JSX, – not very modular of Babel, but we’ll play along – I figured this is a great opportunity to try out JSX. In case you haven’t ever tried React before, you’ve probably heard the saying: “Everyone hates JSX until they try it”. Thus far I’m on the “what is this non-sense” camp, but I’ll probably end up accepting it.

In case you have no idea what I’m talking about, JSX is a templating engine from Facebook that allows you to embed XML into your JavaScript files, enabling seemingly awful lines of code such as React.renderToString(<App />), as we’ll explore in a minute.

Onto the server-side

When it comes to the server-side, I’ll stick to what I’m comfortable with. I’ll be using express. We need something to render the layout surrounding our React application, and I chose express-handlebars for that, but really any kind of templating language would do – and to be fair, we probably could get away with just using ES6 template strings.

npm i express express-handlebars -S

Let’s put together our app.js file using some light ES6 code. First off, some import statements. These are equivalent to doing var express = require('express'); and so forth. We’ll get to the App later on. For now, all you need to know is that this will be the root entry point of our application. The server-side and the client-side will leverage App slightly differently in order to render the application on both the server-side and the client-side.

import express from 'express';
import hbs from 'express-handlebars';
import React from 'react/addons';
import App from './components/app';

Now that we have our dependencies in place, we can configure basic stuff about Express. This sets up express-handlebars so that we can place our layout in views/layout.html. I’ve also turned off the x-powered-by header because it makes no sense to advertise your technology stack like that.

var app = express();
app.engine('html', hbs({ extname: 'html' }));
app.set('view engine', 'html');
app.locals.settings['x-powered-by'] = false;

The view route is where things get a tad more interesting. This route will be hit once, whenever the page is first loaded. After that, the client-side rendering engine in React will take over. We’ll get worried about routing and whatnot another day, for now our focus is on figuring out the correct way to render React apps.

app.get('/', function home (req, res, next) {
  res.render('layout', {
    reactHtml: React.renderToString(<App />)
  });
});

The <App /> expression is just JSX for React.createFactory(App)({}). Good thing JSX is built into Babel! The app should listen on a port, so that you can actually visit the site.

app.listen(process.env.PORT || 3000);

Oh, and the layout.html we’ve been discussing should be placed in views/layout.html. For the moment, we’ll get away with this measly piece of code. This will guarantee that if we can actually see some HTML, it’ll be because it was server-side rendered. Here is layout.html in all its glory.

<main>{{{reactHtml}}}</main>

Now let’s turn into what you came here for.

Some Actual React Code

The App component looks like the piece of code below. It was taken from react-starter-es6-babel (as the app itself is not important) with some minor modifications, so that it works as a universal script. If you head over to their repository, you’ll notice here I’m exporting the component via export default, instead of just calling React.render(<App />, el).

That simple change will allow us to render the component on both the server-side and the client-side alike. The App component extends the React.Component class. It has a state property with an n value – that can be incremented using a button. It gets rendered through a render method which returns the JSX that makes up the component. It also binds a click handler to another method on the component, which changes the state by calling setState on the component.

import React from 'react'

export default class App extends React.Component {
  constructor () {
    super()
    this.state = { n: 0 }
  }
  render () {
    return <div>
      <h1>clicked {this.state.n} times</h1>
      <button onClick={this.handleClick.bind(this)}>click me!</button>
    </div>
  }
  handleClick () {
    this.setState({ n: this.state.n + 1 })
  }
}

Trying it out

If you run npm start, it’ll execute the app.js script via babel-node, and if you visit the application at http://localhost:3000, you should see something like the screenshot below.

The application running on Google Chrome
The application running on Google Chrome

You might also notice that the button doesn’t do anything, even though our component has a click handler and everything. If we retrace our steps, you’ll remember that our layout only consists of the rendered React HTML inside a <main> tag – not very dynamic.

Onto the client-side

We’ve already set up our build to browserify a bundle earlier. Let’s put that bundle together at client/main.js. When you run npm start the next time, a bundle should be created at public/bundle.js.

import React from 'react/addons';
import App from '../components/app';
var main = document.getElementsByTagName('main')[0];

React.render(<App />, main);

You’ll need the serve-static package in order to serve the bundle.

npm i serve-static -S

It should be mounted on the public directory.

import serveStatic from 'serve-static';

app.use(serveStatic('public'));

Also, don’t forget to add the <script> tag to your layout.html!

<main>{{{reactHtml}}}</main>
<script src='/bundle.js'></script>

The bundle takes over what we had already rendered on the server-side, and sets up the click handler. You can now click on the button and things will happen!

Clicking the button increases the counter
Clicking the button increases the counter

Next up we’ll figure out how routing is set up, and we’ll adjust our code as necessary. Think this should turn into an extensive series? It’s kind of fun to write about.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (14)

Jason wrote

Think this should turn into an extensive series?

Yes, please!

Dom wrote

I am surprised for someone who is a strong proponent of plain javascripts modules (I am all for it) you are going for React, have you checked Mithriljs (http://mithril.js.org/)?

  • uses virtual DOM as well,
  • no need for a precompiler,
  • smaller in size,
  • small API,
  • better performance

Plain javascript, very functional, you will be able to use browserify and do isomorphic applications with it too. Cheers

Nicolas Bevacqua wrote

Good question. I find it useful to try and use many different frameworks so I can decide what works best on each situation and not limit myself to any single one. React is popular right now and chances are I’ll end up consulting on a project that uses it, and it was a good excuse to start typing ES6 anyways – which was my end goal

Carlos Vega wrote

Well, Mithril is, technically, an MVC while React handles the View only. Also, I find Mithril’s syntax for defining views really unpleasant (but this is just a matter of opinion):

todo.view = function() {
    return m("html", [
        m("body", [
            m("input"),
            m("button", "Add"),
            m("table", [
                m("tr", [
                    m("td", [
                        m("input[type=checkbox]")
                    ]),
                    m("td", "task description"),
                ])
            ])
        ])
    ]);
};

I like it that Mithril’s templates compile into javascript though.

The problem I see is that the framework itself never gained significant traction. React, on the other hand, is not only hot right now (which shouldn’t be a factor you rely on to make a decision about using it) but it’s been battle tested. I’d like to see bigger applications developed with Mithril.

Dom wrote

That’s what a lot of people come up with, popularity, although I think what Mithril lacks is above all marketing, it doesn’t have corporate backing like React with Facebook has, so it relies on its community, which is quite active on Gitter.

For battle tested apps, you can check those open source apps:

or Flarum (source code: https://github.com/flarum/core, with Mithril specific in js folder)

What I like about it, it is pretty low level and improves my javascript overall instead of learning a new language like you do most of the time when learning a framework.

Brian wrote

So, if babel-node is not considered production-worthy, that begs the question of what should you use to cover that use case in a production environment? I’d be very surprised if there wasn’t a hack similar to CoffeeScript’s “register” out there.

Chris wrote

I would be very interested in knowing the answer to this as well. I think you would only be losing ES7 features if you just relied on IO.js 3.X to run the project instead of babel-node.

Nicolas Bevacqua wrote

The answer to this one is probably to compile into ES5 before-hand and then just run node. Instead of:

babel-node app.js

You should be doing:

babel app.js -o bin/app.js
node bin/app.js

During development you should also add the --watch flag to the babel command for continuous development.

Alex Robinson wrote

In the “Onto the client-side” section, shouldn’t

import App from '../../components/app';

be

import App from '../components/app';

?

Nicolas Bevacqua wrote

You’re correct, I’ve been translating what I actually did with the bevacqua.io codebase into what I wrote in this blog post, and it seems I didn’t account for that statement. The original code was nested inside client/js, which triggered this issue.

Thanks!

Equan wrote

Thanks for the article but i am getting error like this

> es6@1.0.0 babel-node /home/equan/Workspaces/hacking/nodejs/es6
> babel-node --stage 0 "app.js"

/home/equan/Workspaces/hacking/nodejs/es6/components/app.jsx:15
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
                                                                                                          ^

TypeError: Super expression must either be null or a function, not undefined
    at _inherits (/home/equan/Workspaces/hacking/nodejs/es6/components/app.jsx:15:113)
    at /home/equan/Workspaces/hacking/nodejs/es6/components/app.jsx:3:25
    at Object.<anonymous> (/home/equan/Workspaces/hacking/nodejs/es6/components/app.jsx:3:25)
    at Module._compile (module.js:430:26)
    at normalLoader (/home/equan/Workspaces/hacking/nodejs/es6/node_modules/babel/node_modules/babel-core/lib/api/register/node.js:199:5)
    at Object.require.extensions.(anonymous function) [as .jsx] (/home/equan/Workspaces/hacking/nodejs/es6/node_modules/babel/node_modules/babel-core/lib/api/register/node.js:216:7)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Module.require (module.js:365:17)
    at require (module.js:384:17)

any idea? i use react v0.13.3.

gnhuy91 wrote

Hi, Thanks for the great tutorial! How can I pass data from server to React App component (as props and such)?

Blaine Garrett wrote

Great article. Some of the bits are out of date with the latest babel, but I muddled my way though to get it to work.

All that said, what changes would you make for production ready code? Is there a way to get the app.js to transpile to something deployable to node.js (I’m using Google Cloud Platform). At a glance, it seems that it would be transpiled with each instance load, which maybe isn’t so terrible. Perhaps I am misunderstanding where app.js gets transpiled to. Thank you.