Setting up a ReactJS toolchain from Scratch using Webpack, Babel, ESLint and SCSS

2019-04-02

During this tutorial you will set up a ReactJS toolchain from scratch using:

  • Babel
  • Webpack
  • ESLint
  • SASS SCSS

We won't be explaining ReactJS, just how to setup the toolchain.

We assume you have have already node installed as well as npm

Setup

Let's not waste any time and create a new project directory and initialise it with a package.json

$ mkdir from-scratch && cd from-scratch
$ npm init -y

FYI The -y option above stops npm prompting for questions and answers "yes" to them all instead.

Add the minimum ReactJS libraries to your project dependencies:

$ npm install react@16 react-dom@16

Before we can write any code, we need to add a transpiler (if we want to use JSX and be able to use all the cool features from ECMAScript without worrying about browser support)

Add Support for Babel

To add support for Babel add the following dev-dependencies to your project:

$ npm install --save-dev \
        @babel/core@7 \
        @babel/cli@7 \
        @babel/preset-env@7 \
        @babel/preset-react@7

We need a .babelrc and enable two presets:

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

The first (babel-preset-env) allows us "to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s)". The second preset (babel-preset-react) enables react plugins (in particular support for react flavoured JSX)

We are now ready to start coding!

Code!

You might want to open your project directory inside an IDE or text editor to make it easier to code and navigate between files later on.

Create a directory src and inside it add a index.js. Use any ReactJS the code that includes JSX:

const element = (name) => <span>hello, {name}</span>;
ReactDOM.render(
    element('friend'),
    document.getElementById('root')
);

We have setup babel, so we can go ahead and compile/transpile our code:

$ npx babel src -d lib

Checkout the transpiled file inside the lib/ directory and how babel has compiled your JSX to createElement calls.

Test out our code

If you wanted to test this, you could use the following index.html file (the code below assumes you will place this directly inside the

from-scratch project directory)
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta content="width=device-width" name="viewport">
    <title>Hello React</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</head>
<body>

<h1>My React Page</h1>

<div id="root">

</div>

<script src="lib/index.js"></script>
</body>
</html>

You should see your element being rendered by react!

When you are done admiring your work, remove the lib/ directory and the index.html, as we won't be needing it anymore (we will be using webpack next)

$ rm -r index.html lib

Setup Webpack

We will now use webpack to bundle our application. Webpack will process our imports and package it into a single chunk (we will also optimise later which will change this behaviour slightly)

With webpack we can/have to use proper import statements (or require), so add the correct imports to your src/index.js:

import * as React from "react";
import * as ReactDOM from "react-dom";

Install Webpack

With our code all prepared, let's install webpack (locally):

$ npm --save-dev install webpack@4 webpack-cli@3 

Go ahead and try and run webpack (spoiler alert: it will fail!)

$ npx webpack  

It will fail as it does not know what to do with your JSX. This is because by default webpack only knows how to deal with javascript files/modules. We therefore need to configure support for our module type, which is this case is babel.

Configure webpack

We have not provided a webpack configuration, this is possible since webpack 4, but only works when dealing with plain Javascript. By default webpack looks for a file named webpack.config.js or webpackfile.js. We will use the former: add a file named webpack.config.js to the root of our from-scratch directory with the following contents:

const path = require('path');

module.exports = {
    mode: 'development',
    entry: "./src/index.js", // bundle's entry point
    output: {
        path: path.resolve(__dirname, 'dist'), // output directory
        filename: "[name].js" // name of the generated bundle
    },
    devtool: 'source-map',
};

Some details explained:

  1. We are placing webpack in development mode (and we are enabling source-maps via the devtool configuration in the bottom)
  2. the entry specifies the entry point for the bundle(s) (in this case we only have one entry point, hence we will only be creating a single bundle)
  3. The output.path tells webpack where to save the generated files
  4. The output.filename specifies the name of each output (where [name] refers to the name of the "chunk". We could have used a hard coded value such as "main.js", but this prepares it better for the future)

