© Mohamed Bouzid 2020
M. BouzidWebpack for Beginnershttps://doi.org/10.1007/978-1-4842-5896-5_3

3. Loaders and Plugins

Mohamed Bouzid1 
(1)
NA, Morocco
 

Loaders and plugins are the heart of webpack. They make up the engine that empowers webpack to do the “magical” things for you, like reading and transforming files before passing them to webpack or influencing the output of your bundle by extending the compiler capability, etc. In this chapter, we will be exploring some of the most commonly used loaders and plugins. We will go through examples of how to transpile files from one format to another. We will see how to debug our JavaScript efficiently and how to handle images and optimize them as well as our bundle for fast loading. By the end of this chapter, you will have the necessary understanding of how to use both loaders and plugins in the simplest possible way.

Loaders vs. Plugins

Loaders, on one hand, tell webpack how to interpret, translate, and make transformations to your source code (for example, from CoffeeScript to vanilla JavaScript, or SASS to CSS). They are like tasks if you are familiar with any other build tool, and basically they preprocess files whenever you import them.

Plugins, on the other hand, can do anything that loaders cannot do. Webpack considers them the backbone of its core. And you will see that they are object-like, they can take arguments, and they can be instantiated as well in order to perform many things or adding functionalities to the compilation such as optimizing your bundle.

In this chapter, we will explore both loaders and plugins, so don’t worry too much about the definition. We will get familiar with these two notions when we see the examples. What you have to know, though, is that there are tons of loaders and plugins available (official and third-party ones), and our goal is not to cover them all. Instead we will be focusing on the understanding of how you can apply them and use them in your project, so that in the future if you need to have some additional functionality in your bundling process, you will be able to go and search by yourself, then apply that specific loader or plugin.

A pattern that you will notice when using loaders and plugins is as follows:
  • 1 - installing the loader/plugin (using npm install)

  • 2 - requiring it (usually we require plugins but not loaders)

  • 3 - using it (documentation is our valuable friend here)

Again, don’t worry if this seems too general for now. Once we start to go through the examples, you will absorb the principle more clearly and get the full picture. So with that in mind, let’s start exploring our first loader.

Using Babel-Loader

Let’s assume we want to write ES6 (ECMAScript 6) in our code (which is still not 100% supported yet by all the browsers, especially the old ones, at least at the moment I am writing this). While webpack is able to recognize and translate the “import/export” to its own syntax, it won’t do anything for your (other) JavaScript code if you write it in the ES6 style. One of the solutions is to use Babel JS, which is a transpiler that will translate our code from ES6 to ES5.

To use Babel, webpack provides us with a loader called “babel-loader,” which is easy and straightforward to use, so let’s install it first using our terminal:
$ npm install --save-dev babel-loader @babel/core @babel/preset-env

If you are wondering how to know what to install exactly in order to get Babel working, the answer is documentation! In order to install any loader or plugin, all you have to do is to search for “Webpack + the name of loader/plugin,” then most likely the first link will direct you to the documentation page of that loader or plugin (either webpack website or a GitHub repository) where more details are explained.

