Webpack 4: ma configuration pour compiler javascript, html, css, images et fonts

publié le 06/05/2018

webpack 4

Webpack est incontournable pour tout développeur frontend. Tous les frameworks JS (tels React ou Vue) l'utilisent d'ailleurs massivement. Si c'est un outil ultra-puissant pour compiler les assets, il est en revanche difficile de le configurer soi-même. La documentation est longue et le projet très actif évolue à une vitesse telle qu'il est très difficile d'avoir une connaissance parfaite de cette technologie. Dans cet article, je vous propose une configuration possible de webpack 4 pour compiler javascript, html, css, images et fonts.


Les concepts de base

Code source et point d'entrée

Le code source est tout simplement le code que vous évrivez (un code lisible, indenté, commenté pour faciliter le déboggage). Le point d'entrée est votre fichier javascript principal (souvent nommé par convention app.js ou index.js).


Output

C'est tout ce qui se trouve dans le dossier './dist' au terme du processus de compilation de webpack.


Règles de compilation

Selon le type de fichier, il est possible de configurer des règles particulières de compilation.


Loaders

Les loaders sont les transformations qui sont appliquées au code source (exemple: transformer un code es6 en es5 grâce au babel-loader).


Installation de Webpack

Commençons par créer et nous déplacer jusqu'à notre répertoire de travail:

mkdir my-project
cd my-project

Créons ensuite le répertoire qui contiendra notre code source et notre point d'entrée app.js.

mkdir src && touch src/index.js

J'utiliserai Yarn comme gestionnaire de paquets. Initialisons donc le package.json:

yarn init

