Unbundling the JavaScriptUnbundling the JavaScript
module bundlermodule bundler
Luciano Mammino -Luciano Mammino - @loige@loige
21/11/2018
loige.link/bundle-oredev
1
loige.link/bundle-oredev 2
Webpack == PAIN!?Webpack == PAIN!?
twitter.com/search?q=webpack%20pain
@loige 3
@loige 4
@loige 5
@loige 6
@loige
❤
7
@loige 8
@loige 9
@loige 10
It's not Webpack!It's not Webpack!
Module bundling is actually complicated!Module bundling is actually complicated!
@loige 11
Luciano... whoLuciano... who
Find me online:
-  (@loige)
-  (lmammino)
-
-  (loige.co)
Twitter
GitHub
Linkedin
Blog
Solution Architect at
with @mariocasciaro
with @andreaman87
with @Podgeypoos79
12
Luciano... whoLuciano... who
Find me online:
-  (@loige)
-  (lmammino)
-
-  (loige.co)
Twitter
GitHub
Linkedin
Blog
Solution Architect at
with @mariocasciaro
with @andreaman87
with @Podgeypoos79
 
CLICK HERE IF YOU
WANT TO WIN
A (USEFUL) PRIZE
loige.link/oredev-win
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-exposed 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
A good example for dev/prod configA good example for dev/prod config
(with create-react-app)(with create-react-app)
npm i -g create-react-app
create-react-app new-project
cd new-project
npm run eject
cat config/webpack*.js
87
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 88
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 89
Webpack is notWebpack is not
the only possibility!the only possibility!
90
THE PRIZE!THE PRIZE!
npx twaffle --keywords '#oredev #IWANTTOWINAPRIZE'
Checkout if you want to organise Twitter-based raffles.twaffle
91
Tack så mycket!Tack så mycket!
     Special thanks:
Tadeas from Øredev (for reviewing my stuff thoroughly)
, , ,
(reviewers) and
 (inspirations: his and his
)
@Podgeypoos79 @andreaman87 @mariocasciaro
@eugenserbanescu
@MarijnJH amazing book
workshop on JS modules
@loige
Images by .Streamline Emoji pack
loige.link/bundle-oredev 92

