developers

Webpack: A Gentle Introduction to the Module Bundler

Learn the basics of Webpack and how to configure it in your web application.

Dec 5, 201714 min read


TL;DR: In this tutorial, I'll introduce you to Webpack and show you how to configure it for performance optimization in your web application. Check out the repo to get the code.


Webpack is an aggressive and powerful module bundler for JavaScript applications. It packages all the modules in your application into one or more bundles (often, just one) and serves it to the browser. However, Webpack is more than just a module bundler. With the help of loaders and plugins, it can transform, minify and optimize all types of files before serving them as one bundle to the browser. It takes in various assets, such as JavaScript, CSS, Fonts, Images, and HTML, and then transforms these assets into a format that’s convenient to consume through a browser. The true power of Webpack is the sum of its parts.

The true power of Webpack is the sum of its parts.

Tweet This

Webpack is a JavaScript library, built and maintained by Tobias Koppers and the team. It is very well known and backed by the developer community. Virtually every JavaScript framework and project uses Webpack.

Webpack - Backers Webpack - Backers

Webpack - Sponsors Webpack - Sponsors

Currently, many web platforms use Webpack during development. Such platforms include Auth0, Netflix, Instagram, Airbnb, KhanAcademy, Trivago and more. The documentation is very detailed, and there is a vibrant community of users. Webpack currently exists in two GitHub Organizations, Webpack and Webpack-contrib. The Webpack org consists of projects such as webpack, webpack-cli, tapable, while the Webpack-contrib org consists of mostly plugins and loaders.

Webpack - The Core Concepts

Webpack is popularly addressed as a beast in the JavaScript community. A lot of developers know how to use Webpack but are constantly confused as to how it actually works under the hood. Is it magic? Is Sean Larkin a sorcerer? What about Tobias? Is he a first-generation spell-caster? Well, I'll address this quite simply. I ask that you just grab your cup of coffee and follow along.

Webpack builds a dependency graph when it processes your application. It starts from a list of modules defined in its config file (

webpack.config.js
) and recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into a small number of bundles to be loaded by the browser.

Webpack builds a dependency graph when it processes your application.

Tweet This

There are four core concepts you need to grasp to understand how Webpack functions:

  • Entry
  • Output
  • Loaders
  • Plugins

Entry

Every Webpack setup has one or more entry points. The entry point tells Webpack where to start building its dependency graph from. Webpack starts processing the module at the entry point and roams around the application source code to look for other modules that depend on the entry module. Every direct or indirect dependency is captured, processed and outputted into a bundle(s).

webpack.config.js

const config = {
  entry: './app/prosper.js'
};

module.exports = config;

webpack.config.js - separate entries

const config = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  }
};

module.exports = config;

Note: Just simply reiterating. Multiple entry points can be specified in a Webpack config file.

Output

Only one output point can be specified in a Webpack setup. The output config property tells Webpack where to place the bundle(s) it creates and how to name them. It is as simple as specifying the

output
property in the config file like so:

const config = {
    entry: './app/prosper.js',
    output: {
        path: '/unicodeveloper/project/public/dist',
        filename: 'app.bundle.js'
    }
};

module.exports = config;

output.filename
- The name of the bundle webpack produces.
output.path
- The directory to write
app.bundle.js
to.

Loaders

Loaders are like transformers. With loaders, Webpack can process any type of file, not just JavaScript files. Loaders transform these files into modules that can be included in the app's dependency graph and bundle. Check out the example below:

const config = {
  entry: './app/prosper.js',
  output: {
    path: '/unicodeveloper/project/public/dist',
    filename: 'app.bundle.js'
  },
  module: {
    rules: [
      { test: /\.(html)$/, use: 'html-loader' }
    ]
  }
};

module.exports = config;

In the code above, the

html-loader
processes HTML files and exports them as strings. There are several loaders such as css-loader, less-loader, coffee-loader and many more.

Plugins