Inside a webpack configuration we can define how a module should be treated (what kind of processing we want for a particular module type). This typically consists of

  • a condition (when does our module definition apply) often in the form of a test with a regular expression to match resource names
  • applying a set of loaders which describe to webpack how to process non-JavaScript modules
  • optionally some options for the loader

In our case we'll need to use a babel-loader for files matching the regular expression /\.js$/.

First install the babel-loader:

$ npm --save-dev install babel-loader@8

And then configure support for babel modules by adding a module section to our webpack.config.js (after output) and add our rule as shown below:

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            }
        ]
    }

We could have added options such as the babel presets, but we already have those inside the .babelrc file (and these will be picked-up by the loader)

Babel installed and configured plus we have our babel loader setup in Webpack, so it's time to try and run webpack again:

$ npx webpack
Version: webpack 4.20.1
Time: 435ms
Built at: 01/03/2018 3:53:44 PM
  Asset     Size  Chunks             Chunk Names
main.js  907 KiB    main  [emitted]  main
Entrypoint main = main.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
[./src/index.js] 244 bytes {main} [built]
    + 11 hidden modules

Awesome it works! (checkout the dist/ directory)

Let's use our chunk inside a HTML file. Once again create a file named index.html in the root of your project directory and paste the following contents:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta content="width=device-width" name="viewport">
    <title>Hello React</title>

</head>
<body>
<h1>My React Page</h1>
<div id="root">
</div>
<script src="dist/main.js"></script>
</body>
</html>

Open the page in your browser. And voilà it shows the page you were already so proud of, but now using a hip tool such as webpack!

Use the HtmlWebpackPlugin

We can have webpack inject links into our html for our generated chunk files by using the HtmlWebpackPlugin plugin.

As with everything, first install the HtmlWebpackPlugin using npm:

$ npm --save-dev install html-webpack-plugin@3 

And then update our webpack.config.js to add support for the HtmlWebpackPlugin plugin

  • In the top of the file add an import:
    const HtmlWebpackPlugin = require('html-webpack-plugin');
  • Under the module section, add a plugins section:
    plugins : [
           new HtmlWebpackPlugin({
               template: "src/index.html",
               inject : "body"
           })
       ]

We are instructing webpack to use the plugin and we have configured it to inject the assets to the bottom of the body element using src/index.html as a template. By default the outcome can be found inside a file also named index.html (can be changed using the filename option) inside dist directory (which is our configured output.path)

Remove the index.html file you created to test your webpack bundle:

$ rm index.html

And then create the following file src/index.html (notice this is inside the src directory)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta content="width=device-width" name="viewport">
    <title>Hello React</title>
</head>
<body>
<h1>My React/Webpack Page</h1>
<div id="root">
</div>
</html>

Then run webpack again and find your "compiled" html file inside dist/index.html.

Using the dev server

You could have webpack "watch" your files and generate files as needed. You would just run webpack with the --watch flag:

$ npx webpack --watch
...

Now every time you make a change to one of your files webpack will process them. But there is even a neater trick: using its development web server webpack-dev-server.

Let's install locally:

$ npm install --save-dev webpack-dev-server@3

And run webpack-dev-server using npx:

 $ npx webpack-dev-server

By default it also uses webpack.config.js or webpackfile.js (otherwise you would need to use the --config option).

Now open your browser at http://localhost:8080/

Any change you make to the code is live reloaded in your browser, try it out!

note it does not update the files in the dist folder. All assets are in memory!

Adding CSS Support

You are already proud of your page, but you want to make it look even better by applying some styles to it.

Add a css file named src/message.css:

.message {
    background-color: #0086b3;
    font-weight: bold;
}

Change the src/index.js and add your 'message' class to the span (class is a reserved word, so use className inside your JSX instead)

const element = (name) => <span className='message'>hello, {name}</span>;

The code above requires a css to be present on the page. We can accomplish this by adding an import instruction to the top of the file:

// src/index.js
import "./message.css";
...

Look at this import "./message.css" again . What we are doing is really defining a module dependency, but not to a javascript file but to a css file. This is something node.js for example is not capable of doing. This is in fact the whole «raison d'etre» of webpack. It can treat any file in your project as a module.

So as before we need to configure a loader to deal with our css module type. This time it will be for files matching the regular expression /\.css$/. We will need two loaders: the style-loader and the css-loader. (their roles are explained further below)

Install both of these using npm:

$ npm --save-dev install css-loader@2 style-loader@0.23.1

And define a module rule in our rules array (which already contains a rule for Babel):

{
    test: /\.css$/,
    loader: ["style-loader","css-loader"]
}

Rerun your dev-server:

$ npx webpack-dev-server

And notice the style is applied. Wow you feel even more proud of yourself.

So what is going on here? We are instructing webpack to process all files matching our test (/\.css$/) using a pipeline of loaders: first the css-loader and after that the style-loader are applied. The css-loader loads css files and returns css code, the style-loader then adds this as a style element to the DOM. Notice the DOM tree from the resulting page:

Use scss instead

You are slowly using more and more hip tools and technologies, so let's add support for yet another module type: sass css.

In order to use scss we need to install node-sass and a webpack loader:

$ npm install --save-dev node-sass@4 sass-loader@7 

Register the loader under our set of rules:

{
    test: /\.scss$/,
    loader: ["style-loader?sourceMap","css-loader?sourceMap","sass-loader?sourceMap"]
}

Notice we have also included support for source-maps

You may remove the rule for css files.

Rename your message.css to message.scss:

$ mv src/message.css src/message.scss 

Don't forget to update the reference to this renamed file inside our index.js (in case your editor did not do that for you):

import "./message.scss";

And let's throw in some sassiness into the message.scss

$color: #0086b3;

.message {
  background-color: $color;
  font-weight: bold;
  border: solid darken($color, 10%);
  padding: 0.5rem;
}

Test it and see your page looks much hipper now!

And notice the sourcemaps in action (note that there is a problem with these sourcemaps in firefox, you might want to try using a chromium based browser)

Add a linter

When dealing with javascript, it is always a good idea to use a linter. Let's go ahead and add support for ESLint

Install & Configure ESLint

First install eslint:

$ npm --save-dev install eslint@5

We could generate a .eslintrc.json, but we will use the eslint-config-react-app instead.

To install run the following (long) command:

$ npm install --save-dev eslint-config-react-app \
   babel-eslint@9.x \
   eslint-plugin-flowtype@2.x \
   eslint-plugin-import@2.x \
   eslint-plugin-jsx-a11y@6.x \
   eslint-plugin-react@7.x

Then either define a file named eslint-config-react-app or add the following to you package.json

"eslintConfig": {
  "extends": "react-app"
}

If you wanted to create the eslint-config-react-app file, it would just contain the object without the eslintConfig key: { "extends": "react-app" }

Configure the loader

Webpack will be running the linter, so we need to provide a loader again. This time it is the eslint-loader:

$ npm --save-dev install eslint-loader 

