Bundlers
& friends
The era of new javascript toolings

Table of Contents
1
Why?
2
Javascript modules
3
Module bundlers & friends
4
Use case
Why?
Retrospective

Why?
38%
JS
31%
API
31%
Others
Today



Why?
Goal

<script type="application/javascript" src="vendors.js"></script>
<script type="application/javascript" src="app.js"></script>
Why?
#!/usr/bin/env bash
bundle_file="bundle.js"
deps=(
"module_a/a.js"
"module_b/b.js"
"module_c/c.js"
)
for i in "${deps[@]}"; do
[[ -f "${i}" ]] && cat "${i}" >> "${bundle_file}"
done
How
This was good in 2005
Javascript
Evolutions

Modules
Javascript Modules




Maintainability
Reusability
Ease of testing
Javascript Modules
I.I.F.E./Global module
N/A
const module = (() => {
/* ... */
})()
CommonJS (CJS)
~2009
// module.js
const module = {
/* ... */
}
module.exports = module
// app.js
// import full module
const module = require('./module')
// import single functionality
const { method } = require('./module')
AMD
~2009
// module.js
define('module', ['dep'],
function(dep) {
/* ... */
})
// app.js
define(['module'], function(module) {
/* ... */
})

Javascript Modules
I.I.F.E./Global module
N/A
const module = (() => {
/* ... */
})()Cannot import parts of modules
Modules needs to be "fully loaded" in right order
Might generate naming collisions
Works on browsers and server side
Javascript Modules
CommonJS (CJS)
~2009
// module.js
const module = {
/* ... */
}
module.exports = module// app.js
// import full module
const module = require('./module')
// import single functionality
const { method } = require('./module')Most popular with hug number of existing module (npm)
Can import part of module
Synchronous import only
Works natively on server side only (node.js)
Better syntax
Javascript Modules
AMD
~2009
// module.js
define('module', ['dep'],
function(dep) {
/* ... */
})
// app.js
define(['module'], function(module) {
/* ... */
})

Cannot import parts of modules
Very verbose and convoluted syntax
Asynchronous modules loading
Works on Browsers and Server side
Javascript Modules
UMD
~2011
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(["dep"], factory);
} else if (typeof exports === "object") {
module.exports = factory(require("dep"));
} else {
root.module = factory(root.dep);
}
}(this, function (dep) {
const module = {/* ... */}
return module
}));
ESM
~2015
// module.js
const method1 = () => /* .. */
const method2 = () => /* .. */
export { method1, method2 }// app.js
// import full module
import module from './module'
// import single functionality
import { method1 } from './module'Javascript Modules
UMD
~2011
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(["dep"], factory);
} else if (typeof exports === "object") {
module.exports = factory(require("dep"));
} else {
root.module = factory(root.dep);
}
}(this, function (dep) {
const module = {/* ... */}
return module
}));
Complex, the wrapper code is almost impossible to write manually
Allows you to define modules taht ca be used by almost any module loader
Javascript Modules
ESM
~2015
// module.js
const method1 = () => /* .. */
const method2 = () => /* .. */
export { method1, method2 }// app.js
// import full module
import module from './module'
// import single functionality
import { method1 } from './module'Better syntax (close to CJS) BUT import & export are static
Standard format (part of ES2015)
Asynchrone (close to AMD)
Can import part of module
Works seamlessly in browsers & servers

Javascript Modules
Current most popular
- CommonJS (CJS)
- Mainly due to Node.js history
- ESM
- Standard
- Best of every worlds
Javascript Modules

Why CJS still default in NodeJS?
ESM changes a bunch of stuff in JavaScript. ESM scripts use Strict Mode by default (use strict), their this doesn't refer to the global object, scoping works differently, etc.
Switching the default from CJS to ESM would be a big break in backwards compatibility. (Deno, the hot new alternative to Node, makes ESM the default, but as a result, its ecosystem is starting from scratch).
Module
& strategies

bundlers
Reminder
Parsing
Loading
Executing


Less code!

