Installing and configuring Webpack [+ Gif Generator Project]

Do not miss this exclusive book on Binary Tree Problems. Get it now for free.

Table of contents:

Introduction

Before we actually start coding, let's have a brief discussion about Webpack.

What is Webpack?

Webpack is a module bundler. Explained concisely, it takes our input files and transforms them into one or more output files. Those input files usually have dependencies, which Webpack transpiles into static output files, some by default, and others using additional libraries called loaders.

Why should we use Webpack?

Say we have a lot of Javascript code to write. Without considering any additional tool, we could go about writing that code in two ways:

  1. Write it all into a single file and add that major file as a script in our HTML
  2. Write multiple files and add each one of them in our HTML

Which one do you think is the best option?

...
...
...
...
...
...

The answer is... Well, neither.

If we analyze them, both approaches are quite messy. The first one is messy in the sense that the file's interface gets huge. Just finding something in such a file could be a nightmare! The second one, however, makes a lot of unnecessary HTTP requests. Also, good luck keeping all of those scripts in order. What if you delete one accidentally? You may never notice and keep wondering why your application fell apart!

Bundling gives us the best of both worlds. We can split our code into how many files we wish, but we get to keep only one script tag in the HTML!

Moreover, Webpack improves your website's loading speed by a great deal. Whenever you edit a Javascript file, instead of re-reading the entire file, it only needs to load what is modified, therefore making the whole process faster.

What is the aim of this article?

The aim of this article is to get you to a level at which you can confidently utilize Webpack in your projects. We will take a look at all the concepts we briefly noted before as well as additional ones, so that, by the end of this read you will be able to use this tool at its full potential.

Webpack

Installation

Prerequisites

  • Node.js
  • npm

Let's go ahead and initialize npm, generating the package.json file:

npm init

Now, we can go ahead and install Webpack via the command line:

npm install webpack webpack-cli --save-dev

Let's talk a bit about what's happening here.
We're installing two libraries, webpack and webpack-cli. While webpack is the bundler in itself, cli actually stands for "command line interface" and webpack-cli is the tool used to run Webpack on the command line.

Another keyword that you may be unfamiliar with is --save-dev. This sequence tells the command to save the packages you've just installed into a special category inside the package.json file, namely the 'devDependencies'. This way, people that will end up trying to run your code will know what they need to install beforehand. It's a good thing to have for your project.

If you're a programmer, you're probably already used to abbreviations everywhere. Here is a quicker way to write that command:

npm i -D webpack webpack-cli

This one is pretty self-explanatory. While "i" stands for "install", "-D" stands for "--save-dev"

Great! Now let's talk about the structure we need to have in our project.

Before, we've talked about having input and output files. This is also the criteria based on which we separate our code. We'll create two folders inside the root directory: ./src and ./dist. 'src' stands for source, 'dist' stands for distribution. You probably anticipate where we'll write most of our code.

Go ahead and arrange your directory like this:

|-src
    |-index.js
|-dist
    |-index.html

Let's check if our setup worked!
Go ahead and run the following line in your command line:

npx webpack

This line is what we'll be using to compile our code from now on. Whenever we implement a change, go ahead and run this sequence of code.

Now, if all went well, you should notice that a new file has been generated for you in the 'dist' directory. That file is called main.js by default.

This is how your directory should look right now:

|-src
    |-index.js
|-dist
    |-index.html
    |-main.js

If you'd like to check the results, don't forget to also include the autogenerated file in your HTML:

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

Configuration

Let's go ahead and start the configuration. We'll also build a project in the process to demonstrate different functionalities. You can go ahead and code along if you'd like.

The project is going to be a GIF generator about coding. We'll gloss over any part that doesn't have to do with Webpack rather quickly, since that's not within the scope of our article.

First of all, let's create the configuration file for webpack. We'll place it in the root directory of our project and name it webpack.config.js.

Inputs and outputs

You will now have an empty file.

Although nothing is written in it, it still works because some properties are set by default. We'll now set those properties ourselves to learn a few concepts.

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist')
    }
}

Here, we're defining the index.js from our src folder as an input file, and we're telling Webpack to bundle it into a file called 'main.js', and add it to the 'dist' directory.

Take a bit of time to process this if you need it.

Let's get outside of our comfort zone and change a few things. Here is another way of configuration that is quite common:

const path = require('path');

module.exports = {
    entry: {
        bundle: './src/index.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist')
    }
}

What we're doing here isn't all that different, except that now we're naming the entry file, and then instructing Webpack to get that name and apply it to the output file.