Every loader is different in the way it should be used (again, the documentation is how to get details), but the common thing is that they are used as “rules” in the “module” object that we need to add to the webpack configuration file. Listing 3-1 demonstrates an example of babel-loader usage.
module.exports = {
  // ...
  output: {
    // ...
  },
  module: {
    rules: [
      {
        test: /.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}
Listing 3-1

Configuration of babel-loader in order to transpile ES6 to ES5

Note that there is a “module” object, then an array of objects called “rules” where we add the rules for loaders. In this example, we added a rule for Babel; we have basically a “test” key/property with a regular expression “/.m?js$/” that tells webpack that whenever there is a file with an “.mjs or .js” extension, the babel-loader should be applied to it. The “exclude” property, as its name suggests, tells webpack to skip files if they are node modules or bower components, and then finally the property “use” tells webpack the name of the loader to use, and sets some additional options as well. In this example, the “presets” is set to ‘@babel/preset-env’.

Do not worry if you are still wondering if you should know somehow about how these things are written or if you should memorize them. Be assured that nobody does. Every loader is different on its own; and your job is to know that you can search a specific loader, copy/past the configuration in your webpack.config.js, and play with it (if needed at all) by exploring the documentation and the available options you can use for that loader.

Now that we have installed babel-loader and we have configured the webpack.config.js file to use it, let’s write some ES6 and see if it will work as expected.

In our greeting.js file, using the new ES6 syntax, let’s define a variable with the “let” keyword and then use the string interpolation ${} that allows us to put a variable into a string without having the need to concatenate it using the plus (+) sign. Here is our sayHello() function :
function sayHello() {
  let tool = 'webpack';
  alert(`Hello I am ${tool}, welcome to ES6`);
}
export { sayHello };
Open the terminal and call the webpack/build command:
$ npm run build
Note

While we have set the “watch” option to true in the previous chapter to survey our files when updated, in this book I will continue to mention the “npm run build” command (for clarity) whenever we make a change to our source files.

Go now and take a look at the application.js file, see if the “letkeyword was transformed to “var,” and the interpolation ‘Hello I am ${tool}, welcome to ES6’ was transformed to a concatenation. Figure 3-1 shows our application.js file after the translation of our code to ES5.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig1_HTML.jpg
Figure 3-1

“Let” keyword translated to “var,” and the interpolation to “contact” function

Everything’s working fine, our ES6 syntax was transformed to ES5, and so that was it for Babel. Super easy, right? Now you have the tool to use ES6 without worrying about a browser’s compatibility; happy coding!

Debugging Our JavaScript

Let’s move on and talk about something I find quite useful when debugging. As we have seen before, webpack wraps our code in modules and injects some extra code in order to make things work in the webpack way. While this does not affect our browsing experience, when we want to debug our code in the browser’s developer tools, if there is any error, it will show us application.js (which is the resulting bundled file) as the source of where the error has been triggered instead of the original source code file. That makes debugging hard and sometimes impossible!

Source Map

The solution? There is a file we can generate called source map , which as its name suggests, maps our original source code to the bundled source code generated by webpack, it will be really helpful for us, especially in our development phase.

Webpack provides us with a tool to do this. You can find it under the “Devtool” section on the webpack official website: https://webpack.js.org/configuration/devtool/. When you scroll down in the devtool documentation, you will see a table that represents all the possible styles of source mapping you can use. Figure 3-2 is a screenshot taken from the webpack website.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig2_HTML.jpg
Figure 3-2

Available styles for source mapping

You may need to choose the one that is the best for you. Here is a little notice about these values from the webpack website:

Some of these values are suited for development and some for production. For development you typically want fast Source Maps at the cost of bundle size, but for production you want separate Source Maps that are accurate and support minimizing.

Also note that you can use a plugin to generate the source map, but in our case we are going to just use the “devtool” with “cheap-module-eval-source-map” because it’s faster when it comes to rebuilding, and because we need it for our development only.

Note

As mentioned in the webpack documentation, the “cheap-module-eval-source-map” will only show us the line number when debugging, which is enough for our case. But if you think you need other options, then just go ahead and pick whatever is suited for your use case.

For revision purposes, Listing 3-2 shows our full webpack.config.js after adding the devtool.
const path = require('path');
module.exports = {
  watch: true,
  mode: "development",
  devtool: "cheap-module-eval-source-map",
  entry: "./src/index.js",
  output: {
    filename: "application.js",
    path: path.resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        test: /.m?js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}
Listing 3-2

Our full webpack.config.js file

To understand what devtool can do for us, let’s open up the greeting.js file and print something in the browser’s console from our sayHello() function like it’s shown in Listing 3-3.
export function sayHello(){
  let tool = 'webpack';
  alert(`Hello I am ${tool}, welcome to ES6`);
  console.log('Can you find me?');
}
Listing 3-3

Using console.log to debug in the browser

Save the file and don’t forget to re-bundle if you did not do so yet using npm run build . Then open the browser console (Ctrl+shift+i or just right-click somewhere in your browser page, choose “Inspect Element” in order to open the developer tool, then navigate to the “console” tab). The console should look like Figure 3-3.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig3_HTML.jpg
Figure 3-3

Opening the console in the browser

Now make sure you open the index.html in the browser (with the console opened) so we can see our message from sayHello() function logged (refresh the page if you are not seeing anything yet).

Once the “Can you find me?” message appears in your console, look at the right corner where there is the file name plus the line number that basically tells us exactly where this message comes from and which line specifically. Figure 3-4 shows what I am referring to.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig4_HTML.jpg
Figure 3-4

Source map points to the source of our message log

That’s the power of a source map: you can go ahead and click on it to see that it will take you to the right place, meaning the original source file, which is very handy when debugging or when errors appear in our console.

If we haven’t used the source map functionality, we would have our console lead us to application.js, which is the bundled file that contains all our JS together, and you may not know which source file the “logged” line is coming from. Now you see the power of a source map and how useful it can be during the development process.

Handling CSS and SASS Files

As I mentioned earlier, webpack is a bundling tool, not only for JavaScript (which is what it is by default) but also for other types of files as well, including CSS. In this section, we will see how we can bundle CSS and SASS files with webpack.

Importing CSS in JavaScript

I know what you are probably thinking right now: Why would we import our CSS from a JavaScript file at all? Wouldn't it be better to have the CSS managed separately? Is it a good idea to do that?

Indeed, importing CSS from a JavaScript file is quite useful in many situations. If you are importing a third-party library, for example, a calendar or a date picker that comes with its own styling (CSS files), it makes sense that you load both the JavaScript and CSS of that library at the same place, because it will make it independent from having the code in different locations, which makes the code maintenance a lot easier.

In the following example, I am going to demonstrate how you can load CSS from a JavaScript file, but we won’t use a third-party library. Instead we will just simulate that by creating a CSS file within our source code folder and load it in our index.js file. Let’s create a file and name it “lib.css” in our “src” folder, and let’s write the following rule in it to change the background color of our page:
body{
  background-color: magenta;
}
We will then import that CSS file in our index.js (at the top) file using:
import lib from './lib.css';
After that, we will need to bundle our JS, so from our terminal using:
$ npm run build
Oops! We have gotten an error saying that “You may need an appropriate loader to handle this file type , currently no loaders are configured to process this file …” Figure 3-5 is a screenshot taken from my terminal showing the error message I’m talking about.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig5_HTML.jpg
Figure 3-5

Webpack failed to process our css file

The error in Figure 3-5 shows up because webpack doesn’t recognize our CSS file. Webpack knows only how to deal with JavaScript, but if you need to import anything other than JavaScript, you have to configure webpack in order to be able to use it.

Loading CSS with CSS-Loader

As we have seen before with babel-loader, any file that will be detected as a “.js” file will be transpiled from ES6 to ES5. For the CSS we are going to use exactly the same technique, detecting any file with a “.css” extension and make it recognizable for webpack. That’s why we need a loader called “css-loader,” which we are going to use in the next step.

First install css-loader with our terminal:
$ npm install css-loader --save-dev
When the installation is finished, add the CSS rule to your webpack.config.js “rules” like Listing 3-4 shows.
const path = require('path');
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.m?js$/,
        // ...
      },
      {
        test: /.css$/i,
        use: ['css-loader'],
      }
    ]
  }
}
Listing 3-4