Définissons ensuite notre dépendance à webpack et à webpack-cli (qui permet d'utiliser webpack en ligne de commandes).

yarn add --dev webpack webpack-cli

Mon package.json:

{
  "name": "webpack-tuto",
  "version": "1.0.0",
  "main": "src/index.js",
  "license": "MIT",
  "devDependencies": {
	"webpack": "^4.8.1",
	"webpack-cli": "^2.1.3"
  }
}

Webpack est installé. On peut d'ores et déjà l'utiliser pour tester. En ligne de commande:

webpack

La compilation de notre app.js a fonctionné. Webpack a produit un fichier de sortie dans le dossier dist sans que nous l'ayons configuré. Parfait! Vous devriez tout de même obtenir une erreur du genre:


WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

Ce message d'erreur nous donne 1 information importante. Le mode est une option obligatoire. C'est une nouveauté de la version 4. Webapck propose 2 modes par défaut: development et production. Le mode development est notamment plus rapide que le mode production. Il comporte également une option watch pour surveiller les changements dans le code source. Le mode production produit un code optimisé pour la mise en production (minification, ...).

Dès lors, on peut modifier notre script pour lancer webpack et renseigner le mode. Nous allons créer un alias de commande dans le package.json.

{
  "name": "webpack-tuto",
  "version": "1.0.0",
  "main": "src/index.js",
  "license": "MIT",
  "scripts": {
	"dev": "webpack --watch --mode='development'",
	"build": "webpack --mode='production'"
  },
  "devDependencies": {
	"webpack": "^4.8.1",
	"webpack-cli": "^2.1.3"
  }
}

Désormais, en lançant "yarn run dev", webpack se lance en mode development et utilise l'option watch pour surveiller les changements dans le code source. En lançant "yarn run build", app.js est compilé en mode production.


Compiler votre javascript

Quand on écrit son javascript en utilisant les dernières spécifications ES6, des outils tels que Babel sont incontournables afin de transformer son code ES6 (incompatible pour beaucoup de navigateurs) en bon vieux javascript version ES5. C'est ce que nous allons faire maintenant: installer Babel et le configurer pour que Webpack l'utilise quand il rencontre un fichier js

yarn add --dev babel-core babel-loader babel-preset-env

Ensuite, à la racine de votre projet, créer votre fichier de configuration .babelrc. Depuis le terminal, c'est simple:

echo '{ "presets": ["env"] }' > .babelrc

A ce stade, il parait compliqué de continuer sans fichier de configuration webpack. Nous allons le créer dès maintenant et y appliquer les règles de compilation pour notre javascript:

touch webpack.config.js
// webpack.config.js
module.exports = {
  module: {
	rules: [
	  {
		test: /\.js$/,
		exclude: /node_modules/,
		use: {
		  loader: "babel-loader"
		}
	  },
	]
  }
}; 

Webpack va automatiquement lire ce fichier quand il lancera le processus de compilation. Dans ce fichier de configuration, nous exportons un objet ayant une propriété 'module', elle-même étant un objet contenant une propriété 'rules'. Cette dernière propriété fait référence aux règles de compilation que je définissais en début d'article.

Le code parle de lui-même. Nous testons le format du fichier au moyen d'une expression régulière pour ne garder que les fichiers javascript, en prenant soin d'exclure le dossier node_modules qui contient les librairies tierces. Pour ce type de fichier nous utilisons le babel-loader qui transformera notre ES6 en ES5.

Nous pouvons tester notre config en ajoutant un peu de code es6 à notre point d'entrée.

// src/index.js
setTimeout(() => {
	console.log("je teste pour vérifier que ma arrow function aura disparu au terme du processus de compilation de webpack.");
	}, 500);
yarn run build

Puis ouvrez votre fichier dist/index.js. Si la fonction fléchée a disparu et a été remplacée par une fonction classique, vous pouvez passer à la suite. Votre code a été transformé en ES5.


Compiler votre scss ou sass

Même démarche que pour le javascript. On définit une nouvelle de règle de compilation et on ajoute des loaders. C'est tout de même un peu plus complexe que le javascript car le support scss/sass de webpack n'est pas natif. Il va donc falloir configurer un loader pour minimiser notre code dans un environnement de production, ce qui webpack faisait automatiquement pour le javascript.

Récapitulons nos objectifs pour nos fichiers scss

  • transformer notre scss ou sass en CSS
  • auto-préfixer automatiquement nos classes
  • minimiser notre code en mode production
  • extraire un fichier css bien distinct de notre point d'entrée js

Transformer notre scss ou sass en CSS

yarn add --dev sass-loader node-sass css-loader
// webpack.config.js
module.exports = {
  module: {
	rules: [
	  {
		// règles de compilation pour javascript
	  },
	  {
		test: /\.sc|ass$/,
		use: [
		{ loader: "css-loader" },          
		{ loader: "sass-loader" }
		]
	  },
	]
  }
}; 

Vous remarquerez que nous avons ici défini 2 loaders. Chose surprenante quand on débute sur webpack: le 1er loader utilisé par webpack est le dernier de la liste. Dans notre exemple, webpack commencera donc par sass-loader et passera le résultat de ce loader à css-loader. C'est un peu contre-intuitif au départ mais on s'y habitue vite.

Nous pouvons désormais tester. Créons rapidement notre fichier app.scss et importons le depuis notre point d'entrée index.js afin qu'il fasse partie du processus de compilation de webpack.

// src/app.scss

$font-stack: Helvetica, sans-serif;
$primary-color: blue;

body {
  font: 100% $font-stack;
  color: $primary-color;
}
// src/index.js
import css from './app.scss';

setTimeout(() => {
	console.log("je teste pour vérifier que ma arrow function aura disparu au terme du processus de compilation de webpack.");
	}, 500);

Pour tester depuis la ligne de commande, tapez:

yarn run build

La compilation devrait fonctionner mais vous vous demandez peut-être: où est mon fichier css qui en résulte ? Effectivement, si on se rend dans le dossier dist, on remarque que webpack n'a pas créé un fichier css séparé mais a intégré le code css dans le bundle dist/main.js. Je préfère personnellement avoir un fichier css bien distinct. Nous allons faire ça dès maintenant.


Extraire un fichier CSS

Avant la version 4 de webpack, il était courant d'utiliser extract-text-webpack-plugin pour effectuer cette action. Mais il semble qu'il y ait des problèmes d'incompatibilité avec la nouvelle version de webpack. Ce plugin est apparemment devenu dur à maintenir et à faire évoluer. Une autre solution consiste à utiliser mini-css-extract-plugin.

yarn add --dev mini-css-extract-plugin
// webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  module: {
	rules: [
	  {
		// règles de compilation pour javascript
	  },
	  {
		test: /\.sc|ass$/,
		use: [
		{ loader: MiniCssExtractPlugin.loader },
		{ loader: "css-loader" },          
		{ loader: "sass-loader" }
		]
	  },
	]
  },
  plugins: [
	new MiniCssExtractPlugin({
	  filename: "[name].css",
	  chunkFilename: "[id].css"
	}),
  ]
}; 