Nicely done! It's time to get started on the project we've been talking about.
We'll be starting with the HTML structure:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="content">
        <div class="gifBox">
            <img class="emoji" src="" />
            <div class="gifContainer">
                <img class="gif" src="" />
            </div>
            <button class="generateGif">Generate GIF</button>
        </div>
    </div>
    <script src="bundle.js"></script>
</body>
</html>

CSS

Next, let's focus on some CSS.

We usually link the CSS through the HTML file by using a link tag. However, in order to bundle it, we know that we need to import it in Javascript. That way, we can even use preprocessors such as SCSS, since they are transpiled to CSS.

Moreover, this kind of linking also gives a slight performance increase.

So let's go ahead and create the CSS file and then import it!

import './styles.css';

Great! Now let's compile to see if all is well.

...
...
...
...
...
...
...
...


Oops...

What is a loader?

A loader is what allows Webpack to transpile other types of files and tells it to add them to the dependency graph and then consume them.

There are multiple types of loaders. We will present the most important ones through the project we're making.

Now, let's focus on CSS.

We need two special loaders for using CSS in our Webpack project: style-loader and css-loader:

npm i -D style-loader css-loader

Let's configure the webpack.config.js file in order to use loaders.

Here is where we'll put all of the loaders:

module.exports = {
    ...
    module: {
        rules: [
            // ( loaders )
        ]
    },
    ...
}

Each loader is going to be an object, like so for CSS:

{
    test: /\.css$/,
    exclude: /node_modules/,
    use: ['style-loader', 'css-loader']
}

What this sequence tells our bundler is that it should test the CSS files in the project except for the ones in the folders that match the node_modules sequence, using the loaders we downloaded before.

Great job! Now we can quickly add some CSS in our newly configured file to make the project look a tad more stylish:

:root {
    --bg: #8ECAE6;
    --secondary: #219EBC;
    --btn: #023047;
    --btnSecondary: #FB8500;
}
* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

.content {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: var(--bg);
}

.gifBox {
    position: relative;
    width: clamp(250px, 40%, 550px);
    height: 350px;
    padding: 0.5em;
    background: var(--secondary);
    border-radius: 15px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
.emoji {
    position: absolute;
    top: 5px;
    left: 5px;
    height: 45px;
}
.gifContainer {
    max-height: 80%;
    display: flex;
    justify-content: center;
    align-items: center;
}
.gif {
    max-width:200px;
    max-height: 300px;
}
.generateGif {
    position: absolute;
    bottom: 10px;
    right: 10px;
    padding: 0.5em;
    color: var(--btnSecondary);
    background: var(--btn);
    border: 1px solid var(--btnSecondary);
    cursor: pointer;
    font-size: 1.1rem;
    font-weight: bold;
}
.generateGif:hover {
    color: var(--btn);
    border-color: var(--btn);
    background: var(--btnSecondary);
    transition: all 350ms ease-in-out;
}

Great!

SCSS

If we want to go a step further and use a preprocessor like SCSS, we will need one additional loader:sass-loader + the sass library.

npm i -D sass-loader sass

Now, you'll have to configure it by adding another object to our rules array:

{
    test: /\.scss$/,
    exclude: /node_modules/
    use: ['style-loader', 'css-loader', 'sass-loader']
}

You can officially use SCSS in your project!

(Keep in mind we don't actually need this for the project we're building now. This is just for general knowledge and future reference)

Images

Now, if you looked at our HTML, you probably noticed we also added an image tag with the class 'emoji'. Let's download a picture and add it there to make the website look a bit more friendly.

If we want Webpack to optimize it, we have to import it in the Javascript, just like we did for the CSS file:

import './styles.css';
import Emoji from './assets/emoji.png';

window.onload = () => {
    document.querySelector('.emoji').src = Emoji;
}

Now, of course, we've learned our lesson that we need to define any outside resource in our configuration file.

We don't need any additional loaders for this task.

Let's make adding image files possible by adding a new object to the rules array in webpack.config.js:

{
    test: /\.(png|jpg|svg|jpeg|gif)$/i,
    exclude: /node_modules/,
    type: 'asset/resource'
}

Alright... now let's compile.

If everything went right, we should see an autogenerated image file in our dist directory.

Man, that's quite a name!

Don't worry, though! We can set a name of our convenience, just like we did for the Javascript distribution file (bundle.js)

Just add this property inside the output object in webpack.config.js:

assetModuleFilename: '[name][ext]',

Now, when we compile the project again, the generated file will have the same name as the source file. A job well done!

Javascript (+ Babel)

We're going to use a popular external API called GIPHY to generate some GIFs about coding.

Let's create another file and define a function specific for getting a new GIF:

generateGIF.js:

export default async function generateGIF() {

    const response = await fetch('https://api.giphy.com/v1/gifs/translate?api_key=(/* insert your own */)=coding');
    
    const data = await response.json();

    return data.data.images.original.url;
}

Perfect! Importing this function into our index.js file:

import generateGIF from './generateGIF.js';

Calling this is going to return a promise. Let's go ahead and call it when the page is loaded and when the generating button is pressed to get the functionality we want.

The index.js file should turn out to be a variant of this:

import './styles.scss';
import Emoji from './assets/emoji.png';

import generateGIF from './generateGIF.js';

const gif = document.querySelector('.gif');
const generateBtn = document.querySelector('.generateGif');

generateBtn.addEventListener('click', function() {
    generateGIF().then(data => {gif.src = data})
})
window.onload = () => {
    document.querySelector('.emoji').src = Emoji;
    generateGIF().then(data => {gif.src = data})
}

Marvelous! Now it should work.

But, as we move ahead with our projects, we also have to think about browser compatibility. Namely, will this project we just built work on all browsers? Keep in mind we just used the async and await keywords, which are not supported yet by some browsers such as Internet Explorer.

Well, what we do know is that Webpack transpiles using loaders! That's what we can do for Javascript, too.

Let's use Babel, a Javascript compiler that will convert our ES6 code to the ES5 version, which is understood by all of the modern browsers.

Using Babel

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

Now let's also configure our file to support this tool, adding it as an object inside the rules array:

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: ['@babel/preset-env']
        }
    }
}

