Introduction to Webpack

Webpack is a web developer tool for bundling modules. It means webpack takes all the files of your project and turn it into a single file. With the rise of JavaScript as the language of the web and NodeJS that powers JS server, Webpack has been very popular and is used on almost every application. It can also be configured to support different types of assets such as images, fonts, and stylesheets. Since it’s release, Webpack has been improved to optimize performance and load times.

Webpack Setup

Webpack can be installed globally or locally for each project

It is recommended to install webpack locally, because then webpack can be updated per-project as needed. And if you ever want to use the latest webpack features for a new small project, you don’t have to update webpack of your other projects.

To install locally with npm

$ npm i webpack webpack-cli --save-dev

or if you prefer Yarn

$ yarn add webpack webpack-cli -D

Then add a webpack build command in package.json file.

{
  //...
  "scripts": {
    "build": "webpack"
  }
}

Then run npm build or yarn build

Configuration

Webpack can be run without providing any configuration file since version 4.0 with these rules as its default:

  • Default entry point of your app is ./src/index.js
  • Default output directory is ./dist/main.js
  • Default mode is production

Usually developers need their own configuration to better fit the project requirements. We can create a webpack.config.js file in the root directory of the project and webpack will use it automatically. Some configuration we can make with webpack are as follows:

Entry point

Webpack will use the file mentioned the entry point to begin building internal dependency graph It will find out all the imports and collect them all. The default value is src/index.js but you can change it to anything you want

module.exports = {
  entry: './src/app.js'
}

Output directory

Output tells webpack where to emit the bundle of files it creates and what name to use. It defaults to ./dist/main.js but you can change it into anything you want.

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

Mode

Webpack has production and development build mode which dictates how it process the files.

Development mode builds faster but less optimized. It also provides better debugging experience and more detailed error messages and warnings.

module.exports = {
  entry: './src/app.js',
  output: {
    path: path.resolve(__dirname, 'output'),
    filename: 'bundle.js'
  },
  mode: 'development'
}

Loaders

Out of the box, webpack only understands JavaScript and JSON files. Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency. Here is an example of loaders for .txt, .css or image types:

module.exports = {
  entry: './src/app.js',
  mode: 'development',
  output: {
    path: path.resolve(__dirname, 'output'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: 'css-loader' },
      { test: /\.(png|svg|jpg|gif)$/, use: 'file-loader' },
    ]
  }
}

To learn more about loaders you can see the official documentation at https://webpack.js.org/guides/asset-management/

Plugins

Plugins are extensions that enhance the capability of webpack to do what loaders can’t do. For example, adding internationalization support or creating HTML files for serving the bundle file.

Let’s see an example of HtmlWebpackPlugin in action. First we have to install it

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

Then add it to the configuration

var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: './src/app.js',
  mode: 'development',
  output: {
    path: path.resolve(__dirname, 'output'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      { test: /\.txt$/, use: 'raw-loader' },
      { test: /\.css$/, use: 'css-loader' },
      { test: /\.(png|svg|jpg|gif)$/, use: 'file-loader' },
    ]
  },
  plugins: [new HtmlWebpackPlugin()]
}

Now when we run the webpack, it will create an HTML file in the dist folder

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack App</title>
  </head>
  <body>
    <script src="bundle.js"></script>
  </body>
</html>

Visit https://webpack.js.org/plugins/ for a list of useful webpack plugins.

Generate sourcemaps

Since webpack bundles the code, source maps are mandatory to get a reference to the original file that raised an error. For example, if you bundle three source files (a.js, b.js, and c.js) into one bundle (bundle.js) and one of the source files contains an error, the stack trace will simply point to bundle.js. This is problematic as you probably want to know exactly if it’s a, b, or c file that is causing an error.

You can tell webpack to generate source maps using the devtool property of the configuration:

module.exports = {
  entry: './src/app.js',
  mode: 'development',
  devtool: 'inline-source-map',
  output: {
    path: path.resolve(__dirname, 'output'),
    filename: 'bundle.js'
  }
}

Although it will cause slower build, it has no effect on production. Sourcemaps are only downloaded if you open the browser DevTools.

Watching changes and refresh

Webpack can be configured to automatically rebundle for any changes made in any of the resolved files by adding --watch argument into the build script in package.json file.

{
  //...
  "scripts": {
    "build": "webpack --watch"
  }
}

But of course the browser won’t be refreshed and still serving the same app. In order to make browser do a hard refresh after rebundle, you can use webpack-dev-server

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

Then change the build script to use webpack-dev-server

{
  //...
  "scripts": {
    "build": "webpack-dev-server --mode development"
  }
}

WARNING: never use webpack-dev-server for production environment since you probably do not need watch and hard refresh on production. It’s also better to make scripts as verbose as possible by adding environment argument

{
  //...
  "scripts": {
    "build": "webpack --mode production",
    "dev"  : "webpack-dev-server --mode development"
  }
}

This way you can use npm dev to enable watch and browser refresh, but still use the webpack production build by running npm build.

There are even more configurations than these

I only covered configurations that are frequently used in a general JS project. Be sure to checkout webpack official guide at https://webpack.js.org/guides/ For more configs like code splitting, integrating Typescript and tree shaking.

Take your skills to the next level ⚡️

I'm sending out an occasional email with the latest tutorials on programming, web development, and statistics. Drop your email in the box below and I'll send new stuff straight into your inbox!

No spam. Unsubscribe anytime.