Auto-préfixer les règles CSS et minifier le fichier grâce à PostCSS

PostCSS est un outil puissant pour transformer votre CSS. Je vous encourage vivement à aller jeter un oeil à la doc car ce qu'il permet va au-delà de ce que nous allons mettre en place sur cet article. Par exemple, je n'installerai pas cssnext (qui est un peu au CSS ce que babel est au javascript, c'est à dire un outil qui permet de rédiger du CSS nouvelle génération sans se préocupper du support des navigateurs).

Commençons par installer postcss et autoprefixer.

yarn add --dev autoprefixer postcss-loader

Il faut ensuite configurer PostCSS. A la racine du projet, créer un fichier postcss.config.js:

// postcss.config.js
module.exports = ({ file, options, env }) => ({
	plugins: {
		'autoprefixer': env === 'production' ? options.autoprefixer : false,
		'cssnano': env === 'production' ? options.cssnano : false
	}
})

La valeur de l'argument env correspond à celle de NODE_ENV. Malheureusement, il y a comme un problème ici. Lorsque nous définissons le mode sur webpack, webpack définit en retour la valeur de NODE_ENV (cf. documentation). Cependant, j'ai observé que cette valeur ne passe pas par PostCSS et retourne undefined, ce qui nous pose un problème pour l'utilisations de nos plugins. La solution que j'ai adopté consiste à définir explicitement la valeur de NODE_ENV dès le départ et définir le mode de webpack dans sa config. C'est ce que nous allons faire dès maintenant.

Pour commencer, modifions les scripts sur notre package.json pour passer explicitement la valeur de NODE_ENV afin qu'elle soit récupérée par la config de postcss.

 {
  "name": "webpack-tuto",
  "version": "1.0.0",
  "main": "src/index.js",
  "license": "MIT",
  "scripts": {
	"dev": "webpack --watch",
	"build": "NODE_ENV=production webpack"
  },
  "devDependencies": {
	"autoprefixer": "^8.4.1",
	"babel-core": "^6.26.3",
	"babel-loader": "^7.1.4",
	"babel-preset-env": "^1.6.1",
	"css-loader": "^0.28.11",
	"mini-css-extract-plugin": "^0.4.0",
	"node-sass": "^4.9.0",
	"postcss-loader": "^2.1.5",
	"sass-loader": "^7.0.1",
	"webpack": "^4.8.1",
	"webpack-cli": "^2.1.3"
  },
  "dependencies": {},
  "browserslist": [
	">1%", 
	"not ie 11",
	"not op_mini"
	]
}

Remarquez que nous avons défini une propriété browserlist dans notre package.json. Sa valeur est comprise par postcss et lui permet de déterminer la liste des navigateurs avec lesquels vous voulez que votre application soit compatible. Dans notre cas, c'est particulièrement utile pour autoprefixer. Si vous voulez comprendre ici le choix que je fais, je vous invite à lire cette issue sur github.

La suite consiste à déterminer sur la config de webpack le mode en fonction de la valeur de NODE_ENV et à définir l'usage de postcss-loader

// webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

// on récupère la valeur de NODE_ENV
const env = process.env.NODE_ENV;

module.exports = {
  mode: env || 'development', // on définit le mode en fonction de la valeur de NODE_ENV
  module: {
	rules: [
	  {
		test: /\.js$/,
		exclude: /node_modules/,
		use: {
		  loader: "babel-loader"
		}
	  },
	  {
		test: /\.sc|ass$/,
		use: [
		{ loader: MiniCssExtractPlugin.loader },
		{ 
			loader: "css-loader",
			options: {
				importLoaders: 1
			}
		},          
		{
		  loader: 'postcss-loader',
		  options: {
			sourceMap: true,
			config: {
			  ctx: {
				cssnano: {},
				autoprefixer: {}
			  }
			}
		  }
		},
		{ 
			loader: "sass-loader",
			options: {
				sourceMap: true // il est indispensable d'activer les sourcemaps pour que postcss fonctionne correctement
			}
		}
		]
	  },
	]
  },
  plugins: [
	new MiniCssExtractPlugin({
	  filename: "[name].css",
	  chunkFilename: "[id].css"
	}),
  ]
}; 

Ne reste plus qu'à tester si le css est auto-préfixé et minifié en mode production.

// src/app.scss

$font-stack: Helvetica, sans-serif;
$primary-color: blue;

body {
  font: 100% $font-stack;
  color: $primary-color;
  &:fullscreen {
	background-color: pink; // propriété CSS peu supportée par les navigateurs qui devrait être auto-préfixée en mode production
  }
}
yarn run build

pour tester en mode production

yarn run dev

pour tester en mode development que tout fonctionne correctement.

Il n'est pas rare de faire référence à des fonts ou à des images dans nos feuilles de style. Avec la configuration dont nous disposons, nous ne pouvons pas le faire pour l'instant car webpack ne saura pas compiler ces formats de fichier quand il les rencontrera car aucune règle de compilation n'est définie pour les images et les fonts. C'est ce que nous allons faire maintenant.


Gestion des fonts

Nous allons utiliser file-loader pour les fonts.

yarn add --dev file-loader resolve-url-loader
// webpack.config.js
// ...

module.exports = {
  // ...
  module: {
	rules: [
	  {
		// règles de compilation javascript
	  },
	  {
		test: /\.sc|ass$/,
		use: [
		{ loader: MiniCssExtractPlugin.loader },
		{ 
			loader: "css-loader",
			options: {
				importLoaders: 1
			}
		},          
		{
		  loader: 'postcss-loader',
		  options: {
			sourceMap: true,
			config: {
			  ctx: {
				cssnano: {},
				autoprefixer: {}
			  }
			}
		  }
		},
		{
			loader: 'resolve-url-loader' // améliore la résolution des chemins relatifs 
			// (utile par exemple quand une librairie tierce fait référence à des images ou des fonts situés dans son propre dossier)
		},
		{ 
			loader: "sass-loader",
			options: {
				sourceMap: true // il est indispensable d'activer les sourcemaps pour que postcss fonctionne correctement
			}
		}
		]
	  },
	  {
		test: /\.(eot|ttf|woff|woff2)$/,
		loader: 'file-loader',
		options: {
		  name: 'fonts/[name].[hash].[ext]'
		}
	  },
	]
  },
}; 

On définit une nouvelle règle de compilation pour les fonts et on ajoute par la même occasion le loader resolve-url-loader que l'on intercale entre sass-loader et postcss-loader. C'est une petite astuce pour améliorer la résolution des chemins relatifs (notamment quand les assets viennent de librairies externes).

Vous pouvez tester en téléchargeant une police de caractères et en y faisant référence dans votre fichier app.scss. Cela pourrait donner:

// src/app.scss
@font-face {
  font-family: "Roboto";
  src: url("./fonts/Roboto-Regular.ttf") format("ttf"); // adapter l'url en fonction du nom de votre fichier
}

$font-stack: 'Roboto', sans-serif;
$primary-color: #333;

body {
	font: 100% $font-stack;
	color: $primary-color;
	&:fullscreen {
		background-color: pink; // propriété CSS peu supportée par les navigateurs qui devrait être auto-préfixée en mode production
	}
}
yarn run build

pour tester et pour ma part, aucun message d'erreur. Vous remarquerez la présence de votre police de caractères dans le dossier dist/fonts. Un succès!


La gestion des images

La compilation des images ressemble un peu à celle des fonts, dans le sens où on va utiliser file-loader. C'est un plugin déjà installé, donc il ne reste plus qu'à modifier notre configuration webpack.

On va tout de même aller un peu plus loin pour les images en optimisant la taille de celles-ci. Je vous propose d'installer image-webpack-loader.

Loader très pratique, quand on sait à quel point la performance d'un site est impactée par la taille des images. Il est installé avec un certain nombre d'optimiseurs:

  • mozjpeg (compression jpeg)
  • optipng (compression png)
  • pngquant (compression png)
  • svgo (compression svg)
  • gifsicle (compression gif)
yarn add --dev image-webpack-loader imagemin
// webpack.config.js
// ...

module.exports = {
  // ...
  module: {
	rules: [
	  { /* règles de compilation javascript */ },
	  { /* règles de compilation sass/scss */ },
	  { /* règles de compilation fonts */ },
	  {
	   test: /\.(gif|png|jpe?g|svg)$/i,
	   use: [
		'file-loader',
		{ loader: 'image-webpack-loader' },
	   ],
	  },
	]}
}; 

Vider le répertoire dist avant chaque compilation

Histoire d'avoir avoir un dossier dist complètement clean après chaque build, installons clean-webpack-plugin et configurons-le rapidement.

yarn add --dev clean-webpack-plugin
// webpack.config.js
const CleanWebpackPlugin = require('clean-webpack-plugin');

// ...

module.exports = {
  // ...
  module: {
	rules: [
	  { /* règles de compilation javascript */ },
	  { /* règles de compilation sass/scss */ },
	  { /* règles de compilation fonts */ },
	  { /* règles de compilation images */ },
	]},
  plugins: [
	// MiniCssExtractPlugin
    new CleanWebpackPlugin('dist/*.*', {} ), // supprime tous les fichiers du répertoire dist sans pour autant supprimer ce dossier
  ]
}; 

Compilation du HTML et implémentation d'un cache

La compilation du HTML peut être intéressante pour vous dans le cas où les fichiers générés sont hashés, c'est à dire que leur nom comporte une partie qui est un identifiant unique. Au lieu de générer à chaque fois un fichier nommé app.js, on ajoute un hash pour générer un fichier du genre app.656435.js. A la modification suivante, le nom du fichier change complètement (par exemple, app.2662322.js). Comme le nom du fichier change, le navigateur charge automatiquement le fichier et (et du coup les dernières modifications effectuées) alors que si le nom ne changeait pas, il y a de grandes chances pour que le navaigateur serve à l'internaute l'ancien fichier qui aura été mis en cache.

Cette façon de hasher le nom des fichiers est une manière d'implémenter un cache pour le navigateur. Nous allons commencer simplement par inclure le HTML dans le processus de compilation de webpack en installant html-webpack-plugin.


Installation de html-webpack-plugin

yarn add --dev html-webpack-plugin
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

// ...

module.exports = {
  // ...
  module: {
	rules: [
	  { /* règles de compilation javascript */ },
	  { /* règles de compilation sass/scss */ },
	  { /* règles de compilation fonts */ },
	  { /* règles de compilation images */ },
	]},
  plugins: [
	// MiniCssExtractPlugin
    // CleanWebpackPlugin
     new HtmlWebpackPlugin({
      inject: false,
      hash: true,
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}; 

Créons le fichier HTML dans notre répertoire src

<!-- src/index.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Conf webpack</title>
    <link rel="stylesheet" href="main.css">
</head>
<body>

<script src="main.js"></script>
</body>
</html>

Implémenter le cache

yarn add --dev path
// webpack.config.js
// ...
const path = require('path');

module.exports = {
  // ...
  output: {
    filename: '[name].[chunkhash].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
	rules: [
	  { /* règles de compilation javascript */ },
	  { /* règles de compilation sass/scss */ },
	  { /* règles de compilation fonts */ },
	  { /* règles de compilation images */ },
	]},
  plugins: [
    new MiniCssExtractPlugin({
      filename: "main.[contenthash].css",
      chunkFilename: "[id].css"
    }),
    // CleanWebpackPlugin
     new HtmlWebpackPlugin({
      inject: false,
      hash: true,
      template: './src/index.html',
      filename: 'index.html'
    })
  ]
}; 

Modifions notre index.html pour inclure correctement notre javascript et le CSS.

<!-- src/index.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Conf webpack</title>
    <link rel="stylesheet" href="<%=htmlWebpackPlugin.files.chunks.main.css %>">
</head>
<body>

<script src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>
</body>
</html>

Conclusion

Gardez en mémoire que webpack est un projet très actif et que les plugins s'y rapportant étant open-source, les contributeurs n'ont quelquefois pas le temps de soutenir un tel rythme de développement et de maintenance. Ce qui peut expliquer le fait que ce qui fonctionne avec certains loaders dans une version donnée peut ne pas fonctionner dans une autre version du même loader. C'est pourquoi je vais vous mettre en bas de page l'intégralité de mes fichiers, dont le package.json qui liste mes dépendances.

Se familiariser avec Webpack est long. Prenez le temps, soyez patients et procédez par étape. Inutile de se faire un process de compilation parfait dès le départ.

Je tiens à souligner que la plupart des frameworks proposent une intégration simple de webpack. C'est le cas de vuejs, react, ou encore symfony, framework backend avec lequel je développe régulièrement, qui a créé récemment un plugin nommé Encore. C'est le genre d'extension parfait pour commencer à l'utiliser.

L'avantage de faire sa config soi-même réside dans le fait de pouvoir utiliser webpack dans n'importe quelle situation, même dans un projet dont vous héritez datant d'une époque (pas si lointaine) où de tels outils n'existaient pas. Et puis, sans la maîtrise la puissance n'est rien comme dirait l'autre ;-)

Si vous avez des questions, des remarques, repéré des erreurs, alors surtout n'hésitez pas à m'envoyer un message.


package.json

{
  "name": "webpack-tuto",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "webpack --watch",
    "build": "NODE_ENV=production webpack"
  },
  "devDependencies": {
    "autoprefixer": "^8.4.1",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.6.1",
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^0.28.11",
    "file-loader": "^1.1.11",
    "html-webpack-plugin": "^3.2.0",
    "image-webpack-loader": "^4.2.0",
    "imagemin": "^5.3.1",
    "mini-css-extract-plugin": "^0.4.0",
    "node-sass": "^4.9.0",
    "path": "^0.12.7",
    "postcss-loader": "^2.1.5",
    "resolve-url-loader": "^2.3.0",
    "sass-loader": "^7.0.1",
    "webpack": "^4.8.1",
    "webpack-cli": "^2.1.3"
  },
  "dependencies": {
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "IE 10"
  ]
}

webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

// on récupère la valeur de NODE_ENV
const env = process.env.NODE_ENV;

module.exports = {
  mode: env || 'development', // on définit le mode en fonction de la valeur de NODE_ENV
	output: {
    filename: '[name].[chunkhash].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.sc|ass$/,
        use: [
          { loader: MiniCssExtractPlugin.loader },
          { 
            loader: "css-loader",
            options: {
              importLoaders: 1
            }
          },          
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true,
              config: {
                ctx: {
                  cssnano: {},
                  autoprefixer: {}
                }
              }
            }
          },
          {
            loader: 'resolve-url-loader' // améliore la résolution des chemins relatifs 
            // (utile par exemple quand une librairie tierce fait référence à des images ou des fonts situés dans son propre dossier)
          },
          { 
            loader: "sass-loader",
            options: {
              sourceMap: true // il est indispensable d'activer les sourcemaps pour que postcss fonctionne correctement
            }
          }
        ]
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        loader: 'file-loader',
        options: {
          name: 'fonts/[name].[hash].[ext]'
        }
      },
      {
        test: /\.(gif|png|jpe?g|svg)$/i,
        use: [
          'file-loader',
          { loader: 'image-webpack-loader' },
        ],
      },
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "main.[contenthash].css",
      chunkFilename: "[id].css"
    }),
    new CleanWebpackPlugin('dist/*.*', {} ),
    new HtmlWebpackPlugin({
      inject: false,
      hash: true,
      template: './src/index.html',
      filename: 'index.html'
    })

  ]
};

.babelrc

{ "presets": ["env"] }

postcss.config.js

module.exports = ({ file, options, env }) => ({
  plugins: {
    'autoprefixer': env === 'production' ? options.autoprefixer : false,
    'cssnano': env === 'production' ? options.cssnano : false
  }
})