Adding “css-loader” to rules for webpack to handle css file

Again, if you are wondering where I came from with these configuration lines, just search for “webpack css-loader,” and you will get the link to the webpack documentation page that explains it. Finally, let’s bundle our files using the command:
$ npm run build

See that the error we had before disappeared, and webpack is now able to load CSS files. But that’s not all. We still need to make our imported CSS applied to the index.html page, so we are going to use another loader called “style-loader.”

Injecting CSS to Our HTML

What “style-loader” will do for us, is take the imported CSS in the index.js file and inject it into the DOM between <style></style> tags.

Again, you will need to install it first:
$ npm install style-loader --save-dev
Then add it to the previous css-loader rule we added above (refer back to Listing 3-4), so the rule will become:
{
  test: /.css$/i,
  use: ['style-loader', 'css-loader'],
}
Note here that we are using two loaders for files that have a “.css” extension. Also note that the order we put in these loaders is important. You might be guessing that the first loader at the left (style-loader) will be applied first, then the second one (css-loader) will be applied after it. But the reality is that the order webpack will use is the opposite: the order starts from the right to the left:
  • 1- Webpack will use “css-loader” (that will transform our CSS into CommonJS)

  • 2- The “style-loader” will be used secondly (which will inject that CSS in our page <head>)

Now let’s bundle our files:
$ npm run build

