SlideShare a Scribd company logo
1 of 208
Download to read offline
Unbundling the JavaScriptUnbundling the JavaScript
module bundlermodule bundler
Luciano Mammino -Luciano Mammino - @loige@loige
10/06/2019
loige.link/bundle-devit
1
Webpack == PAIN!?Webpack == PAIN!?
twitter.com/search?q=webpack%20pain
@loige 2
@loige 3
@loige 4
@loige 5
@loige
❤
6
@loige 7
@loige 8
@loige 9
It's not Webpack!It's not Webpack!
Module bundling is actually complicated!Module bundling is actually complicated!
@loige 10
11
Hello, I am Luciano!Hello, I am Luciano!
11
Hello, I am Luciano!Hello, I am Luciano!
11
Hello, I am Luciano!Hello, I am Luciano!
11
Hello, I am Luciano!Hello, I am Luciano!
11
Hello, I am Luciano!Hello, I am Luciano!
Cloud Architect
11
Hello, I am Luciano!Hello, I am Luciano!
Cloud Architect
Blog:
Twitter:
GitHub:  
loige.co
@loige
@lmammino
11
Hello, I am Luciano!Hello, I am Luciano!
Cloud Architect
Blog:
Twitter:
GitHub:  
loige.co
@loige
@lmammino
11
loige.link/bundle-devit 12
1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 13
https://poo.loige.co
@loige 14
App featuresApp features
@loige 15
Dynamic DOM manipulation
React, Angular, Vue
are so... overrated!
@loige 16
Dynamic Favicon
favico.js@loige 17
Custom animated tooltips
tippy.js@loige 18
Confetti rainfall
dom-confetti@loige 19
Persist state through Local Storage + UUIDs
 +store2 uuidjs@loige 20
@loige 21
@loige 21
7 Requests only for the JS code!
@loige 21
Current scenarioCurrent scenario
zepto@1.2.0/dist/zepto.min.js 
uuidjs@4.0.3/dist/uuid.core.js 
store2@2.7.0/dist/store2.min.js 
 tippy.js@2.2.3/dist/tippy.all.min.js 
confetti@0.0.10/lib/main.js 
 favico.js@0.3.10/favico­0.3.10.min.js
@loige 22
Ideal scenarioIdeal scenario
zepto@1.2.0/dist/zepto.min.js 
uuidjs@4.0.3/dist/uuid.core.js 
store2@2.7.0/dist/store2.min.js 
 tippy.js@2.2.3/dist/tippy.all.min.js 
confetti@0.0.10/lib/main.js 
 favico.js@0.3.10/favico­0.3.10.min.js
vendors.js
+ 
+ 
+ 
+ 
+ 
=
@loige 23
How to do this?How to do this?
@loige 24
./buildVendors.sh > vendors.js
buildVendors.sh
$
@loige 25
npx lumpy build$
github.com/lmammino/lumpy
@loige 26 . 1
# lumpy.txt 
https://unpkg.com/zepto@1.2.0/dist/zepto.min.js 
https://unpkg.com/uuidjs@4.0.3/dist/uuid.core.js 
https://unpkg.com/store2@2.7.0/dist/store2.min.js 
https://unpkg.com/tippy.js@2.2.3/dist/tippy.all.min.js 
https://unpkg.com/confetti­js@0.0.11/dist/index.min.js 
https://unpkg.com/dom­confetti@0.0.10/lib/main.js 
https://unpkg.com/favico.js@0.3.10/favico­0.3.10.min.js
Lumpy allows you to define
all the vendors in a text file
@loige 26 . 2
lumpy build$
1. Downloads the files from lumpy.txt (and caches them)
2. Concatenates the content of the files
3. Minifies the resulting source code (using )
4. Saves the resulting content in vendors.js
babel-minify
@loige 26 . 3
7 requests
2 requests
@loige 27
7 requests
2 requests
@loige
Even better if you "minify" these!
27
ConcatenationConcatenation
++
MinificationMinification
@loige 28
This is good...This is good...
@loige 29
This is good...This is good...
@loige 29
This is good...This is good...
... in 2008... in 2008
waswas
@loige 29
Today...Today...
We can do better!We can do better!
@loige 30
UpdatingUpdating them should be easythem should be easy
We shouldn't worry aboutWe shouldn't worry about transitive dependenciestransitive dependencies
(dependencies of dependencies)(dependencies of dependencies)
Order of importsOrder of imports shouldn't really mattershouldn't really matter
We rely onWe rely on dependenciesdependencies!!
@loige 31
DependencyDependency
(or coupling)
a state in which one object uses a function of
another object
— Wikipedia
@loige 32
Reusable dependencies...Reusable dependencies...
Modules!Modules!
@loige 33
ModulesModules
The bricks for structuring non-trivial applications,
but also the main mechanism to enforce information
hiding by keeping private all the functions and
variables that are not explicitly marked to be
exported
— *Node.js Design Patterns (Second Edition)
* yeah, I quite like quoting my stuff...
@loige 34
1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 35
Meet my friendMeet my friend I.I.F.E.I.I.F.E.
(Immediately Invoked Function Expression)
loige.link/iife
@loige 36
We generally define a function this wayWe generally define a function this way
@loige 37
We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
@loige 37
We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
oror
@loige 37
We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
oror
@loige 37
We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
oror
then, at some point, we execute it...then, at some point, we execute it...
@loige 37
We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
oror
then, at some point, we execute it...then, at some point, we execute it...
const four = sum(2, 2)
@loige 37
A function in JS creates an isolated scopeA function in JS creates an isolated scope
@loige 38
A function in JS creates an isolated scopeA function in JS creates an isolated scope
(a, b) => {
const secretString = "Hello"
return a + b
}
console.log(secretString) // undefined
@loige 38
A function in JS creates an isolated scopeA function in JS creates an isolated scope
(a, b) => {
const secretString = "Hello"
return a + b
}
console.log(secretString) // undefined
secretStringsecretString is not visible outside the functionis not visible outside the function
@loige 38
IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
@loige 39
IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
A function with its own scope
@loige 39
)(someArg1, someArg2)
IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
(
@loige 39
)(someArg1, someArg2)
IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
(
This wrapper executes the function immediately and passes arguments from the
outer scope
@loige 39
IIFE is a recurring pattern inIIFE is a recurring pattern in
JavaScript modulesJavaScript modules
@loige 40
Let's implement a moduleLet's implement a module
that provides:that provides:
 
Information hidingInformation hiding
exported functionalitiesexported functionalities
@loige 41
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})()
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined @loige42
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})() A module
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined @loige42
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})() A module
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined
IIFE
Creates an isolated scope
and executes it
@loige42
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})() A module
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined
information hiding
non-exported functionality
@loige42
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})() A module
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined
defines exported
functionalities
@loige42
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})() A module
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined
propagates the exports
to the outer scope (assigning it to myModule)
@loige42
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})() A module
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined
Can access exported
functionalities
@loige42
const myModule = (() => {
const privateFoo = () => { /* ... */ }
const privateBar = [ /* ... */ ]
const exported = {
publicFoo: () => { /* ... */ },
publicBar: [ /* ... */ ]
};
return exported
})() A module
myModule.publicFoo()
myModule.publicBar[0]
myModule.privateFoo // undefined
myModule.privateBar // undefined
privateFoo // undefined
privateBar // undefined
No visibility for the
non-exported ones
@loige42
We want modules to beWe want modules to be
  reusablereusable across different apps andacross different apps and
organisations...organisations...
...we need...we need
AA STANDARD MODULESTANDARD MODULE format!format!@loige 43
Module system featuresModule system features
Must have
Simple syntax for import / export
Information hiding
Allows to define modules in separate files
Modules can import from other modules
(nested dependencies)
@loige 44 . 1
Module system featuresModule system features
Nice to have
Ability to import module subsets
Avoid naming collision
Asynchronous module loading
Seamless support for Browsers & Server-side
@loige 44 . 2
JavaScript module systemsJavaScript module systems
globals
CommonJS (Node.js)
AMD (Require.js / Dojo)
UMD
ES2015 Modules (ESM)
Many others (SystemJS, ...)
@loige 45
GlobalsGlobals
var $, jQuery
$ = jQuery = (() => {
return { /* ... */ }
})()
// ... use $ or jQuery in the global scope
$.find('.button').remove()
@loige 46
GlobalsGlobals
Might generate naming collisions
(e.g. $ overrides browser global variable)
 
Modules needs to be "fully loaded" in the right order
 
