How to set up Webpack 5, ES6 with ESLint, PostCSS with Stylelint, CSSNANO and more!

Jon Torrado
8 min readJun 6, 2018

Edit: story updated 9 Nov 2020

This story is just a buch of updates of this one. I also wrote about some other useful tools in my older stories, but for this “updated story” we are going to use:

I’ve already played with all of them, but it’s very difficult to set up everything over and over again and make them work together without problems. Let’s start!

Webpack

First of all, you should have npm already installed in order to globally install webpack (and the upcoming dependencies):

npm i -g webpack

Let’s start by installing the needed dependencies:

npm add webpack webpack-cli webpack-merge --save-dev

That was easy, wasn’t it? Check the package.json file and keep it open, we will be coming back soon. Also check that the installed webpack version is 5.*.

Now, we are going to create the webpack.common.js file. I usually create a folder for each piece of software (keep all your stuff ordered!). So create a webpack folder and put the config file there.

ES6 with Babel

Here you have the dependencies you need. Please, read carefully what they mean, this story is just the recipe to make them work:

npm add @babel/core @babel/preset-env babel-loader --save-dev

A lot of people usually creates a .babelrc file here to load the env preset, but I prefer to load it everything from the configuration files. Let’s start editing our webpack/webpack.common.js file (adapt it with your paths):

const path = require('path');
const loaders = require('./loaders');
const webpack = require('webpack'); // to access built-in plugins
module.exports = {
entry: ["../src/js/app.js"],
module: {
rules: [
loaders.JSLoader
]
},
output: {
filename: "js/[name].bundle.js",
path: path.resolve(__dirname, "dist"),
},
plugins: [
new webpack.ProgressPlugin(),
],
};

As you can read above, I separated all the webpack loaders in a loaders.js file imported at the top. Also, with Webpack 5, you can now create a webpack.dev.js file and a webpack.prod.js file. Here you have my dev file:

'use strict';

const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: path.join(__dirname, 'public'),
},
});

And here you have my prod file:

'use strict';

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
mode: 'production',
});

Now, let’s create that webpack/loaders.js file:

const JSLoader = {
test: /\.js$/i,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
};
module.exports = {
JSLoader: JSLoader
};

Here you can see the presets loaded in the options section. No need for an extra .babelrc file!

Final step: run webpack. Edit the package.json file and add the following scripts:

{
"devDependencies": {
...
},
"scripts": {
"build": "webpack --config resources/webpack/webpack.prod.js ",
"watch": "webpack --watch --config resources/webpack/webpack.dev.js",
}
}

That’s all you need to start working with ES6. Remember to create a src/js/app.js file and start writing your code there (whatever you like!). Then just type npm run build in a terminal to build your code.

Extra: when running webpack with the production config , all your resulting code will be automatically minified, wow! Read the documentation here.

ESLint

Ok, I know you are one of the best coders out there but some linting won’t damage anybody… let’s use ESLint to get our code checked. As always, install the needed dependencies:

npm add eslint eslint-webpack-plugin --save-dev

ESLint, with the eslint-webpack-plugin, needs a new webpack/plugins.js file to keep everyting organized. Create that file (and again, adapt the paths to your needs):

const path = require('path');
const _ESLintPlugin = require('eslint-webpack-plugin');
const ESLintPlugin = new _ESLintPlugin({
overrideConfigFile: path.resolve(__dirname, '.eslintrc'),
context: path.resolve(__dirname, '../src/js'),
files: '**/*.js',
});
module.exports = {
ESLintPlugin: ESLintPlugin,
};

The .eslintrc file (place it in the webpack folder) will tell the linter which rules apply to our code. This is an example, but feel free to check what others are using (such as airbnb packages, here):

