Adding React to an existing web application and build pipeline
30th Jan 2018
2019 Edit
This article is now out of date and it's unlikely that you'll be able to use any of the techniques in it to add react to an existing app. This is because there are too many moving parts with too many breaking changes between versions:
- Webpack has now updated by a major version and with that brings plenty of changes
- Babel also now has updated by a major version and with that all dependent packaged have been renamed
- React itself has moved on but doesn't have any breaking changes within the context of this article
Original article
In order to brush up on my React knowledge, in this post I'm going to outline how I added it into the admin tools of links.edspencer.me.uk.
This walk through will have an emphasis on not just being a hello world application, but will also focus on integrating everything into your existing build pipeline - so you'll actually be able to run your real application in a real environment that isn't your localhost.
I'm writing this up because I felt the setup of React was a bit confusing. You don't just add a reference to a minified file and off you go - the React setup is a lot more sophisticated and is so broken down and modular that you can swap out just about any tool for another tool. You need a guide just to get started.
We'll be going along the path of least resistance by using the toolchain set that most React developers seem to be using - Webpack for building, and Babel for transpiling.
The current application
The current application works on purely server side technology - there is no front end framework currently being used. The server runs using:
- NodeJs
- ExpressJs
- The Pug view engine
The build pipeline consists of the following:
- A docker image built on top of the node 6.9.4 image, with some additionally installed global packages
- An npm install
- A bower install for the front end packages
- A gulp task build (to compile the SCSS, and minify and combine the JS and CSS)
Step 1 - moving to Yarn
You can get your React dependencies through npm, but I figured that moving to Yarn form my dependency management was a good move. Aside from it being suggested in most tutorials (which isn't a good enough reason alone to move):
- Bower is not deprecated , but the Bower devs recommend moving to Yarn
- The current LTS version of Node (8.9.4) ships with npm 5, which has some checksum problems that might cause your builds to fail
- Yarn is included in the Node 8.9.4 docker image
Hello node 8.9.4
While we're here we may as well update the version of node that the app runs under to 8.9.4, which is the current LTS version.
In dockerized applications, this is as easy as changing a single line of code in your docker image:
FROM node:6.9.4
Becomes:
FROM node:8.9.4
Goodbye Bower
Removing Bower was easy enough. I just went through the bower.json file, and ran yarn add for each item in there. This added the dependencies into the package.json file.
The next step was then to update my gulp build tasks to pull the front end dependencies out of the node_modules folder instead of the bower_components folder.
I then updated my build pipeline to not run bower install or npm install, and to instead run yarn install, and deleted the bower.json file. In my case, this was just some minor tweaks to my dockerfile.
Build pipleline updates
The next thing to do was to remove any calls to npm install from the build, and to instead call yarn install.
Yarn follows npm's package file name and folder install conventions, so this was a very smooth change.
Step 2 - Bringing in Webpack
You have to use a bundler to enable you to write modular React code. Most people tend to use Webpack for this, so that's the way we're going to go. You can actually use Webpack to do all of your front end building (bundling, minifying, etc), but I'm not going to do this.
I have a working set of gulp jobs to do my front end building, so we're going to integrate Webpack and give it one job only - bundling the modules.
Firstly, let's add the dependency to webpack.
yarn add webpack --dev
Now we need to add an empty configuration file for Webpack:
touch webpack.config.js
We'll fill out this file later.
Lastly, lets add a yarn task to actually run webpack, which we'll need to run when we make any React changes. Add this to the scripts section of your package.json file:
{
"scripts": { "build-react": "webpack" }
}
You may be thinking that we could just run the webpack command directly, but that will push you down the path of global installations of packages. Yarn steers you away from doing this, so by having the script in your package.json, you know that your script is running within the context of your packages available in your node_modules folder.
Step 3 - Bringing in Babel
Babel is a Javascript transpiler that will let us write some ES6 goodness without worrying too much about the browser support. Babel will dumb our javascript code down into a more browser ready ES5 flavour.
Here's the thing - every tutorial I've seen involves installing these 4 babel packages as a minimum. This must be because Babel has been broken down into many smaller packages, and I do wonder if this was a bit excessive or not:
yarn add babel-core babel-loader babel-preset-es2015 babel-preset-react
Once the above babel packages have been installed, we need to wire it up with webpack, so that webpack knows that it needs to run our react specific javascript and jsx files through Babel.
Update your webpack.config.js to include the following.
module.exports = { module: { loaders: \[ { test: /\\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ } \] } }
Note that the webpack.config.js file is not yet complete - we'll be adding more to this shortly.
Step 4 - Bringing in React
We're nearly ready to write some React. We just need to add a few more packages first, and that'll be it, I promise.
yarn add react react-dom
This will add the core of React, and another React module that will allow us to do some DOM manipulation for our first bit of react coding. This does feel a bit daft to me. Webpack is sophisticated enough to run through our modules and output exactly what is needed into a single JS file. Why, therefore, do we need to break the install of a server side package down so much, if Webpack is going to grab what we need?
Step 5 - Actually writing some React code
We're now at the point where we can write some react. So in my application, I'm going to have a small SPA (small is how SPAs should be - if you need to go big, build a hybrid) that will be the admin tool of my web application.
So, in the root of our web app, let's add a folder named client, and in this folder, let's add the following two files:
client/admin.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './admin-app.jsx';
ReactDOM.render(<App />, document.getElementById('react-app'));
client/admin-app.jsx
import React from 'react';
export default class App extends React.Component {
render() {
return (
<div style={textAlign: 'center'}>
<h1>Hello World - this is rendered by react</h1>
<div>
);
}
}
The above two files aren't doing a lot. The JSX file is declaring our "App" class, and the JS is telling react DOM to push our app JSX into a html element with the id "react-app".
Updating our webpack config
Now we should complete our webpack config to reflect the location of our React files. Update webpack.config.js so that the entire file looks like this:
const path = require('path');
module.exports = {
entry:'./client/admin.js',
output: {
path:path.resolve('dist'),
filename:'admin_bundle.js'
},
module: {
loaders: [
{ test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
{ test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ }
]
}
}
We're telling webpack where then entry point of our React application is, and where to output the bundled code. (dist/admin_bundle.js).
Note we're also leaning on a new package (path) to help us with some directory walking, go ahead and add that using yarn add path.
Now, let's go ahead and ask webpack to bundle our React app:
yarn run build-react
Now if everything as worked as expected, webpack would have generated a bundle for us in dist/admin_bundle.js. Go ahead and take a peek at this file - it contains our code as well as all of the various library code from React that is needed to actually run our application.
Step 6 - Plugging the bundled react code into our application
As this SPA is only going to run in the admin section, we need to add two things to the main page in the admin tool that we want this to run on.
In my case, I'm using the awesome pug view engine to render my server side html. We need to add a reference to our bundled Javascript, and a div with the id "react-app", which is what we coded our react app to look for.
Here's a snippet of the updated admin pug view:
div.container
div.row
div.col-xs-12
h1 Welcome to the admin site. This is rendered by node #react-app
script(type="text/javascript", src="/dist/admin_bundle.js")
Alternatively, here's how it looks in plain old html:
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Welcome to the admin site. This is rendered by node</h1>
</div>
</div>
<div id="react-app"></div>
</div>
<script type="text/javascript" src="/dist/admin_bundle.js">
Now all we need to do is to run our application and see if it works:
Et voila!
Step 8 - A little more build cleanup
As previously stated, I've got an existing set of gulp tasks for building my application, that I know work and that I'd like to not have to re-write.
We've kept out webpack build separate, so let's now tie it up with our existing gulp task.
Previously, I would run a gulp task that I had written that would bundle and minify all Js and SCSS:
gulp build
But now we also need to run webpack. So, let's update our package.json file to include some more custom scripts:
"scripts": {
"build-react": "webpack", "build": "gulp build && webpack"
}
Now, we can simply run:
yarn run build
Now we just need to update our build process to run the above yarn command instead of gulp build. In my case, this was a simple update to a dockerfile.
Conclusion
The above setup has got us to a place where we can actually start learning and writing some React code.
This setup is a lot more involved than with other frameworks I've worked with. This is because the React is a lot less opinionated on how you do things, and the tooling that you use. The disadvantage to this is that it makes the setup a bit trickier, but the advantage is that you have more freedom elsewhere in the stack.
I do however think that there may be a bit too much freedom, and a little more convention and opinion wouldn't hurt. For example some sensible install defaults that could be changed later would cover this off. Whilst there may be some yeoman generators out there, they wont help with integration into existing web applications.
What is interesting is noting the differences between how you build your React applications to how you build your AngularJs applications. With AngularJs, you reference one lib and off you go. You may only use a small part of AngularJs, but you've referenced the whole thing, whereas with React, and Angular, you have a build process which outputs you a file containing exactly what you need.
I think this overall is a good thing, but we should still put this into context and be careful to not quibble over kilobytes.
We also saw how we can integrate the React toolchain into our existing toolchain without throwing everything out.
Next up, I'll be writing a blog post on what you should do in order to run React in production.