Earlier in the post, I mentioned that loaders are like transformers. Plugins are super-man-like in their operations. They can do a lot more tasks than loaders. In fact, just anything ranging from deleting files, to backing up files on services like Cloudinary, to copying files, etc. Check out the example below:

const CompressionWebpackPlugin = require('compression-webpack-plugin');
const webpack = require('webpack');
const path = require('path');

const config = {
  entry: './app/prosper.js',
  output: {
    path: '/unicodeveloper/project/public/dist',
    filename: 'app.bundle.js'
  },
  module: {
    rules: [
      { test: /\.(html)$/, use: 'html-loader' }
    ]
  },
  plugins: [
    new CompressionWebpackPlugin({test: /\.js/})
  ]
};

module.exports = config;

In the code above, the

compression-webpack-plugin
compresses all the JavaScript files before serving them to the browser. There are several plugins such as AggressiveSplittingPlugin, CommonsChunkPlugin, 18nWebpackPlugin and many more.

Webpack Requirements

In order to use Webpack, you need to have the tool mentioned below installed on your machine.

  • Node.js: Navigate to the Node.js website and install the latest version on your machine. Verify that
    npm
    works by running
    npm
    in your terminal.

Setting Up Webpack in an Application

Create a new project directory. Head over to the terminal and run

npm init
to create a
package.json
inside the directory.

Next, go ahead and add

webpack
to the project.

npm install webpack -D

Webpack - Dev Dependency package.json

Create a

js
folder inside the directory with two files,
service.js
and
factory.js
.

js/factory.js

import './service.js';

js/service.js

console.log("This is a functioning service.");

Create a

webpack.config.js
file in the root directory and add the following code to it like so:

const path = require('path');

module.exports = {
  entry: './js/factory.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};

In the code above, we have an entry point,

factory.js
. This config file tells Webpack to start processing the dependency graph from this file, which is dependent on
service.js
. And outputs the result of its operation to
bundle.js
which is written to a new
dist
directory.

Create an

index.html
file in the root directory.

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Test Webpack</title>
    </head>
    <script type="text/javascript" src="dist/bundle.js"></script>
    <body>
      <h2> Testing Webpack.... </h2>
    </body>
    </html>

One last step. Head over to

package.json
. Add
webpack
to the
scripts
section like so:

package.json

{
  "name": "testwebpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^3.8.1"
  }
}

With this action, we can just run

npm run build
from the terminal. The command will invoke
webpack
. Go ahead and try it out.

npm run build

Webpack build Build

Now, open your

index.html
in the browser. You should see the result of
service.js
in the console.

Webpack Results Index file - Results from Webpack

By now, you should understand the basics of using Webpack in a project. Webpack can do a lot more. Let's examine a use case that gives us a better overview of using loaders and plugins with Webpack in a JavaScript project.

Webpack Use Case: Project Asokoro

Remember the Project Lingo we architected with GulpJS? The people of Asokoro were very happy with the results. Now, they want the same results with Project Asokoro.

Project Asokoro is a project that we have embarked upon for the people of Asokoro. It does exactly what Project Lingo does. Project Lingo allows a mere human click a button, input a destination and Lingo automatically teleports the human to their preferred destination in approximately 5 minutes.

In Project Asokoro, we'll have a bunch of JavaScript, Sass and Image files. We need to compress and optimize these files so that Asokoro can be super-fast. I mean, you don't want Asokoro taking over an hour to teleport a human because of our inefficient developer work-flow. We also don't want investors running way.

This is what we need to do:

  • Concatenate all our JavaScript files into just one file. This will make sure our app makes one HTTP request while serving JavaScript rather than multiple HTTP requests.
  • Compile our Sass files into CSS. The browser understands CSS, not Sass.
  • Compress all our image files.

We have these requirements listed above. How do we approach tackling these tasks with Webpack?

Project Asokoro - Install Webpack Loaders and Plugins