{
"extends": "eslint:recommended",
"env": {
"browser": true,
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"rules": {
"no-console": "off",
"strict": ["error", "global"],
"curly": "warn"
}
}

The last step here is to add the recently created plugin. Edit your webpack/webpack.common.js file and append it:

//...
const plugins = require('./plugins');
//...
module.exports = {
...

plugins: [
new webpack.ProgressPlugin(),
plugins.ESLintPlugin,
],
...
};

We are done. You can test it by adding var a; inside the app.js file and then run npm run build. The result should be something like this:

/[…]/js/app.js
1:5 error 'a' is defined but never used no-unused-vars
✖ 1 problem (1 error, 0 warnings)

PostCSS

So, the JavaScript basic part is done, let’s start with the CSS part. This is something tricky with Webpack because our entry point is a JavaScript file (src/js/app.js).

As always, the first step is to install the needed dependencies:

npm add css-loader cssnano file-loader mini-css-extract-plugin postcss postcss-import postcss-loader postcss-preset-env --save-dev

You must read what the heck are those libraries listed there. Hey: YOU MUST. Now, let’s create a new loader inside webpack/loaders.js:

const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
//...const CSSLoader = {
test: /\.css$/i,
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: path.resolve(__dirname, '../dist/css/')
}
},
{
loader: 'css-loader',
options: {importLoaders: 1},
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: path.resolve(__dirname, 'postcss.config.js'),
},
},
},
],
};
//...module.exports = {
CSSLoader: CSSLoader,
JSLoader: JSLoader,
ESLintLoader: ESLintLoader,
};

Wait a second, what happened here? Read carefully that configuration and check two things: we need a adapt the webpack/plugins.js file and a new webpack/postcss.config.jsconfiguration file. The first one will contain that MiniCssExtractPlugin tricky stuff for the CSS extraction. This is what your webpack/plugins.js should contain:

const path = require('path');
const _MiniCssExtractPlugin = require('mini-css-extract-plugin');
const _ESLintPlugin = require('eslint-webpack-plugin');
const MiniCssExtractPlugin = new _MiniCssExtractPlugin({
filename: '[name].bundle.css',
chunkFilename: '[id].css'
});
//...module.exports = {
MiniCssExtractPlugin: MiniCssExtractPlugin,
ESLintPlugin: ESLintPlugin,
};

That was easy. Now, create a webpack/postcss.config.js file:

module.exports = {
plugins: {
'postcss-import': {},
'postcss-preset-env': {
browsers: 'last 2 versions',
stage: 0,
},
'cssnano': {},
},
};

This will allow us lots of things in CSS, but it’s up to you to read all of them. Examples: nesting, using the var keyword, importing files, etc. Want to check it out? Edit your webpack/webpack.common.js file:

const path = require('path');
const loaders = require('./loaders');
const plugins = require('./plugins');
module.exports = {
...
module: {
rules: [
loaders.CSSLoader,
]
},
plugins: [
new webpack.ProgressPlugin(),
plugins.ESLintPlugin,
plugins.MiniCssExtractPlugin,
],
};

It’s time to create a bit of CSS. First of all, create a file src/css/app.css:

@import "settings/colors.css";
@import "components/dummy.css";

Then create the src/css/settings/colors.cssfile:

:root {
--color-black: rgb(0, 0, 0);
}

And also the src/css/components/dummy.cssfile:

.dummy {
color: var(--color-black);
}

But… where should we put our enty app.css file? Here comes the last tricky part. Edit your src/js/app.js file and add the following line:

import '../css/app.css';

Now, just run npm run build and you should have a brilliant app.bundle.css file in your dist folder, awesome!

Stylelint

Last (but not least) main block, let’s lint our CSS code. We are going to use Stylelint which is more or less the same as ESLint but for CSS. First of all, the dependencies:

npm add stylelint stylelint-webpack-plugin --save-dev

Now, edit the webpack/plugins.js file as follows:

const path = require('path');
const _MiniCssExtractPlugin = require('mini-css-extract-plugin');
const _StyleLintPlugin = require('stylelint-webpack-plugin');
const _ESLintPlugin = require('eslint-webpack-plugin');
const MiniCssExtractPlugin = new _MiniCssExtractPlugin({
filename: '[name].bundle.css',
chunkFilename: '[id].css'
});
const ESLintPlugin = new _ESLintPlugin({
overrideConfigFile: path.resolve(__dirname, '.eslintrc'),
context: path.resolve(__dirname, '../src/js'),
files: '**/*.js',
});
const StyleLintPlugin = new _StyleLintPlugin({
configFile: path.resolve(__dirname, 'stylelint.config.js'),
context: path.resolve(__dirname, '../src/css'),
files: '**/*.css',
});
module.exports = {
MiniCssExtractPlugin: MiniCssExtractPlugin,
StyleLintPlugin: StyleLintPlugin,
ESLintPlugin: ESLintPlugin,
};

