Introduction

Par habitude, j’utilise Gulp. Je le trouve assez simple à configurer et il convient parfaitement pour les tâches que je souhaite exécuter. Cependant, webpack est de plus en plus souvent intégré avec d’autres technologies. Il me semblait donc important d’apprendre à le configurer et à l’utiliser. Ainsi, je pourrais étendre les configurations de base si besoin.

webpack
Profitez, il y a peu d’images dans cet article. 🙂

Je me suis donc penché dans la documentation et j’ai décide de remplacer la plupart de mes tâches Gulp par webpack. Au travers de cet article, je vais vous expliquer comment je m’y suis pris.

La base de notre configuration

webpack fournit une commande (npx webpack-cli init) pour générer automatiquement le minimum pour le faire fonctionner. Cependant, je souhaitais comprendre son fonctionnement pour être plus à l’aise avec sa configuration. Je vais donc reprendre toutes les étapes pour configurer webpack sans utiliser cette commande.

Nous avons besoin de quatre fichiers :

  • un .html pour observer le bon fonctionnement,
  • un .js qui servira de point d’entrée,
  • un package.json,
  • et un fichier de configuration webpack.

Je ne vais pas forcément suivre cet ordre là, ça nous évitera des allers-retours entre les fichiers.

Le fichier package.json

Pour commencer, créons un répertoire pour tester webpack et créons notre fichier package.json :

mkdir testons-webpack
cd testons-webpack
npm init -y

Pour utiliser webpack, il nous faut deux dépendances et je vais en rajouter une troisième qui servira pour notre fichier HTML :

npm i -D webpack webpack-cli html-webpack-plugin

Nous allons éditer notre fichier pour y ajouter un script qui servira à générer nos fichiers :

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },

Le point d’entrée

Ensuite, nous créons notre point d’entrée. Il s’agit d’un simple fichier JavaScript avec seulement un console.log pour le moment :

mkdir src
echo "console.log('Hello webpack!');" > src/app.js

Par défaut, webpack va chercher un fichier src/index.js. Pour des questions d’apprentissage, je me suis dit qu’il était plus intéressant de donner un autre autre nom à ce fichier.

Le template HTML (optionnel)

Nous continuons avec notre template HTML bien que cette étape ne soit pas obligatoire. Le paquet html-webpack-plugin que nous avons installé en fournit un par défaut et il est possible de lui passer des options. Cependant, dans certains cas, il peut être intéressant d’utiliser notre propre template. Je vais donc choisir ce cas de figure.

Puisqu’il s’agit d’un template, je choisis un format de template plutôt que .html. Nous allons donc créer un fichier au format EJS :

touch src/index.ejs

Dans ce fichier, j’insère :

<!DOCTYPE html>
<html 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.0">
    <title>
      <%= htmlWebpackPlugin.options.title %>
    </title>
  </head>
  <body>
  </body>
</html>

Le fichier de configuration webpack

Maintenant, il ne nous reste plus qu’à configurer webpack. Pour cela, nous allons créer un nouveau fichier à la racine :

touch webpack.config.js

Dans ce fichier, nous déclarons :

  • notre point d’entrée principal,
  • le nom du fichier de sortie et son chemin,
  • un plugin qui va générer un fichier HTML en passant en option un titre et notre template.

Ainsi, j’insère :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist")
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    })
  ]
};

Si vous avez préféré ne pas créer de template, vous pouvez supprimer la ligne template :

plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!"
    })
  ]

Testons la configuration

Dans votre shell, saisissez :

npm run build

Vous pouvez aller dans le dossier dist et vous y trouverez un fichier index.html. Si vous l’ouvrez dans votre navigateur et si vous ouvrez la console, vous devriez voir notre « Hello webpack! ».

Cependant, vous l’aurez sans doute remarqué, webpack nous affiche un WARNING lors de l’exécution du script :