Additional loaders

Now we'll go over some we won't actually add to our project, but we'll list their dependencies and configuration code for further reference:

Fonts
For fonts, we can use the libraries file-loader or url-loader. The first one is mostly aimed at older browsers, supporting fonts with eot, svg and ttf format, while the second one supports more modern ones, such as woff or woff2.

Here is how to do the setup:

npm i -D file-loader url-loader

And then you only have to add it to webpack.config.js:

module: {
    rules: [
        ...,
        {
            test: /\.(woff|woff2|eot|ttf|otf)$/i,
            exclude: /node_modules/,
            type: 'asset/resource'
        },
    ]
}

Data storing

  • We need the loader csv-loader for .csv and .tsv files.
npm i -D csv-loader
module: {
    rules: [
        ...,
        {
            test: /\.(csv|tsv)$/i,
            exclude: /node_modules/,
            use: ['csv-loader']
        },
    ]
}
  • For XML, we need the xml-loader
npm i -D xml-loader
module: {
    rules: [
        ...,
        {
            test: /\.xml$/i,
            exclude: /node_modules/,
            use: ['xml-loader']
        },
    ]
}

And these should be about all the loaders you need in your entire usage of Webpack.

Great! After everything, we've almost finished our first project using Webpack! But hold on, we're not quite done yet! There are a few things we can improve.

Improvements

The clean property

module.exports = {
    output: {
        ...
        clean: true,
    }
}

It's added in the output object. It's a great practice, because it cleans up your 'dist' directory of all the files that aren't needed in the project.

Source maps
If you have coded along or did your own variation of the app, you probably came up with an error and try to do some basic debugging by using a couple of console.logs here and there.

If you did so, you probably saw that, when you check the developer tools, the message in the console doesn't reference the source file, but rather our autogenerated file, or the bundle.js in our case, which can make it a bit hard to debug the app.

To get rid of this behaviour, we can go ahead and set up a source map that will actually tell Webpack to reference our source code instead of the distribution file:

module.exports = {
    ...
    devtools: 'source-map',
    ...
}

Automatic compiling

Now, if you've noticed, as we've worked on this project, we had to type npx webpack every time we wanted to compile our code. It was a bit tedious, wasn't it?

Here's how we can automate that process, so that whenever we change something, Webpack autocompiles.

Let's talk about two popular methods:

  1. Watch Mode
  2. Webpack-dev-server

1. Watch Mode

This is the easiest to set up.

For starters, go to the package.json file and identify the scripts property. Then, define a 'watch' inside of it, like so:

{
    ...
    "scripts": {
        "watch": "webpack --watch"
     },
    ...
}

Now, you can run the below code to start automating the compiling process.

npm run watch

Keep in mind that, using this command, you still need to refresh the page if you want to see any changes done. You just don't have to run the command anymore, because it will run indefinitely until you tell it to stop.

2. Webpack-dev-server

This one is the one I use the most. I feel like it's the best mix of customizable but not too much.

We need to install a special package in order to implement it:

npm i -D webpack-dev-server

Next, we need to define it inside the configuration file:

module.exports = {
    ...
    devServer: {
        static: {
            directory: path.resolve(__dirname, 'dist')
        },
    }
    ...
}

This is the minimum requirement in order for the dev-server to function: we have to tell it where to deploy from. There are, however, additional properties you can set to the devServer object. Here is the setup I like to use:

module.exports = {
    ...
    devServer: {
        static: {
            directory: path.resolve(__dirname, 'dist')
        },
        port: 3000,
        compress: true,
        hot: true,
        open: true,
    }
}

Let's explain each property briefly.

  • 'port:3000' changes the port to 3000
  • 'compress:true' activates gzip compression
  • 'hot:true' activates Hot Module Replacement, which is what allows our page to apply changes without a reload
  • 'open:true' tells Webpack to open the website as soon as the command is run

Good job! We're not quite done though. We just need to define the command inside of scripts in package.json:

{
    "scripts": {
        ...
        "start": "webpack serve",
        ...
    }
}

Now all we have to do is run npm start when we start coding and the rest will be done for us!

Plugins

Plugins are add-ons to Webpack that carry out various tasks through ServiceWorkers.

It would be an impossible task to talk about all of the plugins that can be added to Webpack, but you can find them all in the Webpack documentation.

The configuration process is the same for all of them, however, so we're just going to focus on applying one, arguably, the most popular plugin, called html-webpack-plugin.

If you've analyzed the project really carefully, you probably saw that files in the dist directory tend to be autogenerated, all but one: the index.html.

Moreover, if you tried implementing the clean optimization technique, you were probably quite perplexed when you saw that your index.html was deleted when you compiled your code.
The explanation here is that setting clean to true tells Webpack that, everytime it runs your project, it should just clean the whole directory and only add the necessary stuff. However, nothing actually autogenerates your HTML, so it got deleted and it was never restored.

This is what the html-webpack-plugin does. It generates a HTML file.

Let's implement it together and finish our project once and for all!

First, we have to install it:

npm i -D html-webpack-plugin

Then, we'll import it like we did the path module at the very beginning:

const HTMLWebpackPlugin = require('html-webpack-plugin');

Now, there is a special plugins array we can append to our module.exports object to define our arrays. Here is how we define plugins:

...
module.exports = {
    ...
    plugins: [
        new HTMLWebpackPlugin(),
        ...
    ],
    ...
}

Now, if we run the code, we will find an index.html file in our dist directory.

Yay, right?

But wait...

<!doctype html><html><head><meta charset="utf-8"><title>Webpack App</title><meta name="viewport" content="width=device-width,initial-scale=1"><script defer="defer" src="bundle.js"></script></head><body></body></html>

It gave us a default index.html file. Maybe we should have expected it, after all we didn't specify anything.

But this won't do. We already have a structure we want for our HTML. Don't worry, though. We can customize this plugin using special properties.

We're going to pass in an object containing those properties. Let's go ahead and do that for our project:

...
module.exports = {
    ...
    plugins: [
        new HTMLWebpackPlugin({
            title: 'GIF Generator',
            template: 'src/template.html',
            favicon: 'src/assets/favicon.png'
        }),
    ]
}

Now it should work. We even managed to add a favicon!

Of course, these are just a few of the properties that can be defined here, but probably the properties you will use most of the time along with this plugin.

You can always refer to the official documentation for the entire list of properties if you find it necessary, though (HTMLWebpackPlugin properties)

This is how our webpack.config.js file should look right now:

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

module.exports = {
    entry: {
        bundle: './src/index.js'
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'dist'),
        assetModuleFilename: '[name][ext]',
        clean: true
    },
    devtool: 'source-map',
    devServer: {
        static: {
            directory: path.resolve(__dirname, 'dist')
        },
        open: true,
        compress: true,
        port: 3000,
        hot: true,
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                exclude: /node_modules/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.(png|jpg|svg|jpeg|gif)$/i,
                exclude: /node_modules/,
                type: 'asset/resource'
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            },
        ]
    },
    plugins: [
        new HTMLWebpackPlugin({
            title: 'GIF Generator',
            template: 'src/template.html',
            favicon: 'src/assets/favicon.png'
        }),
    ]
}

If you got this far, I must say congratulations! I'm proud of you. You just finished your first project using Webpack.

Here is a repository with the final version of the project. You can use this as a reference if you come across any issues:
https://github.com/OpenGenus/gif-generator

This is how your project should look like now if you followed all the steps:

Conclusion

This was a really lengthy article. And for good reason.

Hopefully this is useful for you both to start developing and as a future reference, since I've tried to incorporate all of the things that you need to develop a great application with Webpack.

Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.