The landscape of module bundlers has evolved significantly since the days you would manually copy-paste your libraries to create a package for your frontend app. Like many parts of the JS world, the evolution has happened somewhat haphazardly, and the pace of change can feel overwhelming. Has Webpack ever felt like magic to you? How well do you understand what’s really going on under the hood? In this talk, I will uncover the history of JS module bundlers and illustrate how they actually work. Once we have the basics down, I will dive deeper into some of the more advanced topics, such as bundle cache boost and resolving cycling dependencies. At the end of this session, you will have a much more profound understanding of what’s going on behind the scenes.
20. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 13
37. lumpy build$
1. Downloads the files from lumpy.txt (and caches them)
2. Concatenates the content of the files
3. Minifies the resulting source code (using )
4. Saves the resulting content in vendors.js
babel-minify
@loige 26 . 3
45. UpdatingUpdating them should be easythem should be easy
We shouldn't worry aboutWe shouldn't worry about transitive dependenciestransitive dependencies
(dependencies of dependencies)(dependencies of dependencies)
Order of importsOrder of imports shouldn't really mattershouldn't really matter
We rely onWe rely on dependenciesdependencies!!
@loige 31
48. ModulesModules
The bricks for structuring non-trivial applications,
but also the main mechanism to enforce information
hiding by keeping private all the functions and
variables that are not explicitly marked to be
exported
— *Node.js Design Patterns (Second Edition)
* yeah, I quite like quoting my stuff...
@loige 34
49. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 35
50. Meet my friendMeet my friend I.I.F.E.I.I.F.E.
(Immediately Invoked Function Expression)
loige.link/iife
@loige 36
51. We generally define a function this wayWe generally define a function this way
@loige 37
52. We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
@loige 37
53. We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
oror
@loige 37
54. We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
oror
@loige 37
55. We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
oror
then, at some point, we execute it...then, at some point, we execute it...
@loige 37
56. We generally define a function this wayWe generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
oror
then, at some point, we execute it...then, at some point, we execute it...
const four = sum(2, 2)
@loige 37
57. A function in JS creates an isolated scopeA function in JS creates an isolated scope
@loige 38
58. A function in JS creates an isolated scopeA function in JS creates an isolated scope
(a, b) => {
const secretString = "Hello"
return a + b
}
console.log(secretString) // undefined
@loige 38
59. A function in JS creates an isolated scopeA function in JS creates an isolated scope
(a, b) => {
const secretString = "Hello"
return a + b
}
console.log(secretString) // undefined
secretStringsecretString is not visible outside the functionis not visible outside the function
@loige 38
60. IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
@loige 39
61. IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
A function with its own scope
@loige 39
62. )(someArg1, someArg2)
IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
(
@loige 39
63. )(someArg1, someArg2)
IIFE allows you to define an isolated scopeIIFE allows you to define an isolated scope
that executes itselfthat executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
(
This wrapper executes the function immediately and passes arguments from the
outer scope
@loige 39
64. IIFE is a recurring pattern inIIFE is a recurring pattern in
JavaScript modulesJavaScript modules
@loige 40
65. Let's implement a moduleLet's implement a module
that provides:that provides:
Information hidingInformation hiding
exported functionalitiesexported functionalities
@loige 41
74. We want modules to beWe want modules to be
reusablereusable across different apps andacross different apps and
organisations...organisations...
...we need...we need
AA STANDARD MODULESTANDARD MODULE format!format!@loige 43
75. Module system featuresModule system features
Must have
Simple syntax for import / export
Information hiding
Allows to define modules in separate files
Modules can import from other modules
(nested dependencies)
@loige 44 . 1
76. Module system featuresModule system features
Nice to have
Ability to import module subsets
Avoid naming collision
Asynchronous module loading
Seamless support for Browsers & Server-side
@loige 44 . 2
78. GlobalsGlobals
var $, jQuery
$ = jQuery = (() => {
return { /* ... */ }
})()
// ... use $ or jQuery in the global scope
$.find('.button').remove()
@loige 46
79. GlobalsGlobals
Might generate naming collisions
(e.g. $ overrides browser global variable)
Modules needs to be "fully loaded" in the right order
Cannot import parts of modules
@loige 47
85. CommonJSCommonJS
No naming collisions
(imported modules can be renamed)
Huge repository of modules through
Synchronous import only
Works natively on the server side only (Node.js)
NPM
@loige 49
86. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
@loige 50
87. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
@loige 50
88. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
module name
@loige 50
89. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
dependencies
@loige 50
90. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
factory function used to construct the
module,
receives the dependencies as arguments
@loige 50
91. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
exported value
@loige 50
92. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
@loige 51
93. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
app
@loige 51
94. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
app
Require.js config
jquery will be loaded from
://<currentDomain>/js/lib/jquery-1.9.0.js
@loige 51
95. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
// app.js
// define paths
requirejs.config({
baseUrl: 'js/lib',
paths: {
jquery: 'jquery-1.9.0'
}
})
define(['jquery'], function ($) {
// this is executed only when jquery
// and its deps are loaded
});
app
app main function
Has jquery as dependency
@loige 51
96. AMD (AMD ( ))
Asynchronous Module DefinitionAsynchronous Module Definition
Require.jsRequire.js
Asynchronous modules
Works on Browsers and Server side
Very verbose and convoluted syntax (my opinion™)
@loige 52
97. UMDUMD
Universal Module DefinitionUniversal Module Definition
A module definition that is compatible with
Global modules, CommonJS & AMD
github.com/umdjs/umd
@loige 53 . 1
98. (function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
99. (function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd
IIFE with arguments:
- Current scope (this) and the module
factory function.
- "dep" is a sample dependency of the
module.
@loige 53 . 2
100. (function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
101. (function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
102. (function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dep'))
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['dep'], function (dep) {
return (root.returnExportsGlobal = factory(dep))
})
} else {
// Global Variables
root.myModule = factory(root.dep)
}
}(this, function (dep) {
// Your actual module
return {}
}))
loige.link/umd@loige 53 . 2
103. Allows you to define modules that
can be used by almost any module loader
Complex, the wrapper code
is almost impossible to write manually
UMDUMD
Universal Module DefinitionUniversal Module Definition
@loige 53 . 3
104. ES2015 modulesES2015 modules
Cool & broad subject, it would deserve it's own talk
Wanna know more?
/ syntax referenceimport export
ECMAScript modules in browsers
ES modules: A cartoon deep-dive
ES Modules in Node Today!
@loige 54 . 1
111. // index.html
<html>
<body>
<!-- ... -->
<script type="module">
import { add } from 'calculator.js'
console.log(add(2,2)) // 4
</script>
</body>
</html>
ES2015 modulesES2015 modules
"works" in some modern browsers
@loige 54 . 3
112. ES2015 modulesES2015 modules
Syntactically very similar to CommonJS...
BUT
import & export are static
(allow static analysis of dependencies)
It is a (still work in progress) standard format
Works (almost) seamlessly in browsers & servers
@loige 54 . 4
113. So many options...So many options...
Current most used practice:
Use CommonJS or ES2015 & create "compiled bundles"
@loige 55
114. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 56
115. Let's try to use CommonJSLet's try to use CommonJS
in the browserin the browser
@loige 57
117. Module BundlerModule Bundler
A tool that takes modules with dependencies and emits
static assets representing those modules
Those static assets can be processed by browsers!
@loige 59
118. Dependency graphDependency graph
A graph built by connecting every module with its direct
dependencies.
app
dependency A dependency B
dependency A2
shared
dependency
@loige 60
119. A module bundler has to:A module bundler has to:
1. Construct the dependency graph
(Dependency Resolution)
2. Assemble the modules in the graph into a
single executable asset (Packing)
@loige 61
155. Now you know howNow you know how
Module bundlers work!Module bundlers work!
And how to convert code writtenAnd how to convert code written
usingusing CommonJSCommonJS to a single file thatto a single file that
works in the browserworks in the browser
@loige 66
156. A challenge for you!A challenge for you!
If you do, ... I'll arrange a prize for you!
TIP: you can use or to parse JavaScript files (look for
require and module.exports) and to map relative module paths to
actual files in the filesystem.
Need an inspiration?
Check the awesome and !
let me know
acorn babel-parser
resolve
minipack @adamisnotdead's w_bp_ck
Can you build a (simple) module bundler from scratch?Can you build a (simple) module bundler from scratch?
@loige 67
157. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 68
158. A state of the art moduleA state of the art module
bundler for the webbundler for the web
@loige 69
163. Webpack conceptsWebpack concepts
Entry point: the starting file for dependency resolution.
Output: the destination file (bundled file).
Loaders: algorithms to parse different file types and convert them
into executable javascript (e.g. babel, typescript, but also CSS,
images or other static assets)
Plugins: do extra things (e.g. generate a wrapping HTML or
analysis tools)
@loige 74
169. Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
170. Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
171. Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
172. Everything is a moduleEverything is a module
import React, { Component } from 'react'
import logo from './logo.svg'
import './App.css'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
export default App 76
173. Webpack can load any type of fileWebpack can load any type of file
As long as you can provide a "As long as you can provide a "loaderloader" that" that
tells how to convert the file intotells how to convert the file into
something the browser understands.something the browser understands.
This is how Webpack allows you to use Babel, TypeScript,
Clojure, Elm, Imba but also to load CSSs, Images and other
assets.
77
176. {
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
// Defines how to load .css files (uses a pipeline of loaders)
// parses the file with post-css
78
177. {
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
// Defines how to load .css files (uses a pipeline of loaders)
// parses the file with post-css
// process @import and url()
// statements
78
178. {
test: /.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9',
// Defines how to load .css files (uses a pipeline of loaders)
// parses the file with post-css
// process @import and url()
// statements
// inject the resulting code with a <style> tag
78
179. ...Webpack can do (a lot) more!...Webpack can do (a lot) more!
Dev ServerDev Server
Tree shakingTree shaking
Dependencies analyticsDependencies analytics
Source mapsSource maps
Async require / module splittingAsync require / module splitting
@loige 79
180. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 80
195. Bundle cache bustingBundle cache busting
(Manual) Solution 1(Manual) Solution 1
bundle.js?v=1
bundle.js?v=2
bundle.js?v=3
Doesn't play nice with some CDNs and Proxies
(they won't consider different query parameters to be different resources)
83
199. Bundle cache bustingBundle cache busting
(Manual) Solution 2(Manual) Solution 2
bundle-v1.js
bundle-v2.js
bundle-v3.js
...
Better, but still a lot of diligence and manual effort needed...
84
204. Bundle cache bustingBundle cache busting
Webpack SolutionWebpack Solution
+
Every new asset version will generate a new file
cache is automatically cleaned up on every release
(if content actually changed)
html-plugin will update the reference to the new file
contenthash webpack-html-plugin
86
205. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
AgendaAgenda
@loige 87
206. Module bundlers are your friendsModule bundlers are your friends
Now you know how they work,
they are not (really) magic!
Start small and add more when needed
If you try to build your own, you'll learn a
lot more!
@loige 88
208. Ευχαριστώ!Ευχαριστώ!
Special thanks:
, , ,
(reviewers) and
(inspirations: his and his
)
@Podgeypoos79 @andreaman87 @mariocasciaro
@eugenserbanescu
@MarijnJH amazing book
workshop on JS modules
@loige
Images by
cover Image by from
Streamline Emoji pack
Dimitris Vetsikas Pixabay
loige.link/bundle-devit
90