1
Why?
2
Javascript modules
3
Module bundlers & friends
4
Use case
Retrospective
38%
JS
31%
API
31%
Others
Today
Goal
<script type="application/javascript" src="vendors.js"></script>
<script type="application/javascript" src="app.js"></script>#!/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}"
doneHow
This was good in 2005
Evolutions
Maintainability
Reusability
Ease of testing
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) {
/* ... */
})
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
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
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
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'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
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
Current most popular
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).
& strategies
Parsing
Loading
Executing
Less code = Less to transfer from network + less to parse + less to load + less to execute = 😻
function hello(message) {
console.log(message);
}
hello("Martin");function n(a){console.log(a)}n("Martin");JSMin
2001
Packer
2004
ShrinkSafe
2007
Regular expression
YUI Compressor
2007
Closure compiler
2009
Parser (AST)
Uglify
2011
Terser
2019
SWC
2020
esbuild
2020
Closure compiler
2009
Dojo Builder
2010
RequireJS
2010
Browserify
2011
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
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
Rolldown
2025
Will replace esbuild & rollup
Vite
78.% used it
1.5% used it
// 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
😊
😭
// 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;
}Without code splitting
with code splitting
// 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
foo.js
foo-jdZxkbLl.js
foo.js
foo-BUojTdor.js
// 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
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 |
& solutions