Working with Webpack 3, ES6, PostCSS (and more!)

Jon Torrado
6 min readJan 6, 2018

> Read the updated story for Webpack 4 here!

So, in previous stories, I already talked about how to work with Gulp in order to have your Sass files transformed and linted, and the same goes for the JavaScript code. This time, I’m preparing a Symfony 4 standard project with all the tools I usually need to work (some of them also in my stories, such as deployment) but I’m also going to use InfernoJS, so I decided to include some tools like these ones:

I’ve already played with all of them, but it’s very difficult to configure everything once again to make them work together without problems. These are the steps I followed (the full Symfony 4 standard project I’m creating it’s not released yet).

Webpack

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

npm i -g webpack

Let’s start by installing the needed dependencies:

yarn add webpack@webpack-3 --dev

That was easy, wasn’t it? Check the package.json file and keep it open, we will be coming back soon.

Now, we are going to create the webpack.config.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:

yarn add babel-loader babel-core babel-preset-env --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.config.js file:

const path = require('path');
const loaders = require('./loaders');
module.exports = {
entry: ["./src/js/app.js"],
module: {
loaders: [
loaders.JSLoader
]
},
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/[name].bundle.js"
},
};

As you can read above, I separated all the webpack loaders in a loaders.js file imported at the top. Let’s create that webpack/loaders.js file:

const JSLoader = {
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['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 -p --config ./webpack/webpack.config.js --display-error-details",
"watch": "webpack -d --watch --config ./webpack/webpack.config.js --display-error-details"
}
}

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 -p production flag , 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:

yarn add eslint eslint-loader --dev

Now, we just have to add the loader to our webpack/loaders.js file like this:

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

const ESLintLoader = {
test: /\.js$/,
enforce: 'pre',
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
configFile: __dirname + '/.eslintrc'
},
}
};

module.exports = {
JSLoader: JSLoader,
ESLintLoader: ESLintLoader,
};

The .eslintrc file 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 loader. Edit your webpack/webpack.config.js file and append it:

module.exports = {
...
module: {
loaders: [
loaders.JSLoader,
loaders.ESLintLoader
]
},
...
};

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
3: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:

yarn add extract-text-webpack-plugin css-loader file-loader postcss postcss-loader postcss-cssnext postcss-import --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 plugins = require('./plugins');//...const CSSLoader = {
test: /\.css$/,
use: plugins.ExtractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {importLoaders: 1},
},
{
loader: 'postcss-loader',
options: {
config: {
path: __dirname + '/postcss.config.js'
}
},
},
],
}),
};

module.exports = {
JSLoader: JSLoader,
ESLintLoader: ESLintLoader,
CSSLoader: CSSLoader
};

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

const _ExtractTextPlugin = require('extract-text-webpack-plugin');

const ExtractTextPlugin = new _ExtractTextPlugin('[name].bundle.css');

module.exports = {
ExtractTextPlugin: ExtractTextPlugin
};

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

module.exports = {
plugins: {
'postcss-import': {},
'postcss-cssnext': {
browsers: ['last 2 versions', '> 5%'],
},
},
};

This will allow us lot’s 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.config.js file:

const path = require('path');
const loaders = require('./loaders');
const plugins = require('./plugins');


module.exports = {
...
module: {
loaders: [
loaders.CSSLoader,
loaders.JSLoader,
loaders.ESLintLoader,
]
},
plugins: [
plugins.ExtractTextPlugin,
],

};

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, let’s lint our CSS code. We are goint to use Stylelint which is more or less the same as ESLint. First of all, the dependencies:

yarn add stylelint stylelint-webpack-plugin --dev

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

const path = require('path');
const _ExtractTextPlugin = require('extract-text-webpack-plugin');
const _StyleLintPlugin = require('stylelint-webpack-plugin');

const ExtractTextPlugin = new _ExtractTextPlugin('[name].bundle.css');

const StyleLintPlugin = new _StyleLintPlugin({
configFile: path.resolve(__dirname, 'stylelint.config.js'),
context: path.resolve(__dirname, '../src/css'),
files: '**/*.css',
failOnError: false,
quiet: false,
});

module.exports = {
ExtractTextPlugin: ExtractTextPlugin,
StyleLintPlugin: StyleLintPlugin
};

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

module.exports = {
...
plugins: [
plugins.StyleLintPlugin,
plugins.ExtractTextPlugin,
],
};

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?

So… anything else?

Here you have some usefull topics to read about after configuring your webpack as above.

  • 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)

Well, 5 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.