Optimize images using webpack in React

Tosin Moronfolu

Images are an integral part of any web application. Most web applications in the world have at least one picture. As much as images are important elements of a good and appealing web application, they can also negatively impact its speed and user experience if handled poorly.

In this post, we'd be looking at how to effectively optimize images in our reactjs applications using the image-webpack-loader module.

Sandbox

The completed project is on Codesandbox. Click the link below to view a complete demo of this article on CodeSanbox.

GitHub Repository

https://github.com/folucode/image-webpack-loader

Prerequisite

  • Understanding of JavaScript and React.js.
  • Knowledge of Babel and Webpack is not required but preferred.

Getting started

To use the image-webpack-loader module, we set up our React.js application from scratch to help us understand how images are loaded and bundled.

It will also help us understand the basics of webpack, how webpack is set up or precisely what happens under the hood.

If you already have a React application set up with Webpack and Babel, skip the following portions to the Image optimization section below.

Project setup and installation

We would create a folder on our local machine, which I named mine react-webpack. We can always call it whatever we like.

We would navigate through the command line into the folder we created and initialize the package manager.

1$ npm init -y

-y means “yes” to all the general development questions asked on the command line.

Following the step above, the result will look like the image above.

Next, we will install the React.js dependencies with:

1$ npm i react react-dom

Finally, we will install Babel plugins and webpack loaders as devDependencies like so:

1npm i -D @babel/core @babel/preset-env @babel/preset-react babel-loader file-loader css-loader style-loader webpack webpack-cli html-webpack-plugin webpack-dev-server

Setting up webpack with Babel

After successfully setting up the dependencies and devDependencies, we have to set up Babel and webpack to bundle our app and loading assets, respectively.

We will now create a new file and name it .babelrc in the root directory of our app and add the following presets. This file will transpile react code from jsx to regular js.

1{
2 "presets": [
3 "@babel/preset-env",
4 "@babel/preset-react"
5 ]
6}

Our code and file structure so far would look like this.

In the root of our project folder, we'll create a new file called webpack.config.js. This file essentially runs in the node environment and not the browser. Therefore, we can write vanilla js code in it.

Copy and paste the code below into the newly created webpack.config.js file.

1const path = require('path')
2const HtmlWebpackPlugin = require('html-webpack-plugin')
3
4module.exports = {
5 output: {
6 path: path.join(__dirname, '/dist'),
7 filename: 'index.bundle.js'
8 },
9 devServer: {
10 port: 3000,
11 hot: true
12 },
13 module: {
14 rules: [
15 {
16 test: /\.(js|jsx)$/,
17 exclude: /nodeModules/,
18 use: {
19 loader: 'babel-loader'
20 }
21 },
22 {
23 test: /\.css$/,
24 use: ['style-loader', 'css-loader']
25 }
26 ]
27 },
28 plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
29}

What happens in the webpack.config.js file

  1. First, we import the path module from nodejs. We also import the HTML-webpack-plugin, which simplifies the creation of HTML files to serve our webpack bundles.
  2. In the output, we state where to send the files when bundled.
    • path states the name of the directory created, which will store all the bundled files. We named this folder dist, an industry standard.
    • And filename is the name we set for the new bundled file created after webpack bundles all the JavaScript code.
  3. devServer is used as a development server.
    • We use port to set a port that the development server would run on. We set ours to 3000.
    • hot triggers a full page reload when we change any file in our application.
  4. module is where we pass the rules for bundling files.
    • test states the file extension that needs to be targeted by the specific loader. The babel-loader must bundle all the .js or .jsx files.
    • exclude is where we state files to be ignored by the bundler.
    • It is essential to note that the array of use :['style-loader', 'css-loader'] in the CSS area needs to be written in that exact order.
    • webpack follows this order when bundling the CSS files:
      • It first runs the css-loader, which turns CSS files into commonjs.
      • It then runs style-loader which extracts the CSS into files as a string.
  5. Lastly, we add HtmlWebpackPlugin to ensure that the webpack knows which HTML file template to follow for running the app.

Setting up the react folder structure

We would set up the folder structure like this.

  • public
    • index.html
  • src
    • 1App.css
    • 1App.js
    • 1index.js

We will first create a public folder and then an index.html file inside the public folder. After that, we will create an src folder and then App.css, App.js and index.js files inside the src folder.

In the index.html file, add the following code snippet:



