The Common Man Guide to Webpack (Webpack for Beginners)
Tags: webpack, javascript, webdev, beginners
Published: 2021-01-07 15:57:38

So you've learned some React concepts and can make pretty nice apps using `create-react-app`, but now it's time to make your own bundler setup for custom deployment (and/or development). Maybe you want to bundle your vanilla JS code for easier code management and dont wish to have a million of script tags in your HTML file or you just need a convenient way to manage Babel-compiled code...

The time has come to use a code bundler.

There are plenty to choose from, including the likes of Rollup, Browserify, Parcel and ofcourse...Webpack.

## About this tutorial

This tutorial is aimed at everybody who is starting out in Webpack but finds himself/herself, lost in the official documentation. In here we will cover everything you need to get you started with bundling using Webpack.

This "guide" is written in form of tutorial as to give you hands on expreience (instead of just boilerplate code) through which you will learn the basics and be able to configure Webpack for your own specific needs. Everything will be done step by step, explained in plain english with some behind-the-scenes logic.

This is a beginners tutorial and is by no means an exhaustive guide. If you wish to dive deeper I would recommend the official docs, and will provide links to specific "further reading" at the end of this article.

For those of you that use TypeScript (and there's every reason to do so), I will provide side notes, but our configuration won't really differ much from the basic one.

## The common man tutorials

This is the first tutorial in the series titled "The common man guide to _{Webpack, Babel, ES-Lint, etc.}_". The idea behind the series is to help you utilize these incredible tools without blood, sweat and tears of reading through official docs (not that there's anything wrong with the docs, but they can be quite daunting, frustrating even, for somebody who is just starting out).

## What the hell is Webpack anyway

Webpack is a module bundler, meaning...you've guessed it: It bundles JavaScript code, among other things (CSS, images, etc.), as we will see later in the tutorial.

Eons ago, when JavaScript evolved from being a small "library" (used to animate small sections on your static web page) to the great programming language we all know and love today, it became a really good practice to slice up your code into smaller chunks called modules. Aside from custom modules, every modern JavaScript framework, based in Node.js, also uses Nodes built-in modules as dependencies. Loading these modules (both your custom `.js` files and dependencies) into an HTML web page manually would mean you'd have to manually include each module in `<script>` tags, as well as watch for the right order in which these modules are included. For production ready sites, with large codebases and a zillion of modules, that is just not acceptable. This is where module bundlers, like Webpack, come into play. Under the hood, Webpack follows your `import` / `export` statements (`module.exports` and `require()` for CommonJS), creates dependency graph and bundles all modules into one\* minified `.js` file.

Other than bundling code, Webpack offers some other features, such as `webpack-dev-server` - used in development to preview changes to your code, served from `localhost/` with optional hot reloading feature (hot reloading feature enables you to instantly preview changes to your code in the browser every time you save). `npm start` script in `create-react-app` uses `webpack-dev-server` under the hood and for these purposes, so will we.

_\*well...on larger projects and progressive web apps, the code is bundled into multiple chunks and progressively loaded, according to priority, using AJAX(or similar) requests, but code spliting and isomorphing scope beyond this tutorial. For these puropses I suggest you consult the official docs (https://Webpack.js.org/guides/code-splitting/), or as every other great developer: Google it!_

_**Note:** I will be using React for this tutorial, but the same principles will be aplicable to any kind of JavaScript code. You don't even need to know React and can just copy/paste the code._

So, without further ado...

## Let's get started

### First things first

Webpack runs in Node enviorment, so you will need to have the Node installed globally. To check this go to your terminal and run `node -v`. This will print out the version of Node you have installed. If you need to install Node, you can download it from here: https://nodejs.org/en/download/

With Node installed, we can start setting up our project. For this part, you can follow along, or you can clone the git repo with starter code: *https://github.com/ikusteu/webpack_intro* and run `npm install` inside of `webpack_intro` folder.

Let's create a root of our project, I will call mine `webpack_intro`. So I'll:

`mkdir webpack_intro`

and

`cd webpack_intro `

To initalize our project and create `package.json` file let's run:

`npm init -y`

_-the -y flag fills basic project info with default input, you can edit this later_

Let's create `/src` folder to contain our `.js` files, `index.html` template and `style.css`, so let's:

```bash
mkdir src
cd src
```

In our `/src` folder we will create `index.html`, `style.css`, and two JavaScript files: `index.js` and `App.js` for a simple React App, so let's:

`touch index.html style.css index.js App.js`

We won't need this code until the end of the tutorial but let's get it out of the way. `index.html` will serve as our template so let's just populate it with basic HTML 5 scaffolding, containing div with id of `"app"` to render our app to:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
```

_-notice how we didn't include our `index.js` nor `App.js` into HTML...later we will instruct Webpack to do that for us_

Next, let's create a simple React app, we will:

`npm install react react-dom --save`

_`--save` flag will automatically save installed packages to `package.json` dependencies_

With React installed, in `App.js` write:

```javascript
// App.js
import React from "react"

const App = () => {
  return <h1>Hello Webpack</h1>
}

export default App
```

...and let's render our app to html, in `index.js` write:

```javascript
// index.js
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"

ReactDOM.render(<App />, document.getElementById("app"))
```

We will leave `style.css` empty for now, and we're ready to start with Webpack.

_**Note:** The starter code ends here, from here on out, do follow along._

### The config file

For better part of this tutorial, we will be setting up our `webpack.config.js` file and going through each option with brief explanation. After the setup we will play around with it a bit to see it in action.

Before we start configuring Webpack, we need to install it first, so let's `cd` to our root directory and run:

`npm install webpack --save-dev`

_the `--save-dev` flag will save Webpack to the list of dev dependencies in `package.json`_

With Webpack installed let's create a config file:

`touch webpack.config.js`

When bundling, Webpack will, unless specified otherwise, look for a config file in our root directory (the one that contains `package.json` file) with the default name of `webpack.config.js` . There are ways around that, but I will cover that, as well as working with multiple config files, in a future tutorial.

_**Note:** We can use Webpack without the config file (by either utilizing default presets or CLI), but in this tutorial I cover this approach._

The main part of the config file is basically an object containing various options. We will explore all basic options in the following sections as we add them to our file so for now, let's just add and export an empty object and move on:

```javascript
// webpack.config.js
module.exports = {}
```

### Entry

The first thing we need to specify is an entry.

So, what is an entry?

Webpack is ran as a process in Node enviorment. It starts at an entry point and creates a dependency graph (this is how Webpack creates a bundle and ensures all modules are loaded in the right order). By specifying an entry point, we tell Webpack where to start graphing dependencies, in other words, where does our application start.

In this case our app starts at `index.js` which renders our app into the DOM. So, let's tell Webpack to start there by defining an entry point in our config file as our `index.js`:

```javascript
// webpack.config.js
module.exports = {
  entry: "/src/index.js",
}
```

To understand how Webpack will treat this, let's take a closer look at `index.js` and analize the order in which it is executed:

```javascript
// index.js
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"

ReactDOM.render(<App />, document.getElementById("app"))
```

The execution starts at `ReactDOM.render(<App />, document.getElementById("app"))` which renders our App component to the DOM. In order to render an app to the DOM, we utilize the `ReactDOM.render()` function imported from the `react-dom` module, making `react-dom` a dependency. Our app component is declared in a separate file so we need to import from `./App.js`, making it a dependency as well. Finally, in order to understand our App, which is a JSX, React component we need to import `react`, making it yet another dependency.

What Webpack will now do is, it will start to graph at `index.js`, read the three imported modules (treating them as dependencies) and look into each dependency to find their dependencies, their dependencies' dependencies and so on, until it has crated a full tree of imports. With all imports mapped, Webpack will then resolve the absolute path to each dependency, which conveniently brings us to the next point, the `resolve` option.

_**Note:** You can also use multiple entries, but for those cases, refer to the docs._

_**With Typescript:** If you're using TypeScript, Webpack can process `.ts` and `.tsx` files so your entry point would look something like `index.ts` or `index.tsx` (no need to precompile your files to `.js`)._

### Resolve

After creating a dependency graph, Webpack will resolve every dependency's absolute path. While resolver allows for a few options in configuration, we will take a look at one in particular, and that is `extensions` option. This allows us to specify an array of extensions, telling Webpack which extensions to autocomplete when creating an absolute path. Let's show this in practice. If we add `resolve: {extesions: []}` option in this way:

```javascript
// webpack.config.js
module.exports = {
  entry: "/src/index.js",
  resolve: {
    extensions: [".js", ".jsx"],
  },
}
```

and then we use `import App from './App'`, Webpack will automatically look for a file in local directory `./`, titled `App` with extension of `.js` or `.jsx` and find our `App.js`, making it as if we specified `import App from './App.js'`.

There are some more pretty cool options for resolver, such as aliasing path to often used directories (to avoid heavy use of relative paths in your imports), so if you'd like, do some research on your own on the subject _(https://Webpack.js.org/configuration/resolve/)_

_**With TypeScript:** If using TypeScript you would also specify `.ts` and `.tsx` (for React). However, please note that even though you might only use `.ts` and `.tsx` extensions in your code base, you still need to add `.js` to your extensions. Otherwise, Webpack will throw an error while compiling since it won't be able to resolve any of the node modules, including its own modules because they're all `.js` files._

### Output

So far we have given Webpack an information on where to start building a dependency graph, which will then be compiled and bundled, as well as provided extensions which to autocomplete while resolving. Now we need to specify where to save or output the bundle.

So, let's add an `output` option. Add this to our config file:

```javascript
// webpack.config.js
const path = require("path")

module.exports = {
  /* ...entry and resolve options */
  output: {
    path: path.join(__dirname, "dist"), // directory where our output file will be saved
    filename: "bundle.js", // specifies the name of the output file
    publicPath: "./", // path to bundle.js relative to index.html
  },
}
```

What have we done here?

In an `output` option we need to specify a `path` to the output directory. This is needed because Webpack creates a new directory for which it needs an absolute path (unlike entry, which can be relative to our root folder). To create absolute path we utilize one of Node's core modules called `path`. In this case, `__dirname` (a Node core variable) gives us an absolute path of 'this' file's directory (this being file we are reading, in this case `webpack.config.js` file) which is joined with `'dist'` string creating a path that looks like this `'<...absoute-path-to-root-directory>/dist'`. `filename` is the name of our bundle, where `publicPath` specifies a path to the output folder, relative to our `index.html` file (this is used for auto importing of our bundle into our HTML file using `<script>` tags), in this case `'./'` means both our HTML file and `bundle.js` file are in the same folder.

_**Note:** Don't get confused if you see `path.resolve()` instead of `path.join()` with same argument as above, which, in this case, does the same thing since `path.resolve()` resolves full path whereas `path.join()` simply concatenates paths, but since `__dirname` is absolute, the result is the same (an absolute path)._

### Loaders

Now that Webpack knows where to start looking for dependencies and where to save compiled bundle, we need to tell it how to process these dependencies before bundling. This is where loaders come into play. Loaders tap into the compilation process by adding certain rules/templates on how to process each module. We will use different loaders for different file extensions. Right now, we will only add `babel-loader` for `.js` and come back later. In this tutorial, we will use some of the most common loaders, but there are plenty out there so you can do some resarch on your own.

First, let's install `babel-loader`. Aside from `babel-loader` itself, we will need to install Babel, with some of its presets, as well.
Run:

```bash
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader
```

Let's add `module` option with `rules` array inside to our config file right below `output`:

```javascript
// webpack.config.js
import path from "path"

module.exports = {
  /* ...entry, resolve and output options */
  module: {
    rules: [],
  },
}
```

The `module` option contains all rules regarding modules (how they are loaded, processed etc.). In `rules` array we tell Webpack how and when to apply each loader. We will use Babel to precompile JavaScript (well, technically, 'transpile' would be the right term). For those who are not familiar, Babel is a great tool which transplies newer JavaScript syntax (ES6, JSX, ESNext...you name it) to vanilla JavaScript. I will not go too much in depth on it right now, since I plan on writing a Babel-focused tutorial as well. For purpose of this tutorial, we will just copy/paste basic config.

Let's add a first rule to our `rules` array:

```javascript
// webpack.config.js
const path = require("path")

module.exports = {
  /* ...entry, resolve and output options */
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: "babel-loader",
        options: {
          presets: ["@babel/env", "@babel/react"],
          plugins: [],
        },
      },
    ],
  },
}
```

First we tell Webpack to `test` for files with regex for `.js` and `.jsx` extensions (you don't need `.jsx` if you're not using React). With `loader`, we tell Webpack which loader to load these files with. At last, we specify `options` for our loader, in this case Babel presets.

_**Note:** since `babel-loader` uses Babel, we can also utilize Babel's config file. This is actually best practice with Babel in any scenario, but for purposes of this tutorial (to illustrate applying options to loader in webpack config file), I went with this approach. If you want to do it the "right" way, you would omit `options` property, create `babel.config.json` and inside write the same options, so it would look like this:_

```json
// babel.config.json
{
  "presets": ["@babel/env", "@babel/react"],
  "plugins": []
}
```

_There are a few ways to set up Babel config file, but more on this in Babel tutorial._

_**With TypeScript:** If you're using TypeScript, here you would test for `.ts` and `.tsx` file extensions instead of `.js` / `.jsx` and either install and use `ts-loader` instead of Babel or configure Babel to process TypeScript with `@babel/preset-typescript` preset. (More on that in my Babel tutorial)_

### Scripts

Finally, we have the basic config and are able to start bundling our code.

Now, to start our Webpack process, we need to configure script(s). For this tutorial we will use only one script and we will call it `build`.

_**Note:** I will explain scripts some more and look into using multiple scripts for different tasks in a more advanced tutorial_

To be able use Webpack's scripts we need to install Webpack's CLI module, so let's do just that, run:

```bash
npm install webpack-cli --save-dev
```

This package lets us run Webpack from our terminal or, in this case, add a custom script, so let's navigate to scripts in our `package.json` file, it should look something like this:

```json
// package.json
{
  // ...name, description etc.
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...keywords, dependencies etc.
}
```

We will delete `test` script, since we don't need it now, and replace it with `build` script so our `package.json` should look something like this:

```json
// package.json
{
  // ...name description etc.
  "scripts": {
    "build": "webpack --mode production"
  }
  // ...keywords, dependencies etc.
}
```

What we have done here is: We have created a script called `"build"` which runs a Webpack CLI command `webpack --mode production` (this is also how `create-react-app` `build` script runs in a nutshell). The `--mode production` compiles our code using production default options, I will look into different modes in an advanced tutorial, but for now let's run our script:

```bash
npm run build
```

At this point, if you've followed everything correctly and don't have any typos in your code, Webpack should have ran a compilation, and you should have gotten a message in your terminal looking something like this:

`webpack <Webpack version> compiled successfully in <execution time in ms>`

If you recieved this message, navigate to your root directory, and you should see that Webpack created a `/dist` folder, like we've instructed it to. When we `cd` to `/dist` folder, we should see our `bundle.js` file and when we open the file, we see a bunch of minified code. _'Et voilà',_ we've created our first bundle.

We are not done yet, however. There are still some tweaks we would like to do. For instance, we still need to manually import this bundle to our HTML file. If we were using our bundle as some added feature on our page, we would be perfectly fine with this. However, if our code is central to our app, like in this case, when creating a React app, we would like Webpack to spit out an `index.html` file with our `bundle.js`, automatically included using `<script>` tags. Luckily, we can do this by utilizing a Webpack plugin, so let's jump to the next section.

### Plugins

Webpack plugins _"...do everything a loader doesn't"_. Without getting too much into how plugins work, they, like loaders, tap into compilation process and provide additional templates and, most often serve as loaders and sometimes spit out additional files, like in this example.

The first plugin we will use is `html-webpack-plugin`. This plugin will spit out an `index.html` file in our `/dist` folder, with our bundle included in `<script>` tags.

Let's install the plugin:

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

After we've installed the plugin, we need to import it to our config file, and initialize an instance of it in our `plugins` array:

```javascript
// webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
  /* ...options ending with 'module' option */
  plugins: [new HtmlWebpackPlugin()],
}
```

Let's take a look at what we just did. We've imported `html-webpack-plugin` as `HtmlWebpackPlugin`. We have also added `plugins` array at the bottom of our config object. You can probably guess by now...this array holds initializations of instances of plugins. To elaborate: Webpack plugins are sort of like classes (not entirely, but for purposes of this tutorial, you can think of them as such), therfore, we need to initialize an instance of a plugin. Let's try this out. Save the config file and run:

```bash
npm run build
```

After Webpack compiles, take a look at the changes in the `/dist` folder. You should now see `index.html` file. When we run `index.html` in browser we see that it has our bundle already included in script but nothing is rendered to the screen yet, as if our bundle isn't working...

How does Webpack know where to find the bundle?

This is thanks to specifying `publicPath` property in `output` option we talked about earlier.

Why didn't anything get rendered then?

Well, the created `index.html` is an HTML 5 template provided by the plugin and doesn't contain `<div id="app"></div>`. Remember that, in our `index.js` we use this `<div id="app">` to tell React where to render everything, so how do we sove this. Luckily, Webpack plugins, behaving like classes, allow us to pass parameters to a constructor-like function. This way we can pass our own `/src/index.html` as a template HTML file. Let's add template to our config file like this:

```javascript
// webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
  /* ...options ending with 'module' option */
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "src/index.html"),
    }),
  ],
}
```

So let's test this out, run:

```bash
npm run build
```

If you open `/dist/index.html` you should see the difference applied with the document now containig `<div id="app"></div>` and ofcourse when we run this `index.html` file we now see that everything renders perfectly.

Now that we have an HTML template, let's add some styling to our page.

In `/src/style.css` write:

```css
.title-box {
  width: 100%;
  text-align: center;
}

