What value does a bundler provide when publishing to npm?

84 views Asked by At

For context, I've used Node.js for years, but this is the first time I've published a Node.JS package for others to use. Not knowing where to start, I googled for how to do this for typescript and followed the steps in this article.

It recommended tsup as a bundler. One of the things it does is output two versions of my project. One for CommonJS users and one for ESM users. I see the value in that.

But what's the value in bundling that output? Here are the best reasons I could come up with on my own, but I'm not sure if they're even valid:

  1. Tree shaking. If I published my files as-is, any inaccessible code would be included in that package, increasing the package size.
  2. This is a stab in the dark, but maybe if my package were published as loose files, users could import/require any file they wanted, even parts intended to be internal-only? (Up to this point, I've assumed the package.json main property enforces this.)

I guess I'm assuming it's possible to publish an unbundled version of my package, one for CommonJS and another for ESM. Let me know if that assumption is incorrect.

1

There are 1 answers

3
Mike 'Pomax' Kamermans On BEST ANSWER

Bundling started life for the browser, before JS had any sort of modules or dependency management, and before Node existed, but really took off when React came along and went "what if you don't use JavaScript, but something that hides some of the downright stupid syntax you need if you write real JS, and we just compile that down to JS?" and unlike CoffeeScript, people went nuts over it and anyone who wanted to use React had to bundle their code because it had to run through babel anyway (when it was still called 6to5). This was also when folks went "well if we're bundling anyway, can we also just ..." and added dead code removal, asset hashing, auto-shimming, CSS compilation, multi target transpilation to the browser and Node, etc. etc. We even added things that used to be separate post-processing steps into bundlers, like minification (which was necessary for phones, which came with megabytes of RAM, so reducing a variable from anchorManagerRouter to x actually did something).

Of course: at the expense of completely destroying the browser's ability to cache data. A one character change somewhere? That's a completely new 1MB download, thank you very much.

However, JS grew up (or rather, kept getting worked on) and got a real module system, so that browser code didn't need bundling anymore, and could finally rely on browser caching again. "But now your download would be 20+ separate files!" is true, but the fact that we got browser caching back, paired with things like QUIK made that objection moot.

Bundling was still useful for generating universal libraries, but more and more people were generating separate bundles for separate targets, because they could be optimized separately, and to make a long story shorter: bundling itself is basically pointless today.

The browser doesn't need it, and Node, Deno, and Bun all support modern ESM code, so as long as your code's not tied to "the DOM" or "the filesystem", you're already writing universal JS these days just by writing JS.

And of course Node never needed it, UMD libraries were just a convenient "deploy one thing" solution.

Of all the jobs that modern bundlers like esbuild or tsup perform, the bundling is basically the one thing no one actually needs anymore, it just acts as a vehicle for all the other stuff you want or even need to do to your code, and makes all the other things like treeshaking (arguably useful, arguably not), minification (definitely no longer useful), transpilation (the main reason to use build tools these days), easier for the folks who write those tools.

So what's the value of bundling? Absolutely nothing. But what's the value of using a bundler that happens to output a bundle as part of doing everything else that does have value? Literally "being able to do all those other things". You could run all those tasks separately, yourself, in a large build chain. That's literally how all of this worked in the early days. If we weren't piping data from stream to stream because the folks working on Node liked streams a little too much.

(This answer glosses over a ton of JS dev history. If you were a Grunt or Gulp fan, or you fondly remember requirejs, or Bower was your jam, or you were on the iojs side of "the split", or you remember anything else not mentioned here: this is not "the history of JS dev" =)