Let's continue from where we stopped earlier. Create a new

scss
folder in the root directory and add a
service.scss
file to it.

service.scss

$color: rgb(255, 123, 123);
$font-weight: 300;

h2 {
  color: $color;
  font-weight: $font-weight;
}

span {
    font-weight: $font-weight;
    color: rgb(255, 69, 69) ;
}

Head over to

js/factory.js
and import the sass file like so:

import './service.js';
import '../scss/service.scss';

Go ahead and install the following loaders and plugins via your terminal:

npm install sass-loader node-sass extract-text-webpack-plugin css-loader style-loader --save-dev

A quick breakdown of what each of these plugins and loaders aims to accomplish.

  • sass-loader
    • Loads a SASS/SCSS file and compiles it to CSS. It requires
      node-sass
      to work.
  • node-sass
    • This libarary allows you to natively compile
      .scss
      files to
      css
      at incredible speed and automatically via a connect middleware.
  • extract-text-webpack-plugin
    • Extract text from a bundle, or bundles, into a separate file.
  • css-loader
    • The
      css-loader
      interprets
      @import
      and
      url()
      like
      import/require()
      and resolves them.
  • style-loader
    • Add CSS to the DOM.

Project Asokoro - Rewrite Webpack Config File

Open up the

webpack.config.js
file and modify it to this code below:

const path = require('path');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  entry: './js/factory.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          use: [{
            loader: 'css-loader'
          }, {
            loader: 'sass-loader'
          }],
          fallback: 'style-loader'
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('bundle.css')
  ]
};

In the code above, we have the

extract-text-webpack-plugin
. It extracts all the CSS code into
/dist/bundle.css
. Webpack roams the source code for
.scss
files then use
css-loader
and
sass-loader
to load and embed the stylesheets into the JavaScript bundle.

Quickly add a link to the

bundle.css
in
index.html
like so:

  <link rel="stylesheet" type="text/css" href="dist/bundle.css" />

Now, run the build again,

npm run build
and check out the app in the browser.

Webpack - New Page

The styling was applied to the page and our JavaScript code still logs to the console.

Project Asokoro - Compressing Images

Head over to your terminal and install these two loaders:

npm install file-loader image-webpack-loader

A quick breakdown of what these loaders aim to accomplish.

  • file-loader
    • Instructs webpack to emit the required object as file and to return its public URL.
  • image-webpack-loader
    • Minify PNG, JPEG, GIF and SVG images with
      imagemin
      .

Update

index.html
to have this div with class lili:

<div>
  <h2> Project Asokoro.... <span>The Joy of Webpack</span></h2>
  <div class="lili"></div>
</div>

Update

service.scss
file to have this piece of code.

service.scss

.lili {
  border: 1px solid black;
  background-image: url('../image/lambo.jpg');
  width: 1000px;
  height: 1000px;
}

Note: Create an

image
directory and add the
lambo.jpg
file to it.

The

lambo.jpg
can be found here.

Update

webpack.config.js
to have the
file-loader
and
image-webpack-loader
.

...
{
    test:  /\.(gif|png|jpe?g|svg)$/i,
    use: [
      'file-loader',
      {
        loader: 'image-webpack-loader',
        options: {
          bypassOnDebug: true,
        },
      },
    ],
}
...

In the code above, the

file-loader
loads any image file with these extensions and emits the file with a name that is the result of the MD5 hash of the file's contents in the
dist
directory. The
image-webpack-loader
compresses the file.

Now, run your build,

npm run build
and check your app in the browser.

Webpack - Index Page

Check out your code editor, you should see a new compressed version of the image in the

dist
directory.

Webpack - Compressed and Uncompressed Compressed and Uncompressed

Furthermore, check out the version of the image that is served to the browser.

Webpack - Image served to the browser

Webpack - Image loaded in CSS

There are several loaders and plugins available for Webpack. Head over there, search for any plugin that might suit your use case and take advantage of it.