npm run build
#output#
#output#> webpack@1.0.0 build
#output#> webpack
#output#
#output#asset index.html 283 bytes [compared for emit]
#output#asset build.js 30 bytes [compared for emit] [minimized] (name: main)
#output#./src/app.js 31 bytes [built] [code generated]
#output#
#output#WARNING in configuration
#output#The 'mode' option has not been set, webpack will fallback to 'production' for this value.
#output#Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
#output#You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
#output#
#output#webpack 5.36.2 compiled with 1 warning in 267 ms

Pour fonctionner correctement, webpack a besoin d’un mode. Ce dernier peut avoir la valeur de development, production ou none. Si nous n’en précisons pas, le mode par défaut est production; c’est pourquoi webpack génère quand même notre fichier et émet un simple WARNING.

Ce mode permet d’utiliser différentes configurations en fonction de notre environnement. Par exemple : en production, nous préférons que les fichiers soient minifiés et nous n’avons pas besoin de sourcemap ; en développement, c’est l’inverse. Donc, autant éviter que webpack exécute des fonctions inutiles pour l’environnement de production.

Nous prendrons ce point là en considération pour la suite. Pour le moment, notre configuration de base fonctionne. Donc, nous allons pouvoir passer à la suite et rentrer dans le détail de la configuration. Nous allons ajouter des loaders, définir une configuration différente suivant le mode et définir de nouveaux scripts.

Configurer webpack pour nos tâches

Nous allons voir comment configurer nos différentes tâches avec webpack. Le but est de pouvoir :

  • transpiler Javascript avec Babel,
  • convertir nos fichiers Sass en CSS,
  • optimiser nos images,
  • copier d’autres fichiers comme des polices par exemple,
  • recharger automatiquement nos pages après modification du code.

Je vais traiter chacune de ces tâches séparément afin d’améliorer la lisibilité. Je les combinerai à la fin de cet article. De cette manière, si vous ne voulez pas suivre l’ensemble de l’article, vous pouvez vous concentrer sur la tâche que vous souhaitez accomplir.

Pour commencer, nous allons déjà voir comment configurer le mode de webpack et nous allons ajouter une instructions pour que le dossier de sortie soit nettoyé à chaque build.

Utiliser un mode de build

Nous allons voir comment corriger le WARNING de webpack en définissant un mode. Pour cela, nous allons utiliser les variables d’environnement au travers de Node.

Commençons par modifier les scripts dans package.json :

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:prod": "webpack --node-env=production",
    "build:dev": "webpack --node-env=development"
  },

Nous avons maintenant deux scripts distincts pour compiler nos fichiers suivant le mode que l’on souhaite. Ainsi, nous pourrons facilement tester et voir la différence entre ces deux modes.

Maintenant, nous allons un peu modifier la structure de notre fichier webpack.config.js pour définir les configurations à appliquer pour chacun de ces modes :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    })
  ]
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
  } else {
    config.mode = "development";
  }
  return config;
};

Ici, nous avons ajouté une propriété clean dans output pour que webpack régénère le dossier dist à chaque build. Ainsi, si nous changeons un nom de fichier, par exemple, l’ancien sera supprimé. Nous avons également ajouté une variable pour détecter le mode et séparé la configuration pour pouvoir la modifier suivant ce mode.

Transpiler Javascript

L’objectif ici est de transpiler nos fichiers Javascript avec Babel. En mode development, nous générerons des sourcemaps et en mode production nous minifierons le fichier. Pour cela, nous allons avoir besoin de 3 nouvelles dépendances :

npm i -D babel-loader @babel/core @babel/preset-env

Dans webpack.config.js, nous allons ajouter une propriété module avec une règle, notre loader Babel et sa configuration :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    })
  ]
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
  } else {
    config.mode = "development";
    config.devtool = "inline-source-map";
  }
  return config;
};

Maintenant, pour tester si ça fonctionne, nous allons modifier notre fichier src/app.js et y insérer quelque chose à transpiler avec Babel :

console.log("Hello webpack!");

const functionTest = () => {
  return [1, "string", true];
}

const [a, b, c] = functionTest();

console.log(a);
console.log(b);
console.log(c);

