The first thing most of people new to React learn is — “it’s complicated to set things up, just get create-react-app (or any other boilerplate)”. While this is a valid solution in most cases, especially when you want to jump right into development it might not always be the best solution for your project. Or maybe you’re just curious and want to learn more? Let’s dig in.

In order to use React in today’s browser we will need two things:

way to deliver all the code we wrote along with node_modules to the user

way to make JSX and ES6 run in our browser

To solve those issues we will use two tools: webpack and babel. Both of them can be used separately to do their job, but they best work when paired into one ecosystem. To accompany this article you should clone the repository react-on I’ve created that contains a simple boilerplate.

Overall project information and configuration

First thing you need to understand is the purpose of using package.json — this is a meta-file that contains information about your project such as its name, version etc. but also contains a list of 3rd party libraries and their configuration. Let us skip to devDependencies of package.json — we can notice a couple of important libraries:

babel — this is a transpiler that will change our JSX, ES6 and other types of source into ES5 which runs without issues in most of the browsers, all packages with (using the babel-preset- packages)

packages) a couple of -loader libraries that webpack uses to understand the corresponding file type (more on that in webpack section)

libraries that webpack uses to understand the corresponding file type (more on that in webpack section) eslint — this is a popular linter, a small tool that will perform various checks on your source to make sure all your developers are on the same page with coding standards etc.

express — a small yet powerful http server to serve our application when we are developing it

jest — a test runner that is gaining popularity in the React ecosystem

react — because what else :)

webpack — lastly, webpack will gather all our files, convert them to a format our browser can consume and deliver it in a elegant manner

Aside for that you can find configuration for jest and eslint — those are good to get you started but can be changed to suit your needs.

Webpack

Another important thing in your project is webpack. By default the configuration will live in webpack.config.js but in my example I’ve created a /webpack directory with multiple files — two of which are there to provide config for different environments and one to abstract some shared code. Let’s start with webpack.config.production.js

The file imports a couple of libraries and export one object. Now, webpack configuration can at times be very basic (just JSX and ES6) and other times complex (Hot Modules Reload, PostCSS, optimization, splitting etc.) in our case the configuration is on the light side.

entry — this points to the file (or multiple files) webpack should start at, and resolve all the imports / require statements, once the “tree” of imports is finished (no more imports left to process) the bundling process is mostly done

output — where to put the file generated from the previous step

module — this section contains loaders that tell webpack how to handle different file types (recognized by extension), we will get back to this one in a second

plugins — the power of webpack lies in the plugin system, here we implement only a few:

- a custom function that will clear out /dist directory

- ExtractTextPlugin which will create a style.css file with all the CSS of our application (see module section explanation)

- DefinePlugin — allows us to define some placeholder / variables, which will be replaced by webpack at bundle time (so only once, not every time we run our app — this can’t be dynamic!) with the corresponding value

- UglifyJsPlugin — performs minimization and other optimizations on our JS and CSS files

- HtmlWebpackPlugin — this will create for us a .html file which will load our application (here based on a provided template)

- a custom function that will clear out /dist directory - ExtractTextPlugin which will create a style.css file with all the CSS of our application (see module section explanation) - DefinePlugin — allows us to define some placeholder / variables, which will be replaced by webpack at bundle time (so only once, not every time we run our app — this can’t be dynamic!) with the corresponding value - UglifyJsPlugin — performs minimization and other optimizations on our JS and CSS files - HtmlWebpackPlugin — this will create for us a .html file which will load our application (here based on a provided template) resolve — tells webpack where to look for file when we import / require them, the DirectoryNamedWebpackPlugin allows us request files easier when their name matches the directory:

import Button from "Button";

// will look for Button/index.js but also Button/Button.js

Ok, back to module . Because the config is a JS file we can take advantage of require in it, and keep the shared parts in a separate file — webpack.loaders.js .

Here we export another object (an Array) which tells webpack what to do with each file type. Every entry of the array should contain at least:

test — a regular expression ran against every file that is imported / required

use — information on how to process the file, this can be either an object or a function (usually in the form of plugins).

Lets follow the example for .js files:

{

test: /\.js$/,

exclude: /node_modules/,

use: [{

loader: "babel-loader",

query: {

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

plugins: ["babel-plugin-transform-class-properties"]

}

}]

}

test — will match any file ending with .js

exclude — skip whole node_modules (this is a regular expression pattern)

use.loader — use the babel-loader for this kind of file

use.query — pass some options to the loader; here we’re telling it to handle es2015 (ES6) and react (JSX) file, and to also add the (not yet contained in any preset) support for class properties