1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>React Webpack App</title>
7 </head>
8 <body>
9 <div id="root"></div>
10 <script src="index.bundle.js"></script>
11 </body>
12</html>

**In the `App.js` file**, add the following code snippet**:**



1import React from 'react';
2
3function App() {
4 return (
5 <div>
6 <h2>
7 <b>Welcome to my React App!</b>
8 </h2>
9 <h3>Date : {new Date().toDateString()}</h3>
10 </div>
11 );
12}
13export default App;

**In the `index.js` file add the following code snippet,**


1import React from 'react';
2import ReactDom from 'react-dom';
3import App from './App';
4import './App.css';
5
6ReactDom.render(<App />, document.getElementById('root'));

**Now we would create a script in the `package.json` file to run our app. Add the following code to the scripts section of the file:**


1"scripts": {
2 "dev": "webpack serve --mode development",
3 "build": "webpack --mode production"
4 }

Running the app

To run our app, we run the following command below in the project directory in our terminal:



1$ npm run dev

****Let's now navigate to http://localhost:3000/ to see our app running. We should see this in the browser.




Now that our app is up and running, let's add the image optimization functionality.

Image optimization with image-webpack-loader

To optimize images in our react application, we would use the image-webpack-loader module. We would install the module in our app by using the command below:

1$ npm install image-webpack-loader --save-dev

After successful installation, we need to configure it in our webpack.config.js file. In our webpack.config.js, we would put the object below into the rules array:

1...
2{
3 test: /\.(gif|png|jpe?g|svg)$/i,
4 use: [
5 'file-loader',
6 {
7 loader: 'image-webpack-loader',
8 options: {
9 mozjpeg: {
10 progressive: true,
11 },
12 // optipng.enabled: false will disable optipng
13 optipng: {
14 enabled: false,
15 },
16 pngquant: {
17 quality: [0.65, 0.9],
18 speed: 4,
19 },
20 gifsicle: {
21 interlaced: false,
22 },
23 // the webp option will enable WEBP
24 webp: {
25 quality: 75,
26 },
27 },
28 },
29 ],
30},
31...

The code above configures each optimizer for several image file types. There are several options for customizing them. For more information, see the documentation.

Image optimization in action

We will see the image optimization module in action, but we must add images to our app. Below is a list of three example image links from Unsplash that we downloaded for us.

  1. https://unsplash.com/photos/Mv9hjnEUHR4
  2. https://unsplash.com/photos/yihlaRCCvd4
  3. https://unsplash.com/photos/2l0CWTpcChI

We download the images into a newly created folder named img in the public directory of our app. Our app directory should look like this:

Before we continue, take note of the sizes of these images. See them below:

Importing the images for optimization

We would now import the images into our app. To do that, update the App.js file with the following code snippet:

1import React from 'react';
2import dog1 from '../public/img/charlesdeluvio-Mv9hjnEUHR4-unsplash.jpg';
3import dog2 from '../public/img/marliese-streefland-2l0CWTpcChI-unsplash.jpg';
4import dog3 from '../public/img/oscar-sutton-yihlaRCCvd4-unsplash.jpg';
5
6function App() {
7 return (
8 ...
9 <img src={dog1} alt={dog1} width={300} height={300} />
10 <img src={dog2} alt={dog2} width={300} height={300} />
11 <img src={dog3} alt={dog3} width={300} height={300} />
12 ...
13 );
14}
15
16export default App;

We should make sure to kill the already running server by using CTRL + C on Windows or CMD + C on Mac. Now we will restart the server with the command:

1$ npm run dev

We would then go to the app in our browser and see the images. To ascertain that the optimization happened, we would be inspecting the image elements in the devtools.

Confirming the optimization

We would go to the "img" section in the network tab of our browser's developer tools to see the optimization in action. We then refresh the page to see that the images have reduced size.

Note: If the size area shows in bytes(B) instead, e.g. 204B, hover on it to see the resource size.

Conclusion

This article explained how to optimize images in Reactjs applications using image-webpack-loader, that enhances our application speed and proffers a better user experience.

Resources

These resources are helpful to gain further knowledge.

Tosin Moronfolu

Software Engineer

I'm a software engineer with hands-on experience in building robust and complex applications improving performance and scalability. I love everything tech and science, especially physics and maths. I also love to read books, mainly non-fiction, and I love music; I play the guitar.