Vous pouvez maintenant tester en exécutant npm run build:dev et npm run build:prod pour voir la différence. Vous pouvez également ouvrir la console de votre navigateur et vous verrez nos 3 console.log.

Reprenons les fichiers tels qu’ils étaient après avoir défini les modes et tentons, cette fois, de convertir nos fichiers Sass.

Utiliser Sass

L’objectif de cette tâche est compiler les fichiers .scss en .css en appliquant PostCSS et Autoprefixer. Il faut également générer les sourcemaps en development et minifier la feuille de style en production.

Installation des dépendances

Pour cela, nous aurons besoin de 9 nouvelles dépendances :

npm i -D style-loader css-loader sass-loader sass postcss-loader postcss autoprefixer mini-css-extract-plugin css-minimizer-webpack-plugin

Que font-elles ?

  • style-loader va charger les feuilles de styles dans le DOM,
  • css-loader nous autorise à charger les fichiers .css en utilisant import,
  • sass-loader fait la même chose mais pour les fichiers .sass et .scss,
  • sass est une distribution de Dart Sass pour Node,
  • postcss-loader permet de charger PostCSS dans webpack,
  • postcss permet d’utiliser PostCSS et ses extensions,
  • autoprefixer est une extension pour PostCSS,
  • mini-css-extract-plugin permet de générer une feuille de style plutôt que d’intégrer les styles dans le fichier JS,
  • css-minimizer-webpack-plugin permet de minifier notre feuille de style.

Configuration

Maintenant que nous les avons installés, il faut configurer webpack. Éditons le fichier webpack.config.js :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/i,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: "../",
            }
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: isProduction ? false : true,
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  "autoprefixer",
                ]
              },
              sourceMap: isProduction ? false : true,
            }
          },
          {
            loader: 'sass-loader',
            options: {
              implementation: require('sass'),
              sassOptions: {
                indentWidth: 2,
                outputStyle: 'expanded'
              },
              sourceMap: isProduction ? false : true,
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    }),
    new MiniCssExtractPlugin({
      filename: 'css/style.css',
      chunkFilename: "[id].css"
    })
  ]
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
    config.optimization = {
      minimize: true,
      minimizer: [ new CssMinimizerPlugin() ]
    };
  } else {
    config.mode = "development";
    config.devtool = "inline-source-map";
    config.optimization = {
      minimize: false,
    }
  }
  return config;
};

Ici, nous ajoutons une nouvelle propriété module à laquelle nous appliquons des règles. Ces dernières vont indiquer à webpack quels loaders charger en fonction du type de fichier. Il est possible de configurer chacun des loaders en ajoutant les sourcemaps ou en passant des options par exemple.

À noter : Les loaders de webpack sont chargés en partant de la fin vers le début (de la droite vers la gauche ou du bas vers le haut suivant si vous déclarez en ligne ou par ligne). L’ordre dans lequel vous les déclarez a donc son importance.

Pour qu’autoprefixer fonctionne, il faut également lui indiquer les navigateurs à supporter. Nous allons ajouter un propriété browserslist dans package.json :

  "browserslist": [
    "last 2 versions"
  ]

Test

Pour pouvoir tester si tout fonctionne, nous allons créer un fichier .scss :

mkdir src/scss
touch src/scss/style.scss

Dans ce fichier, nous allons simplement ajouter une couleur au body puisque notre fichier HTML ne contient rien d’autre :

$color-red: hsl(0, 68%, 45%);

body {
  background: $color-red;
}

Pour que webpack sache où trouver le fichier, nous devons l’importer dans notre fichier src/app.js :

import './scss/style.scss';

console.log('Hello webpack!');

Maintenant, exécutons la commande npm run build et vérifions dans notre navigateur que le style est bien appliqué.

Capture d'écran - Configurer webpack pour convertir SCSS en CSS

Si vous regardez dans ./dist, vous verrez un fichier style.css. Vous pouvez changer le mode dans votre fichier package.json pour voir la différence entre les deux modes (l’un est minifié, l’autre non). Ceci dit, avec si peu de styles dans notre fichier style.scss, la différence sera petite.