Webpack Performance Budgets

Addy Osmani submitted an RFC on October 31, 2016 about Webpack Performance Budgets. His argument was that many of the apps bundled with Webpack ship a large, single bundle that ends up pegging the main thread and taking longer than it should for web applications to be interactive.

Webpack - Trace on Chrome

Webpack 2.2 shipped with the Performance Budget feature. And made the feature an opt-out. By default, Webpack prescribes some default performance budgets (for entry points and assets). You can adjust them to suit your use case. All you need to do is add a performance entry in your

webpack.config.js
and customize the maxAssetSize, maxEntryPointSize and hints attribute.

Let's try it in Project Asokoro. Add a performance entry to the

webpack.config.js
like so:

webpack.config.js

...
performance: {
    maxAssetSize: 100,
    maxEntrypointSize: 100,
    hints: 'warning'
}

After running Webpack, this is the result shown below:

Webpack Performance Budgets

Learn more about Webpack Performance Budgets here.

Aside: Webpack and JavaScript at Auth0

At Auth0 we use JavaScript heavily in development and automate tasks using build tools such as Webpack. Using our authentication and authorization server from your JavaScript web apps is a piece of cake.

Auth0 offers a free tier to get started with modern authentication.

It's as easy as installing the

auth0-js
and
jwt-decode
node modules like so:

npm install jwt-decode auth0-js --save
import auth0 from 'auth0-js';

const auth0 = new auth0.WebAuth({
    clientID: "YOUR-AUTH0-CLIENT-ID",
    domain: "YOUR-AUTH0-DOMAIN",
    scope: "openid email profile YOUR-ADDITIONAL-SCOPES",
    audience: "YOUR-API-AUDIENCES", // See https://auth0.com/docs/api-auth
    responseType: "token id_token",
    redirectUri: "http://localhost:9000" //YOUR-REDIRECT-URL
});

function logout() {
    localStorage.removeItem('id_token');
    localStorage.removeItem('access_token');
    window.location.href = "/";
}

function showProfileInfo(profile) {
    var btnLogin = document.getElementById('btn-login');
    var btnLogout = document.getElementById('btn-logout');
    var avatar = document.getElementById('avatar');
    document.getElementById('nickname').textContent = profile.nickname;
    btnLogin.style.display = "none";
    avatar.src = profile.picture;
    avatar.style.display = "block";
    btnLogout.style.display = "block";
}

function retrieveProfile() {
    var idToken = localStorage.getItem('id_token');
    if (idToken) {
        try {
            const profile = jwt_decode(idToken);
            showProfileInfo(profile);
        } catch (err) {
            alert('There was an error getting the profile: ' + err.message);
        }
    }
}

auth0.parseHash(window.location.hash, (err, result) => {
    if(err || !result) {
        // Handle error
        return;
    }

    // You can use the ID token to get user information in the frontend.
    localStorage.setItem('id_token', result.idToken);
    // You can use this token to interact with server-side APIs.
    localStorage.setItem('access_token', result.accessToken);
    retrieveProfile();
});

function afterLoad() {
    // buttons
    var btnLogin = document.getElementById('btn-login');
    var btnLogout = document.getElementById('btn-logout');

    btnLogin.addEventListener('click', function () {
        auth0.authorize();
    });

    btnLogout.addEventListener('click', function () {
        logout();
    });

    retrieveProfile();
}

window.addEventListener('load', afterLoad);

Go ahead and check out our quickstarts for how to implement authentication using different languages and frameworks in your apps.

Conclusion

You have now learned the core concepts of Webpack and how to configure it in an application. You can leverage this gentle introduction for an understanding of intermediate and advanced concepts. Sean Larkin has some awesome Webpack courses on Webpack Academy

As at the time of this writing, Webpack is the most popular build tool amongst web developers. It is a beast that can be tamed to do your bidding in your JavaScript projects.

What's your experience with using Webpack? Let me know in the comments section.😊