We want to use this loader for javascript files, but we already have our Babel loader for that. So what to do? Just as with our css/scss rules we can chain loader together. So we will just add our "eslint-loader" to the beginning of the pipe by adding it to the end of the array (we don't want to lint the transpiled code now do we?)

{
    test: /\.js$/,
    exclude: /node_modules/,
    loader: ['babel-loader','eslint-loader']
},

Rerun your dev-server. If you wanted to see the linter in action, then for example add an invalid aria-label (e.g. the aria-toto from below) to your JSX element:

<span aria-toto className='message'>hello, {name}</span>

Chunk Optimisation

At this point all of the required javascript is added to our single chunk named dist/main.js

Check it our by removing all assets, running webpack (not the dev server) and listing the contents:

$ rm -f dist/* && npx webpack && ls -lh dist

The main.js will be larger than 1M. This is because it includes all required javascript (reactjs, ). Have a look at the sources value inside your map (the command below assumes you have jq installed to query the json)

$ cat dist/main.js.map| jq ".sources"  
[
  "webpack:///webpack/bootstrap",
  "webpack:///./src/message.scss",
  "webpack:///./node_modules/css-loader/dist/runtime/api.js",
  "webpack:///./node_modules/object-assign/index.js",
  "webpack:///./node_modules/prop-types/checkPropTypes.js",
  "webpack:///./node_modules/prop-types/lib/ReactPropTypesSecret.js",
  "webpack:///./node_modules/react-dom/cjs/react-dom.development.js",
  "webpack:///./node_modules/react-dom/index.js",
  "webpack:///./node_modules/react/cjs/react.development.js",
  "webpack:///./node_modules/react/index.js",
  "webpack:///./node_modules/scheduler/cjs/scheduler-tracing.development.js",
  "webpack:///./node_modules/scheduler/cjs/scheduler.development.js",
  "webpack:///./node_modules/scheduler/index.js",
  "webpack:///./node_modules/scheduler/tracing.js",
  "webpack:///./node_modules/style-loader/lib/addStyles.js",
  "webpack:///./node_modules/style-loader/lib/urls.js",
  "webpack:///(webpack)/buildin/global.js",
  "webpack:///./src/index.js",
  "webpack:///./src/message.scss?8c24"
]

It is a good idea to split the chunk up, in three or more chunks:

  • you code
  • vendor code (e.g, coming from node_modules)
  • the webpack runtime engine

This can easily be accomplished using the SplitChunksPlugin.

Add the following configuration to your webpack.config.js (e.g, under the devtool entry)

optimization: {
    splitChunks: {
        chunks: 'all',
        name: true,
    },
    runtimeChunk: true,
},

This activates the SplitChunksPlugin. We are selecting all chunks for optimisation and are naming the split chunks (main, vendor) by setting name to true. We are also splitting the webpack chunk into its own file (runtime) using optimization.runtimeChunk. Note: "It is recommended to set splitChunks.name to false for production builds so that it doesn't change names unnecessarily."

Have a look at the files:

$ rm -f dist/* && npx webpack && ls -lh dist

Notice how you now have multiple chunks. Notice your chunk is now around 4Kb.

Check how the resources are now spread over these chunks:

$ cat dist/*.js.map| jq ".sources"
[
  "webpack:///./src/message.scss",
  "webpack:///./src/index.js",
  "webpack:///./src/message.scss?8c24"
]
[
  "webpack:///webpack/bootstrap"
]
[
  "webpack:///./node_modules/css-loader/dist/runtime/api.js",
  "webpack:///./node_modules/object-assign/index.js",
  "webpack:///./node_modules/prop-types/checkPropTypes.js",
  "webpack:///./node_modules/prop-types/lib/ReactPropTypesSecret.js",
  "webpack:///./node_modules/react-dom/cjs/react-dom.development.js",
  "webpack:///./node_modules/react-dom/index.js",
  "webpack:///./node_modules/react/cjs/react.development.js",
  "webpack:///./node_modules/react/index.js",
  "webpack:///./node_modules/scheduler/cjs/scheduler-tracing.development.js",
  "webpack:///./node_modules/scheduler/cjs/scheduler.development.js",
  "webpack:///./node_modules/scheduler/index.js",
  "webpack:///./node_modules/scheduler/tracing.js",
  "webpack:///./node_modules/style-loader/lib/addStyles.js",
  "webpack:///./node_modules/style-loader/lib/urls.js",
  "webpack:///(webpack)/buildin/global.js"
]

This completes this first tutorial. Remember the project is available on github at edc4it/reactjs-webpack-tutorial

This article does not necessarily reflect the technical opinion of EDC4IT, but purely of the writer. If you want to discuss about this content, please use thecontact ussection of the site