Then open index.html file in the browser, and see that the magenta is applied.

If we inspect using the browser console, we will see that a <style> tag was injected dynamically to our page by JavaScript, with the styling we imported from lib.css file. See Figure 3-6.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig6_HTML.jpg
Figure 3-6

CSS injected to our HTML file by webpack

But what if you would like to use some preprocessor like SASS? Well, in order to recognize SASS, webpack will need two loaders, which we will be exploring next.

Compiling SASS to CSS

In order for webpack to be able to load SASS files, we need a loader called “sass-loader” and another one for the compilation called “node-sass.” Let’s try this and install both of these loaders from our terminal.
$ npm install sass-loader node-sass --save-dev
After the installation completes, we need to add another rule to the webpack.config.js like this:
{
  test: /.scss$/i,
  use: ['style-loader', 'css-loader', 'sass-loader'],
}

Here we are testing if the file ends with “.scss” If so, we apply the “sass-loader” to it first. After that, “sass-loader” recognizes and compiles our SASS files, the “css-loader” will read that CSS turns it to CommonJS, and then our “style-loader” will inject it in the DOM dynamically via JavaScript.

Note

Remember that the order of applying loaders is from right to left), so the “sass-loader” will be used first, which itself calls “node-sass” to compile SASS to CSS (without requiring us to call it explicitly).

