Share
Unbundling the JavaScript module bundler - Øredev 21 Nov 2018
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.
Luciano Mammino
Transcript
204 slides
-
Unbundling the JavaScript Unbundling the JavaScript module bundler module bundler
Luciano Mammino - Luciano Mammino - @loige @loige 21/11/2018 loige.link/bundle-oredev 1 -
It's not Webpack! It's not Webpack! Module bundling is actually
complicated! Module bundling is actually complicated! @loige 11 -
Luciano... who Luciano... who Find me online: - (@loige) -
(lmammino) - - (loige.co) Twitter GitHub Linkedin Blog Solution Architect at with @mariocasciaro with @andreaman87 with @Podgeypoos79 12 -
Luciano... who Luciano... 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 Agenda Agenda @loige 13 -
# lumpy.txt https://unpkg.com/[email protected]/dist/zepto.min.js https://unpkg.com/[email protected]/dist/uuid.core.js https://unpkg.com/[email protected]/dist/store2.min.js https://unpkg.com/[email protected]/dist/tippy.all.min.js https://unpkg.com/confetti[email protected]/dist/index.min.js https://unpkg.com/dom[email protected]/lib/main.js https://unpkg.com/[email protected]/favico0.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 -
This is good... This is good... ... in 2008 ...
in 2008 was was @loige 29 -
Updating Updating them should be easy them should be easy
We shouldn't worry about We shouldn't worry about transitive dependencies transitive dependencies (dependencies of dependencies) (dependencies of dependencies) Order of imports Order of imports shouldn't really matter shouldn't really matter We rely on We rely on dependencies dependencies! ! @loige 31 -
Dependency Dependency (or coupling) a state in which one object
uses a function of another object — Wikipedia @loige 32 -
Modules 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 (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 Agenda Agenda @loige 35 -
Meet my friend Meet 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 way We generally define
a function this way @loige 37 -
We generally define a function this way We generally define
a function this way const sum = (a, b) => a + b @loige 37 -
We generally define a function this way We generally define
a function this way const sum = (a, b) => a + b or or @loige 37 -
We generally define a function this way We generally define
a function this way const sum = (a, b) => a + b function sum(a, b) { return a + b } or or @loige 37 -
We generally define a function this way We generally define
a function this way const sum = (a, b) => a + b function sum(a, b) { return a + b } or or then, at some point, we execute it... then, at some point, we execute it... @loige 37 -
We generally define a function this way We generally define
a function this way const sum = (a, b) => a + b function sum(a, b) { return a + b } or or 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 scope A function
in JS creates an isolated scope @loige 38 -
A function in JS creates an isolated scope A 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 scope A function
in JS creates an isolated scope (a, b) => { const secretString = "Hello" return a + b } console.log(secretString) // undefined secretString secretString is not visible outside the function is not visible outside the function @loige 38 -
IIFE allows you to define an isolated scope IIFE allows
you to define an isolated scope that executes itself that executes itself (arg1, arg2) => { // do stuff here const iAmNotVisibleOutside = true } @loige 39 -
IIFE allows you to define an isolated scope IIFE allows
you to define an isolated scope that executes itself that 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 scope
IIFE allows you to define an isolated scope that executes itself that executes itself (arg1, arg2) => { // do stuff here const iAmNotVisibleOutside = true } ( @loige 39 -
)(someArg1, someArg2) IIFE allows you to define an isolated scope
IIFE allows you to define an isolated scope that executes itself 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 -
IIFE is a recurring pattern in IIFE is a recurring
pattern in JavaScript modules JavaScript modules @loige 40 -
Let's implement a module Let's implement a module that provides:
that provides: Information hiding Information hiding exported functionalities exported 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 @loige 42 -
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 @loige 42 -
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 @loige 42 -
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 @loige 42 -
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 @loige 42 -
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) @loige 42 -
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 @loige 42 -
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 @loige 42 -
We want modules to be We want modules to be
reusable reusable across different apps and across different apps and organisations... organisations... ...we need ...we need A A STANDARD MODULE STANDARD MODULE format! format! @loige 43 -
Module system features 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 -
Module system features 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 -
JavaScript module systems JavaScript module systems globals CommonJS (Node.js) AMD
(Require.js / Dojo) UMD ES2015 Modules (ESM) Many others (SystemJS, ...) @loige 45 -
Globals Globals var $, jQuery $ = jQuery = (()
=> { return { /* ... */ } })() // ... use $ or jQuery in the global scope $.find('.button').remove() @loige 46 -
Globals 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 -
// 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 CommonJS CommonJS @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 CommonJS CommonJS 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 CommonJS CommonJS 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 CommonJS CommonJS 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 CommonJS CommonJS module app using module @loige 48 -
CommonJS 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js Require.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 Definition Asynchronous
Module Definition Require.js Require.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 Definition Asynchronous
Module Definition Require.js 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js Require.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 Definition Asynchronous
Module Definition Require.js 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js 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 -
AMD ( AMD ( ) ) Asynchronous Module Definition Asynchronous
Module Definition Require.js Require.js Asynchronous modules Works on Browsers and Server side Very verbose and convoluted syntax (my opinion™) @loige 52 -
UMD UMD Universal Module Definition Universal 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 UMD UMD Universal Module Definition Universal Module Definition @loige 53 . 3 -
ES2015 modules 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 -
// 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 modules ES2015 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 modules ES2015 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 modules ES2015 modules module exported 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 modules ES2015 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 modules ES2015 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 modules ES2015 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 modules ES2015 modules "works" in some modern browsers @loige 54 . 3 -
ES2015 modules 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 -
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 Agenda Agenda @loige 56 -
Let's try to use CommonJS Let's try to use CommonJS
in the browser in 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 Bundler 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 -
Dependency graph Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency 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 resolution Dependency resolution app calculator log parser resolver 62 (entrypoint) (1) (2) (3) (4) (5) @loige -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } './app': (module, require) => { … }, @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } './app': (module, require) => { … }, './calculator': (module, require) => { … }, @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } './app': (module, require) => { … }, './calculator': (module, require) => { … }, './parser': (module, require) => { … }, @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } './app': (module, require) => { … }, './calculator': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, require path @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules map { } './app': (module, require) => { … }, './calculator': (module, require) => { … }, './log': (module, require) => { … }, './parser': (module, require) => { … }, './resolver': (module, require) => { … }, module factory function @loige 63 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules 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 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules 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 -
During During dependency resolution dependency resolution, , the bundler creates
a the bundler creates a modules map modules 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 -
Packing Packing { : :
: } Modules map Modules map @loige 64 -
Packing Packing { : :
: } Modules map Modules map @loige 64 -
Packing Packing .js { : :
: } Modules map Modules map Single executable Single executable JS file JS file @loige 64 -
Packed executable file Packed 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 file Packed 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 file Packed 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 file Packed 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 file Packed 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 file Packed 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 file Packed 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 file Packed 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 file Packed 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 file Packed 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 how Now you know how Module bundlers
work! Module bundlers work! And how to convert code written And how to convert code written using using CommonJS CommonJS to a single file that to a single file that works in the browser works 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 Agenda Agenda @loige 68 -
A state of the art module A state of the
art module bundler for the web bundler for the web @loige 69 -
webpack mode=development app.js Do not compress the code and add
annotations! @loige 72 -
Webpack concepts 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 -
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 @loige 75 -
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 @loige 75 -
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 @loige 75 -
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 @loige 75 -
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. @loige 75 -
Everything is a module 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 -
Everything is a module 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 -
Everything is a module 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 -
Everything is a module 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 -
Webpack can load any type of file Webpack can load
any type of file As long as you can provide a " As long as you can provide a "loader loader" that " that tells how to convert the file into tells 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 Server Dev Server Tree shaking Tree shaking Dependencies analytics Dependencies analytics Source maps Source maps Async require / module splitting Async 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 Agenda Agenda @loige 80 -
Bundle cache busting Bundle cache busting Device CDN Origin Server
bundle.js (original) 81 -
Bundle cache busting Bundle cache busting Device CDN Origin Server
example.com bundle.js (original) 81 -
Bundle cache busting Bundle cache busting Device CDN Origin Server
example.com bundle.js bundle.js (original) 81 -
Bundle cache busting Bundle cache busting Device CDN Origin Server
example.com bundle.js bundle.js bundle.js (original) 81 -
Bundle cache busting Bundle cache busting Device CDN Origin Server
example.com bundle.js bundle.js bundle.js (original) 81 -
Bundle cache busting Bundle cache busting Device CDN Origin Server
example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81 -
Bundle cache busting Bundle cache busting Device CDN Origin Server
example.com bundle.js bundle.js bundle.js (original) bundle.js (cache copy) 81 -
Bundle cache busting Bundle 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 busting Bundle 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 busting Bundle 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 busting Bundle 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 busting Bundle cache busting (Manual) Solution 1 (Manual)
Solution 1 bundle.js?v=1 bundle.js?v=2 bundle.js?v=3 83 -
Bundle cache busting Bundle 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 busting Bundle cache busting (Manual) Solution 2 (Manual)
Solution 2 bundle-v1.js bundle-v2.js bundle-v3.js ... 84 -
Bundle cache busting Bundle 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 busting Bundle cache busting Webpack Solution Webpack Solution
output: { filename: '[name].[contenthash].js' } 85 -
Bundle cache busting Bundle cache busting Webpack Solution Webpack Solution
... bundle.9f61f58dd1cc3bb82182.js bundle.aacdf58ef1aa12382199.js bundle.ed61f68defef3bb82221.js output: { filename: '[name].[contenthash].js' } 85 -
Bundle cache busting Bundle cache busting Webpack Solution 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 -
A good example for dev/prod config A 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 Agenda Agenda @loige 88 -
Module bundlers are your friends 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 89 -
Webpack is not Webpack 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