Webpack/Babel/React webapp (from scratch) with deployment on Heroku – Part I (webpack)

Yep, not using create-react-app, instead creating a webapp using webpack and React and deploying on Heroku. VERY long read.

Getting started with webpack.

So I started off with the webpack’s documentation to understand how webpack maps project modules and generates bundles for automatic compilation and reducing manual build time.

Create a directory and install webpack and webpack-cli (to be able to run webpack on the command line)

mkdir my-app
cd my-app 
npm init -y 
npm install webpack webpack-cli --save-dev

Create directory structure, files and contents

mkdir src dist

my-app
|- package.json
|- /dist
|- index.html
|- /src
|- index.js

/dist is where webpack minimizes and builds the output, which will eventually be loaded into the browser

In index.js add the following.

function component() {
  const element = document.createElement('div');
  element.innerHTML = 'hello';
  return element;
}

document.body.appendChild(component());

In index.html add the following. This will allow webpack to take the script at src/index.js and generate main.js in the output folder dist/

<!doctype html>
  <html>
   <head>
     <title>Getting Started</title>
   </head>
   <body>
     <script src="main.js"></script>
   </body>
  </html>
npx webpack

Now run npx webpack to see the build. You will also see a ‘Warning in configuration’ message, which we will setup in a bit. npx ships with node and runs the webpack binary. Open index.html in your browser and you should see ‘hello’ on the screen.

In your project my-app add webpack configuration webpack.config.js to the root folder. Your folder structure will now look like this.

my-app
|- package.json
|- webpack.config.js
|- /dist
|- index.html
|- /src
|- index.js

In webpack.config.js add the following.

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
npx webpack

Now run npx webpack to build the output again with the new configuration file.

Next modify the package.json‘s npm script

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

This time, run npm run build to be used in place of npx

Next let’s move on to Webpack’s output management. As the project grows you will need a few plugins to make your life easier.

Following the tutorial, let’s create print.js

my-app
|- package.json
|- webpack.config.js
|- /dist
|- index.html
|- /src
|- index.js
|- print.js

In print.js add the following

export default function printMe() {
  console.log('Print me!');
}

In index.js import the following

import printMe from './print.js';

function component() {
  const element = document.createElement('div');
  const button = document.createElement('button');
  element.innerHTML = 'hello';
  button.innerHTML = 'Click and check the console!';
  button.onclick = printMe;
  
  element.appendChild(button);

  return element;
}

document.body.appendChild(component());

Next modify dist/index.html to prepare for webpack to split out entries

<head>
  <title>Getting Started</title>
  <script src="./print.bundle.js"></script></head>
<body>
  <script src="./app.bundle.js"></script>
</body>

Also modify webpack.config.json to prepare for webpack to split out entries.

entry: {
      app: './src/index.js',
      print: './src/print.js'
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
npm run build

Run npm run build and open index.html in your browser and you should see a button next to ‘hello’ on the screen. Inspect the page and in the console you should see “Print me!”

Now we setup HtmlWebpackPlugin so that we never have to manually update our index.html whenever there are multiple files. HtmlWebpackPlugin generates its own index.html and will replace the one that we initially created in dist/

npm install --save-dev html-webpack-plugin

Import HtmlWebpackPlugin into webpack.config.json

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); --> import plugin

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js',
    },
    plugins: [   --> Add the plugin config
      new HtmlWebpackPlugin({
        title: 'Output Management',
      }),
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
  };
npm run build

Go to dist folder to see how HtmlWebpackPlugin generates the index.html

Another useful plugin is CleanWebpackPlugin. This plugin cleans the /dist folder before each build so that only used files will be generated and all old files removed!

npm install --save-dev clean-webpack-plugin

Modify and import CleanWebpackPlugin in webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = { ....
  plugins: [
     new CleanWebpackPlugin(),
     new HtmlWebpackPlugin({...
npm run build

Now inspect the /dist folder to see files generated only from build

Next we will be adding development environment. In webpack.config.js add mode and devtool. Devtool allows for easier tracking of errors and warnings by mapping the compiled code back to the source code.

module.exports = {
   mode: 'development',
   entry: {
     app: '....
   },
   devtool: 'inline-source-map',
   plugins : [...

Here, we will be using a devtool that helps us automatically compile our code whenever it changes. We will be using webpack-dev-server

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

In webpack.config.js tell dev server where to look for files

..
devtool: 'inline-source-map',
devServer: {
      contentBase: './dist',    
},
plugins: [....

Change the script in package.json to run the dev server. It will load files from dist directory on localhost:8080

    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "start": "webpack-dev-server --open",
      "build": "webpack"
    },
npm start

Now run npm start and the browser will automatically load up our page

One last thing for webpack and before adding React (go get yourself a cup of water! 🙂 ) to our project, we will split webpack’s env into development and production

npm install --save-dev webpack-merge

Start by install webpack-merge to replace our webpack.config.js to common config, production config and development config.

my-app
|- package.json
|- webpack.common.js
|- webpack.dev.js
|- webpack.prod.js
|- /dist
|- index.html
|- /src
|- index.js
|- print.js

In webpack.common.js move the following in there.

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js',
    },
    plugins: [
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        title: 'Production',
      }),
    ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
  };

In webpack.dev.js move the following over

const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist',
  },
});

And in webpack.prod.js move these code over. Add devtool source map to be used in production config

const merge = require('webpack-merge');
const common = require('./webpack.common');

module.exports = merge(common, {
    mode: 'production',
    devtool: 'source-map',
});

Modify package.json with the latest webpack configurations

  "main": "src/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server --open --config webpack.dev.js",
    "build": "webpack-dev-server --open --config webpack.prod.js"
  },
npm run start

Moving on to add React to project. 😀 Part II

Leave a Reply

Your email address will not be published. Required fields are marked *