To test if this is working , go ahead and create a SASS file under the “src” folder, name it “application.scss,” and then let’s write some SASS syntax into it:
$gradient: linear-gradient(to right, #00467f, #a5cc82);
body{
  background-image: $gradient;
}
DO NOT FORGET to import application.scss in the index.js file:
import application from "./application.scss"
Save the file, then in the terminal use the command:
$ npm run build
Open index.html file in the browser and see that the background gradient was applied to our page. Also, if you check the console, you will notice, as shown in Figure 3-7, that another <style> tag was added to the head of the page containing the (compiled) styles we have in our application.scss file.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig7_HTML.jpg
Figure 3-7

Webpack is now recognizing scss files and the gradient color is applied

Now that we are able to use CSS and SASS in our project, let’s talk about browser compatibility and how we can apply vendor prefixes to our CSS using postcss-loader.

Prefixing CSS with Vendor Prefixes

Not all CSS features are fully supported by all browsers, so that’s why we need to use browser prefixes or what we call vendor prefixes. An example of this case is the linear-gradient color we used previously, and which can be prefixed as follows:
background: -webkit-gradient(linear, left top, right top, from(#00467f), to(#a5cc82));
background: -o-linear-gradient(left, #00467f, #a5cc82);
background: linear-gradient(to right, #00467f, #a5cc82)
Using “postcss-loader” will allow us to do that automatically and add all the necessary prefixes without having to specify them ourselves, which is super cool. Let’s install it from our terminal:
$ npm install postcss-loader --save-dev
Here is a link to the documentation where the “postcss-loader” is documented: https://webpack.js.org/loaders/postcss-loader/. If you scroll down, you will find a recommended way to use it in the webpack.config.js file. Listing 3-5 shows what our CSS and SCSS rules will become after applying “postcss-loader.”
{
  test: /.css$/i,
  use: ['style-loader', 'css-loader', 'postcss-loader'],
},
{
  test: /.scss$/i,
  use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
}
Listing 3-5

Applying css-loader to our css/scss files

If you read the documentation carefully, there is an important note saying:

This loader cannot be used with CSS Modules out of the box due to the way css-loader processes file imports. To make them work properly, either add the css-loader’s importLoaders option, or use postcss-modules instead of css-loader.

In our case, we are going to use the option importLoaders to prevent any issue with css-loader. Listing 3-6 shows the updated snippet.
{
  test: /.css$/i,
  use: [
    'style-loader',
    { loader: 'css-loader', options: { importLoaders: 1 } },
    'postcss-loader'
  ],
},
{
  test: /.scss$/i,
  use: [
    'style-loader',
    { loader: 'css-loader', options: { importLoaders: 1 } },
    'postcss-loader',
    'sass-loader'
  ],
}
Listing 3-6

Using importLoaders option to prevent issues with css-loader

All we did here is replace the string ‘css-loader’ by an object where we specify the name of the loader, and we set additional options. In this case, we have set importLoaders to 1.

Another thing we need to do in order to make “postcss-loader” work like we want is to use a plugin called “autoprefixer.” We have to alter our previous code slightly and change ‘postcss-loader’ from a string to an object where we can add some more options like specifying which browsers we should add prefixes to.

Note

You can specify these browsers using browserlist property in the package.json file. But as we want to keep this option in our config file for this example, I am going to use another property called overrideBrowserslist that will work fine too.

Here is what our ‘postcss-loader’ string should become:
{
  loader: 'postcss-loader',
  options: {
    plugins: [
      require('autoprefixer')({
      overrideBrowserslist: ['last 3 versions', 'ie >9']
      })
    ]
  }
}
Listing 3-7 shows the rules for both CSS and SCSS.
const path = require('path');
module.exports = {
  // ...
  module: {
    rules: [
      // ...
      {
        test: /.css$/i,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { importLoaders: 1 } },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [
                require('autoprefixer')({
                overrideBrowserslist: ['last 3 versions', 'ie >9']
                })
              ]
            }
          }
        ],
      },
      {
        test: /.scss$/i,
        use: [
          'style-loader',
          { loader: 'css-loader', options: { importLoaders: 1 } },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [
                require('autoprefixer')({
                overrideBrowserslist: ['last 3 versions', 'ie >9']
                })
              ]
            }
          },
          'sass-loader'
        ],
      }
    ]
  }
}
Listing 3-7

Full rules for CSS and SCSS files

Don’t forget to install “autoprefixer” before proceeding:
$ npm install autoprefixer --save-dev
Then run webpack to get our bundle updated:
$ npm run build
If you remember, we were using “linear-gradient” in the application.scss file. Now, if you open the file index.html in the browser and you see the styles injected in the page (from the web console), you will see that gradient was autoprefixed with the necessary prefixes like those shown in Figure 3-8.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig8_HTML.jpg
Figure 3-8

Vendor prefixes applied to “linear-gradient” by autoprefixer

In this case, two prefixes were added, one for webkit browsers and another one for opera. This can be really useful when dealing with CSS and can save you a considerable amount of time and effort.

Extract CSS to Its Own Separate File

Now that we have configured what we need for our CSS/SCSS to work, there is one thing though that is kind of annoying: our CSS is injected in the index.html page between <style></style> tags. That might be okay when we have just some few styles, but if we have a lot of CSS, then embedding it directly within our HTML page is not a good idea at all.

With webpack, we can extract the CSS we imported to its own file with the help of a plugin called “mini-css-extract-plugin.” Let’s try to do it by first installing it:
$ npm install --save-dev mini-css-extract-plugin
Once the plugin installed, this time you have to require it at the top of webpack config file (webpack.config.js):
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
To use the plugin, just add the following to the configuration object:
plugins: [
  new MiniCssExtractPlugin({
    filename: 'application.css'
  })
]

