How to set up a React project without flipping tables

How to set up a React project without flipping tables

Rabea Gleissner
Rabea Gleissner

May 26, 2017

When I first started learning React, I went through the “Intro to React” tutorial by Facebook, which you can conveniently follow on Codepen. It all made sense to me and I felt like I was ready to write my own to-do list application, as you do when you’re learning a new JavaScript framework.

I installed some npm modules that sounded sensible, used Gulp as my task runner as I normally like doing, and… nothing worked. I ended up spending a whole afternoon trying to set up my project, and never got round to actually writing any code. Why was this so hard?

Based on anecdotal evidence from colleagues and friends, I know that I’m not the only one feeling this frustration. And I assume even the developers at Facebook must have experienced similar afternoons of banging their heads on their desks, because they have kindly created an open source React project starter kit.

I had a look through the repo, and it’s huge! It contains a lot of setup with dependencies that I had never heard of before. I also had a look through other React starter code and example repos, which left my head spinning from the sheer number of npm packages used. I’m not a specialist JavaScript developer, but I still like to understand what all the dependencies that I’m cloning into my project do. And ideally I would like to keep them to a minimum.

So, I decided to do some detective work in an attempt to find the most minimalist setup for a React project possible, and to really understand what each of the necessary dependencies does.

Because surely it can’t be that hard? Surely there must be a minimal setup with which you can write a React app?

The essentials

What we really need to get started with a React project are three ingredients: the React framework (surprise!), a tool that transpiles our React code into vanilla JavaScript, and a tool that tells the transpiler which files to look at and where to put the transpiled files.

ES5 or ES6?

Before we start going over our shopping list of npm packages, we need to make a decision about which version of JavaScript to use. ES6 is the new and improved version of JavaScript. However, it is still not understood by all browsers, which means that we need to transpile it into ES5, the old version of JavaScript. And for this job, we need an additional dependency. ES5 works with React and in the spirit of having a minimal setup, it would make sense to stick with ES5.

However, it is much, much more convenient to use ES6 with React. Imports are easier, extending the React class is simpler, function declarations are shorter, and more. There’s a great blog post on the npmjs website that compares React with ES5 and ES6 using loads of examples that illustrate the advantages of using ES6. Another argument for using ES6 is that most of the documentation and examples you find online use ES6.

Shopping list

React

Let’s start with the most obvious dependency: React! But behold: React is not enough. There are two packages that you need for a React web application: react and react-dom. That was my first source of confusion. I just wanted to write some React—why do I need two dependencies? Turns out that these packages used to be combined, but react-dom was split out about a year ago. The reason is that React can be used for applications other than websites (React Native, anyone?), and rather than having one big package for all purposes, they were separated out so you can pick and choose exactly what you need.

Webpack

So you require the two React packages in your code, and now you can use the React syntax and functionality. Yay! But somehow the React code needs to be transpiled to JavaScript code in order for browsers to understand it. There are some npm packages that we can use to deal with this task. But these packages need to be held together by something. They need to be told which files to work with, and they need to be configured. It’s kind of like musicians in an orchestra that need a conductor to help them play together well. In this case, the React and the ES6 transpilers are musicians and the conductor job will be done by a file bundler like webpack or Browserify.

A bundler can use lots of different plugins to transform our source code. You just need to specify which plugins you want to use, which files should be processed by those plugins, and where to put the final product after the processing is finished.

Most React tutorials suggest webpack, but I’ve also worked with Browserify. Those two bundlers have slightly different philosophies: Browserify came first and was built to run Node.js code in the browser. webpack was created later, and with the primary focus of managing static assets for the front-end. Browserify is more modular, while webpack comes with more features out of the box. Browserify is driven by convention, while webpack is a lot more flexible, which makes it a bit harder to learn. The feature that ultimately sold me on webpack is hot module replacement. It is used together with the very convenient webpack dev server, which watches your files for any changes and reloads the page automatically. With hot module replacement, it will only reload the section of a page that is affected by any changes to a module. The advantage of this over refreshing the whole page is that all the other components will keep their state. Double bonus! No more command + shift + R repetitive strain injury, plus you don't have to potentially perform lots of user actions to get all your components back into a particular state.

However, this blog post is about the most minimal setup, and you can definitely use webpack without the dev server and hot module reloading. So, I won't include these features into the setup for now.

As a side note—there is a bit of a difference between task runners such as Gulp and Grunt and bundlers like webpack and Browserify. Generally, task runners are more concerned with overall automation like building your project, getting it ready for deployment, or running your tests. And bundlers are for concatenating all your files and translating your fancy frameworks into vanilla ES5. There is quite a bit of overlap of responsibilities though. It is possible to use webpack in combination with Gulp or Grunt, but a lot of people just use npm scripts to complete the tasks that webpack doesn’t handle.

