How to setup Webpack config 📦
Tags: javascript, webpack, webdev
Published: 2019-01-31 10:10:13

**This post is taken from [my blog](https://areknawo.com) so be sure to check it out for more up-to-date content 😉**

Bundling assets and code has been pretty common in recent days. It allows creating portable packages that not only are easy to reuse and transfer but also great for fast delivery and thus better user experience (performance). It has gained exceptional boost since the release of **ES6 modules** specification - standardized way of providing modularity to your JS code. While not being rapidly adopted by the browsers, they quickly gained popularity among developers, replacing other inferior systems, such as **AMD** and **CommonJS**. With better modularity also came greater demand for bundlers. **Webpack**, due to its great functionality and extendability, quickly gained the upper hand. But with the number of plugins, extensions, loaders etc. at your disposal, it's not easy to provide one proper solution or more specific configuration for all users with different needs. That's why Webpack configuration can be a little bit hard and exhausting for some to be dealt with. And that's why this tutorial even exists. Here I'll try to introduce you to the basics of creating your Webpack config. I really advise you to read this from top to bottom because there's a **prize** waiting at the end. 😂 Without further ado, let's first take a look at Webpack itself.

# Webpack & company

Webpack is advertised as a static module bundler for modern JavaScript applications. It's a popular tool for bundling web apps. With support for ES6 modules, CommonJS, AMD and **@import**s it can pretty much handle all resources used by everyday web apps. It has a wide community behind it with a really vast ecosystem of **plugins** and **loaders** for many different assets. With that being said, it's not the only right tool for the work. There are plenty more high-quality bundlers out there. One of which being **Rollup.js**. It's just another bundler, but a bit more tailored towards bundling **libraries** and other JS tools rather than web apps. There's also a new player in the field called **Parcel.js**. It can be a perfect solution for everybody who doesn't like configuration and stuff. Parcel.js provides true out-of-the-box support for many different assets and formats. These 3 are my favorites and while there's definitely more other and based-on tools out there, I won't be naturally listing them all. 🙃 Now, that you know of possible alternatives, here's how to configure your Webpack step by step.

# Config

To be more specific, let's define what exactly our config should do. The following configuration should fulfill every demand of our project. In this case, it'll be a simple **SPA** and **PWA** based on **React** and written in **TypeScript**. We'll also use **SCSS** (with no support for CSS whatsoever) for a better experience while defining our styles. Let's begin! 😁

Take a look at a skeleton of Webpack config file.

```javascript
const path = require('path');

module.exports = {
  entry: './src/index.tsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  },
  resolve: {
      extensions: []
  },
  module: {
      rules: []
  },
  plugins: []
}
```

So here you have it. The basic structure of our config. It's located in the **webpack.config.js** file which utilizes CommonJS syntax to export our config object. Inside it, we have the `entry` field relatively pointing to the file where the bundler should start its work from. Then we have the `output` object with the proper `path` and `filename` for the generated bundle. The name uses **[name]** placeholder to indicate that the name of the output should correspond to the name of our module (**main** by default). `Resolve.extensions` section is basically an array of **file extensions** that Webpack should read and process. Next, we have `module.rules` which is arguably one of the most important places in the whole config. It is here where we define our **loaders** that should process specific files. At the end comes the `plugins` field where all Webpack **plugins** will find their place. Now, let's populate it with some content, shall we?

```javascript
// ...
    resolve: {
        extensions: [ '.tsx', '.ts', '.js', '.jsx' ]
    },
    module: {
        rules: [{
            test: /\.tsx?$/,
            use: ['babel-loader', 'ts-loader'],
            exclude: /node_modules/
        }]
    },
// ...
```

And... that's mostly all that's required to process TypeScript! Let's take a closer look at what's going on. In `extensions`, we've added all possible extensions we're going to use in the future. In the `rules`, we provided our first rule. Its an object with 3 properties. The `test` is a regexp that matches all files that end with **.ts** or **.tsx** extensions and processes them with **ts-loader** and then **babel-loader** provided in the `use` field. Using two processors gives us the ability to process code outputted from the TS compiler using Babel. Remember that loaders are used **from the last to the first** provided in the array. Finally, we exclude **node_modules** from matching, because who would possibly need to process these and lag his system? 😂 It's worth mentioning that you don't need to require ts-loader in any way, just to install it. And while we're talking about installing, I might have forgotten to mention anything about Webpack installation, so let's fix all that with one simple command:

```bash
npm install --save-dev webpack webpack-cli typescript @babel/core babel-loader ts-loader
```

Now let's add support for **SCSS**!

```javascript
// ...
{
    test: /\.scss$/,
    use: [
        'style-loader',
        { loader: 'css-loader', options: { importLoaders: 1 } },
        'sass-loader',
    ],
},
// ...
```

Here, we need to use as much as 3 loaders, so let's install them first and don't forget about **node-sass** for processing SCSS!

```bash
npm install --save-dev node-sass style-loader css-loader sass-loader
```

Generally what we're doing right here is processing SCSS files using **sass-loader** with the node-sass lib, transform all **@import**s and **URL**s with **css-loader** and actually use/insert our styles with **style-loader**. The `importLoaders` option for css-loader is indicating how many loaders are used before the CSS one. In our example, it's just one - sass-loader. Take a look at the syntax for providing loader with additional options.

Lastly, let's get fancy and add support for bundling **images** aka **static files**!

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

```javascript
// ...
{
    test: /\.(jpe?g|png|gif|svg)$/i,
    loader: 'file-loader'
},
// ...
```

With the **file-loader**, Webpack processes every matching import into proper URLs. Notice, that loader field can be used instead of use when defining single loader.

Also, don't forget about other config files, such as **tsconfig.json** for [**TypeScript**](http://typescriptlang.org/)...

```json
{
    "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": false,
        "module": "commonjs",
        "target": "es5",
        "jsx": "react",
        "lib": ["es5", "es6", "dom"]
    },
    "include": [
        "./src/**/*"
    ],
}
```

...and **.babelrc** for [**Babel**](https://babeljs.io/):

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

```javascript
{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ],
  "env": {
    "development": {
      "presets": ["@babel/preset-typescript"]
    }
  }
}
```

I won't cover these as they're a bit out-of-topic, check out links to their pages if you want to know more - all of the tools listed in this article have awesome docs. 📙⚡

Let's get onto plugins now.

```bash
npm install --save-dev clean-webpack-plugin html-webpack-plugin
workbox-webpack-plugin webpack-pwa-manifest
```

```javascript
const CleanPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const WebpackPwaManifest = require('webpack-pwa-manifest');
// ...
plugins: [
    new CleanPlugin(["dist"]),
    new HtmlWebpackPlugin({
        filename: 'index.html',
        title: 'Webpack Config',
        template: './src/index.html'
    }),
    new WebpackPwaManifest({
        name: 'Webpack Config',
        short_name: 'WpConfig',
        description: 'Example Webpack Config',
        background_color: '#ffffff'
    }),
    new WorkboxPlugin.GenerateSW({
        swDest: 'sw.js',
        clientsClaim: true,
        skipWaiting: true,
    })
],
// ...
```

In the snippet above we're greeted with as much as 4 plugins! Each of them has its own specific purposes. **Clean-webpack-plugin** is responsible for cleaning output directory - a simple task. **Html-webpack-plugin** setups our HTML file using provided data and template file.

```html
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta 
          name="viewport" 
          content="width=device-width, initial-scale=1, shrink-to-fit=no"
        >
	<title><%= htmlWebpackPlugin.options.title %></title>
</head>

<body>
	<noscript>
		You need to enable JavaScript to run this app.
	</noscript>
	<div id="root"></div>
</body>

</html>
```

That's our **template file** BTW with the title taken straight from the plugin's config object. Finally, **workbox-webpack-plugin** and **webpack-pwa-manifest** provide **PWA** functionalities for **offline service-workers** and **app manifest** respectively. Some of these have a lot of customization options, so go to their project pages to learn more if you plan to use them.

# Production

At this point, we can safely say that our config is quite operational. But it's not enough. With Webpack you can have **multiple** configs for different use-cases. The most popular example is to have 2 configs for **production** and **development** as each environment has its own specific requirements. Let's break our webpack.config.js into **3 pieces**.

**Webpack.common.js** will contain configuration that is the same for both development and production configs.

```javascript
const CleanPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const WebpackPwaManifest = require('webpack-pwa-manifest');
const path = require("path");

module.exports = {
  entry: "./src/index.tsx",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js"
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js", ".jsx"]
  },
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          "style-loader",
          { loader: "css-loader", options: { importLoaders: 1 } },
          "sass-loader"
        ]
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        loader: "file-loader"
      }
    ]
  },
  plugins: [
    new CleanPlugin(["dist"]),
    new HtmlWebpackPlugin({
        filename: 'index.html',
        title: 'Webpack Config',
        template: './src/index.html',
    }),
    new WebpackPwaManifest({
        name: 'Webpack Config',
        short_name: 'WpConfig',
        description: 'Example Webpack Config',
        background_color: '#ffffff'
    }),
    new WorkboxPlugin.GenerateSW({
        swDest: 'sw.js',
        clientsClaim: true,
        skipWaiting: true,
    })
  ]
};
```

Now, let's create our **webpack.prod.js** config. We'll need to merge it with our common config. To do this we can utilize **webpack-merge** - a tool for doing just that. So let's install it and 2 other plugins we'll later use.

```bash
npm install --save-dev webpack-merge uglifyjs-webpack-plugin hard-source-webpack-plugin
```

```javascript
const merge = require('webpack-merge');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'production',
    devtool: 'source-map',
    module: {
        rules: [{
            test: /\.tsx?$/,
            use: ["babel-loader", "ts-loader"],
            exclude: /node_modules/
        }]
    },
    optimization: {
        minimizer: [new UglifyJsPlugin({
            sourceMap: true
        })],
    },
});
```

Here we can see two new properties - `mode` and `devtool`. `Mode` indicates our current environment - its either *"production"*, *"development"* or *"none"*. This allows some tools to apply optimizations proper for chosen env. `Devtool` property refers to the way of generating **source maps**. Webpack has many options built-in for this property. There are also many plugins that provide additional functionalities. But *"source-map"* option that produces source maps from content files, is enough for us right now. Then we have our old-fashioned **.ts** files loader. It's followed by new, self-explaining fields in our config. The `optimization.minimizer` allows us to specify a plugin used to **minimize** our files, which is naturally useful when targeting production. Here I'll use **uglifyjs-webpack-plugin** which is well battle-tested and has good performance with solid output. Don't forget about `sourceMap` option for this plugin, without that your source maps **won't be generated**! Now, let's go over to the development config file - **webpack.dev.js**.

```javascript
const merge = require('webpack-merge');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'eval-source-map',
    module: {
        rules: [{
            test: /\.tsx?$/,
            loader: "babel-loader",
            exclude: /node_modules/
        }]
    },
    plugins: [
        new HardSourceWebpackPlugin()
    ]
});
```

At development, we only care about **speed**. No optimizations need to be done at that point. We only want for our code to be bundled **fast**. The same applies to source mapping which this time uses much faster, but the not-so-optimized *"eval-source-map"* option. Then, when defining our loader for TypeScript, we use only one, single loader - **babel-loader**. By doing this we only transpile our .ts files **without type-checking** them, which has a huge impact on bundling speed. That's why earlier I defined the **@babel/preset-typescript** to be used on the development stage in the **.babelrc** file. Lastly, we have the **hard-source-webpack-plugin** which provides an easy way for caching our files, so our second bundling will be even faster!
And... that's it! We have our proper environment-specific configs ready to be used!

# Hot reload 🔥

So we've got nice configs, but who needs a fast development config without **hot reloading**!? That's right - it's getting hot!🔥 So, let's put aside our production config for now and let's implement this wonderful feature, shall we? Using **webpack-dev-server** it's really simple! You can install it with:

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

For configuration add `devServer` config object to our **webpack.dev.js** file.

```javascript
// ...
devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9000
}
// ...
```

Here we provide basic options like port, directory to serve and if compression should be done. And that's it! To finish it let's add two **scripts** to our **package.json** for easier development.

```json
"scripts": {
    "start": "webpack-dev-server --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
}
```

By using **--config** option we provide the location of our env-specific Webpack config.

So here you have it! Your very own Webpack config with support for TS/TSX, SCSS, optimize production and development settings and HMR! As a side-note, our HMR works just fine, but when it comes to React-specific stuff, there's room for improvement. For example, if you would like to preserve your components' states across reloads. For this, you can use **react-hot-loader** and follow [**this awesome guide**](http://gaearon.github.io/react-hot-loader/getstarted/) while using the config you've already created here.

# A gift 🎁

So, as you can see by following this tutorial, creating Webpack config isn't difficult. It's just a bit time-consuming process that can require some googling from time to time. But it can also be fun for some. But if you're in the other group I have something special for you. I've created a simple **CLI tool** for creating **basic boilerplate** for your **Webpack config**. By using this you won't have to spend time setting the same things up yourself over and over again. It's called **webpack-suit-up** and you can download it from [**NPM**](https://www.npmjs.com/package/webpack-suit-up). So, yeah, check it out if you're interested.

I hope this tutorial helped you with the process of configuring your Webpack. For more info on Webpack, you can check out its official website. But, just as I said on the beginning, there are many other great tools that may not even require configuration. There are even those which are based on Webpack and automatically configures it. Also, even Webpack itself from **v4** doesn't require configuration, but it's really necessary in most cases. Maybe you would like to see a complete list of interesting **web bundlers** out there? Or rather a guide on configuring **Rollup.js**? Write in the comments below. **Share this article**, so that others can discover it quicker. Also, **[follow me on Twitter](https://twitter.com/areknawo)** or **[on my Facebook Page](https://www.facebook.com/Areknawos-Blog-770620059945105)** for more up-to-date content. 😁