edit turns out jest does not support babel configuration without .babelrc file, so the current state of repository has the query section removed and added in .babelrc file. If you do not plan to use jest, feel free to use the above mentioned format instead.

Another common loader you will find is used to load LESS files:

{

test: /\.less$/,

use: ExtractTextPlugin.extract({

use: [{

loader:'css-loader',

options: {

modules: true,

localIdentName: '[local]--[hash:base64:5]',

}

}, {

loader:'less-loader'

}]

})

},

Here we’re using ExtractTextPlugin in addition to normal configuration (passed as a parameter to ETP).

Lets start from the inside:

use — you might notice that there are multiple entries defined in the inner use field, in that case all the configuration is analyzed right-to-left or “from the end”; here — the file will be first passed through less-loader and then css-loader. We also define some configuration options for css-loader to support css-modules which are very handy when working on big projects where class names might unintentionally overlap

ExtractTextPlugin — by default, all the CSS we import into our project by doing import Styles from "style.less" would end up in the bundle and be embeded in the HTML file by creating a <style> node. This might not be the best solution, because it’s not very performance friendly and would increase the size of our js file; to get around that we will use ExtractTextPlugin to pull out all of the CSS and place it in style.css (remember? we talked about it in the overall webpack.config.production.js file)

Webpack configured for developement

Ok, we have our basic webpack config done, this will allow us to generate a bundle with our application but it’s not very developer friendly. Let’s look into webpack.config.develpment.js and see what we can change:

devtool — our code is going to be transpiled, which means that the source in browser will no longer match our original source; in order to combat this and see where the errors really happen we will use source maps (here: as a .map file) that our browser can use to translate the bundle into source code

file) that our browser can use to translate the bundle into source code entry — our entry now contains two entry points, webpack will get them both and combine into a single file (in order which we tell it to); here we’re loading Hot Module Replacement functionality, which will allow us to change the code of our application and see the changes in browser without reloading the window

plugins — we also need to load the HMR plugin

In our example the rest of the file remains mostly the same, we remove the Uglify plugin to make our code look a bit better (even though we’re using source map) and faster to compile.

By this point we are more-or-less done with configuring the (bare bone) boilerplate and can open /src/index.js to start writing our app. Once we’re done we can just run webpack in the console, and our code will be bundled into bundle.js , style.css and index.html all in /dist . This is good, but again — not really developer friendly. We need to run the command every time — and what of the Hot Module Replacement you’ve read about?

Serving the application (for development needs!)

We have a couple of ways to make it easier — we can get webpack-dev-server which will once ran from the console monitor our source, bundle in memory and expose the application from a built in webserver. Or we can implement express to make a simple http server. Let’s take a look:

var webpack = require('webpack');

var config = require('./webpack/webpack.config.developement.js');

var compiler = webpack(config); app.use(require('webpack-dev-middleware')(compiler, {

noInfo: true,

publicPath: config.output.publicPath

})); app.use(require("webpack-hot-middleware")(compiler));

This will require the webpack configuration we want, create a in-memory compiler (bundler) and add it as a express extension while enabling the HMR middleware.

app.get('*', function(req, res) {

res.sendFile(path.join(__dirname, 'src', 'public', 'index.ejs'));

});

Here again we use our template of HTML file, and set it to be served on every request made to the server (this is important to make HTML5 history work!)

All that’s left is to start our server at a given port.

Adding scripts to npm

We now have our simple boilerplate set up, let’s add a few scripts to our package.json file so we can run them with npm run :

"start": "node server.js",

"test": "jest",

"lint": "eslint src",

"build": "NODE_ENV=production webpack --config webpack/webpack.config.production.js --progress --hide-modules -p"

start — this will start our development server we just finished

test / lint — this will run both unit testing and linting on our code, it’s a good practice to have them running in watch mode in the background as we code, but if that’s too much, run them and fix any errors before you commit!

build — this will build a production-ready bundle and place it in our /dist directory

On the topic of production-build

A topic that comes up often is “what server do I need for my React application”. And the not-so-obvious answers is: any. As long as you’re not running a universal / isomorphic / server-side-rendering application you can use any server you like. Once bundled, React application can be as small as one .js file, that can be sent to the browser from any server.

Where are the configuration files?

You might have noticed that the boilerplate is missing some files — .eslintrc . Are we OK with using the default settings? No — we are using it all in package.json — Dan Abramov recently tweeted, and I agree with him:

If you configure your application using a “dotfile” (a file starting with a dot) someone might miss it, because dotfiles are hidden by default in most Unix-like systems.

In conclusion

I hope this article made you realize the inner-workings of your boilerplate a bit more and maybe will encourage you to experiment and try to implement a boilerplate that’s suited just for you.

I invite you once more to browse the code of: