React from Scratch Part 1: Webpack

What many of the other React getting started tutorials tend to leave out is all the surrounding things such as WebPack and the server side code. They tend to have you clone an existing repo, use codepen, or use dumbed down tool chain to show the basics. More ramblings over here…

What I want to show in this series is how to get started from scratch. Starting from having no files, to having a running project.

In this article we’ll look at setting up the fundamentals to prepare for React development.

Prerequisites

For this journey we will undertake you will need:

And that’s it.

Step 1: Setup

Let’s create our project folder somewhere:

$ mkdir react-from-scratch
$ cd react-from-scratch/

Next let’s init NPM and Git.

$ git init
Initialized empty Git repository in blah/react-from-scratch/.git/
$ npm init
This utility will walk you through blah blah blah
About to write to blah/react-from-scratch/package.json
{
"name": "react-from-scratch",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT"
}
Is this ok? (yes)
$

Next let’s install Express for basic hosting.

$ npm install --save express
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN react-from-scratch@1.0.0 No description
npm WARN react-from-scratch@1.0.0 No repository field.
+ express@4.15.4
added 42 packages in 8.109s
$

And create a server/index.js file

const express = require('express');
const app = express();
app.use(express.static('www'));
app.listen(3000, () => {
console.log('Example app listening on port 3000!')
});

And a client/index.html file

<!DOCTYPE html>
<html lang="en">
<head>
<title>React from scratch</title>
</head>
<body>
Hello World!
</body>
</html>

And run!

$ node server
Example app listening on port 3000!
It works!

Now we have a very basic web server with static file hosting. We’ll keep it like this for now.

Note: I’m skipping things like creating folders, adding stuff to .gitignore, committing, getting the editor set up, etc.

Step 2: webpack

Next we’ll install webpack which will be our bundler. It will take all our assets and scripts and squash them together properly.

$ npm install webpack --save-dev

> uglifyjs-webpack-plugin@0.4.6 postinstall blah
> node lib/post_install.js
npm WARN react-from-scratch@1.0.0 No description
npm WARN react-from-scratch@1.0.0 No repository field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.1.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.1.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
+ webpack@3.5.4
added 362 packages in 47.499s
$

Note: This section more or less follows the WebPack guide, with less exposition. If you want more detail head over there…

For webpack, a config file is needed. We’ll create a basic webpack.config.js file for starters:

const path = require('path');
module.exports = {
entry: './client/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'www')
}
};

And a basic client/index.js file

function component() {
var element = document.createElement('div');
element.innerHTML = "Hello world!";
return element;
}
document.body.appendChild(component());

Now if you run webpack from the command line it will build output:

$ npx webpack
Hash: fa1454c9f41b0eb6748e
Version: webpack 3.5.4
Time: 57ms
Asset Size Chunks Chunk Names
bundle.js 2.65 kB 0 [emitted] main
[0] ./client/index.js 175 bytes {0} [built]
$

Note: you may have noticed I’m calling npx above. This was included in npm 5.2.0 and allows one to run npm package scripts easily. If you don’t have it you can update npm by running npm install -g npm (might need sudo on Linux).

Now let’s change our html to reference the bundled file:

<!DOCTYPE html>
<html lang="en">
<head>
<title>React from scratch</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>

And run!

$ node server
Example app listening on port 3000!
Looks quite the same as before

Now that we have a working webpack setup, let’s add a few modules to make things a bit more useful.

HTML module

First, let’s replace that HTML file we made ourselves with one generated by webpack. Install this:

$ npm install --save-dev html-webpack-plugin
... stuff ...
+ html-webpack-plugin@2.30.1
added 153 packages in 27.109s

And edit the webpack.config.js file to utilize the new plugin:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './client/index.js',
plugins: [
new HtmlWebpackPlugin({
title: 'React from scratch'
})
],

output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'www')
}
};

Now when we build it will generate the index.html file for us:

$ ./node_modules/.bin/webpack
Hash: d451e987f4ef8beb712e
Version: webpack 3.5.4
Time: 418ms
Asset Size Chunks Chunk Names
bundle.js 2.65 kB 0 [emitted] main
index.html 189 bytes [emitted]
[0] ./client/index.js 175 bytes {0} [built]
Child html-webpack-plugin for "index.html":
1 asset
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
+ 2 hidden modules

Clean Module

Let’s also add the clean module. It will ensure old files are removed from our www directory.

$ npm install clean-webpack-plugin --save-dev
npm WARN optional ...
npm WARN notsup ...
+ clean-webpack-plugin@0.1.16
added 122 packages in 19.149s

And then our webpack config:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const dist = path.resolve(__dirname, 'www');
module.exports = {
entry: './client/index.js',
plugins: [
new CleanWebpackPlugin([dist]),
new HtmlWebpackPlugin({
title: 'React from scratch'
})
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, dist)
}
};

I also added the dist constant for convenience.

Source Maps

To make development practical, let’s enable source maps in webpack.config.js:

module.exports = {
entry: './client/index.js',
devtool: 'inline-source-map',
plugins: [
...
};

There are several other options to choose from, but let’s use inline-source-map for now. This setting will typically change for production, but we’ll leave it like this for now.

Server

To make the development process easier, it would be useful if the bundle is rebuilt every time a file changes. For this there are three options: using webpack-dev-server which takes over the hosting and has all the niceties built in (like live reloading). There is watch mode (run webpack --watch) where webpack runs in the background and watches the files. Then there is webpack-dev-middleware which does the same thing but will be run as part of our Express server.

We’ll use webpack-dev-middleware in this guide.

Why not use webpack-dev-server? This comes with the premise of this guide. My complaint is that many of the new technologies have these do-everything-for-you tools. Like webpack-dev-server. While they make things easy initially they do not help you on the day you need to go to production. I know of projects where this was such a problem that they ended up using webpack-dev-server in production. So let’s rather figure out a practical solution that we won’t need to substantially change when we go to production.

$ npm install --save-dev webpack-dev-middleware

As per the documentation, we change server/index.js:

const express = require('express');
const webpack = require('webpack');
const webpackconfig = require('../webpack.config');
const webpackMiddleware = require("webpack-dev-middleware");

const app = express();
app.use(express.static('www'));
const wpmw = webpackMiddleware(webpack(webpackconfig),{});
app.use(wpmw);
app.listen(3000, () => {
console.log('Example app listening on port 3000!')
});

And now if we run:

$ node server
clean-webpack-plugin: ... has been removed.
Example app listening on port 3000!
Hash: d451e987f4ef8beb712e
Version: webpack 3.5.4
Time: 423ms
Asset Size Chunks Chunk Names
bundle.js 6.47 kB 0 [emitted] main
index.html 189 bytes [emitted]
[0] ./client/index.js 175 bytes {0} [built]
Child html-webpack-plugin for "index.html":
Asset Size Chunks Chunk Names
index.html 544 kB 0
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./node_modules/html-webpack-plugin/default_index.ejs 538 bytes {0} [built]
[1] ./node_modules/lodash/lodash.js 540 kB {0} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
webpack: Compiled successfully.

It compiles webpack when we run and keeps in memory. Now change the client/index.js file and see what happens:

webpack: Compiling...
Hash: 163359edcf1649bf3d75
Version: webpack 3.5.4
Time: 51ms
Asset Size Chunks Chunk Names
bundle.js 6.49 kB 0 [emitted] main
index.html 189 bytes [emitted]
[0] ./client/index.js 182 bytes {0} [built]
Child html-webpack-plugin for "index.html":
Asset Size Chunks Chunk Names
index.html 544 kB 1
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./node_modules/html-webpack-plugin/default_index.ejs 538 bytes {1}
[1] ./node_modules/lodash/lodash.js 540 kB {1}
[2] (webpack)/buildin/global.js 509 bytes {1}
[3] (webpack)/buildin/module.js 517 bytes {1}
webpack: Compiled successfully.

Now if you reload the page it will be refreshed!

Step 3: Styles and Files

So now we have webpack functioning, bundling our JavaScript (not React yet but we’ll get there), let’s move on to images and styles.

Images

As a matter of example, let’s download this image: https://facebook.github.io/react/img/logo.svg which looks like this:

https://facebook.github.io/react/img/logo.svg

And put it in client/content.

In order to be able to import the image we are going to need file-loader :

$ npm install --save-dev file-loader

+ file-loader@0.11.2
added 116 packages in 14.652s

And we need to update our configuration to use the file loader:

module.exports = {
entry: './client/index.js',
devtool: 'inline-source-map',
plugins: [
...
],
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader']
}
]
},

