DEV Community

Cover image for Why do we use JavaScript bundlers?
Marcin Wosinek for How to dev

Posted on • Originally published at how-to.dev

Why do we use JavaScript bundlers?

JavaScript is an interpreted language and doesn’t need compilation. Your browser can execute the same code that you write. So why do we use JavaScript bundlers?

Fewer JS files

Historically, the number of JS files used by a website was crucial because of the performance penalty of having many small files. Browsers loaded each file with a separate HTTP request. Every request needed a connection between the browser and server, and those took time to establish. Thanks to HTTP/2, the number of files is much less of an issue now. Still, having files bundled together makes sense. Each request is cached separately, so having plenty of files makes it more challenging to ensure that the browser doesn’t get stale code from the cache.

Besides that, until 2018, many browsers were not supporting ES modules. You were just loading many files from the HTML, and they all shared the same global scope. The JS bundlers address both issues, as they

  • allow you to keep your codebase split into many, well-defined files and
  • bundle the code into big files for deployment.

Image description

Easy import from node_modules

Bundlers provide you with a way of importing dependencies, which is much nicer than loading them as ES modules. To use node packages from the browser, you would need to

  • deploy node_modules to your production server, and
  • use a relative path from your file to the file you want to import

The relative path is a big headache because it forces you to write the import slightly differently depending on how deep in the folder structure you are. So, for using Lodash, you would have:

// in ./src/core.js 
var _ = require('../node_modules/lodash/lodash.js');

// in ./src/app/main.js
var _ = require('../../node_modules/lodash/lodash.js');
Enter fullscreen mode Exit fullscreen mode

The bundlers allow you to write simply:

// anywhere
var _ = require('lodash');
Enter fullscreen mode Exit fullscreen mode

Image description

Import other file types

Your codebase is not only JavaScript. When you organize your code by components or routes, each will come with its own template and styling. Native ES modules don’t let you import resource types other than JS. This limitation would make you import the CSS from the HTML, whereas the rest of the component is imported in JavaScript—thereby forcing you to maintain two unrelated files in sync. JS bundlers fix this problem by letting you manage all those dependencies directly from your JS files:

import ./core.js;
import ./style.css;

const template = require(./view.html);
Enter fullscreen mode Exit fullscreen mode

Image description

Transpile code

A lot of JavaScript is not simple JavaScript; it’s written in languages such as TypeScript and then compiled to JavaScript. This code-to-code compilation is called transpilation. Most of the JavaScript is transpiled for a few reasons.

Code minification

If you’re writing your code as you should, you are doing the following:

  • giving meaningful names to variables
  • indenting the code
  • leaving comments for other developers

This adds clutter that means nothing for the interpreter. Minification is the first step to reducing the payload size. It removes everything that has no impact on your application.

Image description

Downgrade for older browsers

As the language receives new features, there is this period during which

  • developers want to use it already, and
  • not all browsers support it.

Luckily, this period is becoming significantly shorter thanks to the evergreen browser, but there is still a need for a project like Babel. Babel allows you to use the newest language version while coding and transpile it to a version that the older browser will understand.

JavaScript flavors

Besides the plain JavaScript, you can use many of its flavors:

  • TypeScript
  • PureScript
  • Elm
  • CoffeeScript

JavaScript bundlers can handle even mixing of different flavors in one project—which sounds like a bad idea until you end up working with legacy code and need a lot of flexibility to pick the right priorities.

Image description

Separate build for different use cases

Once you start compiling your code with a bundler, new possibilities arise. From the beginning, you will most likely compile the code one way for production and another way for local development. If you write unit tests, maybe you are interested in knowing how well they cover your code. There are code coverage tools that do exactly this. They require a dedicated build that includes tools that count visits to every line of code during the test execution.

How about you?

What JS bundler are you planning to use in your next project? Let me know in the poll, so I know which should get more attention on this blog.

What’s next?

You can check out my article about using native ES modules, or:

Top comments (8)

Collapse
 
canolcer profile image
Can Olcer

There are also a lot of downsides to using a bundler, and one of them is added complexity and opaqueness. I'm happy that Rails 7 is moving to more simple default setup, taking advantage of ESM, import maps and HTTP/2.

Switching my webpack projects to ESM really reduced complexity :-)

Collapse
 
marcinwosinek profile image
Marcin Wosinek

Good point! And import map is a cool feature I wasn’t aware of - thanks for pointing it out. Seems I will need to expand my ES module post

Collapse
 
adam_cyclones profile image
Adam Crockett 🌀

Damn beat me to it

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited

Each request is cached separately

If anything, this is a massive reason not to bundle: let the browser cache individual modules individually. If some module needs to be re-downloaded, then the browser only needs to request that one module instead of the whole bundle.

use a relative path from your file to the file you want to import

That's not entirely true though; you can easily just use absolute paths for your modules, in which case it's up to you to structure your directories conveniently.

Import other file types

This one is questionable at best. The only truth about it is that you can't use the same import syntax for other assets than js, but you can still import anything you want by simply fetching it.

 Transpile code

This has nothing to do with bundling. You could just as well compile any source language module into a javascript module and leave them as individual files.

Code minification

Same as transpilation.

JavaScript flavors

This is really just transpilation listed a second time.

Separate build for different use cases

Again, this is not related to bundling at all. If you have a build step at all, you can just as easily have it do different things depending on your environment, without spitting out one monolithic javascript file.

Collapse
 
marcinwosinek profile image
Marcin Wosinek

Thanks for your comment! Good point about absolute path and fetching. I’m very used to bundler taking care of those things for me, but you can use other build process and figure out the paths differently.

I’m obviously in the “bundle JS” camp. Two points that you make:

  • keeping files small to be cacheable
  • not bundling everything into one massive file

I would just manage with my bundler configuration - which can be a case “everything looks like a nail if hammer is the only tool you have” on my part.

If you ever write an article about your bundler-free setup, let me know! I will be happy to read it.

Collapse
 
lexlohr profile image
Alex Lohr

It seems you messed up your markdown after

// anywhere
var _ = require('lodash');
Enter fullscreen mode Exit fullscreen mode

You forgot to close the code area.

Collapse
 
marcinwosinek profile image
Marcin Wosinek

thanks for pointing this out!

Collapse
 
ben profile image
Ben Halpern

Nice post