Babel

Next on the shopping list is the transpiler, a tool that can translate ES6 to browser-friendly ES5 code and React to vanilla JavaScript. Babel can help us out here. We can plug it into webpack as a loader using the npm package called babel-loader. But once again, the Babel loader is not enough. We have to explicitly install the peer dependencies. To get all the functionality we require, we need three additional npm packages: babel-core, babel-preset-es2015 and babel-preset-react.

But that is really all we need to write a React application!

Step-by-step guide to get started

Let’s get started with the setup! You need to have Node installed to run all these commands. I use Node Version Manager to manage my versions but it’s not a must.

Let’s initialise a new project:

 npm init

Just hit enter to get all the default values filled in automatically. You can always change them later. The package.json file has now been created for us.

And now comes a big, long command to install all the dependencies from our shopping list.

 npm install --save-dev react react-dom webpack babel-loader babel-core babel-preset-es2015 babel-preset-react

If we look at our package.json file we can see that all these dependencies have now been added under devDependencies. And they have also been installed in your /node_modules/ directory.

The package.json file is a way for your application to remember all the dependencies it has. So you never need to commit your /node_modules/ directory because you can just run npm install, which will look at your package.json file to know which node modules it needs to install.

Next we need to create two folders—one for our source code and one for the processed version of the code that webpack will spit out. Let’s say the names for the folders are src/ and dist/ (i.e. source and distribution).

 mkdir src dist

And now we need a file into which we can write our React code. It should live inside the src directory.

And let’s not forget the index.html, which does not need to be transpiled, so we can put it directly into the dist/ folder.

If you were following the commands, continue with:

 touch src/app.js dist/index.html

Now let’s write some code! In the index.html, add the standard HTML boilerplate code and then, inside the body, add an element with an id and a script tag.

<!--index.html-->

<body>
 <div id='app'></div>
 <script src="bundle.js"></script>
</body>

The script that we’re referencing here does not exist yet, but once we’ve got some React code, we will let webpack do its magic and automatically output a bundle.js file with our transpiled code.

And the React code will go into app.js.

<!--app.js-->

import React from 'react';
import ReactDOM from 'react-dom';

class Main extends React.Component {
		render() {
				return (
						<div>
								<h1>Hello World</h1>
						</div>
				);
		}
}

const app = document.getElementById('app');
ReactDOM.render(<Main />, app);

And now let’s get to the pièce de résistance of our whole setup: the webpack config file!

touch webpack.config.js

As the bare minimum, it needs to export an object with three properties: entry, output, and loaders.

  • entry specifies the entry path to your application, which in our case is the app.js
  • output specifies the folder into which webpack will place the automatically generated file with the transpiled code
  • loaders is an array of tasks that webpack will carry out

Here is what the config file looks like:

//webpack.config.js
const path = require('path')

const config = {
		entry: './src/app.js',
		output: {
				path: path.resolve(__dirname, 'dist'),
				filename: 'bundle.js'
		},
		module: {
				rules: [
						{ test: /\.js$/,
								loader: 'babel-loader',
								exclude: /node_modules/
						}
				]
		}
}

module.exports = config

The test property in the loader object has nothing to do with setting up unit test frameworks, but is related to the unix test command. The test key's value specifies for which files the loader should be responsible. So when it goes through all the files, it first “tests” if it is the correct file before applying the processing.

A note about the output path: You can hardcode the path, but it is better practice to use Node.js’s path module. The path module's default operation varies based on the operating system that you are using to run the code. Any differences between Windows and Mac will be handled by this module, so that's one less thing for us to worry about.

As the last step, we need to tell the Babel loader that we want to use it to compile ES6 to ES5 and React to vanilla JS. We need to create a .babelrc for that with the following specifications.

// .babelrc

{
 "presets":[
 "es2015", "react"
 ]
}

The last step is to add the webpack command to our package.json. Find the scripts object and add another key-value pair under the existing test key.


{
		"scripts": {
				"test": "echo \"Error: no test specified\" && exit 1",
				"build": "webpack"
		}
}

To transpile and bundle the code, we use the command:

 npm run build

And now we can open the index.html in the browser and see our “Hello World” headline. All done!

Bonus material

I really do recommend installing the webpack-dev-server. It is just so convenient! Let’s install webpack as a development dependency:

 npm install --save-dev webpack-dev-server

Now we can add the command to run the server to our package.json. Underneath the build command inside the scripts object, add another key-value pair. Let’s call the command watch.

{
		"scripts": {
				"test": "echo \"Error: no test specified\" && exit 1",
				"build": "webpack",
				"watch": "webpack-dev-server --content-base dist"
		}
}

To start the server, run

 npm run watch

...and your code will run on localhost. Now make a change in your React Main module. See what I mean? Amazing!