Less code = Less to transfer from network + less to parse + less to load + less to execute = 😻
Minification
Minification (mangling)
function hello(message) {
console.log(message);
}
hello("Martin");function n(a){console.log(a)}n("Martin");Minification
JSMin
2001
Packer
2004
ShrinkSafe
2007

Regular expression
YUI Compressor
2007
Closure compiler
2009
Parser (AST)



Minification
Uglify
2011
Terser
2019
SWC
2020

esbuild
2020






Concatenation
Bundling
Bundling forerunners
Closure compiler
2009
Dojo Builder
2010
RequireJS
2010

Browserify
2011




JS based bundlers*
Webpack
2009
Rollup
2010
Parcel
2010

* Rollup v4 now used SWC parser



PROD bundle

85.3% used it

43% used it

23.5% used it


Vite

78.% used it

Next gen bundlers*
esbuild
2020
SWC
2020
Turbopack
2022

* Spoiler alert, isn't using JS

DEV bundle

49.6%

Vite

78.% used it
Biome
(formelly Rome)
2021
rspack
2023







used it

24.9%
used it

9.6%
used it

19.1%
used it

3.3%
used it
Underdog rookie bundler
Rolldown
2025


Will replace esbuild & rollup

Vite

78.% used it



1.5% used it
Scope hoisting
// lib.js
export function add(a, b) {
return a + b;
}// main.js
import { add } from "./utils.js";
console.log(add(2, 3));var __utils__ = (function() {
function add(a, b) {
return a + b;
}
return { add };
})();
var __main__ = (function(utils) {
console.log(utils.add(2, 3));
})(__utils__);function add(a, b) {
return a + b;
}
console.log(add(2, 3));Bundling without scope hoisting
Bundling with scope hoisting
😊
😭
Tree shaking
Tree shaking
// two-and-three.js
import { one } from "./one.js";
export function two() {
return one() + 1;
}
export function three() {
return one() + 2;
}
// main.js
import { two } from "./two-and-three.js";
console.log(two());function one() {
return 1;
}
function two() {
return one() + 1;
}
function three() {
return one() + 2;
}
console.log(two());function one() {
return 1;
}
function two() {
return one() + 1;
}
console.log(two());Without tree-shaking
With tree-shaking
😊
😭
// one.js
export function one() {
return 1;
}Code splitting
Without code splitting
with code splitting
Code splitting (shared components)
Import hoisting
// one.js
export function one() {
return 1;
}// two.js
import { one } from './one.js'
export function two() {
return one() + 2;
}// index.js
import { two } from './two.js'
console.log(two());// index.js
import { two } from './two-bunlde.js';
console.log(two());// index.js
import { two } from './two-bunlde.js';
import './one-bundle.js';
console.log(two());Without import hoisting
With import hoisting
Content hashing
foo.js
foo-jdZxkbLl.js
foo.js
foo-BUojTdor.js
Asset inlining
// index.js
import logo from "./logo.svg";
console.log(`logo: ${logo}`);var logo = "./logo-IJARBYVW.svg";
console.log(`logo: ${logo}`);var logo = `data:image/svg+xml,
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
stroke-miterlimit="10"
viewBox="142.92 136.23 738.38 729.8">
...
</svg>%0A`;
// index.js
console.log(`logo: ${logo}`);
Without asset inlining
With asset inlining
Asset loading
preload vs modulepreload vs prefetch
| Attribute | Main Purpose | Loaded When? | Typical Use Cases |
|---|---|---|---|
preload |
Load critical resources immediately | As soon as possible | CSS, images, fonts, videos |
modulepreload |
Load a JavaScript module and dependencies | As soon as possible | ES Modules scripts |
| prefetch | Load resources for future use | In the background | Next pages, images |
Use
& solutions
case

Agenda
- Debugger network tool
- Compression
- vite-bundle-analyzer
- Manual chunk
- Dynamic import
- Dynamic import prefetch


Bundlers & friends
By kakawait
Bundlers & friends
- 91