Here, we created a “plugins” property that takes a hash (of plugins) as value; we instantiated our MiniCssExtractPlugin and we passed a filename (application.css) to it as an argument.

Wait, that’s not all. We still have the “style-loader” doing the job of injecting CSS into our HTML page. We need to change it from using “style-loader” here:
{
  test: /.css$/i,
  use: [
    'style-loader',
     // ...
  ]
}
To use MiniCssExtractPlugin.loader instead:
{
  test: /.css$/i,
  use: [
    MiniCssExtractPlugin.loader,
    // ...
  ]
}
The same thing applies to our SCSS files:
{
  test: /.scss$/i,
  use: [
    'style-loader',
    // ...
  ]
}
Which should become:
{
  test: /.scss$/i,
  use: [
    MiniCssExtractPlugin.loader,
    // ...
  ]
}
Now back to the terminal. Use the command “npm run build” to bundle our files, and let’s see if this time we will have our CSS extracted in a separate file, Figure 3-9 confirms that it does.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig9_HTML.jpg
Figure 3-9

Extracting our styles to a separate application.css file

If you check your build folder, you will find a new created file “application.css” that contains all our CSS from “lib.css” and “application.scss.” But now if you open the index.html in your browser, the styles are gone. That’s normal because we are no longer using “style-loader” so our styles are no longer injected in the HTML page. We need to specify a link to our stylesheet between <head></head> of the index.html file, as the following:
<link rel="stylesheet" href="build/application.css">

Go to your browser and see that our page styling are back. Very cool!

Note that our CSS is not minified because we are using “development” mode; if we change that to “production,” it will be minified, right? Let’s try and change mode to “production” in our webpack.config.js as follows:
module.exports = {
  mode: "production",
  // ...
  // ...
}

Don’t forget to run the command npm run build” to bundle. Then, go to the build folder. You’ll see that our application.js is minified, but our application.css is not. Webpack will do the minification only for JavaScript files; anything else, you need to add it and specify it by yourself.

Minifying the Extracted CSS

So how are we going to minify a CSS file? There is another plugin that we can use called “optimize-css-assets-webpack-plugin,” which will help us do exactly this. First, install the plugin:
$ npm install optimize-css-assets-webpack-plugin --save-dev
Then we need to require it at the top of our webpack.config.js:
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
Webpack documentation suggests using the “optimization.minimizer” setting, seen as follows:
optimization: {
  minimizer: [
    new TerserJSPlugin({}),
    new OptimizeCSSAssetsPlugin({})
  ],
}
Note that besides the “OptimizeCSSAssetsPlugin,” there is another one called “TerserJSPlugin,” which is a plugin that already comes with webpack. You won’t need to install it yourself; that’s the plugin used by default to minify your JavaScript when you use “production” mode. However, the thing you need to do is require it at the top of your webpack configuration file:
const TerserJSPlugin = require('terser-webpack-plugin');

You may ask why we are using TerserJSPlugin explicitly above when we know it’s already used by default to minify our JavaScript?

Well, as it is stated in the webpack documentation:

Setting optimization.minimizer overrides the defaults provided by webpack, so make sure to also specify a JS minimizer.

This means that by using optimization.minimizer in your webpack.config.js, it will override the default behavior of webpack (for minimization) so you need to specify explicitly all the plugins you need to use for the minification part. Listing 3-8 shows what webpack.config.js should look like after requiring the necessary plugins and the usage of optimization.minimizer.
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
module.exports = {
  watch: true,
  mode: "production",
  devtool: "cheap-module-eval-source-map",
  entry: "./src/index.js",
  output: {
    filename: "application.js",
    path: path.resolve(__dirname, 'build')
  },
  optimization: {
    minimizer: [
      new TerserJSPlugin({}),
      new OptimizeCSSAssetsPlugin({})
    ],
  },
  module: {
    // ...
  },
  plugins: [
    // ...
  ]
}
Listing 3-8

Usage of the plugins needed to minify both JavaScript and CSS

Now you can run the bundle command in your terminal:
$ npm run build

