Webpack — why, what, and how

Rowadz
Edraak Engineering
Published in
8 min readNov 29, 2020

Giving webpack a small look

Photo by Nick Cooper on Unsplash

A background on modules — Simple example on why we might use webpack

JavaScript programs started off pretty small — most of its usage in the early days was to do isolated scripting tasks, providing a bit of interactivity to your web pages where needed, so large scripts were generally not needed. Fast forward a few years and we now have complete applications being run in browsers with a lot of JavaScript, as well as JavaScript being used in other contexts (Node.js, for example).

It has therefore made sense in recent years to start thinking about providing mechanisms for splitting JavaScript programs up into separate modules that can be imported when needed. Node.js has had this ability for a long time, and there are a number of JavaScript libraries and frameworks that enable module usage (for example, other CommonJS and AMD-based module systems like RequireJS, and more recently Webpack and Babel).

source

In other simple words if you are running JavaScript in Node.js context you can split up your code into multiple files (modules) and include them in each other, for example:

// ./main.jsconst func = require('./util/funcs.js')
// ./util/funcs.jsmodule.exports = () => {
// code that dose stuff
}

In the above example, we have two files ./main.js and ./util.funcs.js and we exported an arrow function from the funcs.js file and imported it with the name func into main.js.

But remember this will run in Node.js context and if you want to run the code from main.js in the context of a browser, you might include it in a script tag like so:

// ./index.html
<html>
<head></head>
<body>
<script src="./main.js"></script>
</body>
</html>

But when you open your index.html in your browser, you will see the following error message

The error message if you tried to use the require function in a browser

That happened because the require function does not exist in any browser, and to solve this we can use webpack.

The above issue is one of the reasons why we might use webpack and other reasons:

  • using webpack allows us to use NPM packages inside our frontend application (NPM packages were made for Node.js)
  • we can use it to compile and build react applications to make them run inside browsers ( since browsers can’t understand jsx )

What Is Webpack

At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles.

source

To make this simpler webpack will look into your js code and see all the file that depend on each other and build a dependency graph and by building this it can bundle your js code (put the code for the files that depend on each other in the same output file)

So in our example above main.js depends on funcs.js so webpack will put the code from funcs.js into main.js ( roughly speaking ).

Then after bundling the js code webpack will output a file that includes all the bundled (combined) code which you can put into your <script> tag.

How to use Webpack

Before starting using webpack we need to have Node.js and NPM installed on our machine, and that’s because:

  • webpack runs in a Node.js context
  • to install it we use NPM

Let’s start by creating an NPM project, go to any directory you want then run the following command in your terminal

$ npm init -y

this will generate a pacakge.json inside the directory and the -y is to answer “yes” to all the questions npm will ask us.

after running npm init -y

now after we have our package.json file we can install any npm package we want so let’s install webpack, webpack-cli and rxjs

$ npm i webpack webpack-cli -D

-D means it’s a Develpment dependency which is a way to say that this package help automates and develop the application not to build the actual functionality for it.

webpack will use webpack-cli that’s why we installed it as well

$ npm i rxjs

now let’s create the following empty files [main.jsindex.htmlutil/funcs.js], our project will look like this:

.
├── index.html
├── main.js
├── package-lock.json
├── package.json
└── util
└── funcs.js
└── node_modules

webpack and rxjs are inside node_modules

now to hook webpack we need to create an npm script that runs it from the node_modules folder, so in your package.json change the script object to look like this

"scripts": {
"dev": "webpack --config webpack.config.js"
}

by adding this npm script, we can run npm run dev to tell webpack to bundle our project based on the configuration we will add inside a file called webpack.config.js.

here is the webpack.config.js file and in the next points I’ll explain each part of it.

const { join } = require('path')module.exports = {
entry: {
main: join(__dirname, 'main.js'),
},
output: {
filename: '[name].js',
path: join(__dirname, 'dist'),
}
}

Remember that webpack runs in a nodejs context so I can use the require function to load the join function from the path built-in module in nodejs ( it’s a function that makes it easy to build file system paths without worrying about the operating system or / \ characters)

Your webpack file should export an object that contains information about how webpack should build your files ( if you think about it webpack will read this object by requiring your file )

In that object we have 2 properties:

  • entry: this is where webpack will start to build the dependency graph, you can have multiple entry points and for each entry point webpack will create an output file.
  • output: this is the configuration for each output file webpack will do, so each file will be named as the value from filename property which is [name].js and this [name] is a placeholder that points to the current file being processed, so in our case we will have a file called main.js as an output file, and the path property is where to put the bundled files.

__dirname is always the directory in which the currently executing script resides

so let’s put the following code in util/funcs.js

export const sayHi = () => {
alert('hi')
}
export const sayBye = () => {
alert('bye')
}

note that we are using ES6 module system ( feel free to use Commonjs (require) or ES6 modules )

in our main.js

import { sayBye, sayHi } from './util/funcs'sayHi()
sayBye()

in our index.html

<!DOCTYPE html>
<html lang="en">
<head>
<title>webpack</title>
</head>
<body>
<script src="./dist/main.js"></script>
</body>
</html>

now run npm run dev you will see the following output

after running npm run dev

go to ./dist/main.js you will notice that all the dependency code have been added to the file!

./dist/main.js content

(()=>{"use strict";alert("hi"),alert("bye")})();

now if you opened your index.html you will have 2 alerts popping up.

Remember that we installed RxJs so let’s import a couple of functions from it and bundle our js again

our util/funcs.js will stay the same and I’ll create a new file util/rxjs.js with the following content

import { delay } from 'rxjs/operators'
import { of } from 'rxjs'
export const delayedHi = of('hi..').pipe(delay(2000))

and in our min.js

import { sayBye, sayHi } from './util/funcs'
import { delayedHi } from './util/rxjs'
import { repeat } from 'rxjs/operators'
sayHi()
sayBye()
delayedHi.pipe(repeat(3)).subscribe(console.log)

Now run npm run dev and open the index.html in a browser, you will see that everything works fine, and this is a good enough intro for webpack and how to bundle your project.

give your ./dist/main.js a look, cool right?

Notice here that we created our own ES6 modules util/funcs.js and imported functions from node_modules (Rxjs) and run all of that after bundling on a browser which is very cool.

if you writing the code with this atricle you might notcied that writing npm run dev over and over again is annoying, to fix it just put watch: true in the webpack exported object

Since we moved the basic out of the way, let’s talk more about some intermediate subjects in webpack

Webpack Loaders

Out of the box, webpack only understands JavaScript and JSON files. Loaders allow webpack to process other types of files and convert them into valid modules that can be consumed by your application and added to the dependency graph.

If you read that carefully, it means we can import another type of files using loaders for example

  • SASS loader allows you to load your scss into js and the styles will apply to the DOM ( you might have seen this in react)
  • URL loader allows you to load images into your js and it does that by converting the images to base64 while bundling

There are a lot of loaders for webpack which allows you to customize it in a very unique way, and I’ll give you now an example of using the SASS loader.

Using SASS loader ( import our scss files to our js code )

we’ll work on the same project and create a file in the following path scss/main.scss and add the following content to it

$color: #000;
body {
background: $color;
h1 {
font-size: 200px;
}
}

now install the SASS loader, dart-sass (which will be used by the SASS loader), style-loader and css-loader

$ npm install sass-loader sass style-loader css-loader -D

then go to your webpack.config.js and add the code for the loader

const { join } = require('path')module.exports = {
entry: {
main: join(__dirname, 'main.js'),
},
output: {
filename: '[name].js',
path: join(__dirname, 'dist'),
},
module: {
rules: [{
test: /\.s[ac]ss$/i,
use: [
'style-loader',
'css-loader',
'sass-loader',
]
}]
}
}

Notice the only change is inside the module property and for each loader you have you will put its rules there inside the module.rules array

Now for each file that is being processed by webpack and matches the regex in the test property webpack will pass it to the correct loader in the use array

Here is the purpose of each one of these loaders

  • style-loader: Inject CSS into the DOM.
  • css-loader: will resolve your imported css files
  • sass-loader: Loads a Sass/SCSS file and compiles it to CSS

now go to your main.js and import the main.scss file

import { sayBye, sayHi } from './util/funcs'
import { delayedHi } from './util/rxjs'
import { repeat } from 'rxjs/operators'
import './scss/main.scss'
sayHi()
sayBye()
delayedHi.pipe(repeat(3)).subscribe(console.log)

now run npm run dev

you will see that the styles from the .scss file has been applied.

Another important loader is babel-loader which compiles your js code to old versions so it will run in almost all browsers ( I think you can add this loader by yourself now )

Other Webpack concepts

we barely scratched the surface of webpack features and I recommend that you read more about

End & Final Notes

The good news is that modern browsers have started to support module functionality natively, [….]. This can only be a good thing — browsers can optimize loading of modules, making it more efficient than having to use a library and do all of that extra client-side processing and extra round trips.

— read more about this here and give the support chart a look here

--

--