Getting Started with Webpack [With Example Project]

Webpack is a module bundler that facilitates building complex JavaScript applications. It has gained serious traction since the React community chose it as its main build tool. Webpack is neither a package manager nor a task runner as it uses a different (more advanced) approach, but its goal is also to set up a dynamic build process.

Webpack works with vanilla JavaScript. You can use it to bundle the static assets of an application, such as images, fonts, stylesheets, scripts into one single file while taking care of all the dependencies.

You won’t need Webpack to create a simple app or website, for instance one that has only one JavaScript and one CSS file and a few images, however it can be a life-saver for a more complex application with several assets and dependencies.

Webpack vs. task runners vs. Browserify

So, how does Webpack stack up compared to other build tools such as Grunt, Gulp, or Browserify?

Grunt and Gulp are task runners. In their config file, you specify the tasks, and the task runner executes them. The workflow of a task runner basically looks like this:

IMAGE: pro-react.com

However, Webpack is a module bundler that analyzes the whole project, sets up a dependency tree, and creates a bundled JavaScript file that it serves to the browser.

IMAGE: pro-react.com

Browserify is closer to Webpack than task runners, as it also creates a dependency graph but it does so only for JavaScript modules. Webpack goes one step further, and it doesn’t only bundle source code but also other assets such as images, stylesheets, fonts, etc.

If you want to know more on how Webpack compares to other build tools, I recommend you two articles:

  1. Andrew Ray’s Webpack: When to Use and Why on his own blog
  2. Cory House’s Browserify vs Webpack on freeCodeCamp (with awesome illustrations)

The two illustrations above are from the Webpack materials of the Pro React handbook, another resource that’s worth a look.

Four core concepts of Webpack

Webpack has four main configuration options called “core concepts” that you will need to define in the development process:

  1. Entry – the starting point of the dependency graph (one or more JavaScript files).
  2. Output – the file where you want the output to be bundled to (one JavaScript file).
  3. Loaders – transformations on the assets that turn them into Webpack modules so that they can be added to the dependency graph. For instance, css-loader is used for the import of CSS files.
  4. Plugins – custom actions and functionalities performed on the bundle. For instance, the i18n-webpack-plugin embeds localization into the bundle.

Loaders work on a per-file basis before the compilation takes place. Plugins are executed on the bundled code, at the end of the compilation process.

Install Webpack

To install Webpack, open the command-line, navigate to your project folder, and run the following command:


npm init

If you don’t want to do the configuration yourself, you can make npm populate the package.json file with the default values with the following command:


npm init -y

Next, install Webpack:


npm install webpack --save-dev

If you have used the default values this is how your package.json file should look like now (the properties can be in a different order):


{
  "name": "_tests",
  "version": "1.0.0",
  "description": "",
  "main": "webpack.config.js",
  "dependencies": {
    "webpack": "^3.6.0"
  },
  "devDependencies": {
    "webpack": "^3.6.0"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Create the configuration file

You need to create a webpack.config.js file in the root folder of the project. This configuration file has a central role, as this is where you will define the four core concepts (entry points, output, loaders, plugins).

The webpack.config.js file holds a configuration object of which properties you need to specify. In this article, we will specify the four properties that correspond to the four core concepts (entry, output, module, and plugin), but the config object has other properties as well.

1. Create the entry point(s)

You can have one or more entry points. You need to define them in the entry property.

Insert the following code snippet into the webpack.config.js file. It specifies one entry point:


module.exports = {
  entry: "./src/script.js"
};

To specify more than one entry points you can use either the array or the object syntax.

In your project folder, create a new src folder and a script.js file inside it. This will be your entry point. For testing purposes, just place a string inside it. I used the following one (however, you can also use a more interesting one):


const greeting = "Hi. I'm a Webpack starter project.";
document.write(greeting);

2. Define the output

You can have only one output file. Webpack bundles all the assets into this file. You need to configure the output property in the following way:


const path = require("path");

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

The filename property defines the name of the bundled file, while the path property specifies the name of the directory. The example above will create the /dist/bundle.js file.

Although it’s not compulsory, it’s better to use the path.resolve() method when you define the path property, as it ensures accurate module resolution (the absolute path of the output is created according to different rules in different environments, module resolution solves this discrepancy). If you use path.resolve, you need to require the path Node module at the top of the webpack.config.js file.

3. Add the loaders

To add the loaders, you need to define the module property. Below, we add the babel-loader that allows you to safely use the ECMAScript 6 features in your JS files:


const path = require("path");

module.exports = {
  entry: "./src/script.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: "babel-loader",
          options: { presets: ["env"] }
        }
      }
    ]
  }
};

The config may seem difficult but it’s just copy-pasted from Babel loader’s documentation. Most loaders come with either a readme file or some kind of documentation, so you can (almost) always know how to configure them properly. And, the Webpack docs also have a page that explains how to configure module.rules.

You can add as many loaders as you need, here’s the full list. Note that you also need to install each loader with npm to make them work. For the Babel loader, you need to install the necessary Node packages with npm:


npm install --save-dev babel-loader babel-core babel-preset-env webpack

If you have a look at your package.json file, you’ll see that npm added three Babel-related packages to the devDependencies property, these will take care of the ES6 compilation.

4. Add the plugins

To add the plugins, you need to specify the plugins property. Plus, you also have to require the plugins one by one, as they are external modules.

In our example, we add two Webpack plugins: the HTML Webpack plugin and the Preload Webpack plugin. Webpack has a nice plugin ecosystem, you can browse the full list here.

To require the plugins as Node modules, create two new constants: HtmlWebpackPlugin and PreloadWebpackPlugin and use the require() function.


const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const PreloadWebpackPlugin = require("preload-webpack-plugin");

module.exports = {
  entry: "./src/script.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: "babel-loader",
          options: { presets: ["env"] }
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin(),
    new PreloadWebpackPlugin()
  ]
};

Just like in the case of loaders, you also have to install the Webpack plugins with npm. To install the two plugins in the example, run the following two commands in your command line:


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

If you check your package.json file now, you’ll see that npm added the two plugins to the devDependencies property.

Run Webpack

To create the dependency tree and output the bundle, run the following command in the command line:


webpack

It usually takes one or two minutes for Webpack to build the project. When finished, you’ll see a similar message in your CLI:

If everything went right Webpack created a dist folder in the root of your project and placed the two bundled files (bundle.js and index.html) inside it.

Github repo

If you want to check out, download, or fork the whole project, have a look at our Github repo.