1. 1. “Idiots guide to Webpack configuration”
  2. 2. Node modules setup
  3. 3. Project setup
  4. 4. Configuration file
    1. 4.1. Entry
    2. 4.2. Output
    3. 4.3. Loaders
    4. 4.4. Plugins
    5. 4.5. Run webpack
      1. 4.5.1. Not covered here

Understanding webpack

Webpack is a module bundler that takes all your assets and turns them into easily distributable packages. Once you understand it, it can drastically simplify your workflow, but getting started with it can be painful.

What I found difficult in starting to use it, was that I didn’t have a good model of what was going on. It seemed like magic. This made it hard to fix errors when they occurred and scared me away from trying to tailor the configurations I was using.

So I wrote myself an idiots guide to configuring Webpack. It helped me, maybe it can help you.

“Idiots guide to Webpack configuration”

Webpack bundles modules for web applications. Pretty much anything can be treated as a module. Images, CSS, JS, HTML, preprocessor files (.pug / .styl / .sass etc)… anything. It takes in all your modules (or files), figures out their dependencies and outputs some nice static assets, structured the way you want.

Node modules setup

The modules we need to npm install for this are:

  • babel-core
  • babel-loader
  • babel-preset-latest
  • css-loader
  • extract-text-webpack-plugin
  • html-webpack-plugin
  • pug
  • pug-loader
  • stylus
  • stylus-loader
  • webpack

Project setup

We will use a basic example project structure to help make this example easy to understand. This theoretical project contains ES2015 javascript code - split into some modules. It has styles written with a CSS preprocessor (Stylus) and a html template written with an HTML preprocessor (Pug).

| Source files

1
2
3
4
5
6
7
8
9
10
11
12
webpack.config.js
src/
├── main.js
├── js/
│ ├── module-1.js
│ └── module-2.js
├── styles/
│ ├── main-styles.styl
│ ├── type.styl
│ └── index.styl
└── template/
└── index.pug

| Desired output

1
2
3
4
5
6
dist/
├── index.html
├── js/
│ └── app.js
└── styles/
└── styles.css

| Example contents of JS files

1
2
3
4
5
6
7
/* modules/module-1.js */
// trivial demonstration code
export default {
alertMe () {
alert('There has been an alert!')
}
}
1
2
3
4
5
/* modules/module-2.js */
// Trivial demonstration code
export default {
confirmAlert: question => window.confirm(question)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* main.js */
// Import our modules
import one from './modules/module-1.js'
import two from './modules/module-2.js'
// Import our styles
import styles from './styles/index.styl'
...
// Code that uses functions imported from module-1 and module-2 e.g.
function doSomething (question) {
const setAlert = two.confirmAlert(question)
if (setAlert) one.alertMe()
}
// Run doSomething
doSomething('Would you like to be alerted?')

Configuration file

Webpack uses a javascript file to configure it’s operations. By default it looks for a file named webpack.config.js, but you can call it anything and pass it as the --config option. There are four key parts of this configuration file that will get your Webpack build going.

Entry

The starting point of your application. This option tells Webpack where to start. It will follow dependencies it finds in this file. It will then do the same for each dependency it finds.

1
2
3
4
/* webpack.config.js */
module.exports = {
entry: './src/main.js' // The entry file
};

Output

The output option tells Webpack how to output your bundled assets.

1
2
3
4
5
6
7
8
9
10
11
/* webpack.config.js */
const path = require('path');
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'), // Output directory
filename: 'js/app.js' // filename... create app.js in js/
// -> dist/js/app.js
}
};

Loaders

Webpack treats every file as a module, but it can only understand javascript. Loaders transform files into modules as they’re processed. Loaders identify which files they should handle with the test property and they process and transform the files so they can be added to your bundle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* webpack.config.js */
const path = require('path');
// ExtractTextPlugin is used to extract our CSS into it's own file
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const config = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/app.js'
},
module: {
rules: [
{
test: /\.js$/, // Match javascript files
use: {
// Process and transform files with babel
loader: 'babel-loader',
options: {
// Set some babel options. babel-loader can automatically
// get these options from .babelrc if it exists but I've
// put them here to be more explicit.
presets: [
['latest', {
'es2015': { 'modules': false }
}]
],
comments: false
}
}
},
{
test: /\.styl$/, // Match stylus files
use: ExtractTextPlugin.extract({
use: 'css-loader!stylus-loader'
}) // Process and transform files to css
},
{
test: /\.pug$/, // Match pug template files
use: 'pug-loader' // Process and transform files to html
}
]
}
};
module.exports = config;

Plugins

Loaders only run transforms on a per-file basis, once you have compiled your files into a bundle you can use plugins to further customise and perform actions on your outputted bundle.

To use a plugin, you require() it and add it to the plugins array. Most plugins have options that can be customised when they are used. You initiate the plugin by calling it with new. This is because you can use the same plugin multiple times with different configurations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* webpack.config.js */
// HtmlWebpackPlugin is used to generate our index.html template
// that includes all our webpack bundles
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// Requiring webpack gives us access to the built in plugins
const webpack = require('webpack')
const path = require('path')
const config = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/app.js'
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['latest', {
'es2015': { 'modules': false }
}]
],
comments: false
}
}
},
{
test: /\.(styl)$/,
use: ExtractTextPlugin.extract({
use: 'css-loader!stylus-loader',
fallback: 'style-loader'
})
},
{
test: /\.(pug)$/,
use: 'pug-loader'
}
]
},
plugins: [
// Extract our resulting css to css/styles.css
new ExtractTextPlugin('css/styles.css'),
// Optimize/minify javascript
new webpack.optimize.UglifyJsPlugin(),
// generate html file from our pug template,
// bundles will be injected
new HtmlWebpackPlugin({template: './src/templates/index.pug'})
]
};
module.exports = config;

Run webpack

If you have named your config with the default name, like in the above example you only need to run webpack from the command line in the same directory as your Webpack config. If you named it something else, say ‘config.js’, you can pass the configuration file to Webpack on the CLI like so: webpack --config config.js

Thats it. Webpack will process the files and output the finished product into the directory structure you want, ready to deploy to a webserver.

Not covered here

There’s a plethora of options that are not going to be in this. Not because they aren’t useful but because they can be safely ignored in the interest of understanding. Once you have wrapped your brain around the general model of whats occurring, I find the rest is all fairly simple.