Cannot import parts of modules
@loige 47
// or import single functionality
const { concat } = require('./loDash')
concat([1], [2], [3])
// app.js
// import full module
const _ = require('./loDash')
_.concat([1], [2], [3])
// loDash.js
const loDash = {
/* ... */
}
module.exports = loDash
CommonJSCommonJS
@loige 48
// or import single functionality
const { concat } = require('./loDash')
concat([1], [2], [3])
// app.js
// import full module
const _ = require('./loDash')
_.concat([1], [2], [3])
// loDash.js
const loDash = {
/* ... */
}
module.exports = loDash
CommonJSCommonJS
module
@loige 48
// or import single functionality
const { concat } = require('./loDash')
concat([1], [2], [3])
// app.js
// import full module
const _ = require('./loDash')
_.concat([1], [2], [3])
// loDash.js
const loDash = {
/* ... */
}
module.exports = loDash
CommonJSCommonJS
module
app using module
@loige 48
// or import single functionality
const { concat } = require('./loDash')
concat([1], [2], [3])
// app.js
// import full module
const _ = require('./loDash')
_.concat([1], [2], [3])
// loDash.js
const loDash = {
/* ... */
}
module.exports = loDash
CommonJSCommonJS
module
app using module
@loige 48
// or import single functionality
const { concat } = require('./loDash')
concat([1], [2], [3])
// app.js
// import full module
const _ = require('./loDash')
_.concat([1], [2], [3])
// loDash.js
const loDash = {
/* ... */
}
module.exports = loDash
CommonJSCommonJS
module
app using module
@loige 48
CommonJSCommonJS
No naming collisions
(imported modules can be renamed)
Huge repository of modules through
 
Synchronous import only
Works natively on the server side only (Node.js)
NPM
@loige 49
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
@loige 50
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
@loige 50
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
module name
@loige 50
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
dependencies
@loige 50
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
factory function used to construct the
module,
receives the dependencies as arguments
@loige 50
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
exported value
@loige 50
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
@loige 51
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
app
@loige 51
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
app
Require.js config
jquery will be loaded from
://<currentDomain>/js/lib/jquery-1.9.0.js
@loige 51
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
app
app main function
Has jquery as dependency
@loige 51
AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
Asynchronous modules
Works on Browsers and Server side
 
Very verbose and convoluted syntax (my opinion™)
@loige 52
UMDUMD
Universal Module DefinitionUniversal Module Definition
A module definition that is compatible with
Global modules, CommonJS & AMD
 
 github.com/umdjs/umd
@loige 53 . 1
(function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
(function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd
IIFE with arguments:
 - Current scope (this) and the module
factory function.
 - "dep" is a sample dependency of the
module.
@loige 53 . 2
(function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
(function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
(function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
Allows you to define modules that
can be used by almost any module loader
 
Complex, the wrapper code
is almost impossible to write manually
UMDUMD
Universal Module DefinitionUniversal Module Definition
@loige 53 . 3
ES2015 modulesES2015 modules
Cool & broad subject, it would deserve it's own talk
Wanna know more?
/  syntax referenceimport export
ECMAScript modules in browsers
ES modules: A cartoon deep-dive
ES Modules in Node Today!
@loige 54 . 1
// calculator.js
const add = (num1, num2) => num1 + num2
const sub = (num1, num2) => num1 - num2
const div = (num1, num2) => num1 / num2
const mul = (num1, num2) => num1 * num2
export { add, sub, div, mul }
// app.js
import { add } from './calculator'
console.log(add(2,2)) // 4
ES2015 modulesES2015 modules
@loige 54 . 2
// calculator.js
const add = (num1, num2) => num1 + num2
const sub = (num1, num2) => num1 - num2
const div = (num1, num2) => num1 / num2
const mul = (num1, num2) => num1 * num2
export { add, sub, div, mul }
// app.js
import { add } from './calculator'
console.log(add(2,2)) // 4
ES2015 modulesES2015 modules
module
@loige 54 . 2
// calculator.js
const add = (num1, num2) => num1 + num2
const sub = (num1, num2) => num1 - num2
const div = (num1, num2) => num1 / num2
const mul = (num1, num2) => num1 * num2
export { add, sub, div, mul }
// app.js
import { add } from './calculator'
console.log(add(2,2)) // 4
ES2015 modulesES2015 modules
moduleexported functionalities
@loige 54 . 2
// calculator.js
const add = (num1, num2) => num1 + num2
const sub = (num1, num2) => num1 - num2
const div = (num1, num2) => num1 / num2
const mul = (num1, num2) => num1 * num2
export { add, sub, div, mul }
// app.js
import { add } from './calculator'
console.log(add(2,2)) // 4
ES2015 modulesES2015 modules
module
app
@loige 54 . 2
// calculator.js
const add = (num1, num2) => num1 + num2
const sub = (num1, num2) => num1 - num2
const div = (num1, num2) => num1 / num2
const mul = (num1, num2) => num1 * num2
export { add, sub, div, mul }
// app.js
import { add } from './calculator'
console.log(add(2,2)) // 4
ES2015 modulesES2015 modules
module
app
import specific functionality
@loige 54 . 2
// index.html
<html>
<body>
<!-- ... -->
<script type="module">
import { add } from 'calculator.js'
console.log(add(2,2)) // 4
</script>
</body>
</html>
ES2015 modulesES2015 modules
@loige 54 . 3
// index.html
<html>
<body>
<!-- ... -->
<script type="module">
import { add } from 'calculator.js'
console.log(add(2,2)) // 4
</script>
</body>
</html>
ES2015 modulesES2015 modules
"works" in some modern browsers
@loige 54 . 3
ES2015 modulesES2015 modules
Syntactically very similar to CommonJS...
BUT
import & export are static
(allow static analysis of dependencies)
 
It is a (still work in progress) standard format
 
Works (almost) seamlessly in browsers & servers
@loige 54 . 4
So many options...So many options...
Current most used practice:
Use CommonJS or ES2015 & create "compiled bundles"
@loige 55
1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 56
Let's try to use CommonJSLet's try to use CommonJS
in the browserin the browser
@loige 57
const $ = require('zepto')
const tippy = require('tippy.js')
const UUID = require('uuidjs')
const { confetti } = require('dom-confetti/src/main')
const store = require('store2')
const Favico = require('favico.js')
!(function () {
const colors = ['#a864fd', '#29cdff', '#78ff44', '#ff718d', '#fdff6a']
const todoApp = (rootEl, opt = {}) => {
const todos = opt.todos || []
let completedTasks = opt.completedTasks || 0
const onChange = opt.onChange || (() => {})
const list = rootEl.find('.todo-list')
const footer = rootEl.find('.footer')
const todoCount = footer.find('.todo-count')
const insertInput = rootEl.find('.add-todo-box input')
const insertBtn = rootEl.find('.add-todo-box button')
const render = () => {
let tips
list.html('')
The browser doesn't know how
to process require.
It doesn't support CommonJS!
@loige 58
Module BundlerModule Bundler
A tool that takes modules with dependencies and emits
static assets representing those modules
 
Those static assets can be processed by browsers!
@loige 59
Dependency graphDependency graph
A graph built by connecting every module with its direct
dependencies.
app
dependency A dependency B
dependency A2
shared
dependency
@loige 60
A module bundler has to:A module bundler has to:
1. Construct the dependency graph
(Dependency Resolution)
2. Assemble the modules in the graph into a
single executable asset (Packing)
@loige 61
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
62@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
62
(entrypoint)
(1)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator
62
(entrypoint)
(1)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator
62
(entrypoint)
(1)
(2)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator
parser
62
(entrypoint)
(1)
(2)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator
parser
62
(entrypoint)
(1)
(2)
(3)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator
parser resolver
62
(entrypoint)
(1)
(2)
(3)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator
parser resolver
62
(entrypoint)
(1)
(2)
(3)
(4)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator log
parser resolver
62
(entrypoint)
(1)
(2)
(3)
(4)
@loige
// app.js
const calculator = require('./calculator')
const log = require('./log')
log(calculator('2 + 2 / 4'))
// log.js
module.exports = console.log
// calculator.js
const parser = require('./parser')
const resolver = require('./resolver')
module.exports = (expr) => resolver(parser(expr))
// parser.js
module.exports = (expr) => { /* ... */ }
// resolver.js
module.exports = (tokens) => { /* ... */ }
Dependency resolutionDependency resolution
app
calculator log
parser resolver
62
(entrypoint)
(1)
(2)
(3)
(4)
(5)
@loige
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./parser': (module, require) => { … },
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … },
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … },
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … },
require path
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … },
module factory function
@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … },
const parser = require('./parser');const resolver = 
require('./resolver');module.exports = (expr) => 
resolver(parser(expr))@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … },
const parser = require('./parser');const resolver = 
require('./resolver');module.exports = (expr) => 
resolver(parser(expr))@loige 63
DuringDuring dependency resolutiondependency resolution,,
the bundler creates athe bundler creates a modules mapmodules map
{ 
 
 
 
 
 
 
 
} 
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … },
const parser = require('./parser');const resolver = 
require('./resolver');module.exports = (expr) => 
resolver(parser(expr))@loige 63
PackingPacking
{ 
  ­­­ : ­­­ 
  ­­­ : ­­­­ 
  ­­­ : ­­ 
}
Modules mapModules map
@loige 64
PackingPacking
{ 
  ­­­ : ­­­ 
  ­­­ : ­­­­ 
  ­­­ : ­­ 
}
Modules mapModules map
@loige 64
PackingPacking
.js
{ 
  ­­­ : ­­­ 
  ­­­ : ­­­­ 
  ­­­ : ­­ 
}
Modules mapModules map
Single executableSingle executable
JS fileJS file
@loige 64
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
) 65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
IIFE passing the modules map as
argument
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
Custom require function:
it will load the modules by
evaluating the code from the
modules map
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
A reference to a module with an
empty module.exports. 
This will be filled at evaluation
time
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
Invoking the factory function for
the given module name.
(Service locator pattern)
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
The current reference module is
passed, the factory function will
modify this object by adding the
proper exported values.
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
The custom require function is
passed so, modules can
recursively require other modules
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
The resulting module.exports is
returned
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
)
The entrypoint module is
required, triggering the actual
execution of the business logic
65
@loige
Packed executable filePacked executable file
((modulesMap) => {
const require = (name) => {
const module = { exports: {} }
modulesMap[name](module, require)
return module.exports
}
require('./app')
})(
{
'./app': (module, require) => { … },
'./calculator': (module, require) => { … },
'./log': (module, require) => { … },
'./parser': (module, require) => { … },
'./resolver': (module, require) => { … }
}
) 65
@loige
Now you know howNow you know how
Module bundlers work!Module bundlers work!
  