Take a look at the build folder files (application.js and application.css). You will find that the code of both files is minified as expected.

Handling Images

One last thing I want to discuss before moving into the next chapter is using images in your CSS. For example, in case you are using the “background-image” property with an image as value, webpack won’t be able to recognize it. But, like other types of files, there are loaders that will make this possible, so in this section we will discuss how we can handle images with webpack.

Loading Image Files

I am going to use an image of a cat here from pixabay (which is a website full of free images), but you can grab any image you want. If you prefer to use the image I’m using, please refer to the source files accompanying this book.

Once you have the image in your computer, rename it to cat.jpg (for clarity) and then put it into the “src” folder.

Let’s change our page background, by replacing the contents of the “application.scss” file as follows:
body{
  background-image: url('cat.jpg');
  background-size: cover;
}
The background-size property here is just to make sure the background image expands to the full width and height of our screen, so it will look better. Go back to your terminal and run webpack using:
$ npm run build

As you might be expecting, webpack throws an error because we have no loader yet to process images, so you should get a long red message saying, “You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.

In order to recognize images, webpack will need two loaders: one called “url-loader” and another one called “file-loader.” What these loaders do is basically the same, but “url-loader” will serve us a lot when dealing with small images or icons; it will directly transform any small image to base64 code and insert it for us instead of url(‘...’). That will help reduce the call to the server for every small icon or image set in our CSS. Let’s start first by installing “url-loader” and go from there:
$ npm install url-loader --save-dev
In our webpack.config.js, we will add another rule that checks for files ending with extensions related to images (jpg, gif, png, etc). Here is the rule that I am going to add:
{
  test: /.(png|jpg|gif|svg)$/i,
  use: [
    {
      loader: 'url-loader',
      options: {
        limit: 8192,
      },
    },
  ],
}

Again, for the usage of any plugin or loader, you have to check the webpack documentation or its GitHub page. For “url-loader,” here is the direct link to the documentation: https://webpack.js.org/loaders/url-loader/.

Here, we are using the “url-loader” with a “limit” option. What this option does is; for all our images with a size less or equal 8192 (size <= 8192) bytes, it encodes them as base64 and injects the result into our CSS instead of url(‘...’). Let’s open the terminal and see what will happen by calling the build command:
$  npm run build
What you will get is a different error. Webpack was able to recognize our image but finds that our image is more than the limit we specified (8192). If you read the thrown error, it says, “Cannot find module 'file-loader,” which means we had to use another loader called “file-loader.” Let’s go ahead and get it installed:
$ npm install file-loader --save-dev

Don’t run the command “npm run build” just yet, because we still need to specify a “name” option within the “url-loader” rule.

So far, we used the “limit” option, which can be of three types: Number|Boolean|String. In our case, we used a Number (that represents the max size of a file in bytes). If you check the documentation of “url-loader,” there is a part that says:

If the file size is equal or greater than the limit file-loader will be used (by default) and all query parameters are passed to it.

What this means is, that “url-loader” will try to turn our images to base64 (based on a size limit) but if the file has equal or greater size than what we specify, the “file-loader” will be used instead, and if any parameter there (for the “url-loader”) it will be passed to the “file-loader.”. In our case, we are going to add the “name” option to our “url-loader” so if the file is equal or greater than the limit size, that option will be used for the loaded file:
{
  test: /.(png|jpg|gif|svg)$/i,
  use: [
    {
      loader: 'url-loader',
      options: {
        limit: 8192,
        name: '[name].[hash:7].[ext]'
      },
    },
  ],
}

Note that we used a template: '[name].[hash:7].[ext]'. We will discuss what this is in the next chapter, but all you have to know for now is that the “url-loader” will take our file and save it (thanks to the file-loader) to the build folder by giving it a name composed of the original file name followed by a hash, followed by the original file’s extension and each part separated by a dot. So, in our case, given the template we specified above, “cat.jpg” will become something like “cat.1c28f43.jpg”.

