Webpack 4.x : The essential loaders and plugins you need to have installed

This article is directly derived from Colt Steele video tutorial

This will be an introduction list on the essential loaders and plugins one need to have installed in order to work with styles as CSS, SASS files and files in general such for example images.

The list will not be complete and will been update in the future based on the package plugins updates / deprecation and readers feedback.

So then let's start.

As a recup let's remember what loaders are:

Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph. They are transformations that are applied to the source code of a module. They allow you to pre-process files as you import or “load” them. Thus, loaders are kind of like “tasks” in other build tools and provide a powerful way to handle front-end build steps. Loaders can transform files from a different language (like TypeScript) to JavaScript or load inline images as data URLs. Loaders even allow you to do things like import CSS files directly from your JavaScript modules!

So now that we understand why this one should be the first loader to consider to install in your frontend application part, if you want webpack to be able to include .css file into the bundler file then you need this two loaders :

css-loader and style-loader

The first interprets @import and url() like import/require() and will resolve them. Basically turns .CSS into Commonjs.

This is how you setup it into the webpack file:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

So then we then will have:

 use: ['style-loader', 'css-loader', ],

Here we need to note that webpack does start to "use" first the last-right element of the use array, so in this case the css-loader loader that let webpack include into the bundler css code as Javascript one.

Of course we need to import the .css file into the entry point where the loader will be looking for the .css file.

The style-loader instead inject CSS into the DOM.

The next one is essential only if you want to use .scss instead of .css files. They can be used both but for now we show the SASS one only used.

                             **sass.loader **    -->  Turns scss into standard css

that import in the entry point the sass.file or the second part vendors such bootstrap.

Now let's move to plugins that are from the official webpack docs , defined as such:

Plugins are the backbone of webpack. webpack itself is built on the same plugin system that you use in your webpack configuration! They also serve the purpose of doing anything else that a loader cannot do. You can find an extensive list here.

The one you probably are going to use if you want to make your life easier, is the

                              **HtmlWebpackPlugin** -->  simplifies creation of HTML files to serve your webpack bundles. 

This is especially useful for webpack bundles that include a hash in the filename which changes every compilation. You can either let the plugin generate an HTML file for you, supply your own template using lodash templates, or use your own loader.

This will indeed make sure that if you do use cache busting feature as in filename:

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

module.exports = {
 mode: "production",
  output: {
    filename: "[name].[contentHash].bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

that is the ability of webpack to generate a file name of the bundler file based on an Hash function( e.g the md5 algorithm ), so a filename that change based on the file content, html-webpack-plugin will include every time the new filename as the source of the bundle cleaning old one, still keeping the feature of cache busting.

So this is how this plugin should be used once have installed it:

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

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: "[name].[contentHash].bundle.js",
  },
  plugins: [new HtmlWebpackPlugin()]
};

With this basic setting it will generate a dist/index.html containing the following:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>webpack App</title>
  </head>
  <body>
    <script src="index_bundle.js"></script>
  </body>
</html>

At this point the plugin won't know which template use, so we need still to tell it either to use a plan template.html or a library or connect to a template system.

Going for the simply solution, providing a template.html file as an object:

 plugins: [new HtmlWebpackPlugin({
 template: 'yourTemplatePath/template.html'
})]

Our template will be used to fill the index.html generated by the plugin with the link to the last bundler with the Hash number concatenated as name.

The next plugin is one that is quite necessary for any project that involve a Development environment and a Production one( well theoretically any).

We will assume then to have 3 different webpack files:

webpack.common.js --> containing the setting in common to both production and development

and of course webpack.dev.js and a webpack.prod.js

Like the following:

webpack.common.js :

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

module.exports = {
  entry: {
    main: "./src/index.js",
  },
  module: {
    rules: [
      {
        test: /\.html$/,
        use: ["html-loader"]
      },
      {
        test: /\.(svg|png|jpg|gif)$/,
        use: {
          loader: "file-loader",
          options: {
            name: "[name].[hash].[ext]",
            outputPath: "imgs"
          }
        }
      }
    ]
  }
};

Meanwhile the webpack.dev.js will become simpler as we don't need the template plugin as already in web.common.js, so the webpack.dev.js will become:

const path = require("path");
const common = require("./webpack.common");

module.exports = {
  mode: "development",
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist")
  }, 
};

similarly the webpack.prod.js that will keep the hash functionality.

We then will include in both the prod and the dev file the common one, but how we can use it?

Here when it come into play the merge plugin, that once installed and required need to be used in this way in both the dev and prod version of webpack:

webpakc.dev.js


const path = require("path");
const common = require("./webpack.common");
const merge = require("webpack-merge");

module.exports = merge(common, {
  mode: "development",
    output: {
       filename: "[name].bundle.js",
       path: path.resolve(__dirname, "dist")
    },

 });

Of course we will have different package.json script to run before the webpack.de.js version and then another that will use the webpack.prod.js

This of course require an extra work but enable more flexbility once our application process development start to involve more aspect for development and production but with a large base from both.

For example in development we don't need to build our bundle file everytime so the 2 process could use separate server.

In the webpack.dev.js we can integrate a development server, simply installing it:

npm install webpack-dev-server

That will us enable to be called directly from a package.json script as such:

"start": "webpack-dev-server config webpack.dev.js --open"

Where the open flag let also open a local instance at the default address(localhost:8080).

The advantage f the web-dev-server is also that if does not exist a dist folder where the bundler should be put, it does it create in memory and update it at every change.

At one point we will need also to load some image and this can be manage of course via webpack without put hard coded link.

We will assume to have and image folder under the root/src where image will be uploaded during development.

Unfortunately webpack will not know where this folder is so we need some help and that when it comes in the html-loader :

HTML Loader --> Exports HTML as string. HTML is minimized when the compiler demands.

Here how we should use after have installed:

module: {
     rules: [
       {
          test: /\.html$/,
          use: ["html-loader"]
        },
     ],
},

Unfortunately this plugin will push the image into the js file produced from webpack but we would get an error if not handle image inside webpack.

Here when it comes the File-loader loader that does create dinamically the src for image inside the bundler produced by webpack.

This is how to use it in your webpack.common.js

    {
        test: /\.(svg|png|jpg|gif)$/,
        use: {
          loader: "file-loader",
          options: {
            name: "[name].[hash].[ext]",
            outputPath: "images"
          }
        }
      }

So file-loader will manage to handler the image imported by html-loader and provide an hash name in the above way.

Indeed in the src/index.html we will have :

  <img src="images/YourImageName.hashnumber.ext">

Of course simply add more image extension if you need to support them.

Another plugin that should not be left out is the clean-webpack, as from github repo :

remove/clean your build folder

As we had split the webpack between commom, dev and prod as we don't use the HtmlwebpackPlugin in production every time we build we end up with many bundlers file, but with the above plugin it cleans the output folder and let use only the last bundler.

We use it this way:

const CleanWebpackPlugin = require("clean-webpack-plugin");

module.exports = merge(common, {
    mode: "production",
    output: {
         filename: "[name].[contentHash].bundle.js",
         path: path.resolve(__dirname, "dist")
    },
    plugins: [
         new CleanWebpackPlugin()
    ]
});

We could have the necessity to produce differents bundles based on different libraries( JQuery, React etc).

In this case first we need to change the entry point configuration in the webpack.common.js like:

 entry: {
   main: "yourPathToindex.js",
   vendor: "yourPathtoReactenty.js"
},

The last plugin we are going to list is the one that let improve our user experience.

Indeed having the bundle in the end of the file means that the CSS file are injected together with javascript then only once everything is loaded.

Unfortunately in this way the browser will flag for some milliseconds an error saying that cannot find those styles.

This is when it comes in hand the mini-css-extract-plugin that we will use only in production as load css require time andin development face we don't care that much regarding the flag warning.

webpack.prod.js

  plugins: [
    new MiniCssExtractPlugin({ filename: "[name].[contentHash].css" }),
    new CleanWebpackPlugin()
  ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader, //3. Extract css into files
          "css-loader", //2. Turns css into commonjs
          "sass-loader" //1. Turns sass into css
        ]
      }
    ]
  }

What we will get will be a cssfilenameHashnumber.css that will be not minified so still not the best in term of performance.

In order to achieve this performance improvement we can use the optimize-css-assets-webpack-plugin.

It can be used as it says in official repo or in a better way create an apropriate object

optimization: {
    minimizer: [
      new OptimizeCssAssetsPlugin(),
      new TerserPlugin(),
      new HtmlWebpackPlugin({
        template: "./src/template.html",
        minify: {
          removeAttributeQuotes: true,
          collapseWhitespace: true,
          removeComments: true
        }
      })
    ]
  },

We need new TerserPlugin() because OptimizeCssAssetsPlugin override the minifying feature of webpack so it does not then proceed to minify all files but only CSS.

This was a quite extensive list based on the above tutorial but will eventually be added more plugin list and be update depending also on readers feedbacks