Idiot Note: Webpack

Today, we'll be talking about one of the greatest tools in web development, Webpack. There's so much to learn about Webpack, hence this will only serve as an introduction.

What is it for?

Webpack is a static module bundler for Javascript applications. Essentially, it bundles Javascript files (and other files like CSS, images, etc.) into one or more bundled files so that they could be used in a browser. Webpack also helps to manage all dependencies of your Javascript files. In this regard, Webpack is quite similar to browserify. 

Why use it?

One thing that makes Webpack interesting is that it forces modularity within your projects. For example, let's say if you have two separate Javascript files: main.js (main script) and button.js (for managing buttons). main.js is included after and dependent on button.js. The problem with this situation is that the dependency is implicit and if button.js is missing, the program wouldn't work. Webpack helps you by forcing to explicitly declare such dependency

How to use it?

Suppose we have a simple web application that has an image and a button. The project structure is like this:

.
    ├── src
    │   ├── js
    │   |   ├── main.js            # main page's script
    │   |   ├── button.js          # custom button's script
    │   ├── css
    │   |   ├── main.css           # main page's style
    │   |   ├── button.css         # custom button's style
    │   ├── img
    │   |   ├── logo.jpg           
    ├── package.json         #
    └── index.html           # main page

Here are the contents of each file:

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Webpack Example</title>
    
        <!-- include styles -->
        <link rel="stylesheet" href="src/css/main.css"/>
        <link rel="stylesheet" href="src/css/button.css"/>
    </head>
    <body>
        <div id="app"></div>

        <!-- include scripts -->
        <script type="text/javascript" src="src/js/button.js"></script>   
        <script type="text/javascript" src="src/js/main.js"></script>
    </body>
</html>

button.js

function MyButton(text) {
    let btn = document.createElement('button');
    btn.innerHTML = text;
    btn.classList.add('my-button');

    return btn;
}

main.js

function app() {
    let element = document.createElement('div');
    element.classList.add('container');

    let img = new Image();
    img.src = './src/img/logo.jpg';
    element.appendChild(img);

    // main.js use 'MyButton()' function that is declared on button.js file
    let btn = MyButton('Click me');
    element.appendChild(btn);

    return element;
}

document.getElementById('app').appendChild(app());

button.css

.my-button {
    border: none;
    padding: 5px 10px;
    background: blue;
    color: white;
}

main.css

.container {
    padding: 10px;
    width: 200px;
    border: 1px solid black;
}
.container img {
    width: 50px;
    height: 50px;
}

logo.jpg

logo

If you open index.html, you'll get a result like this:  

The problem with this is:

  1. main.js implicitly dependent to MyButton() function in button.js. If button.js is not present main.js would not work.
  2. If main.js is included before button.js, then the program also wouldn't work.
  3. As your project gets larger, you might include all your CSS and Javascript files to your page even though some of them are not used.

We could fix this situation with Webpack. Here are the steps:

  1. Install webpack and webpack-cli to your project.
    npm install webpack webpack-cli --save-dev​
  2. To bundle CSS and image files, we need to install Webpack loaders. Loaders help to bundle non-JS files (fonts, images, CSS, etc.)
    // for CSS files
    npm install css-loader style-loader --save-dev
    
    // for image files
    npm install file-loader --save-dev​
  3. Next, we'll refactor the project into components. Our new project structure will look like this: 
    .
        ├── src
        │   ├── main.js                   # main page's script
        │   ├── main.css                  # main page's style
        │   ├── img
        │   |   ├── logo.jpg           
        │   ├── button
        │   |   ├── button.js             # custom button's script
        │   |   ├── button.css            # custom button's style
        ├── package.json         #
        └── index.html           # main page​

    We move button.js and button.css to a new folder. And move main.js and main.css to root folder. Now, that the custom button codes are isolated in the /button folder, you can easily use the custom button in another project.

  4. Now, we'll update the content of each Javascript file.
    main.js
    This is the new content main.js. Now, each dependency is explicitly stated (JS, CSS, and image). 
    // import custom button
    import MyButton from './button/button.js';
    // import CSS style
    import './main.css';
    // import image
    import Logo from './img/logo.jpg';
    
    function app() {
        let element = document.createElement('div');
        element.classList.add('container');
    
        let img = new Image();
        img.src = Logo; 
        element.appendChild(img);
    
        let btn = MyButton('Click me');
        element.appendChild(btn);
    
        return element;
    }
    
    document.getElementById('app').appendChild(app());​

    button.js
    In button.js, we import the CSS file and export MyButton() function. 

    // import CSS style
    import './button.css';
    
    // export this function
    export default function MyButton(text) {
        let btn = document.createElement('button');
        btn.innerHTML = text;
        btn.classList.add('my-button');
    
        return btn;
    }
  5. Now, we need to bundle all these source files. We need to create a webpack.config.js file in the root folder to do so. 

    // webpack.config.js
    const path = require('path');
    
    module.exports = {
        entry: './src/main.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist')
        },
        module: {
            rules: [
                {
                    test: /\.css$/,
                    use: [
                        'style-loader',
                        'css-loader'
                    ]
                },
                {    
                    test: /\.(png|svg|jpg|gif)$/,
                    use: [
                        'file-loader'
                    ]
                },
            ]
        }
    };

    This configuration will read code starting from main.js (entry file) and bundles all files into a new file, bundle.js. The module property is used to configure the loaders we use, namely for loading CSS and image files.

  6. Run Webpack command to start the bundling process.

    npx webpack --config webpack.config.js
  7. Move index.html to dist folder and update its content like this:
    <!DOCTYPE html>
    <html>
        <head>
            <title>Webpack Example</title>
        </head>
        <body>
            <div id="app"></div>
    
            <!-- include scripts -->
            <script type="text/javascript" src="./bundle.js"></script>
        </body>
    </html>​
  8.  You're done! All your codes are nicely encapsulated into modules and bundled into one file (bundle.js).

This is the final project structure: 

.
    ├── dist
    │   ├── bundle.js                                            # the bundle file
    │   ├── e7643af462351cd46b1d9a23c8530283.jpg                 # the logo image (the image's filename is changed by webpack)
    │   ├── index.html                                           # the main page
    ├── src
    │   ├── main.js                   # main page's script
    │   ├── main.css                  # main page's style
    │   ├── img
    │   |   ├── logo.jpg           
    │   ├── button
    │   |   ├── button.js             # custom button's script
    │   |   ├── button.css            # custom button's style
    ├── package.json     
    ├── webpack.config.js

 

That's it for now!

P.S.

Starting 2019 (next week), Idiot Note will be posted every 2 weeks. The next one will be on 13th Jan. See ya!