Through Webpack lies the path to madness. This I have gathered, reading various articles scattered like breadcrumbs amongst the Interwebs' tangles.
Rather than follow those crumbs, I have decided to cut my own path. My hope is to create a base Webpack configuration, simply, and hewing as closely as I can to those modules and plugins maintained by the Webpack Team themselves. In this way, I hope to minimize the wonkiness and hackiness that many articles seem to encourage in their configurations.
At time of writing, Webpack 4.8.3 is current.
This article assumes some familiarity with Node.js and npm, and with setting up a new project folder or repository.
You might be wondering, do we need another Webpack article? Maybe you don't, but I do.
I am new to Webpack, and it's been a struggle. Most articles I've read are out-of-date, or just completely insane, steering the reader into brambles, then handing them band-aids to cover the wounds, these being third-party loaders and plugins that seem less like feature adds, and more like hacks. Or they're just not in sync with the current version of Webpack, and they break.
This is a living document that I can update as Webpack and/or my needs continue to evolve, and I hope to avoid bloating my package.json
or Webpack config with anything nonessential.
This article does not aim to be comprehensive. Instead, I hope to build a solid base and a sane introduction to Webpack. By writing things down, I hope to help myself and others to come to better grips with this challenging packager, and to stave off the madness that so many authors seem to encourage.
Let's get to it.
Initialize the project folder with:
$ npm init
It's okay to accept the defaults, and you will end up with a package.json
ready to work with.
Install Webpack and its command-line interface as development dependencies with:
$ npm install webpack webpack-cli --save-dev
Within the project folder, create a src
folder, containing two files, index.html
and index.js
. At this point, your folder should look like this:
node_modules
src
index.html
index.js
package-lock.json
package.json
Create a .gitignore
file, and let's ignore both node_modules
and dist
, a folder which will be created later.
.gitignore
dist/
node_modules/
By default, Webpack will create the dist
folder when it runs, and dump its output there. But we might like to clean up the existing dist
when building anew, so let's set that up.
$ npm install del-cli --save-dev
This allows the deletion of files and directories, and is useful in build scripts. del-cli
is fully separate from Webpack, so we can use it without cluttering the configuration file that we'll be building shortly.
Open the package.json
file, and edit the "scripts" section to match the following.
package.json
...js
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prebuild": "del-cli dist -f",
"webpack": "webpack",
"build": "npm run prebuild -s && npm run webpack -s"
},
The prebuild
will remove an existing dist
folder, webpack
will recreate it, and we'll use build
to run both things at once, using:
$ npm run build
Go on, try it. =D
To get Webpack doing more of the things we want, we should employ a configuration file. We might think about the configuration file as our map, a record of where we've been, and a guide for where we next expect to go.
In the project root, create a new file, webpack.config.js
, with these contents:
webpack.config.js
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
watch: false,
};
As written, this essentially mimics Webpack's default behavior. We'll sketch in more of the terrain as we travel forward. For details on what's here, see Webpack documentation for Entry, Output and Mode. These are core concepts you'll want to understand.
If you'd like Webpack to run automatically when you change files, set 'watch: true'.
Because it's 2018 and the newer ECMAScript is all the rage, you probably want to include Babel, helpful for teaching the new slang to fogey browsers.
Install the following with npm.
$ npm install babel-core babel-loader babel-preset-env --save-dev
To use these, we'll need to grow our configuration file to include modules.
webpack.config.js
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: /src/,
options: {
presets: ['env']
}
},
]//rules
}//module
};
The regular expression used above, /.jsx?$/
, will match both .js
and .jsx
, just in case you want to use React in your app. Speaking of, we might include React transpiling; skip this next step if you don't need it.
npm install babel-preset-react --save-dev
And we add the React preset to our rules:
webpack.config.js
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: /src/,
options: {
presets: ['env', 'react']
}
},
]//rules
}//module
Now let's get our HTML on. For this, we need the HTML Webpack Plugin
. In keeping with our mission statement, this is maintained by the Webpack Team.
$ npm install html-webpack-plugin --save-dev
In our configuration file, we must require it, and add the plugins
configuration.
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: /src/,
options: {
presets: ['env', 'react']
}
},
]//rules
},//module
plugins: [
new HtmlWebpackPlugin({
title: 'My App',
template: 'src/index.html'
})
]//plugins
};
Now let's revist the empty index.html
file we created at the top of the article. Open it up, and paste in the following:
src/index.html
<!doctype html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
</body>
</html>
Note the strange enclosure in the HTML <title>
element. This allows us to dynamically pull the app's name from the HtmlWebpackPlugin title
option in our Webpack configuration file.
It's also worth noting that our HTML template does not explicitly include the script file. Don't worry! Webpack will add the script inclusions before the body
element closes.
And finally, we are including a link
element referencing a stylesheet that does not yet exist. That's next.
To here, the road we have cut has been fairly direct. As we get into CSS, though, I must warn you that I've found the terrain somewhat swampy.
Webpack's documentation recommends pairing css-loader
and style-loader
. Install them with:
$ npm install style-loader css-loader --save-dev
Then update the rules in our module config.
webpack.config.js
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: /src/,
options: {
presets: ['env', 'react']
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]//rules
},//module
As Webpack invokes loaders from right-to-left, the sequence above -- 'style-loader', 'css-loader' -- is important. Don't swap them.
Create a style.css
file in your src
folder, and put something -- anything! -- inside of it. Here's something:
src/style.css
html {
background: #ffffff;
color: #333333;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 16px;
height: 100%;
text-rendering: optimizelegibility;
-ms-touch-action: manipulation;
touch-action: manipulation;
}
body {
height: 100%;
margin: 0;
padding: 36px 48px;
}
Finally, because Webpack can have only a single entry point, and that's our index.js
file, we must reference our new CSS file within it. At the very top:
src/index.js
import css from './style.css';
Run your build, and your CSS should appear in the compiled main.js
file. It works, but it's also lame. Because putting CSS into your JavaScript files is lame. toUpperCase(). LAME.
To combat lameness, here we break from our first-party mission statement, and reach for a third-party solution. That solution is mini-css-extract-plugin
.
$ npm install mini-css-extract-plugin --save-dev
In reading elsewhere, you may find references toward using extract-text-webpack-plugin
, but visiting that module's Github page, you will find them stating, "Since webpack v4 the extract-text-webpack-plugin should not be used for css. Use mini-css-extract-plugin instead." And so we shall.
Our configuration file requires some updates: a require statement, module rules, and a plugin declaration. Here's the updated file in full:
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: /src/,
options: {
presets: ['env', 'react']
}
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
}
]//rules
},//module
plugins: [
new HtmlWebpackPlugin({
title: 'My App',
template: 'src/index.html'
}),
new MiniCssExtractPlugin({
filename: "style.css"
})
]//plugins
};
Running the build, you will now find a style.css
file in your dist
folder!
The content below is likely to evolve, or may change entirely. I'm in the midst of writing it, so proceed at your own risk.
We're well on our way through the swamp, but not out of the muck just yet. Images may appear in your HTML and/or CSS, and Webpack is still leaving them behind. Let's work on including them in our build.
Create an images
folder inside of src
, and this is where you'll drop image files.
src
images
And we'll need two Webpack loaders, file-loader
and url-loader
.
$ npm install file-loader url-loader --save-dev
In your configuration file, update your module rules to include the below rule for images files, following the existing CSS rule.
webpack.config.js
...
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name: '[path]/[name].[ext]'
}
}
]
}
]//rules
url-loader
will inline images smaller than the set limit as base64 strings; images larger than the limit will fall back to file-loader
by default, and will be moved to dist
folder in accordance with the 'name' option. As written above, we keep the existing file name, and drop the images into dist/images
.
SASS, perhaps. Minification. Assets handling?
This document is still evolving, so I'll add to it as needed.
Webpack Documentation (essential)
Awesome Webpack, on Github
mini-css-extract-plugin on Github (useful)
Webpack - A Detailed Introduction, by Joseph Zimmerman (out-of-date)
A tale of Webpack 4 and how to finally configure it in the right way, by Margarita Obraztsova (informative, but a mess)
Handling Images, by Bharat Tiwari