Reprenons les fichiers tels qu’ils étaient après avoir défini les modes et tentons, cette fois, d’optimiser nos images.

Optimiser les images

L’objectif ici est de pouvoir placer nos images dans notre dossier src et, en exécutant webpack, elles seront compressées et déplacées dans le fichier dist. Pour cela, nous commençons par installer une nouvelle dépendance :

npm i -D image-minimizer-webpack-plugin

Ce plugin utilise imagemin pour optimiser les images. Il faut donc installer des plugins pour imagemin. Ici, c’est à vous de voir ceux que vous voulez utiliser. Vous trouverez des recommandations sur le site de webpack. Voici ce que je vais installer pour l’article :

npm i -D imagemin-gifsicle imagemin-mozjpeg imagemin-optipng imagemin-svgo

Maintenant, il faut configurer webpack. Dans le fichier webpack.config.js :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist"),
    assetModuleFilename: 'img/[name][ext]',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: 'asset',
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    }),
    new ImageMinimizerPlugin({
      minimizerOptions: {
        plugins: [
          ['gifsicle', { interlaced: true }],
          ['mozjpeg', { progressive: true, quality: 75 }],
          ['optipng', { optimizationLevel: 5 }],
          [
            'svgo',
            {
              plugins: [
                { removeTitle: true },
                { removeViewBox: false },
                { removeXMLNS: false },
              ],
            },
          ],
        ],
      },
    })
  ]
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
  } else {
    config.mode = "development";
    config.devtool = "inline-source-map";
  }
  return config;
};

Ici, nous avons modifié la propriété output pour spécifier le chemin et le nom de sortie de nos images grâce à assetModuleFilename. Nous avons également ajouté une règle dans la propriété module pour cibler nos images. Enfin, nous avons ajouté un nouveau plugin et nous avons configuré les options à utiliser pour chaque extension d’imagemin.

Vous pouvez tester si ça fonctionne en plaçant des images dans src/img. Puis vous devez les importer dans src/app.js de cette manière :

import './img/image1.jpg';
import './img/image2.png';

console.log("Hello webpack!");

Maintenant, si vous exécutez npm run build:dev (ou prod), vos images vont être optimisées et être placées dans dist/img.

Reprenons les fichiers tels qu’ils étaient après avoir défini les modes et tentons, cette fois, de copier d’autres ressources comme des polices d’écriture.

Copier d’autres types de fichiers

L’objectif ici est de pouvoir copier automatiquement les fichiers que l’on place dans src, comme des polices (ce que nous allons utiliser pour l’exemple), dans le dossier dist. Ces fichiers ne subiront pas de traitement particulier. Ils seront simplement copier, ce qui permet de travailler uniquement dans le dossier src et de laisser webpack s’occuper du dossier dist. Pour cela, nous aurons besoin d’une nouvelle dépendance :

npm i -D copy-webpack-plugin

Maintenant, il faut modifier le fichier webpack.config.js pour ajouter ce nouveau plugin :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    }),
    new CopyPlugin({
      patterns: [
        { from: 'src/fonts/', to: 'fonts/'}
      ]
    })
  ]
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
  } else {
    config.mode = "development";
    config.devtool = "inline-source-map";
  }
  return config;
};

Ici, nous indiquons simplement le chemin relatif vers notre dossier comme source et le nom de notre dossier de destination. Ce dernier sera placé par webpack dans dist, donc nous n’avons pas besoin de l’indiquer dans le chemin de destination.

Il existe différentes façons de déclarer la source et la destination. Il est également possible d’utiliser quelques options suivant ce que vous souhaitez accomplir. Si vous souhaitez d’autres exemples, je vous conseille de lire la documentation du plugin.

Vous pouvez tester si tout fonctionne en exécutant :

npm run build:dev

Reprenons les fichiers tels qu’ils étaient après avoir défini les modes et tentons, cette fois, de configurer le serveur de développement avec rechargement automatique.

