Why Bundle?

Why Bundle? Modules, Transpiling, Minification and Tree Shaking

Browsers can load ES modules directly, and an import map lets you use package names without any tooling. The page below does exactly that with the date-fns 36,647 library, then counts the files the browser had to fetch to call one function.

Importing an npm package with no bundler, then counting the requestsHTMLLive
<script type="importmap">
  { "imports": { "date-fns": "https://cdn.jsdelivr.net/npm/date-fns@4.4.0/index.js" } }
</script>
<p id="out" style="font: 20px system-ui, sans-serif">Loading...</p>
<script type="module">
  const { formatDistance } = await import('date-fns');
  const files = performance.getEntriesByType('resource')
    .filter((r) => r.name.includes('date-fns')).length;
  document.querySelector('#out').innerHTML =
    `${formatDistance(0, 90_000)} of work took <b>${files} requests</b>`;
</script>
Browser output of Listing 1.44
Browser output of 44

One function cost 250 HTTP requests, because index.js re-exports every date-fns module and the browser must fetch them all before any code runs. On a slow mobile connection that waterfall takes seconds, and it is the first problem bundlers solve. A production build does five jobs:

The jobs of a production build
Job What happens Why it helps
Resolve Follow imports into a module graph Finds every needed file
Transform Strip types, compile JSX, lower syntax Runs in target browsers
Tree shake Drop exports nothing imports Smaller downloads
Bundle and split Join modules into chunks Fewer requests, lazy loading
Minify and hash Shorten code, hash file names Smaller files, long caching

Tree shaking is easy to see with esbuild 126 ; note that square() never reaches the output:

Two modules and a tree-shaken bundle
# src/math.js exports square() and cube(); src/index.js contains:
#   import { cube } from './math.js';
#   console.log(`3 cubed is ${cube(3)}`);
npx esbuild src/index.js --bundle --format=esm
Output
// src/math.js
function cube(x) {
  return x * x * x;
}
// src/index.js
console.log(`3 cubed is ${cube(3)}`);

Practical advice: prefer ES module builds of libraries (lodash-es, not lodash), avoid default-exported objects full of functions, and load rarely used features with import() so they land in their own chunk. For a page with a few small scripts, native modules without a build step remain a good choice.