An introduction to webpack’s philosophy

Aggelos Arvanitakis
webmonkeys
Published in
7 min readApr 18, 2017

Much has been said about Webpack but I constantly come across people who are unaware, not only of its existence but of its actual purpose as well. In this article I’ll try and explain to you why Webpack exists, why is it needed and how can you benefit from integrating it with your project.

Introduction

In the past few years, the web development industry has seen an ever increasing rise in Single Page Applications (SPAs). Single page applications, much like offline applications that you would install on a computer, offer full fledged functionality to the user. Unfortunately, as we know, the more functionalities you add, the more code you must write and, considering the average complexity of SPAs, eventually you will reach a point where your codebase may consist of hundreds, if not thousands of .js, .css, and many other files.

So the following problem arises: how do you manage a codebase of this size? How do you manage the load order of the files/modules that make your application work when you have so many of them? In times of old, this was considerably easier. You would be putting script tags for those files in a certain order in your html file’s body. So, for example, if file B needed to be loaded after file A and file C needed to be loaded after both file A and B were loaded, you would probably do something like that:

<html>
<body>


<script type='text/javascript' src='path/to/A.js'></script>
<script type='text/javascript' src='path/to/B.js'></script>
<script type='text/javascript' src='path/to/C.js'></scrip
</body>
</html>

As applications got more and more complex though these files started growing in number and the dependencies started being constantly more and more complicated. So, what if your file B now also needed to be loaded after another file D, which in turn needed to be loaded after another file E and file A also needed to be loaded after file D? You probably got a little lost already, and we only have 5 files so far.

Try and imagine the dependency relations between those files though. Write them down if that helps you. The construct that you will end up with, is what we call a dependency graph; that is essentially a tree that describes the order that the files should be loaded.

Maintaining this graph manually is a hard task, as you realized already. When you create a new file you‘ll have to know where should the load occur, within the loading chain of scripts, as well as which external files it depends upon and eventually where should the <script … ></script> be placed within the .html files.

On top of that, good practices tell us that we should create a single .js file (or as few as possible anyway) out of all these separate A.js, B.js, C.js etc. to avoid the cost of multiple HTTP load-requests for each .js file. This is a process known as bundling where different files are bundled into a single file while maintaining their dependency order. Doing that by yourself will require a lot of copy & pasting each time you create or alter a .js file, while simultaneously risking making mistakes.

Enter Webpack.

What is Webpack & why do I need it

Simply put, Webpack is a tool whose main purpose is to put together your various .js files into however many bundles you want, while making sure that the outputted bundles contain all the .js files of your project in the correct order. That’s it.

How does it work & how can I use it?

In order to use Webpack there is a single big requirement; your JavaScript must be organized in a module format. This means that each file must explicitly require all the other .js files that it depends upon and potentially make some items available to other .js files. With old-school techniques, there is no way of declaring file dependency apart from saying “Hey I’ll make sure that the script tag of A.js will get loaded before the script tag of B.js”.

In the modern world though there are mainly 3 different techniques of doing that:

a) CommonJS,
b) AMD modules
c) ES6 modules

Whether you are creating a project from scratch or you already have an ongoing project, you’ll have to structure it in such a way, as to comply with one of the above. At this point, i should mention that with the release of ES6 (or ES2015 or “the new version of JavaScript”), commonJS and AMD patterns are considered mostly deprecated, so unless you have a good reason of picking either of these, i would urge you to stick with ES6 modules when creating a new project, since that’s where the market is slowly shifting towards.

So, as we said earlier, each file must explicitly describe what it needs as inputs and what it will make available to other files. Whatever is not made available to other files, stays within the confines of the file it was declared in and will never be exposed. In a pseudo-language, this would look something like that:

// make sure function "add" from file "add.js" has loaded// make sure variables "number1" and "number2" from file
// "numbers.js" have loaded
// variable X = add(number1, number2)// make X available to other JS files

So how would you be requiring other files? Well first of all you would need to scrap all of your <script … ></script> tags within the .html files. After that you would need to inform all of your .js files of their loading priority, which would translate to “Hey you need to be loaded after another file loads first”. In the module format world, requirement of files translates to a requirement of variables, functions, components, objects, etc., and it would resemble something close to the following:

In ES6:

// add.js
export default function add (a,b) {
return a + b
};
// numbers.js
export const number1 = 3;
export const number2 = 5;
// whatever.js
import add from './path/to/add.js';
import { number1, number2 } from './path/to/numbers.js';
const X = add(number1, number2);export const X;

In CommonJS:

// add.js
module.exports = {
add: function(a,b) {
return a + b
}
}
// numbers.js
module.exports = {
number1: 3,
number2: 5
};
// whatever.js
var math = require('./path/to/add.js');
var numbers = require('./path/to/numbers.js')

module.exports = {
X: math.add(numbers.number1, numbers.number2);
};

In AMD:

// add.js
define([] , function () {
return {
add: function(a, b) {
return a + b
}
});
// numbers.js
define([] , function () {
return {
number1: 3,
number2: 5
});

// whatever.js
define([
'./path/to/add',
'./path/to/numbers'
] , function (math, numbers) {
var X = math.add(numbers.number1, numbers.number2);
return X;
});

The statements above, define add.js and variables.js as a dependency for our whatever.js. With that in mind, .js files have dependencies that fully describe what needs to be loaded, calculated or ran before they themselves can be loaded or ran. Webpack now takes these statements and tries to create a directed acyclic graph, which in plain English means that it tries to put all dependencies in order without any cycles. Having a cycle would mean that file A depends on file B and file B depends on file A, making the bundling impossible. If no cycles are detected however, Webpack is able to take the contents of all those files and inject them into a single .js file with the correct order.

But how does Webpack know which files to bundle, since there are no <script> tags present anywhere? The answer is that there is simply no way of knowing it. That’s why we need to declare a file that will work as an entry point. Webpack would describe the entry point as such: “Point me to a file so that when I recursively traverse its requirements, then everything that your project might need would have been imported”. In a modern case scenario that would be the .js file that holds the core/base component of your application. In an older case scenario that would be the file that needs to be loaded after every other .js file has been loaded. In our specific example from above, the entry point would be the whatever.js file.

As a final step, you only need to add a single <script> tag that will point to the bundled .js file that Webpack produced for us.

Final Notes

Although there are alternatives to Webpack (i.e. Browserify), Webpack is currently the most popular choice, not only due to the amount of developers actively supporting it, but also due to the number of additional functionality that’s available for it, in the form of loaders and plugins. With them you can easily run different processes both before and after Webpack has bundled your files. This allows you to easily build a static asset pipeline similar to the following:

  1. Have your .js files translated from ES6 to ES5.
  2. Have your .less, .scss or .sass files translated into plain .css.
  3. Have all of your files minified (to reduce file size).
  4. Have all of the comments removed from all the files (to further reduce file size).
  5. Have all of your custom fonts & icons placed within the outputted .js file(s) (in order to be instantly available without delay & without additional HTTP requests).
  6. Have all your custom code be placed in a single .js file and all of your libraries be placed into a separate .js file (in order to be cached into the browser since the latter will hardly ever change in a stable project).
  7. Have all of your .css bundled together in a single file.
  8. Have all the outputted files be automatically versioned (to avoid browser caching issues when contents have changed).
  9. Have all of the outputted files automatically injected as <link> or <script> tags into your .html file (so you don’t have to manually change the tags each time the versions of the files change).

In the following weeks, an article with a step-by-step webpack 2 configuration will be available, so if you enjoyed this article i’m sure you’ll find the next one very interesting.

Thanks for reading :)

P.S. 👋 Hi, I’m Aggelos! If you liked this, consider to following me on twitter and sharing the story with your developer friends 😀

--

--