.title {
  color: red;
}
```

also refactor our `App.js` a bit to look like this:

```javascript
import React from "react"
import "./style.css"

const App = () => {
  return (
    <div className="title-box">
      <h1 className="title">Hello Webpack</h1>
    </div>
  )
}

export default App
```

and run our `build` script.

If you've done everything right...Webpack should throw an error saying, in a nutshell, that it doesn't recognize this ".css thing".

You've probably guessed the solution, and that is to utilize a loader for `.css`. Let's go ahead and install it. Run:

```bash
npm install css-loader --save-dev
```

and add a rule in our config file to test for `.css` and use `css-loader` as loader.

You should be able to do this on your own by now, so do try it.

After applying a rule, our config file should look like this:

```javascript
// webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
  /* ...entry, resolve, etc. */
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: "babel-loader",
        options: {
          presets: ["@babel/env", "@babel/react"],
          plugins: [],
        },
      },
      {
        test: /\.css$/,
        loader: "css-loader",
      },
    ],
  },
  /* plugins */
}
```

Let's run `build` script and inspect out HTML file.

As you can see, we've managed to mitigate the compilation error but we don't see any CSS applied to our document. What happend here is, we told Webpack to process CSS using `css-loader`. `css-loader` told Webpack how to process CSS, but it didn't tell it what to do with CSS when processed, for that we need another loader. This is a common convention with Webpack loaders - each loader does exactly one thing, but we can chain them together for a desired effect. One solution here would be to use `style-loader`, which will write our compiled `style.css` at the begining of our `index.html` between `<style>` tags. This is a good solution, but we will use something more interesting.

We will utilize `mini-css-extract-plugin`

For practise, do install the said plugin, import it in our config file and initialize inside `plugins` array (you don't need to pass any parameters to plugin initialization) and then check the steps below.

Installation:

```bash
npm install --save-dev mini-css-extract-plugin
```

config file:

```javascript
// webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")