And how to convert code writtenAnd how to convert code written
usingusing CommonJSCommonJS to a single file thatto a single file that
works in the browserworks in the browser
@loige 66
A challenge for you!A challenge for you!
If you do, ... I'll arrange a prize for you!
 
TIP: you can use  or  to parse JavaScript files (look for
require and module.exports) and to map relative module paths to
actual files in the filesystem.
 
Need an inspiration?
Check the awesome  and !
let me know
acorn babel-parser
resolve
minipack @adamisnotdead's w_bp_ck
Can you build a (simple) module bundler from scratch?Can you build a (simple) module bundler from scratch?
@loige 67
1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 68
A state of the art moduleA state of the art module
bundler for the webbundler for the web
@loige 69
npm install webpack webpack­cli
@loige 70
webpack app.js
yep, recent versions of Webpack work without config!
@loige 71
webpack ­­mode=development app.js
Do not compress the code and add
annotations!
@loige 72
loige.link/sample-webpacked-file
@loige73
Webpack conceptsWebpack concepts
Entry point: the starting file for dependency resolution.
Output: the destination file (bundled file).
Loaders: algorithms to parse different file types and convert them
into executable javascript (e.g. babel, typescript, but also CSS,
images or other static assets)
Plugins: do extra things (e.g. generate a wrapping HTML or
analysis tools)
@loige 74
const { resolve, join } = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
entry: './app.js',
output: {
path: resolve(join(__dirname, 'build')),
filename: 'app.js'
},
module: {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
},
plugins: [
new CompressionPlugin()
]
}
Webpack.config.js
@loige75
const { resolve, join } = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
entry: './app.js',
output: {
path: resolve(join(__dirname, 'build')),
filename: 'app.js'
},
module: {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
},
plugins: [
new CompressionPlugin()
]
}
Webpack.config.js
Entrypoint
Build the dependency graph starting from ./app.js
@loige75
const { resolve, join } = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
entry: './app.js',
output: {
path: resolve(join(__dirname, 'build')),
filename: 'app.js'
},
module: {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
},
plugins: [
new CompressionPlugin()
]
}
Webpack.config.js
Output
Save the resulting bundled file in ./build/app.js
@loige75
const { resolve, join } = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
entry: './app.js',
output: {
path: resolve(join(__dirname, 'build')),
filename: 'app.js'
},
module: {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
},
plugins: [
new CompressionPlugin()
]
}
Webpack.config.js
Loaders
All the files matching "*.js" are processed with babel
and converted to ES5 Javascript
@loige75
const { resolve, join } = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
entry: './app.js',
output: {
path: resolve(join(__dirname, 'build')),
filename: 'app.js'
},
module: {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
]
},
plugins: [
new CompressionPlugin()
]
}
Webpack.config.js
Plugins
Uses a plugin that generates a gzipped copy of every
emitted file.
@loige75
Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
Webpack can load any type of fileWebpack can load any type of file
As long as you can provide a "As long as you can provide a "loaderloader" that" that
tells how to convert the file intotells how to convert the file into
something the browser understands.something the browser understands.
This is how Webpack allows you to use Babel, TypeScript,
Clojure, Elm, Imba but also to load CSSs, Images and other
assets.
77
{
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', 78
{
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
// Defines how to load .css files (uses a pipeline of loaders)
78
{
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
// Defines how to load .css files (uses a pipeline of loaders)
// parses the file with post-css
78
{
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
// Defines how to load .css files (uses a pipeline of loaders)
// parses the file with post-css
// process @import and url()
// statements
78
{
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
// Defines how to load .css files (uses a pipeline of loaders)
// parses the file with post-css
// process @import and url()
// statements
// inject the resulting code with a <style> tag
78
...Webpack can do (a lot) more!...Webpack can do (a lot) more!
Dev ServerDev Server
Tree shakingTree shaking
Dependencies analyticsDependencies analytics
Source mapsSource maps
Async require / module splittingAsync require / module splitting
@loige 79
1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 80
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
bundle.js
(original)
81
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com
bundle.js
(original)
81
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js
bundle.js
(original)
81
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
bundle.js
(original)
81
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
bundle.js
(original)
81
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
bundle.js
(original)
bundle.js
(cache copy)
81
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
bundle.js
(original)
bundle.js
(cache copy)
81
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
bundle.js
(original)
bundle.js
(cache copy)
81
bundle.js
(cache copy)
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
82
bundle.js
(original)
bundle.js
(cache copy)
bundle.js
(cache copy)
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
82
bundle.js
(original)
bundle.js
(cache copy)
bundle.js
(cache copy)
NEW VERSION
STALE!
Bundle cache bustingBundle cache busting
Device CDN
Origin
Server
example.com bundle.js bundle.js
82
bundle.js
(original)
bundle.js
(cache copy)
bundle.js
(cache copy)
NEW VERSIONSTALE!
Bundle cache bustingBundle cache busting
83
Bundle cache bustingBundle cache busting
(Manual) Solution 1(Manual) Solution 1
83
Bundle cache bustingBundle cache busting
(Manual) Solution 1(Manual) Solution 1
bundle.js?v=1
bundle.js?v=2
bundle.js?v=3
83
Bundle cache bustingBundle cache busting
(Manual) Solution 1(Manual) Solution 1
bundle.js?v=1
bundle.js?v=2
bundle.js?v=3
Doesn't play nice with some CDNs and Proxies
(they won't consider different query parameters to be different resources)
83
Bundle cache bustingBundle cache busting
84
Bundle cache bustingBundle cache busting
(Manual) Solution 2(Manual) Solution 2
84
Bundle cache bustingBundle cache busting
(Manual) Solution 2(Manual) Solution 2
bundle-v1.js
bundle-v2.js
bundle-v3.js
...
84
Bundle cache bustingBundle cache busting
(Manual) Solution 2(Manual) Solution 2
bundle-v1.js
bundle-v2.js
bundle-v3.js
...
Better, but still a lot of diligence and manual effort needed...
84
Bundle cache bustingBundle cache busting
85
Bundle cache bustingBundle cache busting
Webpack SolutionWebpack Solution
85
Bundle cache bustingBundle cache busting
Webpack SolutionWebpack Solution
output: { 
  filename: '[name].[contenthash].js' 
}
85
Bundle cache bustingBundle cache busting
Webpack SolutionWebpack Solution
...
bundle.9f61f58dd1cc3bb82182.js 
bundle.aacdf58ef1aa12382199.js 
bundle.ed61f68defef3bb82221.js
output: { 
  filename: '[name].[contenthash].js' 
}
85
Bundle cache bustingBundle cache busting
Webpack SolutionWebpack Solution
 +
 
Every new asset version will generate a new file
cache is automatically cleaned up on every release
(if content actually changed)
html-plugin will update the reference to the new file
contenthash webpack-html-plugin
86
1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 87
Module bundlers are your friendsModule bundlers are your friends
Now you know how they work,
they are not (really) magic!
Start small and add more when needed
If you try to build your own, you'll learn a
lot more!
@loige 88
Webpack is notWebpack is not
the only possibility!the only possibility!
89
Ευχαριστώ!Ευχαριστώ!
     Special thanks:
, , ,
(reviewers) and
 (inspirations: his and his
)
@Podgeypoos79 @andreaman87 @mariocasciaro
@eugenserbanescu
@MarijnJH amazing book
workshop on JS modules
@loige
Images by
cover Image by  from  
Streamline Emoji pack
Dimitris Vetsikas Pixabay
loige.link/bundle-devit
90

More Related Content

What's hot

Gae icc fall2011
Gae icc fall2011Gae icc fall2011
Gae icc fall2011
Juan Gomez
 
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for JavaHave Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
Howard Lewis Ship
 
C# for-java-developers
C# for-java-developersC# for-java-developers
C# for-java-developers
Dhaval Dalal
 

What's hot (20)

Objective-C Crash Course for Web Developers
Objective-C Crash Course for Web DevelopersObjective-C Crash Course for Web Developers
Objective-C Crash Course for Web Developers
 
Gae icc fall2011
Gae icc fall2011Gae icc fall2011
Gae icc fall2011
 
Doctrine with Symfony - SymfonyCon 2019
Doctrine with Symfony - SymfonyCon 2019Doctrine with Symfony - SymfonyCon 2019
Doctrine with Symfony - SymfonyCon 2019
 
Mirah Talk for Boulder Ruby Group
Mirah Talk for Boulder Ruby GroupMirah Talk for Boulder Ruby Group
Mirah Talk for Boulder Ruby Group
 
5 Tips for Better JavaScript
5 Tips for Better JavaScript5 Tips for Better JavaScript
5 Tips for Better JavaScript
 
Javascript Common Design Patterns
Javascript Common Design PatternsJavascript Common Design Patterns
Javascript Common Design Patterns
 
The Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaThe Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony Barcelona
 
Bytecode manipulation with Javassist and ASM
Bytecode manipulation with Javassist and ASMBytecode manipulation with Javassist and ASM
Bytecode manipulation with Javassist and ASM
 
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for JavaHave Your Cake and Eat It Too: Meta-Programming Techniques for Java
Have Your Cake and Eat It Too: Meta-Programming Techniques for Java
 
Iphone course 1
Iphone course 1Iphone course 1
Iphone course 1
 
Advanced JavaScript
Advanced JavaScriptAdvanced JavaScript
Advanced JavaScript
 
Kotlin is charming; The reasons Java engineers should start Kotlin.
Kotlin is charming; The reasons Java engineers should start Kotlin.Kotlin is charming; The reasons Java engineers should start Kotlin.
Kotlin is charming; The reasons Java engineers should start Kotlin.
 
C# for-java-developers
C# for-java-developersC# for-java-developers
C# for-java-developers
 
Metaprogramming in julia
Metaprogramming in juliaMetaprogramming in julia
Metaprogramming in julia
 
(국비지원학원/재직자교육/실업자교육/IT실무교육_탑크리에듀)#4.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(국비지원학원/재직자교육/실업자교육/IT실무교육_탑크리에듀)#4.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)(국비지원학원/재직자교육/실업자교육/IT실무교육_탑크리에듀)#4.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
(국비지원학원/재직자교육/실업자교육/IT실무교육_탑크리에듀)#4.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)
 
Kotlin Overview (PT-BR)
Kotlin Overview (PT-BR)Kotlin Overview (PT-BR)
Kotlin Overview (PT-BR)
 
Clean Code Development
Clean Code DevelopmentClean Code Development
Clean Code Development
 
Building High Perf Web Apps - IE8 Firestarter
Building High Perf Web Apps - IE8 FirestarterBuilding High Perf Web Apps - IE8 Firestarter
Building High Perf Web Apps - IE8 Firestarter
 
JavaScript and UI Architecture Best Practices
JavaScript and UI Architecture Best PracticesJavaScript and UI Architecture Best Practices
JavaScript and UI Architecture Best Practices
 
JavaScript - From Birth To Closure
JavaScript - From Birth To ClosureJavaScript - From Birth To Closure
JavaScript - From Birth To Closure
 

Similar to Unbundling the JavaScript module bundler - DevIT

Dependency Inversion and Dependency Injection in PHP
Dependency Inversion and Dependency Injection in PHPDependency Inversion and Dependency Injection in PHP
Dependency Inversion and Dependency Injection in PHP
mtoppa
 
Navigating the wild seas of es6 modules
Navigating the wild seas of es6 modulesNavigating the wild seas of es6 modules
Navigating the wild seas of es6 modules
Gil Tayar
 

Similar to Unbundling the JavaScript module bundler - DevIT (20)

Unbundling the JavaScript module bundler - Road to Coderful
Unbundling the JavaScript module bundler - Road to CoderfulUnbundling the JavaScript module bundler - Road to Coderful
Unbundling the JavaScript module bundler - Road to Coderful
 
Unbundling the JavaScript module bundler - Luciano Mammino - Codemotion Rome ...
Unbundling the JavaScript module bundler - Luciano Mammino - Codemotion Rome ...Unbundling the JavaScript module bundler - Luciano Mammino - Codemotion Rome ...
Unbundling the JavaScript module bundler - Luciano Mammino - Codemotion Rome ...
 
Luigi future
Luigi futureLuigi future
Luigi future
 
From Android NDK To AOSP
From Android NDK To AOSPFrom Android NDK To AOSP
From Android NDK To AOSP
 
Fpga 13-task-and-functions
Fpga 13-task-and-functionsFpga 13-task-and-functions
Fpga 13-task-and-functions
 
Javascript Best Practices
Javascript Best PracticesJavascript Best Practices
Javascript Best Practices
 
OpenWebBeans and DeltaSpike at ApacheCon
OpenWebBeans and DeltaSpike at ApacheConOpenWebBeans and DeltaSpike at ApacheCon
OpenWebBeans and DeltaSpike at ApacheCon
 
JavaScript, qué hermoso eres
JavaScript, qué hermoso eresJavaScript, qué hermoso eres
JavaScript, qué hermoso eres
 
Construire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradleConstruire une application JavaFX 8 avec gradle
Construire une application JavaFX 8 avec gradle
 
Creational pattern 2
Creational pattern 2Creational pattern 2
Creational pattern 2
 
Digging into asyncio
Digging into asyncioDigging into asyncio
Digging into asyncio
 
Exciting JavaScript - Part I
Exciting JavaScript - Part IExciting JavaScript - Part I
Exciting JavaScript - Part I
 
From Legacy to Hexagonal (An Unexpected Android Journey)
From Legacy to Hexagonal (An Unexpected Android Journey)From Legacy to Hexagonal (An Unexpected Android Journey)
From Legacy to Hexagonal (An Unexpected Android Journey)
 
He stopped using for/while loops, you won't believe what happened next!
He stopped using for/while loops, you won't believe what happened next!He stopped using for/while loops, you won't believe what happened next!
He stopped using for/while loops, you won't believe what happened next!
 
The things we don't see – stories of Software, Scala and Akka
The things we don't see – stories of Software, Scala and AkkaThe things we don't see – stories of Software, Scala and Akka
The things we don't see – stories of Software, Scala and Akka
 
Dependency Inversion and Dependency Injection in PHP
Dependency Inversion and Dependency Injection in PHPDependency Inversion and Dependency Injection in PHP
Dependency Inversion and Dependency Injection in PHP
 
Release management with NuGet/Chocolatey/JIRA
Release management with NuGet/Chocolatey/JIRARelease management with NuGet/Chocolatey/JIRA
Release management with NuGet/Chocolatey/JIRA
 
Mastering Python lesson 4_functions_parameters_arguments
Mastering Python lesson 4_functions_parameters_argumentsMastering Python lesson 4_functions_parameters_arguments
Mastering Python lesson 4_functions_parameters_arguments
 
The Naked Bundle - Tryout
The Naked Bundle - TryoutThe Naked Bundle - Tryout
The Naked Bundle - Tryout
 
Navigating the wild seas of es6 modules
Navigating the wild seas of es6 modulesNavigating the wild seas of es6 modules
Navigating the wild seas of es6 modules
 

More from Luciano Mammino

More from Luciano Mammino (20)

Did you know JavaScript has iterators? DublinJS
Did you know JavaScript has iterators? DublinJSDid you know JavaScript has iterators? DublinJS
Did you know JavaScript has iterators? DublinJS
 
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
What I learned by solving 50 Advent of Code challenges in Rust - RustNation U...
 
Building an invite-only microsite with Next.js & Airtable - ReactJS Milano
Building an invite-only microsite with Next.js & Airtable - ReactJS MilanoBuilding an invite-only microsite with Next.js & Airtable - ReactJS Milano
Building an invite-only microsite with Next.js & Airtable - ReactJS Milano
 
From Node.js to Design Patterns - BuildPiper
From Node.js to Design Patterns - BuildPiperFrom Node.js to Design Patterns - BuildPiper
From Node.js to Design Patterns - BuildPiper
 
Let's build a 0-cost invite-only website with Next.js and Airtable!
Let's build a 0-cost invite-only website with Next.js and Airtable!Let's build a 0-cost invite-only website with Next.js and Airtable!
Let's build a 0-cost invite-only website with Next.js and Airtable!
 
Everything I know about S3 pre-signed URLs
Everything I know about S3 pre-signed URLsEverything I know about S3 pre-signed URLs
Everything I know about S3 pre-signed URLs
 
Serverless for High Performance Computing
Serverless for High Performance ComputingServerless for High Performance Computing
Serverless for High Performance Computing
 
Serverless for High Performance Computing
Serverless for High Performance ComputingServerless for High Performance Computing
Serverless for High Performance Computing
 
JavaScript Iteration Protocols - Workshop NodeConf EU 2022
JavaScript Iteration Protocols - Workshop NodeConf EU 2022JavaScript Iteration Protocols - Workshop NodeConf EU 2022
JavaScript Iteration Protocols - Workshop NodeConf EU 2022
 
Building an invite-only microsite with Next.js & Airtable
Building an invite-only microsite with Next.js & AirtableBuilding an invite-only microsite with Next.js & Airtable
Building an invite-only microsite with Next.js & Airtable
 
Let's take the monolith to the cloud 🚀
Let's take the monolith to the cloud 🚀Let's take the monolith to the cloud 🚀
Let's take the monolith to the cloud 🚀
 
A look inside the European Covid Green Certificate - Rust Dublin
A look inside the European Covid Green Certificate - Rust DublinA look inside the European Covid Green Certificate - Rust Dublin
A look inside the European Covid Green Certificate - Rust Dublin
 
Monoliths to the cloud!
Monoliths to the cloud!Monoliths to the cloud!
Monoliths to the cloud!
 
The senior dev
The senior devThe senior dev
The senior dev
 
Node.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community VijayawadaNode.js: scalability tips - Azure Dev Community Vijayawada
Node.js: scalability tips - Azure Dev Community Vijayawada
 
A look inside the European Covid Green Certificate (Codemotion 2021)
A look inside the European Covid Green Certificate (Codemotion 2021)A look inside the European Covid Green Certificate (Codemotion 2021)
A look inside the European Covid Green Certificate (Codemotion 2021)
 
AWS Observability Made Simple
AWS Observability Made SimpleAWS Observability Made Simple
AWS Observability Made Simple
 
Semplificare l'observability per progetti Serverless
Semplificare l'observability per progetti ServerlessSemplificare l'observability per progetti Serverless
Semplificare l'observability per progetti Serverless
 
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
Finding a lost song with Node.js and async iterators - NodeConf Remote 2021
 
Finding a lost song with Node.js and async iterators - EnterJS 2021
Finding a lost song with Node.js and async iterators - EnterJS 2021Finding a lost song with Node.js and async iterators - EnterJS 2021
Finding a lost song with Node.js and async iterators - EnterJS 2021
 

Recently uploaded

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Recently uploaded (20)

Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024AXA XL - Insurer Innovation Award Americas 2024
AXA XL - Insurer Innovation Award Americas 2024
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdfRising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdf
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 

Unbundling the JavaScript module bundler - DevIT

  • 1. Unbundling the JavaScriptUnbundling the JavaScript module bundlermodule bundler Luciano Mammino -Luciano Mammino - @loige@loige 10/06/2019 loige.link/bundle-devit 1
  • 2. Webpack == PAIN!?Webpack == PAIN!? twitter.com/search?q=webpack%20pain @loige 2
  • 10. It's not Webpack!It's not Webpack! Module bundling is actually complicated!Module bundling is actually complicated! @loige 10
  • 11. 11
  • 12. Hello, I am Luciano!Hello, I am Luciano! 11
  • 13. Hello, I am Luciano!Hello, I am Luciano! 11
  • 14. Hello, I am Luciano!Hello, I am Luciano! 11
  • 15. Hello, I am Luciano!Hello, I am Luciano! 11
  • 16. Hello, I am Luciano!Hello, I am Luciano! Cloud Architect 11
  • 17. Hello, I am Luciano!Hello, I am Luciano! Cloud Architect Blog: Twitter: GitHub:   loige.co @loige @lmammino 11
  • 18. Hello, I am Luciano!Hello, I am Luciano! Cloud Architect Blog: Twitter: GitHub:   loige.co @loige @lmammino 11
  • 20. 1. Why we need modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 13
  • 23. Dynamic DOM manipulation React, Angular, Vue are so... overrated! @loige 16
  • 27. Persist state through Local Storage + UUIDs  +store2 uuidjs@loige 20
  • 30. 7 Requests only for the JS code! @loige 21
  • 33. How to do this?How to do this? @loige 24
  • 37. lumpy build$ 1. Downloads the files from lumpy.txt (and caches them) 2. Concatenates the content of the files 3. Minifies the resulting source code (using ) 4. Saves the resulting content in vendors.js babel-minify @loige 26 . 3
  • 39. 7 requests 2 requests @loige Even better if you "minify" these! 27
  • 41. This is good...This is good... @loige 29
  • 42. This is good...This is good... @loige 29
  • 43. This is good...This is good... ... in 2008... in 2008 waswas @loige 29
  • 44. Today...Today... We can do better!We can do better! @loige 30
  • 45. UpdatingUpdating them should be easythem should be easy We shouldn't worry aboutWe shouldn't worry about transitive dependenciestransitive dependencies (dependencies of dependencies)(dependencies of dependencies) Order of importsOrder of imports shouldn't really mattershouldn't really matter We rely onWe rely on dependenciesdependencies!! @loige 31
  • 46. DependencyDependency (or coupling) a state in which one object uses a function of another object — Wikipedia @loige 32
  • 48. ModulesModules The bricks for structuring non-trivial applications, but also the main mechanism to enforce information hiding by keeping private all the functions and variables that are not explicitly marked to be exported — *Node.js Design Patterns (Second Edition) * yeah, I quite like quoting my stuff... @loige 34
  • 49. 1. Why we need modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 35
  • 50. Meet my friendMeet my friend I.I.F.E.I.I.F.E. (Immediately Invoked Function Expression) loige.link/iife @loige 36
  • 51. We generally define a function this wayWe generally define a function this way @loige 37
  • 52. We generally define a function this wayWe generally define a function this way const sum = (a, b) => a + b @loige 37
  • 53. We generally define a function this wayWe generally define a function this way const sum = (a, b) => a + b oror @loige 37
  • 54. We generally define a function this wayWe generally define a function this way const sum = (a, b) => a + b function sum(a, b) { return a + b } oror @loige 37
  • 55. We generally define a function this wayWe generally define a function this way const sum = (a, b) => a + b function sum(a, b) { return a + b } oror then, at some point, we execute it...then, at some point, we execute it... @loige 37
  • 56. We generally define a function this wayWe generally define a function this way const sum = (a, b) => a + b function sum(a, b) { return a + b } oror then, at some point, we execute it...then, at some point, we execute it... const four = sum(2, 2) @loige 37
  • 57. A function in JS creates an isolated scopeA function in JS creates an isolated scope @loige 38
  • 58. A function in JS creates an isolated scopeA function in JS creates an isolated scope (a, b) => { const secretString = "Hello" return a + b } console.log(secretString) // undefined @loige 38
  • 59. A function in JS creates an isolated scopeA function in JS creates an isolated scope (a, b) => { const secretString = "Hello" return a + b } console.log(secretString) // undefined secretStringsecretString is not visible outside the functionis not visible outside the function @loige 38
  • 60. IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope that executes itselfthat executes itself (arg1, arg2) => { // do stuff here const iAmNotVisibleOutside = true } @loige 39
  • 61. IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope that executes itselfthat executes itself (arg1, arg2) => { // do stuff here const iAmNotVisibleOutside = true } A function with its own scope @loige 39
  • 62. )(someArg1, someArg2) IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope that executes itselfthat executes itself (arg1, arg2) => { // do stuff here const iAmNotVisibleOutside = true } ( @loige 39
  • 63. )(someArg1, someArg2) IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope that executes itselfthat executes itself (arg1, arg2) => { // do stuff here const iAmNotVisibleOutside = true } ( This wrapper executes the function immediately and passes arguments from the outer scope @loige 39
  • 64. IIFE is a recurring pattern inIIFE is a recurring pattern in JavaScript modulesJavaScript modules @loige 40
  • 65. Let's implement a moduleLet's implement a module that provides:that provides:   Information hidingInformation hiding exported functionalitiesexported functionalities @loige 41
  • 66. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined @loige42
  • 67. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() A module myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined @loige42
  • 68. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() A module myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined IIFE Creates an isolated scope and executes it @loige42
  • 69. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() A module myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined information hiding non-exported functionality @loige42
  • 70. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() A module myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined defines exported functionalities @loige42
  • 71. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() A module myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined propagates the exports to the outer scope (assigning it to myModule) @loige42
  • 72. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() A module myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined Can access exported functionalities @loige42
  • 73. const myModule = (() => { const privateFoo = () => { /* ... */ } const privateBar = [ /* ... */ ] const exported = { publicFoo: () => { /* ... */ }, publicBar: [ /* ... */ ] }; return exported })() A module myModule.publicFoo() myModule.publicBar[0] myModule.privateFoo // undefined myModule.privateBar // undefined privateFoo // undefined privateBar // undefined No visibility for the non-exported ones @loige42
  • 74. We want modules to beWe want modules to be   reusablereusable across different apps andacross different apps and organisations...organisations... ...we need...we need AA STANDARD MODULESTANDARD MODULE format!format!@loige 43
  • 75. Module system featuresModule system features Must have Simple syntax for import / export Information hiding Allows to define modules in separate files Modules can import from other modules (nested dependencies) @loige 44 . 1
  • 76. Module system featuresModule system features Nice to have Ability to import module subsets Avoid naming collision Asynchronous module loading Seamless support for Browsers & Server-side @loige 44 . 2
  • 77. JavaScript module systemsJavaScript module systems globals CommonJS (Node.js) AMD (Require.js / Dojo) UMD ES2015 Modules (ESM) Many others (SystemJS, ...) @loige 45
  • 78. GlobalsGlobals var $, jQuery $ = jQuery = (() => { return { /* ... */ } })() // ... use $ or jQuery in the global scope $.find('.button').remove() @loige 46
  • 79. GlobalsGlobals Might generate naming collisions (e.g. $ overrides browser global variable)   Modules needs to be "fully loaded" in the right order   Cannot import parts of modules @loige 47
  • 80. // or import single functionality const { concat } = require('./loDash') concat([1], [2], [3]) // app.js // import full module const _ = require('./loDash') _.concat([1], [2], [3]) // loDash.js const loDash = { /* ... */ } module.exports = loDash CommonJSCommonJS @loige 48
  • 81. // or import single functionality const { concat } = require('./loDash') concat([1], [2], [3]) // app.js // import full module const _ = require('./loDash') _.concat([1], [2], [3]) // loDash.js const loDash = { /* ... */ } module.exports = loDash CommonJSCommonJS module @loige 48
  • 82. // or import single functionality const { concat } = require('./loDash') concat([1], [2], [3]) // app.js // import full module const _ = require('./loDash') _.concat([1], [2], [3]) // loDash.js const loDash = { /* ... */ } module.exports = loDash CommonJSCommonJS module app using module @loige 48
  • 83. // or import single functionality const { concat } = require('./loDash') concat([1], [2], [3]) // app.js // import full module const _ = require('./loDash') _.concat([1], [2], [3]) // loDash.js const loDash = { /* ... */ } module.exports = loDash CommonJSCommonJS module app using module @loige 48
  • 84. // or import single functionality const { concat } = require('./loDash') concat([1], [2], [3]) // app.js // import full module const _ = require('./loDash') _.concat([1], [2], [3]) // loDash.js const loDash = { /* ... */ } module.exports = loDash CommonJSCommonJS module app using module @loige 48
  • 85. CommonJSCommonJS No naming collisions (imported modules can be renamed) Huge repository of modules through   Synchronous import only Works natively on the server side only (Node.js) NPM @loige 49
  • 86. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // jquery-1.9.0.js define( 'jquery', ['sizzle', 'jqueryUI'], function (sizzle, jqueryUI) { // Returns the exported value return function () { // ... } } ) @loige 50
  • 87. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // jquery-1.9.0.js define( 'jquery', ['sizzle', 'jqueryUI'], function (sizzle, jqueryUI) { // Returns the exported value return function () { // ... } } ) module @loige 50
  • 88. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // jquery-1.9.0.js define( 'jquery', ['sizzle', 'jqueryUI'], function (sizzle, jqueryUI) { // Returns the exported value return function () { // ... } } ) module module name @loige 50
  • 89. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // jquery-1.9.0.js define( 'jquery', ['sizzle', 'jqueryUI'], function (sizzle, jqueryUI) { // Returns the exported value return function () { // ... } } ) module dependencies @loige 50
  • 90. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // jquery-1.9.0.js define( 'jquery', ['sizzle', 'jqueryUI'], function (sizzle, jqueryUI) { // Returns the exported value return function () { // ... } } ) module factory function used to construct the module, receives the dependencies as arguments @loige 50
  • 91. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // jquery-1.9.0.js define( 'jquery', ['sizzle', 'jqueryUI'], function (sizzle, jqueryUI) { // Returns the exported value return function () { // ... } } ) module exported value @loige 50
  • 92. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // app.js // define paths requirejs.config({ baseUrl: 'js/lib', paths: { jquery: 'jquery-1.9.0' } }) define(['jquery'], function ($) { // this is executed only when jquery // and its deps are loaded }); @loige 51
  • 93. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // app.js // define paths requirejs.config({ baseUrl: 'js/lib', paths: { jquery: 'jquery-1.9.0' } }) define(['jquery'], function ($) { // this is executed only when jquery // and its deps are loaded }); app @loige 51
  • 94. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // app.js // define paths requirejs.config({ baseUrl: 'js/lib', paths: { jquery: 'jquery-1.9.0' } }) define(['jquery'], function ($) { // this is executed only when jquery // and its deps are loaded }); app Require.js config jquery will be loaded from ://<currentDomain>/js/lib/jquery-1.9.0.js @loige 51
  • 95. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js // app.js // define paths requirejs.config({ baseUrl: 'js/lib', paths: { jquery: 'jquery-1.9.0' } }) define(['jquery'], function ($) { // this is executed only when jquery // and its deps are loaded }); app app main function Has jquery as dependency @loige 51
  • 96. AMD (AMD ( )) Asynchronous Module DefinitionAsynchronous Module Definition Require.jsRequire.js Asynchronous modules Works on Browsers and Server side   Very verbose and convoluted syntax (my opinion™) @loige 52
  • 97. UMDUMD Universal Module DefinitionUniversal Module Definition A module definition that is compatible with Global modules, CommonJS & AMD    github.com/umdjs/umd @loige 53 . 1
  • 98. (function (root, factory) { if (typeof exports === 'object') { // CommonJS module.exports = factory(require('dep')) } else if (typeof define === 'function' && define.amd) { // AMD define(['dep'], function (dep) { return (root.returnExportsGlobal = factory(dep)) }) } else { // Global Variables root.myModule = factory(root.dep) } }(this, function (dep) { // Your actual module return {} })) loige.link/umd@loige 53 . 2
  • 99. (function (root, factory) { if (typeof exports === 'object') { // CommonJS module.exports = factory(require('dep')) } else if (typeof define === 'function' && define.amd) { // AMD define(['dep'], function (dep) { return (root.returnExportsGlobal = factory(dep)) }) } else { // Global Variables root.myModule = factory(root.dep) } }(this, function (dep) { // Your actual module return {} })) loige.link/umd IIFE with arguments:  - Current scope (this) and the module factory function.  - "dep" is a sample dependency of the module. @loige 53 . 2
  • 100. (function (root, factory) { if (typeof exports === 'object') { // CommonJS module.exports = factory(require('dep')) } else if (typeof define === 'function' && define.amd) { // AMD define(['dep'], function (dep) { return (root.returnExportsGlobal = factory(dep)) }) } else { // Global Variables root.myModule = factory(root.dep) } }(this, function (dep) { // Your actual module return {} })) loige.link/umd@loige 53 . 2
  • 101. (function (root, factory) { if (typeof exports === 'object') { // CommonJS module.exports = factory(require('dep')) } else if (typeof define === 'function' && define.amd) { // AMD define(['dep'], function (dep) { return (root.returnExportsGlobal = factory(dep)) }) } else { // Global Variables root.myModule = factory(root.dep) } }(this, function (dep) { // Your actual module return {} })) loige.link/umd@loige 53 . 2
  • 102. (function (root, factory) { if (typeof exports === 'object') { // CommonJS module.exports = factory(require('dep')) } else if (typeof define === 'function' && define.amd) { // AMD define(['dep'], function (dep) { return (root.returnExportsGlobal = factory(dep)) }) } else { // Global Variables root.myModule = factory(root.dep) } }(this, function (dep) { // Your actual module return {} })) loige.link/umd@loige 53 . 2
  • 103. Allows you to define modules that can be used by almost any module loader   Complex, the wrapper code is almost impossible to write manually UMDUMD Universal Module DefinitionUniversal Module Definition @loige 53 . 3
  • 104. ES2015 modulesES2015 modules Cool & broad subject, it would deserve it's own talk Wanna know more? /  syntax referenceimport export ECMAScript modules in browsers ES modules: A cartoon deep-dive ES Modules in Node Today! @loige 54 . 1
  • 105. // calculator.js const add = (num1, num2) => num1 + num2 const sub = (num1, num2) => num1 - num2 const div = (num1, num2) => num1 / num2 const mul = (num1, num2) => num1 * num2 export { add, sub, div, mul } // app.js import { add } from './calculator' console.log(add(2,2)) // 4 ES2015 modulesES2015 modules @loige 54 . 2
  • 106. // calculator.js const add = (num1, num2) => num1 + num2 const sub = (num1, num2) => num1 - num2 const div = (num1, num2) => num1 / num2 const mul = (num1, num2) => num1 * num2 export { add, sub, div, mul } // app.js import { add } from './calculator' console.log(add(2,2)) // 4 ES2015 modulesES2015 modules module @loige 54 . 2
  • 107. // calculator.js const add = (num1, num2) => num1 + num2 const sub = (num1, num2) => num1 - num2 const div = (num1, num2) => num1 / num2 const mul = (num1, num2) => num1 * num2 export { add, sub, div, mul } // app.js import { add } from './calculator' console.log(add(2,2)) // 4 ES2015 modulesES2015 modules moduleexported functionalities @loige 54 . 2
  • 108. // calculator.js const add = (num1, num2) => num1 + num2 const sub = (num1, num2) => num1 - num2 const div = (num1, num2) => num1 / num2 const mul = (num1, num2) => num1 * num2 export { add, sub, div, mul } // app.js import { add } from './calculator' console.log(add(2,2)) // 4 ES2015 modulesES2015 modules module app @loige 54 . 2
  • 109. // calculator.js const add = (num1, num2) => num1 + num2 const sub = (num1, num2) => num1 - num2 const div = (num1, num2) => num1 / num2 const mul = (num1, num2) => num1 * num2 export { add, sub, div, mul } // app.js import { add } from './calculator' console.log(add(2,2)) // 4 ES2015 modulesES2015 modules module app import specific functionality @loige 54 . 2
  • 110. // index.html <html> <body> <!-- ... --> <script type="module"> import { add } from 'calculator.js' console.log(add(2,2)) // 4 </script> </body> </html> ES2015 modulesES2015 modules @loige 54 . 3
  • 111. // index.html <html> <body> <!-- ... --> <script type="module"> import { add } from 'calculator.js' console.log(add(2,2)) // 4 </script> </body> </html> ES2015 modulesES2015 modules "works" in some modern browsers @loige 54 . 3
  • 112. ES2015 modulesES2015 modules Syntactically very similar to CommonJS... BUT import & export are static (allow static analysis of dependencies)   It is a (still work in progress) standard format   Works (almost) seamlessly in browsers & servers @loige 54 . 4
  • 113. So many options...So many options... Current most used practice: Use CommonJS or ES2015 & create "compiled bundles" @loige 55
  • 114. 1. Why we need modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 56
  • 115. Let's try to use CommonJSLet's try to use CommonJS in the browserin the browser @loige 57
  • 116. const $ = require('zepto') const tippy = require('tippy.js') const UUID = require('uuidjs') const { confetti } = require('dom-confetti/src/main') const store = require('store2') const Favico = require('favico.js') !(function () { const colors = ['#a864fd', '#29cdff', '#78ff44', '#ff718d', '#fdff6a'] const todoApp = (rootEl, opt = {}) => { const todos = opt.todos || [] let completedTasks = opt.completedTasks || 0 const onChange = opt.onChange || (() => {}) const list = rootEl.find('.todo-list') const footer = rootEl.find('.footer') const todoCount = footer.find('.todo-count') const insertInput = rootEl.find('.add-todo-box input') const insertBtn = rootEl.find('.add-todo-box button') const render = () => { let tips list.html('') The browser doesn't know how to process require. It doesn't support CommonJS! @loige 58
  • 117. Module BundlerModule Bundler A tool that takes modules with dependencies and emits static assets representing those modules   Those static assets can be processed by browsers! @loige 59
  • 118. Dependency graphDependency graph A graph built by connecting every module with its direct dependencies. app dependency A dependency B dependency A2 shared dependency @loige 60
  • 119. A module bundler has to:A module bundler has to: 1. Construct the dependency graph (Dependency Resolution) 2. Assemble the modules in the graph into a single executable asset (Packing) @loige 61
  • 120. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution 62@loige
  • 121. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app 62 (entrypoint) (1) @loige
  • 122. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator 62 (entrypoint) (1) @loige
  • 123. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator 62 (entrypoint) (1) (2) @loige
  • 124. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator parser 62 (entrypoint) (1) (2) @loige
  • 125. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator parser 62 (entrypoint) (1) (2) (3) @loige
  • 126. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator parser resolver 62 (entrypoint) (1) (2) (3) @loige
  • 127. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator parser resolver 62 (entrypoint) (1) (2) (3) (4) @loige
  • 128. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator log parser resolver 62 (entrypoint) (1) (2) (3) (4) @loige
  • 129. // app.js const calculator = require('./calculator') const log = require('./log') log(calculator('2 + 2 / 4')) // log.js module.exports = console.log // calculator.js const parser = require('./parser') const resolver = require('./resolver') module.exports = (expr) => resolver(parser(expr)) // parser.js module.exports = (expr) => { /* ... */ } // resolver.js module.exports = (tokens) => { /* ... */ } Dependency resolutionDependency resolution app calculator log parser resolver 62 (entrypoint) (1) (2) (3) (4) (5) @loige
  • 130. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map @loige 63
  • 131. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  @loige 63
  • 132. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, @loige 63
  • 133. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, @loige 63
  • 134. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './parser': (module, require) => { … }, @loige 63
  • 135. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, @loige 63
  • 136. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, @loige 63
  • 137. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, require path @loige 63
  • 138. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, module factory function @loige 63
  • 139. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, const parser = require('./parser');const resolver =  require('./resolver');module.exports = (expr) =>  resolver(parser(expr))@loige 63
  • 140. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, const parser = require('./parser');const resolver =  require('./resolver');module.exports = (expr) =>  resolver(parser(expr))@loige 63
  • 141. DuringDuring dependency resolutiondependency resolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, const parser = require('./parser');const resolver =  require('./resolver');module.exports = (expr) =>  resolver(parser(expr))@loige 63
  • 145. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) 65 @loige
  • 146. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) IIFE passing the modules map as argument 65 @loige
  • 147. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) Custom require function: it will load the modules by evaluating the code from the modules map 65 @loige
  • 148. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) A reference to a module with an empty module.exports.  This will be filled at evaluation time 65 @loige
  • 149. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) Invoking the factory function for the given module name. (Service locator pattern) 65 @loige
  • 150. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) The current reference module is passed, the factory function will modify this object by adding the proper exported values. 65 @loige
  • 151. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) The custom require function is passed so, modules can recursively require other modules 65 @loige
  • 152. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) The resulting module.exports is returned 65 @loige
  • 153. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) The entrypoint module is required, triggering the actual execution of the business logic 65 @loige
  • 154. Packed executable filePacked executable file ((modulesMap) => { const require = (name) => { const module = { exports: {} } modulesMap[name](module, require) return module.exports } require('./app') })( { './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … } } ) 65 @loige
  • 155. Now you know howNow you know how Module bundlers work!Module bundlers work!    And how to convert code writtenAnd how to convert code written usingusing CommonJSCommonJS to a single file thatto a single file that works in the browserworks in the browser @loige 66
  • 156. A challenge for you!A challenge for you! If you do, ... I'll arrange a prize for you!   TIP: you can use  or  to parse JavaScript files (look for require and module.exports) and to map relative module paths to actual files in the filesystem.   Need an inspiration? Check the awesome  and ! let me know acorn babel-parser resolve minipack @adamisnotdead's w_bp_ck Can you build a (simple) module bundler from scratch?Can you build a (simple) module bundler from scratch? @loige 67
  • 157. 1. Why we need modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 68
  • 158. A state of the art moduleA state of the art module bundler for the webbundler for the web @loige 69
  • 160. webpack app.js yep, recent versions of Webpack work without config! @loige 71
  • 161. webpack ­­mode=development app.js Do not compress the code and add annotations! @loige 72
  • 163. Webpack conceptsWebpack concepts Entry point: the starting file for dependency resolution. Output: the destination file (bundled file). Loaders: algorithms to parse different file types and convert them into executable javascript (e.g. babel, typescript, but also CSS, images or other static assets) Plugins: do extra things (e.g. generate a wrapping HTML or analysis tools) @loige 74
  • 164. const { resolve, join } = require('path') const CompressionPlugin = require('compression-webpack-plugin') module.exports = { entry: './app.js', output: { path: resolve(join(__dirname, 'build')), filename: 'app.js' }, module: { rules: [ { test: /.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ] } } } ] }, plugins: [ new CompressionPlugin() ] } Webpack.config.js @loige75
  • 165. const { resolve, join } = require('path') const CompressionPlugin = require('compression-webpack-plugin') module.exports = { entry: './app.js', output: { path: resolve(join(__dirname, 'build')), filename: 'app.js' }, module: { rules: [ { test: /.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ] } } } ] }, plugins: [ new CompressionPlugin() ] } Webpack.config.js Entrypoint Build the dependency graph starting from ./app.js @loige75
  • 166. const { resolve, join } = require('path') const CompressionPlugin = require('compression-webpack-plugin') module.exports = { entry: './app.js', output: { path: resolve(join(__dirname, 'build')), filename: 'app.js' }, module: { rules: [ { test: /.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ] } } } ] }, plugins: [ new CompressionPlugin() ] } Webpack.config.js Output Save the resulting bundled file in ./build/app.js @loige75
  • 167. const { resolve, join } = require('path') const CompressionPlugin = require('compression-webpack-plugin') module.exports = { entry: './app.js', output: { path: resolve(join(__dirname, 'build')), filename: 'app.js' }, module: { rules: [ { test: /.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ] } } } ] }, plugins: [ new CompressionPlugin() ] } Webpack.config.js Loaders All the files matching "*.js" are processed with babel and converted to ES5 Javascript @loige75
  • 168. const { resolve, join } = require('path') const CompressionPlugin = require('compression-webpack-plugin') module.exports = { entry: './app.js', output: { path: resolve(join(__dirname, 'build')), filename: 'app.js' }, module: { rules: [ { test: /.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { presets: [ ['@babel/preset-env'] ] } } } ] }, plugins: [ new CompressionPlugin() ] } Webpack.config.js Plugins Uses a plugin that generates a gzipped copy of every emitted file. @loige75
  • 169. Everything is a moduleEverything is a module import React, { Component } from 'react' import logo from './logo.svg' import './App.css' class App extends Component { render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> </div> ); } } export default App 76
  • 170. Everything is a moduleEverything is a module import React, { Component } from 'react' import logo from './logo.svg' import './App.css' class App extends Component { render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> </div> ); } } export default App 76
  • 171. Everything is a moduleEverything is a module import React, { Component } from 'react' import logo from './logo.svg' import './App.css' class App extends Component { render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> </div> ); } } export default App 76
  • 172. Everything is a moduleEverything is a module import React, { Component } from 'react' import logo from './logo.svg' import './App.css' class App extends Component { render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> </div> ); } } export default App 76
  • 173. Webpack can load any type of fileWebpack can load any type of file As long as you can provide a "As long as you can provide a "loaderloader" that" that tells how to convert the file intotells how to convert the file into something the browser understands.something the browser understands. This is how Webpack allows you to use Babel, TypeScript, Clojure, Elm, Imba but also to load CSSs, Images and other assets. 77
  • 174. { test: /.css$/, use: [ require.resolve('style-loader'), { loader: require.resolve('css-loader'), options: { importLoaders: 1, }, }, { loader: require.resolve('postcss-loader'), options: { ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), autoprefixer({ browsers: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', 78
  • 175. { test: /.css$/, use: [ require.resolve('style-loader'), { loader: require.resolve('css-loader'), options: { importLoaders: 1, }, }, { loader: require.resolve('postcss-loader'), options: { ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), autoprefixer({ browsers: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // Defines how to load .css files (uses a pipeline of loaders) 78
  • 176. { test: /.css$/, use: [ require.resolve('style-loader'), { loader: require.resolve('css-loader'), options: { importLoaders: 1, }, }, { loader: require.resolve('postcss-loader'), options: { ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), autoprefixer({ browsers: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // Defines how to load .css files (uses a pipeline of loaders) // parses the file with post-css 78
  • 177. { test: /.css$/, use: [ require.resolve('style-loader'), { loader: require.resolve('css-loader'), options: { importLoaders: 1, }, }, { loader: require.resolve('postcss-loader'), options: { ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), autoprefixer({ browsers: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // Defines how to load .css files (uses a pipeline of loaders) // parses the file with post-css // process @import and url() // statements 78
  • 178. { test: /.css$/, use: [ require.resolve('style-loader'), { loader: require.resolve('css-loader'), options: { importLoaders: 1, }, }, { loader: require.resolve('postcss-loader'), options: { ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), autoprefixer({ browsers: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // Defines how to load .css files (uses a pipeline of loaders) // parses the file with post-css // process @import and url() // statements // inject the resulting code with a <style> tag 78
  • 179. ...Webpack can do (a lot) more!...Webpack can do (a lot) more! Dev ServerDev Server Tree shakingTree shaking Dependencies analyticsDependencies analytics Source mapsSource maps Async require / module splittingAsync require / module splitting @loige 79
  • 180. 1. Why we need modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 80
  • 181. Bundle cache bustingBundle cache busting Device CDN Origin Server bundle.js (original) 81
  • 182. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js (original) 81
  • 183. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js (original) 81
  • 184. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) 81
  • 185. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) 81
  • 186. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81
  • 187. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81
  • 188. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81 bundle.js (cache copy)
  • 189. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js 82 bundle.js (original) bundle.js (cache copy) bundle.js (cache copy)
  • 190. Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js 82 bundle.js (original) bundle.js (cache copy) bundle.js (cache copy) NEW VERSION
  • 191. STALE! Bundle cache bustingBundle cache busting Device CDN Origin Server example.com bundle.js bundle.js 82 bundle.js (original) bundle.js (cache copy) bundle.js (cache copy) NEW VERSIONSTALE!
  • 192. Bundle cache bustingBundle cache busting 83
  • 193. Bundle cache bustingBundle cache busting (Manual) Solution 1(Manual) Solution 1 83
  • 194. Bundle cache bustingBundle cache busting (Manual) Solution 1(Manual) Solution 1 bundle.js?v=1 bundle.js?v=2 bundle.js?v=3 83
  • 195. Bundle cache bustingBundle cache busting (Manual) Solution 1(Manual) Solution 1 bundle.js?v=1 bundle.js?v=2 bundle.js?v=3 Doesn't play nice with some CDNs and Proxies (they won't consider different query parameters to be different resources) 83
  • 196. Bundle cache bustingBundle cache busting 84
  • 197. Bundle cache bustingBundle cache busting (Manual) Solution 2(Manual) Solution 2 84
  • 198. Bundle cache bustingBundle cache busting (Manual) Solution 2(Manual) Solution 2 bundle-v1.js bundle-v2.js bundle-v3.js ... 84
  • 199. Bundle cache bustingBundle cache busting (Manual) Solution 2(Manual) Solution 2 bundle-v1.js bundle-v2.js bundle-v3.js ... Better, but still a lot of diligence and manual effort needed... 84
  • 200. Bundle cache bustingBundle cache busting 85
  • 201. Bundle cache bustingBundle cache busting Webpack SolutionWebpack Solution 85
  • 202. Bundle cache bustingBundle cache busting Webpack SolutionWebpack Solution output: {    filename: '[name].[contenthash].js'  } 85
  • 203. Bundle cache bustingBundle cache busting Webpack SolutionWebpack Solution ... bundle.9f61f58dd1cc3bb82182.js  bundle.aacdf58ef1aa12382199.js  bundle.ed61f68defef3bb82221.js output: {    filename: '[name].[contenthash].js'  } 85
  • 204. Bundle cache bustingBundle cache busting Webpack SolutionWebpack Solution  +   Every new asset version will generate a new file cache is automatically cleaned up on every release (if content actually changed) html-plugin will update the reference to the new file contenthash webpack-html-plugin 86
  • 205. 1. Why we need modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 87
  • 206. Module bundlers are your friendsModule bundlers are your friends Now you know how they work, they are not (really) magic! Start small and add more when needed If you try to build your own, you'll learn a lot more! @loige 88
  • 207. Webpack is notWebpack is not the only possibility!the only possibility! 89
  • 208. Ευχαριστώ!Ευχαριστώ!      Special thanks: , , , (reviewers) and  (inspirations: his and his ) @Podgeypoos79 @andreaman87 @mariocasciaro @eugenserbanescu @MarijnJH amazing book workshop on JS modules @loige Images by cover Image by  from   Streamline Emoji pack Dimitris Vetsikas Pixabay loige.link/bundle-devit 90