Node.js & React Part 3
Universal React App — ES6 and React on Node
Let’s build off of what we learned in part 1 and part 2 to build an interesting and useful “universal” React web app to format and validate JSON. We’ll write an ES6/ES2015 React component that will be used on both the server and client. The server will turn our JSX into HTML, and the client will take that HTML and attach event handlers to it.
Here’s a screenshot of the simplified JSONLint clone we will build.
See the end of part 2 to understand our motivation for universal React.
The code for this example is in this github repo.
Server-side React
Tooling
We want to write our React app with ES6+/ES2015+ JavaScript. However, neither the browser nor Node fully support all ES6 features. Our ES6 code can run on the server if we can translate it into ES5 using Babel.
Getting Started
First, make sure node and Babel are installed. (If you walked through part 1 and part 2 you should have these already).
Edit: This was written using Babel 5. Babel 6 was just released with breaking changes. I’ll try to get this running with Babel 6 and update the post with the necessary changes. In the meantime, use Babel 5 (see my package.json).
Now we’ll create our JSONLint clone in our React file so the server can import the it and turn it into HTML.
The following update to ReactApp.jsx returns a simple app with a form and a button. There is an event handler to validate and format the JSON when the button is pressed. It has some simple styles and uses Flexbox to easily center things. Lastly, the app displays error messages for invalid JSON.
Node + React
Before we write our server code lets transpile our ES6 to ES5. For the client-side code we can still use webpack as we configured it in part 2.
webpack
However, we can’t use Webpack because it is for the browser; it uses the babel-loader to transpile ES6 to ES5, but then bundles all of the transpiled files into a single JS file (react-app.js in this tutorial).
For now, we’ll use babel directly from the command line to transpile all files in our components directory and put the corresponding ES5 output of each file in a new folder called es5-lib. (We only have one JSX component for now, but if you extend this example you’ll probably have more).
babel components -d es5-lib
Now we’ll update our Node server code.
First, we’ll import the transpiled ES5 code that Node is comfortable with. Next, we’ll use React.createFactory to instantiate our React component and ReactDOMSever.renderToString to convert our the React component (JSX) to HTML. Then we’ll create the entire HTML page using ES6 string templating (which is easier than learning and configuring yet another templating engine like Jade or Mustache) to send off to the browser.
At this point we can run the server and play with our app at http://localhost:9000/
node server.js
Notice that if we make errors in JSON, our app responds accordingly:
Automation with Gulp and nodemon
Yay, we have a working universal React app! However, you might notice that it’s a painful process if we want to make changes to the code. We have to kill the server, transpile and bundle React for the client, transpile React for the server, and then fire the server up again. This is not a sustainable practice if you’re actually trying to build your own universal React app.
Let’s see if we can automate all of the intermediate steps so that we can simply update our React code, hit save, and refresh the browser.
We could use babel-node which is a version of node that can also transpile ES6 code for us, but Babel’s docs tell us not to use it in production.
You should not be using babel-node in production. It is unnecessarily heavy, with high memory usage due to the cache being stored in memory. You will also always experience a startup performance penalty as the entire app needs to be compiled on the fly.
Instead, we’ll continue to transpile all of the server-side React code up front.
Let’s have gulp watch our code for changes and kick off the server-side transpilation and client-side transpilation and bundling when necessary. For the server, we’ll use gulp-babel to transpile and gulp-util to output any errors that happen along the way. We can’t use gulp-babel in the same manner for the front-end code because it outputs CommonJS (or AMD or…) but the browser doesn’t know how to read CommonJS. So we’ll stick with our existing webpack process for the front-end bundling and again use gulp-util for logging. Here’s our resulting gulpfile.js.
To run this you’ll need to do some npm installing.
npm install --global gulp
npm install --save-dev gulp
npm install --save-dev gulp-babel
npm install --save-dev gulp-util
Now we can open a terminal and run “gulp” from the top-level directory where our gulpfile.js is located and we’ll see gulp doing it’s thing.
When we change ReactApp.jsx, our React code will be transpiled and bundled accordingly.
Lastly, we need to get node to restart our server whenever the source files change. Fortunately, a Google search points us to nodemon, which does exactly that.
npm install -g nodemon
Our development process is now as simple as navigating to our top level directory and
1. run “gulp” in a terminal
2. run “nodemon” in another terminal
Now when you make a change all you have to do is refresh your browser. Eventually, refreshing the browser becomes a pain especially when you have a stateful app (i.e. you have to click several things to see if your change worked). That’s where hot module reloading comes in :) See Dan Abramov’s talk for inspiration.
Summary
We built a simplified JSONLint as a universal React app. We then used gulp to speed up our development process.