Rechargement automatique des pages

Afin de gagner du temps, j’ai l’habitude d’utiliser Browser Sync avec Gulp pour recharger les pages dans le navigateur automatiquement à chaque changement dans le code. Il est possible de faire la même chose avec webpack. Pour cela, nous allons installer une nouvelle dépendance :

npm i -D webpack-dev-server

Ensuite, nous allons modifier le fichier package.json pour y ajouter deux nouveaux scripts :

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:prod": "webpack --node-env=production",
    "build:dev": "webpack --node-env=development",
    "watch": "webpack --watch",
    "serve": "webpack serve --node-env=development"
  },

Les deux scripts n’ont pas exactement le même but :

  • watch permet de recompiler automatiquement à chaque changement dans le code. Il est utile si vous n’utilisez pas votre navigateur mais que vous souhaitez tout de même automatiser la compilation.
  • serve va démarrer un serveur de développement et peut, suivant la configuration, ouvrir automatiquement votre navigateur.

Passons à la configuration dans webpack.config.js :

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist"),
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    }),
  ],
  devServer: {
    host: 'localhost',
    open: true,
  }
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
  } else {
    config.mode = "development";
    config.devtool = "inline-source-map";
    config.target = 'web';
  }
  return config;
};

Maintenant, si vous exécutez npm run serve votre navigateur devrait s’ouvrir et si vous modifier vos fichiers, la page se rechargera automatiquement. Vous pouvez essayer en remplaçant console.log("Hello webpack!") par console.log("Hello world!") dans src/app.js par exemple.

webpack-dev-server possède de nombreuses options. Je vous invite à aller voir la documentation si vous avez besoin de personnaliser le serveur (changer de port, utiliser un serveur virtuel, etc.). Il est également possible de remplacer open: true par open: 'un navigateur' si vous en utilisez un autre pour le développement.

À noter : Le rechargement des pages possède, à l’heure où j’écris, un bug si nous définissons browserlist (comme nous l’avons fait pour Sass). Pour palier au problème, il faut utiliser target: 'web'. Ce bug devrait être corrigé avec la prochaine version ; il est donc possible que vous n’en n’ayez pas besoin.

Assemblage des plugins et modules

Maintenant que nous avons vu comment déclarer chacune de nos tâches avec webpack, il est temps de tout assembler.

Le package.json

{
  "name": "configurer-webpack",
  "version": "1.0.0",
  "description": "Configuration de webpack.",
  "keywords": [
    "webpack"
  ],
  "license": "MIT",
  "author": {
    "name": "Armand Philippot",
    "email": "contact@armandphilippot.com",
    "url": "https://www.armandphilippot.com/"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:prod": "webpack --node-env=production",
    "build:dev": "webpack --node-env=development",
    "watch": "webpack --watch",
    "serve": "webpack serve --node-env=development"
  },
  "devDependencies": {
    "@babel/core": "^7.14.0",
    "@babel/preset-env": "^7.14.1",
    "autoprefixer": "^10.2.5",
    "babel-loader": "^8.2.2",
    "copy-webpack-plugin": "^8.1.1",
    "css-loader": "^5.2.4",
    "css-minimizer-webpack-plugin": "^2.0.0",
    "html-webpack-plugin": "^5.3.1",
    "image-minimizer-webpack-plugin": "^2.2.0",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-mozjpeg": "^9.0.0",
    "imagemin-optipng": "^8.0.0",
    "imagemin-svgo": "^9.0.0",
    "mini-css-extract-plugin": "^1.6.0",
    "postcss": "^8.2.14",
    "postcss-loader": "^5.2.0",
    "sass": "^1.32.12",
    "sass-loader": "^11.0.1",
    "style-loader": "^2.0.0",
    "webpack": "^5.36.2",
    "webpack-cli": "^4.7.0",
    "webpack-dev-server": "^3.11.2"
  },
  "browserslist": [
    "last 2 versions"
  ]
}