And, of course, add the plugin in the webpack/webpack.common.js file:

module.exports = {
...
plugins: [
new webpack.ProgressPlugin(),
plugins.ESLintPlugin,
plugins.StyleLintPlugin,
plugins.MiniCssExtractPlugin,
],
};

One last thing is missing: if you read the plugin configuration, we must create a webpack/stylelint.config.js file. Here you have one example configuration:

module.exports = {
rules: {
"at-rule-no-unknown": true,
"block-no-empty": true,
"color-no-invalid-hex": true,
"comment-no-empty": true,
"declaration-block-no-duplicate-properties": [
true,
{
ignore: ["consecutive-duplicates-with-different-values"]
}
],
"declaration-block-no-shorthand-property-overrides": true,
"font-family-no-duplicate-names": true,
"font-family-no-missing-generic-family-keyword": true,
"function-calc-no-unspaced-operator": true,
"function-linear-gradient-no-nonstandard-direction": true,
"keyframe-declaration-no-important": true,
"media-feature-name-no-unknown": true,
"no-descending-specificity": true,
"no-duplicate-at-import-rules": true,
"no-duplicate-selectors": true,
"no-empty-source": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"property-no-unknown": true,
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-element-no-unknown": true,
"selector-type-no-unknown": true,
"string-no-newline": true,
"unit-no-unknown": true
}
};

Do you want to check it out? Edit your src/css/components/dummy.css file with something like this:

.dummy {
color: var(--color-black);
font-size: 12jon;
}

Now, when you run npm run build, you should read something like this:

ERROR in 
src/css/components/dummy.css
3:14 ✖ Unexpected unknown unit “jon” unit-no-unknown

Everything is working as expected. So, now what?

Extra: FileLoader and Clean Webpack Plugin

Two amazing tools super easy to use and very powerful. First, let’s install FileLoader:

npm add file-loader --save-dev

Now, let’s edit the webpack/loaders.js file:

//...const FileLoader = {
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
outputPath: 'images',
publicPath: path.resolve(__dirname, 'dist/')
},
},
],
};
module.exports = {
CSSLoader: CSSLoader,
JSLoader: JSLoader,
FileLoader: FileLoader,
};

And, of course, add the new loader to the webpack.common.js configuration file:

//...module: {
rules: [
loaders.CSSLoader,
loaders.JSLoader,
loaders.FileLoader,
]
},
//...

That’s all you need to do. Read the documentation for further instructions.

Another super useful tool is the Clean Plugin for Webpack. This tool will automatically clean your build directory. Let’s install it:

npm add clean-webpack-plugin --save-dev

And now, we just have to add it as a plugin, and it will automatically read our common configuration for its values. Edit the webpack/plugins.js file:

//...const { CleanWebpackPlugin } = require('clean-webpack-plugin');//...


module.exports = {
CleanWebpackPlugin: new CleanWebpackPlugin(),
MiniCssExtractPlugin: MiniCssExtractPlugin,
StyleLintPlugin: StyleLintPlugin,
ESLintPlugin: ESLintPlugin,
};

And now just add it in the webpack.common.js file:

plugins: [
new webpack.ProgressPlugin(),
plugins.CleanWebpackPlugin,
plugins.ESLintPlugin,
plugins.StyleLintPlugin,
plugins.MiniCssExtractPlugin,
],

It seems that we’ve created an awesome boilerplate, haven’t we?

So… anything else?

Here you have some usefull topics to read about after configuring your Webpack as above (same as in the older story).

  • ITCSS: structuring your CSS files (link)
  • Redux: state container in JS (link)
  • InfernoJS: react-like library extremely fast (link)
  • Service workers: read the doc (link)
  • DDD: Domain Driven Design (link)
  • Deno (link)

Well, 6 topics for your homework… sorry about that.

I hope you enjoyed this story, see you soon!

--

--

Jon Torrado

IT Manager at Demium. Former CTO of different companies and startups. Father of one. Health learning lover.