DEV Community

Bruno Moura
Bruno Moura

Posted on

Why am'I using the webpack tool?

Everyone today is using webpack tool. When we enter the webpack website the first information that we see is,

Webpack image

But, let me step back and ask something… Do I really know what that means?

So, doing a superficial analysis I can understand that we have many assets from same type and then webpack compile into only one, e.g. there are the files a.js, b.js, c.js and then after perform webpack we have only “abc.js”, or “main.js”, whatever. And that’s it. It is what I’am getting from this image.

Webpack is a tool that so many people and companies using today, to understand the reason of its popularity, we have to expose the problem that webpack solves and then we finally can bring light to the darkness.

Let’s begin with “What is HTTP Protocol”?

The word protocol means, the system of rules that produces a expected behavior. According to this, make sense that there is a well defined protocol to exchange information through internet, once any type of data is supported, so a patterned one fits well.

HTTP messages is pure text but a well structured one, becoming it in a protocol.

The HTTP specification can be find here and is held by Internet Engineering Task Force.

Http Server

In short, is a software that serves static assets using HTTP protocol. Is a implementation of the HTTP specification.

Here I will use the npm module http-server, but you are free to choose any other implementation, e.g apache http server, to replicate what is exposed here in this article.

Let’s install the http server,

$ npm i -g http-server

And now start the server,

> ~$ http-server dist/
< Starting up http-server, serving /dist
< Available on:
< http://127.0.0.1:8080
< http://192.168.0.25:8080

in order to server everything under folder dist/ and port 8080. The entry point is dist/index.html.

You can change this behavior, but I will not cover it here ok? :)

Let’s test our http server using a tool called curl, a command line tool,

$ curl http://localhost:8080 -v
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: \*/\*
> 
< HTTP/1.1 200 OK
< server: ecstatic-3.3.2
< cache-control: max-age=3600
< last-modified: Tue, 21 Apr 2020 10:38:02 GMT
< etag: W/"3674320-600-2020-04-21T10:38:02.508Z"
< content-length: 600
< content-type: text/html; charset=UTF-8
< Date: Tue, 21 Apr 2020 10:38:19 GMT
< Connection: keep-alive
< 
<!DOCTYPE html>
<html lang="en">
<head>
    <!-- <link rel="icon" href="http://localhost:8080/favicon.ico?v=2" /> -->
    <meta charset="UTF-8">
    <link rel="stylesheet" href="table/position.css">
    <link rel="stylesheet" href="table/color.css">
    <link rel="stylesheet" href="table/layout.css">
    <script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
    <title>Hello World</title>
</head>
<body>
    <div id="app"></div>
    <script type="module" src="./main.js"></script>
</body>
</html>

Notice that, with curl, only one file is downloaded, the index.html.

Everything that starts with ‘>’ we are sending and everything that starts with ‘<’ we are receiving from the http server, i.e. request and response respectively.

The HTTP Request always will look like that, some header can be added or removed, but the structure will be always that way.

Using a browser client

Let’s make the same request on Google Chrome. Notice the client(browser) request many files to the server in order to display the complete html page.

Some tags into html file, e.g. <script>, <link>, etc, make a new request to the server, and that is the reason in which that we see many request besides that we did.

Chrome dev tools

Notice, when we open the chrome developer tools, in network tab is allowed to see files being downloaded in order to present the page that we requested.

Each file is a entirely new request for the server, note the http headers,

Http headers

And requests takes time to do their job done,

Request processing time

When we type “localhost:8080” into browser toolbar we are requesting a information. The browser know how to deal with HTML language and when it finds a resource that needed by file HTML, the browser make a new request in order to mount the graphic view.

And what about performance? Is the page loading fast enough?

This characteristic is called non-functional requirement, meaning that what limits we have to design, or build, our application. No matter if your site is beautiful and use the latest technologies, if it doesn’t do the things fast enough then we won’t have satisfied users, i.e. users happy with our brand, users are buying in our site, returning to it and speaking well about it as well.

Based on what has been shown so far, what the first thing that we get think in order to get a better performance?

...

Decrease the size of files that will be downloaded by the client.

Minimizing Resources

Minification is the process of removing unnecessary or unnecessary data without affecting the way resources are processed by the browser.

When we write the files, .html for instance, we need do it in a way that we, or any other person, is able to understand and maintain the code. All the comments and indentations is for us humans, not for computers.