Note that the hash “1c28f43” was resulted from the hash algorithm used by webpack based on the content of the file, so you’ll get a different hash whenever the image file changed.

Now it’s time to re-bundle our source files using the command:
$ npm run build

If you did everything correctly, you will see no errors. But what just happened?

Check the “build” folder and see that our image was saved there, and the best part is that webpack has outputted that name dynamically for us in our generated “application.css” file. Here is what I’ve gotten in my CSS:
body{background-image:url(cat.1c28f43.jpg);background-size:cover;background-color:#f0f}
Let’s make sure it’s working by opening the index.html file in the browser, Figure 3-10 shows our page with the cat background image.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig10_HTML.jpg
Figure 3-10

Cat image applied to the page background

Note

If you don’t want the possibility to turn your “small-sized” images to inline base64 images, you can use “file-loader” directly instead of “url-loader,” and that would be fine too.

That’s all for this part, in the next step, we will see how we can reduce the size of our image in order to make it load fast by using Webpack Image Loader.

Compressing Images

Before we compress our image, I would like to show you my terminal screenshot (from the last build) which demonstrates the size of the bundled files. The cat image in Figure 3-11 has the size of 20.5 KiB.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig11_HTML.jpg
Figure 3-11

The size of bundled files after compilation

Webpack has a loader called Image-Webpack-Loader that can be used to compress PNG, JPEG, GIF, SVG and WEBP images. From its GitHub repository, as a note for macOS users, when installing this loader, it may complain about missing “libpng” dependency. In this case, if you are a Mac user, then you may need to install that library first using the command:
$ brew install libpng
To install the loader:
$ npm install image-webpack-loader --save-dev
Now let’s use the “image-webpack-loader” in our webpack config file. All we need to do is add another loader for our images:
{
  test: /.(png|jpg|gif|svg)$/i,
  use: [
    {
      loader: 'url-loader',
      options: {
        limit: 8192,
        name: '[name].[hash:7].[ext]'
      },
    },
    { loader: 'image-webpack-loader' }
  ],
}
Then run the build command from the terminal:
$ npm run build
Finally, let’s check the image size again. Figure 3-12 shows the new size of the image after using the ImageWebpackPlugin.
../images/494641_1_En_3_Chapter/494641_1_En_3_Fig12_HTML.jpg
Figure 3-12

The new image size after applying ImageWebpackPlugin

See that our cat image size has decreased from 20.5 KiB to 12.5 KiB, which is super cool. Imagine what that can do for you when you have bigger images; it’s a very useful loader that I recommend using whenever possible when loading images.

There is one last note about the regular expression we used for our “file-loader” which is currently limited to image files (/.(png|jpg|gif|svg)$/i). You may want to add more file formats, like fonts for example. Here is an extended RegEx that you can use for this purpose:
/(png|jpg|gif|svg|woff2?|eot|ttf|otf|wav)(?.*)?$/i

Here, we just added some file extensions like woff2, eot, ttf etc, that it's possible to load with the file-loader, but you can basically add any other type of file, loaders are here for that purpose. At this point, you start to see the power of webpack, and how you can configure it to do a lot of helpful things for you. Once you grasp the syntax, you can even separate your configuration file to multiple files and include them as part of the main webpack.config.js if this later gets bigger (or in case you find it hard to read), but for the sake of simplicity in this book, we will stick with one file only.

Summary

This was a long chapter about loaders and plugins, but I wanted to provide you with as many examples as possible in order for you to understand the concept and to see the difference between both. We have seen how to use Babel in order to write modern JavaScript, and we have explored source mapping, which allows us to debug our code efficiently. We have also seen how to use CSS and SASS loaders to recognize and bundle our stylesheets, as well as images, without forgetting the minimization of our assets and the optimization of our images. The possibilities are endless with webpack as you can imagine; there are a lot of loaders and plugins out there that can do all kinds of things you may think of. At this point, I hope you are ready to explore more loaders and plugins on your own and use them in your future projects. With that said, it’s time to move on to a new chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.135.224