output: ...
};

Now let’s import it from our JavaScript:

import reactlogo from './content/react.svg';
function component() {
var element = document.createElement('div');
element.innerHTML = "Hello world!";
    var logo = new Image();
logo.src = reactlogo;
element.appendChild(logo);
    return element;
}

And webpack build:

$ ./node_modules/.bin/webpack
clean-webpack-plugin: blah has been removed.
Hash: b981df6dcd6b8f00c593
Version: webpack 3.5.4
Time: 482ms
Asset Size Chunks Chunk Names
3d593052bb2819bdf7709d9f7e512c8f.svg 1.84 kB [emitted]
bundle.js 7.71 kB 0 [emitted] main
index.html 189 bytes [emitted]
[0] ./client/index.js 307 bytes {0} [built]
[1] ./client/content/react.svg 82 bytes {0} [built]
Child html-webpack-plugin for "index.html":
1 asset
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
+ 2 hidden modules

As you can see it now created an svg as well. If we run we’ll see the logo!

So sexy

Since that’s hideous let’s move on to styles.

Styles

For CSS styles one can use style-loader and css-loader, however we’ll be using Sass instead of normal CSS files.

To start let’s create a sass file to make things look a bit better. Create client/styles/main.scss:

body {
background: #222;
color: white;
font-size: 1.1rem;
font-family: Arial, Helvetica, sans-serif;
    div {
margin: 20px;
font-size: 2em;
text-align: center;
        & > img {
width: 300px;
height: 300px;
display: block;
margin: 20px auto;
}
}
}

Install the sass-loader. We’ll also need node-sass as well as style-loader and css-loader. These will transform the import from SCSS to CSS, from CSS to javascript, and from javascript to style nodes.

$ npm install sass-loader node-sass \
css-loader style-loader
--save-dev

+ node-sass@4.5.3
+ style-loader@0.18.2
+ sass-loader@6.0.6
+ css-loader@0.28.5
added 114 packages and updated 4 packages in 29.43s

We need to register these in our webpack config.

...
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader']
},
{
test: /\.(scss|sass)$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}

]
},
...

Now we can import it from our client/index.js file:

import reactlogo from './content/react.svg';
import './styles/main.scss';

And now if we run:

This is looking much better!

Step 4: Other things

No that we have a basic webpack setup going we can look at some other things.

Hot Module Replacement

Hot Module Replacement/Reloading/Refreshing/Replacing (just HMR) allows modules (the images, styles, etc.) to be reloaded while the application is running (in your browser). This is achieved by detecting changes to files (while the application is running), doing a webpack recompile, and syncing the changes in your browser. If you use webpack dev server this comes almost for free, but if you use webpack-dev-middleware we need to make on or two small changes. I’ve written another article which goes over it. Now is a good time to look over it if you want this feature:

Opn

Sometimes it’s useful to have the browser open every time you run your project. We can have this by using opn. Let’s add it:

$ npm install --save-dev opn

And update server/index.js:

const express = require('express');
...
const opn = require('opn');
const development = process.env.NODE_ENV != 'production';
const port = 3000;

const app = express();
...
app.listen(port, () => {
console.log(`Example app listening on port ${port}!`);
    if (development) {
opn(`
http://127.0.0.1:${port}/`);
}

});

Now the browser will open every time you run the server! Much more convenient.

Some random tweaks

We can add some scripts to our package.json to make things easier:

{
"name": "react-from-scratch",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\"",
"build": "webpack",
"start": "node server"
},

"author": "Cillié Malan",
...
}

so now we can:

$ npm run build
> react-from-scratch@1.0.0 build blah
> webpack
clean-webpack-plugin: blah blah
Hash: blah blah
yada yada
...
...

or:

$ npm start
> react-from-scratch@1.0.0 start blah
> node server
clean-webpack-plugin: blah blah
Example app listening on port 3000!
webpack: wait until bundle finished: /
webpack built 65f222c3fcfabd3d95d2 in 1266ms
blah blah
...

These things make things just a bit better.


What’s Next

Next we’ll look at adding some React to the webpack dev setup we have here.

To see all the code we made in this article, you can check out the specific branch in the github repo:

https://github.com/cilliemalan/react-from-scratch/tree/step-1

Next: React by Example…