Let’s see an example a code structured and commented,

 
<!DOCTYPE html>
<html lang="en">
<head>
    <!-- <link rel="icon" href="http://localhost:8080/favicon.ico?v=2" /> -->
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="table/position.css">
    <link rel="stylesheet" type="text/css" href="table/color.css">
    <link rel="stylesheet" type="text/css" href="table/layout.css">
    
    <!-- importing jquery -->
    <script src="jquery-3.5.0.js"></script>
    <title>Hello World</title>
</head>
<body>
    <div id="app"></div>
    <script type="module" src="./main.js"></script>
</body>
</html>

That way we can understand the code, so it is possible to fix a possible bug or make an improvement.

How was shown above, the browser understand it as well and processes the html file with success.

One point to notice is, a file formatted that way have a size,

Size of index.html in bytes.

Now we’ll minify that same file in which we’ll be deleting comments and indentation, I will use the tool willpeavy.com to minify the assests,

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="table/position.css"> <link rel="stylesheet" type="text/css" href="table/color.css"> <link rel="stylesheet" type="text/css" href="table/layout.css"> <script src="jquery-3.5.0.js"></script> <title>Hello World</title></head><body> <div id="app"></div><script type="module" src="./main.js"></script></body></html>

The browser continues understand and processing the html file,

index.html shown in a browser.

Resulting in a new size,

Size of index.html in bytes.

Following that reasoning line, we can do more. So let’s minify the .js and CSSs files as well.

The .js assets we have 2322,6 bytes,

Size of .js files in bytes.

That minified we have 1615,5 bytes,

Size in bytes of .js assets.

CSSs assets we have tree of them that summed 323 bytes,

Size in bytes of .css assets.

When each is minified we have 104 bytes,

Size in bytes of .css assets minified.

And the browser continues to understand and processes the files successfully,

index.html shown in a browser.

But three CSSs files? Why not only one? This would result in only one request and consequently, less time for the client receive all assets by the server. Let’s put all CSSs contents in one file and called it bundle.css,

table{background-color: aqua;}table, th, td{border: 1px solid black; padding: 10px;}table{margin: 0 auto;}

Size in bytes of .css assets.

As we create a new asset, we need to modify our index.html in order it find that new asset and load. We minify the index.html, so it’s a little more hard to alter that, but we are pros, we’ll get it!

We can revert the minification to a formatted file and then alter or we can alter the file minified as well, whatever, but we need alter it.

Once altered the index.html to point the right path to the new asset the browser continues rendering the page,

Size in bytes of .js assets.

Let’s create another new asset, bundle.js, and put all content of .js files in it,

To do that we will have to deal with some hassles in order to get merge all files in only one and don’t forget that we need modify our index.html again, but we’ll get it! go go go!

Size in bytes of .js assets.

And the application continues to work,

index.html shown in a browser.

And now? What about the time the performance? When we look at the dev tools is possible to see the decrease of size of files and, consequently, the load page time as well.

At the footer of each image there are some informations that means,

  • requests: requests made by the client to the server
  • transferred: bytes transferred over network
  • resources: bytes loaded by the page
  • Finish: time to load all assets and show the page
  • DOMContentLoaded: time to load only the DOM(HTML)
  • Load: time to load the DOM and its dependencies

None of assets were minified or bundled.

Assets were minified but not bundled.

Assets were minified and bundled.

Looking at the informations above, is easy to see that application now is spending less time to load, means that we get a better performance.

But the only constant is change…

And how about now? After all changes we made to get a better performance, how can we get continuous changing things and formatting all the code to reach a better performance? The natural trend the applications is grow, that means more assets for minify and bundling. We won’t be able to deal with because in any moment a simple change would be result in so many time to implement. And if, nevertheless, do we decided use a new tecnology? We will have to deal with that as well.

Webpack to the rescue!

As we just saw, before webpack we had to deal with all these things. We had to know the every detail of our application like file dependencies each other, if a file is being really used, etc.

Webpack is able to bundle any static resource. You are free to build a any tupe of loader.

Just to make sure that we are in the same point, let’s do a very simple usage of webpack with the previously application introduced.

Out of the box, webpack bundle only javascript files.
It searches for index.js into ./src and put the new generated asset, main.js, into /dist
. Let’s see an example.

Our structure project is,

Project structure

Within the folder withwebpack, firstly we need rename the file main.js to index.js in order webpack get its job done,

$ mv src/main.js src/index.js
Enter fullscreen mode Exit fullscreen mode

main.js renamed to index.js

After, we will init a node project and install the two modules needed by webpack,

$ npm init -y && npm i webpack webpack-cli --save-dev
Enter fullscreen mode Exit fullscreen mode

Three new artifacts will appeared,

Alt Text

And now, run webpack,

$ npx webpack
Enter fullscreen mode Exit fullscreen mode

And that’s it. The output will be within the /dist.

Alt Text

That way we get keep our structure organized and we don’t need to deal with by ourselves all minification and bundling hassles and sure, we gain a better performance.

When we open the main.js, we can see a lot of javascript code. Analyzing the first part, we can see how webpack solves the transpilation, translation/compilation, in order to fit for the older browsers’ compabilities.

In the last part we find our javascript code, slightly altered but not its functionality, in order to fits in the generated bundle by webpack. Let’s see how it turned out,

function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";r.r(t);class n{constructor(){this._field=""}tr(e){return this._field=`${e}`,this}create(){return this._field}}class i{constructor(){this._field=""}td(e){return this._field=`${e}`,this}create(){return this._field}}class o{constructor(){this._field=""}th(e){return this._field=`${e}`,this}create(){return this._field}}let u=new class{constructor(e,t){this._rows=[];let r="";e.map(e=>{r+=(new o).th(e).create()}),this._rows.push((new n).tr(r).create()),t.map(e=>{let t="";Object.keys(e).map(r=>t+=(new i).td(e[r]).create()),this._rows.push((new n).tr(t).create())})}create(){return`
\n ${this._rows.join("")}\n
`}}(["Name","Age"],[{name:"Bruno",age:33},{name:"Daniela",age:32}]).create();document.querySelector("#app").innerHTML=u}]);

Our code starts around when the word class appears for the first time. Here we have only javascript code.

And the CSSs?

As I said before, webpack is a resource’s bundler and we just need teach him to bundle a resource type(loader) and what resource(file) it’ll bundle.

In order to bundle the CSS files, we must install a new loader,

$ npm i css-loader --save-dev
Enter fullscreen mode Exit fullscreen mode

Always we need a alike behavior from “out of the box”, webpack requires a configuration. We get that using the file webpack.config.js, so let create it,

const path = require('path');

module.exports = {
    entry: [
        './src/index.js',
        './src/table/color.css',
        './src/table/position.css',
        './src/table/layout.css',
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            
            {
                test: /\.css$/,
                use: [ 'css-loader', ],
            }
        ]
    }
}

The entry node means the start point that webpack will create its dependency graph. I configured the index.js as well because we need it to build the table within html file and the three css files that exists in the project, because there isn’t dependency between them.

Actually, in real world, I think that isn’t used, at least I have never seen. Later, I will show how to use css files with import reserved word within a javascript file.

The module node define how each module will be treated. Here I defined that every file .css will be transpiled with css-loader, means that teaching to webpack how to deal with css’ particularities and put it into generated bundle, just it, the css won’t be applied to any file. When we run webpack and start the http server, the result is our table without style,

An html page without style.

But the css is contained within the bundle.js,

bundle.js file

So that we can inject the css into the DOM we need install another loader, style-loader.

$ npm i style-loader --save-dev
Enter fullscreen mode Exit fullscreen mode

Configuring it in webpack.config.js,

const path = require('path');

module.exports = {
    entry: [
        './src/index.js',
        './src/table/color.css',
        './src/table/position.css',
        './src/table/layout.css',
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            
            {
                test: /\.css$/,
                use: [ 'style-loader', 'css-loader', ],
            }
        ]
    }
}

Pay atention the loaders’ order. They are performed from right to left, thus the css-loader will be run first and the style-loader will be the next, i.e. the CSSs will be translated to be then will be injected within the html.

Restarting the server,

$ http-server dist/
Starting up http-server, serving dist/
Available on:
  http://127.0.0.1:8080
  http://192.168.0.13:8080
Hit CTRL-C to stop the server

Refreshing the page,

An html page with style.

And there it is!

But let’s get better the configuration in webpack.config.js file, removing all three css entries,

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            
            {
                test: /\.css$/,
                use: [ 'style-loader', 'css-loader', ],
            }
        ]
    }
}

And importing them into Table.js file,

import Row from './Row.js';
import Column from './Column.js';
import Header from './Header.js';

import color from './color.css';
import position from './position.css';
import layout from './layout.css';

export default class Table {
    constructor(tableHeader, tableData){
        this._rows = [];
        
        let headersRow = '';
        tableHeader.map( header => {
            headersRow += new Header().th(header).create();
        });
        this._rows.push(new Row().tr(headersRow).create());

        tableData.map(data => {
            let dataRow = '';
            Object.keys(data).map( field => dataRow += new Column().td(data[field]).create());
            this._rows.push( new Row().tr(dataRow).create() );
        });
    }

    create(){
        return `<table>
            ${this._rows.join('')}
        </table>`;
    }
}

Running webpack again and restaring the server, the result is the same before,

An html page with style.

Minify CSSs file

In order to extract the css’s code from js file and put it in a totally new file, we’ll use the plugin mini-css-extract-plugin,

Install it,

$ npm i mini-css-extract-plugin --save-dev
Enter fullscreen mode Exit fullscreen mode

About the Plugins, while a loader teach webpack how to deal with a specific file type, plugin do things like minify, remove code’s comments and spaces, etc.

Let’s to alter our webpack.config.js,


const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

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

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

And when we run webpack again, the result is a new ./dist/main.css file bundling the all three css files,

/* 
 * file: color.css 
 *
 * Sets the table's colors 
 */
table {
    background-color: aqua;
};
/* 
 * file: position.css 
 *
 * Sets the table's position on a screen 
 */
table {
    margin: 0 auto;
};
/* 
 * file: layout.css 
 *
 * Sets the table's layout 
 */
table, th, td {
    border: 1px solid black;
    padding: 10px;
};

Now we’ll minify and remove the comments, let’s install another plugin,

$ npm i optimize-css-assets-webpack-plugin --save-dev
Enter fullscreen mode Exit fullscreen mode

Modifying the webpack.config.js,

const path = require('path');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
    plugins: [        
        new MiniCssExtractPlugin(),
        new OptimizeCSSAssetsPlugin({
            cssProcessorPluginOptions: {
                discardComments: { removeAll: true },
            },
        }),
    ],
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [

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

Running,

$ npx webpack
Enter fullscreen mode Exit fullscreen mode

And we get the ./dist/main.css minified and without comments,

table,td,th{border:1px solid #000;padding:10px}table{background-color:#0ff;margin:0 auto}
Enter fullscreen mode Exit fullscreen mode

And again, the application continue to work,

Html page without style.

The style is gone, once we ask to webpack to generate a new file, so it have to be requested by the html page as well, however we didn’t do that.

Adding the link tag into our index.html,

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="main.css">
    <meta charset="UTF-8">
    <title>Hello World</title>
</head>
<body>
    <div id="app"></div>
    <script type="module" src="bundle.js"></script>
</body>
</html>

Refreshing the html page,

Alt Text

So if we create a new asset, means that every time we have to deal with it? We’ll need to put it manually into index.html?

Index.html with all its dependencies auto-generated

Installing html-webpack-plugin simplifies our life because the all assets needed by html will be referenced by webpack into index.html. Let’s install it,

$ npm i html-webpack-plugin --save-dev
Enter fullscreen mode Exit fullscreen mode

and setup it in webpack.config.js specifying the template in which we already have been using,

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        new MiniCssExtractPlugin(),
        new OptimizeCSSAssetsPlugin({
            cssProcessorPluginOptions: {
                discardComments: { removeAll: true },
            },
        }),
    ],
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [

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

Running,

  $ npx webpack
Enter fullscreen mode Exit fullscreen mode

Starting the server and opening the browser,

Alt Text

When we compare the spending time with and without webpack we have practically the same result in time and file’s size but, as well, we have the all facilities that webpack provide for us, no headache when evolving and keep the application.

Conclusion

And that’s it. All assets generated here is available on my github.
What I’am showing here is just a basic using of webpack, but my goal here is let you more comfortable when using that incredible tool that is very requisited by many companies out there.
Don’t stop here, go to visit the webpack.js.org to know more the tool and have fun!

Useful links:

Why webpack

Dependency Graph

Asset Management

Top comments (0)