Unbundling the JavaScript module bundler - Øredev 21 Nov 2018

  • 1.
    Unbundling the JavaScriptUnbundlingthe JavaScript module bundlermodule bundler Luciano Mammino -Luciano Mammino - @loige@loige 21/11/2018 loige.link/bundle-oredev 1
  • 2.
  • 3.
    Webpack == PAIN!?Webpack== PAIN!? twitter.com/search?q=webpack%20pain @loige 3
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
    It's not Webpack!It'snot Webpack! Module bundling is actually complicated!Module bundling is actually complicated! @loige 11
  • 12.
    Luciano... whoLuciano... who Findme online: -  (@loige) -  (lmammino) - -  (loige.co) Twitter GitHub Linkedin Blog Solution Architect at with @mariocasciaro with @andreaman87 with @Podgeypoos79 12
  • 13.
    Luciano... whoLuciano... who Findme online: -  (@loige) -  (lmammino) - -  (loige.co) Twitter GitHub Linkedin Blog Solution Architect at with @mariocasciaro with @andreaman87 with @Podgeypoos79   CLICK HERE IF YOU WANT TO WIN A (USEFUL) PRIZE loige.link/oredev-win 12
  • 14.
    1. Why weneed modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 13
  • 15.
  • 16.
  • 17.
    Dynamic DOM manipulation React,Angular, Vue are so... overrated! @loige 16
  • 18.
  • 19.
  • 20.
  • 21.
    Persist state throughLocal Storage + UUIDs  +store2 uuidjs@loige 20
  • 22.
  • 23.
  • 24.
    7 Requests onlyfor the JS code! @loige 21
  • 25.
  • 26.
  • 27.
    How to dothis?How to do this? @loige 24
  • 28.
  • 29.
  • 30.
  • 31.
    lumpy build$ 1. Downloads thefiles 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
  • 32.
  • 33.
    7 requests 2 requests @loige Evenbetter if you "minify" these! 27
  • 34.
  • 35.
    This is good...Thisis good... @loige 29
  • 36.
    This is good...Thisis good... @loige 29
  • 37.
    This is good...Thisis good... ... in 2008... in 2008 waswas @loige 29
  • 38.
    Today...Today... We can dobetter!We can do better! @loige 30
  • 39.
    UpdatingUpdating them shouldbe 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
  • 40.
    DependencyDependency (or coupling) a statein which one object uses a function of another object — Wikipedia @loige 32
  • 41.
  • 42.
    ModulesModules The bricks forstructuring 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
  • 43.
    1. Why weneed modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 35
  • 44.
    Meet my friendMeetmy friend I.I.F.E.I.I.F.E. (Immediately Invoked Function Expression) loige.link/iife @loige 36
  • 45.
    We generally definea function this wayWe generally define a function this way @loige 37
  • 46.
    We generally definea function this wayWe generally define a function this way const sum = (a, b) => a + b @loige 37
  • 47.
    We generally definea function this wayWe generally define a function this way const sum = (a, b) => a + b oror @loige 37
  • 48.
    We generally definea 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
  • 49.
    We generally definea 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
  • 50.
    We generally definea 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
  • 51.
    A function inJS creates an isolated scopeA function in JS creates an isolated scope @loige 38
  • 52.
    A function inJS 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
  • 53.
    A function inJS 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
  • 54.
    IIFE allows youto 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
  • 55.
    IIFE allows youto 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
  • 56.
    )(someArg1, someArg2) IIFE allowsyou 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
  • 57.
    )(someArg1, someArg2) IIFE allowsyou 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
  • 58.
    IIFE is arecurring pattern inIIFE is a recurring pattern in JavaScript modulesJavaScript modules @loige 40
  • 59.
    Let's implement amoduleLet's implement a module that provides:that provides:   Information hidingInformation hiding exported functionalitiesexported functionalities @loige 41
  • 60.
    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
  • 61.
    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
  • 62.
    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
  • 63.
    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-exposed functionality @loige42
  • 64.
    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
  • 65.
    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
  • 66.
    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
  • 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 No visibility for the non-exported ones @loige42
  • 68.
    We want modulesto 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
  • 69.
    Module system featuresModulesystem 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
  • 70.
    Module system featuresModulesystem features Nice to have Ability to import module subsets Avoid naming collision Asynchronous module loading Seamless support for Browsers & Server-side @loige 44 . 2
  • 71.
    JavaScript module systemsJavaScriptmodule systems globals CommonJS (Node.js) AMD (Require.js / Dojo) UMD ES2015 Modules (ESM) Many others (SystemJS, ...) @loige 45
  • 72.
    GlobalsGlobals var $, jQuery $= jQuery = (() => { return { /* ... */ } })() // ... use $ or jQuery in the global scope $.find('.button').remove() @loige 46
  • 73.
    GlobalsGlobals Might generate namingcollisions (e.g. $ overrides browser global variable)   Modules needs to be "fully loaded" in the right order   Cannot import parts of modules @loige 47
  • 74.
    // or importsingle 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
  • 75.
    // or importsingle 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
  • 76.
    // or importsingle 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
  • 77.
    // or importsingle 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
  • 78.
    // or importsingle 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
  • 79.
    CommonJSCommonJS No naming collisions (importedmodules can be renamed) Huge repository of modules through   Synchronous import only Works natively on the server side only (Node.js) NPM @loige 49
  • 80.
    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
  • 81.
    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
  • 82.
    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
  • 83.
    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
  • 84.
    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
  • 85.
    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
  • 86.
    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
  • 87.
    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
  • 88.
    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
  • 89.
    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
  • 90.
    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
  • 91.
    UMDUMD Universal Module DefinitionUniversalModule Definition A module definition that is compatible with Global modules, CommonJS & AMD    github.com/umdjs/umd @loige 53 . 1
  • 92.
    (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
  • 93.
    (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
  • 94.
    (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
  • 95.
    (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
  • 96.
    (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
  • 97.
    Allows you todefine 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
  • 98.
    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
  • 99.
    // 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
  • 100.
    // 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
  • 101.
    // 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
  • 102.
    // 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
  • 103.
    // 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
  • 104.
    // 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
  • 105.
    // 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
  • 106.
    ES2015 modulesES2015 modules Syntacticallyvery 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
  • 107.
    So many options...Somany options... Current most used practice: Use CommonJS or ES2015 & create "compiled bundles" @loige 55
  • 108.
    1. Why weneed modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 56
  • 109.
    Let's try touse CommonJSLet's try to use CommonJS in the browserin the browser @loige 57
  • 110.
    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
  • 111.
    Module BundlerModule Bundler Atool that takes modules with dependencies and emits static assets representing those modules   Those static assets can be processed by browsers! @loige 59
  • 112.
    Dependency graphDependency graph Agraph built by connecting every module with its direct dependencies. app dependency A dependency B dependency A2 shared dependency @loige 60
  • 113.
    A module bundlerhas 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
  • 114.
    // 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
  • 115.
    // 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
  • 116.
    // 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
  • 117.
    // 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
  • 118.
    // 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
  • 119.
    // 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
  • 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 app calculator parser resolver 62 (entrypoint) (1) (2) (3) @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 calculator parser resolver 62 (entrypoint) (1) (2) (3) (4) @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 log parser resolver 62 (entrypoint) (1) (2) (3) (4) @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 log parser resolver 62 (entrypoint) (1) (2) (3) (4) (5) @loige
  • 124.
    DuringDuring dependency resolutiondependencyresolution,, the bundler creates athe bundler creates a modules mapmodules map @loige 63
  • 125.
    DuringDuring dependency resolutiondependencyresolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  @loige 63
  • 126.
    DuringDuring dependency resolutiondependencyresolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, @loige 63
  • 127.
    DuringDuring dependency resolutiondependencyresolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, @loige 63
  • 128.
    DuringDuring dependency resolutiondependencyresolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './parser': (module, require) => { … }, @loige 63
  • 129.
    DuringDuring dependency resolutiondependencyresolution,, the bundler creates athe bundler creates a modules mapmodules map {                }  './app': (module, require) => { … }, './calculator': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, @loige 63
  • 130.
    DuringDuring dependency resolutiondependencyresolution,, 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
  • 131.
    DuringDuring dependency resolutiondependencyresolution,, 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
  • 132.
    DuringDuring dependency resolutiondependencyresolution,, 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
  • 133.
    DuringDuring dependency resolutiondependencyresolution,, 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
  • 134.
    DuringDuring dependency resolutiondependencyresolution,, 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
  • 135.
    DuringDuring dependency resolutiondependencyresolution,, 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
  • 136.
  • 137.
  • 138.
  • 139.
    Packed executable filePackedexecutable 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
  • 140.
    Packed executable filePackedexecutable 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
  • 141.
    Packed executable filePackedexecutable 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
  • 142.
    Packed executable filePackedexecutable 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
  • 143.
    Packed executable filePackedexecutable 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
  • 144.
    Packed executable filePackedexecutable 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
  • 145.
    Packed executable filePackedexecutable 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
  • 146.
    Packed executable filePackedexecutable 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
  • 147.
    Packed executable filePackedexecutable 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
  • 148.
    Packed executable filePackedexecutable 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
  • 149.
    Now you knowhowNow 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
  • 150.
    A challenge foryou!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
  • 151.
    1. Why weneed modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 68
  • 152.
    A state ofthe art moduleA state of the art module bundler for the webbundler for the web @loige 69
  • 153.
  • 154.
    webpack app.js yep, recent versionsof Webpack work without config! @loige 71
  • 155.
    webpack ­­mode=development app.js Do not compressthe code and add annotations! @loige 72
  • 156.
  • 157.
    Webpack conceptsWebpack concepts Entrypoint: 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
  • 158.
    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
  • 159.
    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
  • 160.
    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
  • 161.
    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
  • 162.
    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
  • 163.
    Everything is amoduleEverything 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
  • 164.
    Everything is amoduleEverything 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
  • 165.
    Everything is amoduleEverything 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
  • 166.
    Everything is amoduleEverything 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
  • 167.
    Webpack can loadany 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
  • 168.
    { 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
  • 169.
    { 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
  • 170.
    { 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
  • 171.
    { 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
  • 172.
    { 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
  • 173.
    ...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
  • 174.
    1. Why weneed modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 80
  • 175.
    Bundle cache bustingBundlecache busting Device CDN Origin Server bundle.js (original) 81
  • 176.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js (original) 81
  • 177.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js bundle.js (original) 81
  • 178.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) 81
  • 179.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) 81
  • 180.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81
  • 181.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81
  • 182.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81 bundle.js (cache copy)
  • 183.
    Bundle cache bustingBundlecache busting Device CDN Origin Server example.com bundle.js bundle.js 82 bundle.js (original) bundle.js (cache copy) bundle.js (cache copy)
  • 184.
    Bundle cache bustingBundlecache 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
  • 185.
    STALE! Bundle cache bustingBundlecache 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!
  • 186.
  • 187.
    Bundle cache bustingBundlecache busting (Manual) Solution 1(Manual) Solution 1 83
  • 188.
    Bundle cache bustingBundlecache busting (Manual) Solution 1(Manual) Solution 1 bundle.js?v=1 bundle.js?v=2 bundle.js?v=3 83
  • 189.
    Bundle cache bustingBundlecache 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
  • 190.
  • 191.
    Bundle cache bustingBundlecache busting (Manual) Solution 2(Manual) Solution 2 84
  • 192.
    Bundle cache bustingBundlecache busting (Manual) Solution 2(Manual) Solution 2 bundle-v1.js bundle-v2.js bundle-v3.js ... 84
  • 193.
    Bundle cache bustingBundlecache 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
  • 194.
  • 195.
    Bundle cache bustingBundlecache busting Webpack SolutionWebpack Solution 85
  • 196.
    Bundle cache bustingBundlecache busting Webpack SolutionWebpack Solution output: {    filename: '[name].[contenthash].js'  } 85
  • 197.
    Bundle cache bustingBundlecache busting Webpack SolutionWebpack Solution ... bundle.9f61f58dd1cc3bb82182.js  bundle.aacdf58ef1aa12382199.js  bundle.ed61f68defef3bb82221.js output: {    filename: '[name].[contenthash].js'  } 85
  • 198.
    Bundle cache bustingBundlecache 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
  • 199.
    A good examplefor dev/prod configA good example for dev/prod config (with create-react-app)(with create-react-app) npm i -g create-react-app create-react-app new-project cd new-project npm run eject cat config/webpack*.js 87
  • 200.
    1. Why weneed modules 2. JavaScript module systems 3. How a module bundler works 4. Webpack in 2 minutes! 5. Advanced module bundling AgendaAgenda @loige 88
  • 201.
    Module bundlers areyour 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 89
  • 202.
    Webpack is notWebpackis not the only possibility!the only possibility! 90
  • 203.
    THE PRIZE!THE PRIZE! npxtwaffle --keywords '#oredev #IWANTTOWINAPRIZE' Checkout if you want to organise Twitter-based raffles.twaffle 91
  • 204.
    Tack så mycket!Tackså mycket!      Special thanks: Tadeas from Øredev (for reviewing my stuff thoroughly) , , , (reviewers) and  (inspirations: his and his ) @Podgeypoos79 @andreaman87 @mariocasciaro @eugenserbanescu @MarijnJH amazing book workshop on JS modules @loige Images by .Streamline Emoji pack loige.link/bundle-oredev 92