module.exports = {
  /* ...other options */
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "src/index.html"),
    }),
    new MiniCssExtractPlugin(),
  ],
}
```

Now we've created an instance of `mini-css-extract-plugin`, but didn't tell it what to do.

Remember our statement about plugins doing everything loaders don't do, well here's an example. Mini CSS plugin extracts precompiled CSS from Webpack bundle to a separate `main.css` file, combined with HTML plugin it links said file to `/dist/index.html`. It acts as a loader, to "take in" the compiled CSS and spits it out to a file. In order for its loader to work properly it needs to be chained after `css-loader`. To acomplish this, let's refactor our CSS rule in config file to look like this:

```javascript
// webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")

module.exports = {
  /* ...other options */
  module: {
    rules: [
      /* .js, .jsx rule */
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, "src/index.html"),
    }),
    new MiniCssExtractPlugin(),
  ],
}
```

What you see here, is a rule with chained loaders, the difference here is that we didn't pass `loader` property, instead we added our loaders to `use` array. This is how we use chained loaders. The important thing to note here is that chained loaders are applied from right to left (or bottom up) so here CSS gets loaded by `css-loader` and the output is then passed to `MiniCssExtractPlugin.loader` to extract it to a `main.css` file.

_**Note:** If we wanted to pass custom options to each loader, inside of our `use` array, we could, instead of queueing loaders as strings queue objects containing `loader` property and `options` property for each loader like this:_

```javascript
// instead of this
{
  test: /\.css?/,
  use: ["loader2", "loader1"]
},
// we write it like this
{
  test: /\.css?/,
  use: [
    {
      loader: "loader2",
      options: {
        // loader2 options
      },
    },
    {
      loader: "loader1"
      options: {
        // loader1 options
      }
      }]
}
```

If we run our `build` script, we should now see `style.css` created in `/dist` folder and applied to `index.html`

Now that we've covered all of the basic Webpack concepts and config, options, feel free to move your files around and edit the config file to practise what you have just learned. Hopefully you now posses firm understanding of how everything works so you can build on that and get into more advanced topics.

### Where to go from here?

One excercise you can do is set up an image loader so that you can import images from local drive into React using (commonjs/ES6 imports). Do try it on your own and I will upload the final code base (with solution to this excercise) as a branch on the starter code git repo: *https://github.com/ikusteu/webpack_intro*

I plan to make another tutorial on some of the use cases we haven't covered in this one.
Other resources I would recommend are official docs: _https://webpack.js.org/_, and this video from the creators of Webpack: _https://www.youtube.com/watch?v=gEBUU6QfVzk&t=562s_

That's it,

Happy Coding :)