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.
13. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
Agenda
@loige 13
30. 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
38. Updating them should be easy
We shouldn't worry about transitive dependencies
(dependencies of dependencies)
Order of imports shouldn't really matter
We rely on dependencies!
@loige 31
41. Modules
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
* yeah, I quite like quoting my stuff... 😅@loige 34
42. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
Agenda
@loige 35
43. Meet my friend I.I.F.E.
(Immediately Invoked Function Expression)
loige.link/iife
@loige 36
45. We generally define a function this way
const sum = (a, b) => a + b
@loige 37
46. We generally define a function this way
const sum = (a, b) => a + b
or
@loige 37
47. We generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
or
@loige 37
48. We generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
or
then, at some point, we execute it...
@loige 37
49. We generally define a function this way
const sum = (a, b) => a + b
function sum(a, b) {
return a + b
}
or
then, at some point, we execute it...
const four = sum(2, 2)
@loige 37
50. A function in JS creates an isolated scope
@loige 38
51. A function in JS creates an isolated scope
(a, b) => {
const secretString = "Hello"
return a + b
}
console.log(secretString) // undefined
@loige 38
52. A function in JS creates an isolated scope
(a, b) => {
const secretString = "Hello"
return a + b
}
console.log(secretString) // undefined
secretString is not visible outside the function
@loige 38
53. IIFE allows you to define an isolated scope
that executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
@loige 39
54. IIFE allows you to define an isolated scope
that executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
A function with its own scope
@loige 39
55. )(someArg1, someArg2)
IIFE allows you to define an isolated scope
that executes itself
(arg1, arg2) => {
// do stuff here
const iAmNotVisibleOutside = true
}
(
@loige 39
56. )(someArg1, someArg2)
IIFE allows you to define an isolated scope
that 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
57. IIFE is a recurring pattern in
JavaScript modules
@loige 40
58. Let's implement a module
that provides:
Information hiding
exported functionalities
@loige 41
67. We want modules to be
reusable across different apps and
organisations...
...we need
A STANDARD MODULE format!@loige 43
68. Module 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
69. Module 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
71. Globals
var $, jQuery
$ = jQuery = (() => {
return { /* ... */ }
})()
// ... use $ or jQuery in the global scope
$.find('.button').remove()
@loige 46
72. Globals
👎 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
78. CommonJS
👍 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
79. AMD ( )
Asynchronous Module Definition
Require.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
@loige 50
80. AMD ( )
Asynchronous Module Definition
Require.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
@loige 50
81. AMD ( )
Asynchronous Module Definition
Require.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
module name
@loige 50
82. AMD ( )
Asynchronous Module Definition
Require.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
dependencies
@loige 50
83. AMD ( )
Asynchronous Module Definition
Require.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
84. AMD ( )
Asynchronous Module Definition
Require.js
// jquery-1.9.0.js
define(
'jquery',
['sizzle', 'jqueryUI'],
function (sizzle, jqueryUI) {
// Returns the exported value
return function () {
// ...
}
}
)
module
exported value
@loige 50
85. AMD ( )
Asynchronous Module Definition
Require.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
86. AMD ( )
Asynchronous Module Definition
Require.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
87. AMD ( )
Asynchronous Module Definition
Require.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
88. AMD ( )
Asynchronous Module Definition
Require.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
89. AMD ( )
Asynchronous Module Definition
Require.js
👍 Asynchronous modules
👍 Works on Browsers and Server side
👎 Very verbose and convoluted syntax (my opinion™)
@loige 52
90. UMD
Universal Module Definition
A module definition that is compatible with
Global modules, CommonJS & AMD
👉 👈github.com/umdjs/umd
@loige 53 . 1
91. (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
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
IIFE with arguments:
- Current scope (this) and the module
factory function.
- "dep" is a sample dependency of the
module.
@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@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. 👍 Allows you to define modules that
can be used by almost any module loader
👎 Complex, the wrapper code
is almost impossible to write manually
UMD
Universal Module Definition
@loige 53 . 3
97. ES2015 modules
Cool & broad subject, it would deserve it's own talk
Wanna know more?
🔗 / syntax reference
🔗
🔗
🔗
import export
ECMAScript modules in browsers
ES modules: A cartoon deep-dive
ES Modules in Node Today!
@loige 54 . 1
104. // index.html
<html>
<body>
<!-- ... -->
<script type="module">
import { add } from 'calculator.js'
console.log(add(2,2)) // 4
</script>
</body>
</html>
ES2015 modules
"works" in some modern browsers
@loige 54 . 3
105. ES2015 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
106. So many options...
Current most used practice:
Use CommonJS or ES2015 & create "compiled bundles"
@loige 55
107. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
Agenda
@loige 56
108. Let's try to use CommonJS
in the browser
@loige 57
110. Module 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
111. Dependency graph
A graph built by connecting every module with its direct
dependencies.
app
dependency A dependency B
dependency A2
shared
dependency
@loige 60
112. 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
148. Now you know how
Module bundlers work!
And how to convert code written
using CommonJS to a single file that
works in the browser
@loige 66
149. 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?
@loige 67
150. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
Agenda
@loige 68
151. A state of the art module
bundler for the web
@loige 69
156. Webpack 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
162. Everything 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
163. Everything 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 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 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. Webpack can load any type of file
As long as you can provide a "loader" that
tells how to convert the file into
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
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)
// parses the file with post-css
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
// process @import and url()
// statements
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
// inject the resulting code with a <style> tag
78
172. ...Webpack can do (a lot) more!
Dev Server
Tree shaking
Dependencies analytics
Source maps
Async require / module splitting
@loige 79
173. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
Agenda
@loige 80
188. Bundle cache busting
(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
192. Bundle cache busting
(Manual) Solution 2
bundle-v1.js
bundle-v2.js
bundle-v3.js
...
Better, but still a lot of diligence and manual effort needed...
84
197. Bundle cache busting
Webpack 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
198. 1. Why we need modules
2. JavaScript module systems
3. How a module bundler works
4. Webpack in 2 minutes!
5. Advanced module bundling
Agenda
@loige 87
199. Module 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
201. Grazie!
Special thanks:
, , ,
(reviewers) and
(inspirations: his and his
)
@Podgeypoos79 @andreaman87 @mariocasciaro
@eugenserbanescu
@MarijnJH amazing book
workshop on JS modules
@loige
Images by
Pug-Unicorn cover image by
Background cover image by
Streamline Emoji pack
1smr1 from Pixabay
Gerd from Pixabay
loige.link/bundle-coderful
90