Le fichier webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

const config = {
  entry: {
    main: "./src/app.js"
  },
  output: {
    filename: "build.js",
    path: path.resolve(__dirname, "dist"),
    assetModuleFilename: 'img/[name][ext]',
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      },
      {
        test: /\.(sa|sc|c)ss$/i,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: "../",
            }
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: isProduction ? false : true,
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  "autoprefixer",
                ]
              },
              sourceMap: isProduction ? false : true,
            }
          },
          {
            loader: 'sass-loader',
            options: {
              implementation: require('sass'),
              sassOptions: {
                indentWidth: 2,
                outputStyle: 'expanded'
              },
              sourceMap: isProduction ? false : true,
            }
          }
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: 'asset',
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Testons webpack!",
      template: path.resolve(__dirname, "src", "index.ejs")
    }),
    new MiniCssExtractPlugin({
      filename: 'css/style.css',
      chunkFilename: "[id].css"
    }),
    new ImageMinimizerPlugin({
      minimizerOptions: {
        plugins: [
          ['gifsicle', { interlaced: true }],
          ['mozjpeg', { progressive: true, quality: 75 }],
          ['optipng', { optimizationLevel: 5 }],
          [
            'svgo',
            {
              plugins: [
                { removeTitle: true },
                { removeViewBox: false },
                { removeXMLNS: false },
              ],
            },
          ],
        ],
      },
    }),
    new CopyPlugin({
      patterns: [
        { from: path.resolve(__dirname, 'src', 'fonts'), to: 'fonts' }
      ]
    })
  ],
  devServer: {
    host: 'localhost',
    open: true,
  }
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
    config.optimization = {
      minimize: true,
      minimizer: [ new CssMinimizerPlugin() ]
    };
  } else {
    config.mode = "development";
    config.devtool = "inline-source-map";
    config.optimization = {
      minimize: false,
    },
    config.target = 'web';
  }
  return config;
};

Le fichier app.js

import './scss/style.scss';
import './img/image1.jpg';
import './img/image2.png';

console.log("Hello webpack!");

const functionTest = () => {
  return [1, "string", true];
}

const [a, b, c] = functionTest();

console.log(a);
console.log(b);
console.log(c);

Améliorer cette configuration

Dans cet article, la configuration est assez sommaire. Voici quelques possibilités non abordées :

  • placer certaines configurations (browserlist par exemple) dans un fichier séparé,
  • utiliser certaines options dans les scripts npm plutôt que webpack.config.js,
  • séparer la configuration de webpack en plusieurs fichiers (un prod, un dev et un commun),
  • changer de loader suivant le mode.

De même l’optimisation des images peut sans doute être améliorée. J’ai l’impression que mon CPU a un peu de mal, et surtout c’est moins rapide qu’avec Gulp. Pourtant, j’utilise les mêmes plugins imagemin avec les mêmes réglages. Je n’ai pas cherché d’autres solutions pour le moment.

Vous l’aurez également peut-être remarqué en accédant à dist/index.html (sans npm serve donc), il y a une erreur dans la console :

TypeError: URL constructor: null is not a valid URL.

Je n’ai pas encore trouvé l’origine de ce problème…

Conclusion

La configuration de webpack n’est finalement pas si compliquée, contrairement à ce que je pensais en testant webpack Encore. La documentation est plutôt complète et claire. Hormis la page du plugin pour optimiser les images que j’ai trouvé un peu plus confus…

Pour continuer la comparaison avec Gulp, j’ai regardé rapidement les fichiers nécessaires. En me limitant à ces tâches, gulpfile.js et webpack.config.js contiennent à peu près le même nombre de lignes. Côté dépendances, il faut également installer à peu près le même nombre de dépendances. Par contre, je n’ai pas comparé le poids et l’arbre de dépendances des paquets.

Il ne vous reste plus qu’à vous approprier l’outil et à personnaliser la configuration !

Aucun commentaire sur “Apprendre à configurer webpack” pour le moment.

S'abonner aux commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *