What are bundlers?

No matter if we realize it or not, when we start a new web project today, we are almost certainly using a "bundler." Most of the time, we don't even need to think much about what the bundler is doing, but eventually, there comes a point where a deeper understanding can be helpful. We want to explore why bundlers have become almost ubiquitous in the web world, what their purpose is, and how they impact our daily development work.

Backstory - From 2000-line Files, to "Task Runners" to Bundlers

To understand where bundlers come from, we need to jump back about 20 years. Back then, we implemented web projects with the following folder structure:

/project
- assets/
  - main.css
  - main.js
  - jquery.js
- index.html
- contact.html
- about-us.html

So, we had a handful of HTML files and linked a few static assets from them, such as a global CSS file or selected JavaScript libraries like jQuery.

As project complexity increased, the file structure also became more complex. More pages, code that needed to be shared, code that should only run on a specific page, and many other interesting use cases.

Task Runners to Transform Our Code

It quickly became clear that we no longer wanted to work with just one JavaScript file or one CSS file. So, we started splitting our code into multiple files. However, back then (before HTTP 2), we had to send a separate request to our server for each file, so each additional file added some overhead. Thus, we began developing tools that would gather all the CSS files in the project and combine them into one large file. This is how task runners like Gulp were born. These tools, however, did their work with relatively little intelligence and usually just concatenated all the files from the project and maybe compressed them, so users had to download fewer kilobytes.

But with this step, a barrier was broken: the code we had to write in the project was no longer necessarily the same code that was executed in the browser. This allowed us to make our lives easier with tools without browsers or users having to notice. This led to a rapid development of libraries that were never meant to be executed at runtime, such as the following:

  1. Less, to make writing CSS easier
  2. CoffeeScript, to make writing JS easier
  3. Browserify, which for the first time allowed us to make dependencies between files and libraries usable in the browser

Browserify was particularly exciting: By analyzing the require() calls (predecessor of today's ES Modules and the import functionality) in our source code on our machine, we could apply specific tools to certain imports, such as converting modern JavaScript syntax into syntax that the browser could understand. And this approach expanded.

JavaScript at the Center of Web Development

JavaScript increasingly moved to the center of web development. Build tools were written in JavaScript, we used JavaScript to define our source code transformation steps, and we defined our packages and dependencies in JavaScript. Tools that understood these dependencies became more and more powerful. From this came the first fully-fledged bundlers, such as Webpack or Vite. These took a JavaScript file as an entry point and analyzed all the imports made from our entry file. They quickly supported other file formats as well, such as images, CSS files, or fonts, so that the bundler really controlled every file that was eventually served to the user.

Today, a common project structure looks like this:

/project
- src/
  - components/
    - Button.js
    - Button.css
    - ButtonIcon.png
  - App.js 
- dist/
  - bundle.js
  - a23b23c.png
  - bundle.css

So we have our source code folder, push it through the bundler machinery, and get a streamlined dist (for "distribution") or build folder, which is then served by our web server to the users.

Bundlers in Development

Today, we use bundlers not just to deliver files to our end users. Even in development, we launch tools like Vite that inspect our source code. They now even include web servers that make our code available to the browser, so we no longer open the files directly from the file system but through the localhost URL. They also only transform our files when they are really needed, speeding up development cycles because the whole project doesn't need to be fully built and optimized before I can see results in the browser.

Conclusion: Bundlers Are "Here to Stay"

A world without bundlers is now almost unimaginable. We enjoy so many benefits from tools like Tailwind or TypeScript, which are all based on analyzing and transforming our code before it can be executed in the browser. Therefore, it is clear that bundlers will continue to be our constant companion in the future, even if the tools may become more and more "invisible" as they are hidden in the IDE or behind libraries like Next.js.