Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unbundling the JavaScript module bundler - Dubl...

Unbundling the JavaScript module bundler - DublinJS July 2018

Today we all use Webpack (right?), but I remember a time when you had to manually copy-paste JavaScript files to create a package of libraries you could use in your frontend application. Many years have passed since then and the landscape of module bundlers evolved significantly along with the evolution of JavaScript and Node.js. In this talk, I will try to uncover some JavaScript module history and illustrate how a module bundler actually works, so that the next time you will use Webpack you will be able to understand what's going on behind the scenes.

Luciano Mammino

July 03, 2018
Tweet

More Decks by Luciano Mammino

Other Decks in Technology

Transcript

  1. Unbundling the JavaScript module bundler Unbundling the JavaScript module bundler

    Luciano Mammino ( Luciano Mammino ( ) ) @loige @loige DublinJS 03/07/2018 loige.link/bundle-dublinjs 1
  2. It's not Webpack! It's not Webpack! Module bundling is actually

    complicated! Module bundling is actually complicated! @loige 11
  3. 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
  4. 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
  5. 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
  6. Updating dependencies should be easy Updating dependencies should be easy

    We don't want to worry about dependencies of We don't want to worry about dependencies of dependencies dependencies We don't have to worry about the order of imports We don't have to worry about the order of imports Today... Today... @loige 31
  7. Dependency Dependency (or coupling) a state in which one object

    uses a function of another object — Wikipedia @loige 32
  8. 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
  9. 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
  10. Meet my friend Meet my friend I.I.F.E. I.I.F.E. (Immediately Invoked

    Function Expression) loige.link/iife @loige 36
  11. We generally define a function this way We generally define

    a function this way const sum = (a, b) => a + b @loige 37
  12. We generally define a function this way We generally define

    a function this way const sum = (a, b) => a + b or or @loige 37
  13. We generally define a function this way We generally define

    a function this way const sum = (a, b) => a + b sum(a, b) { return a + b } or or @loige 37
  14. We generally define a function this way We generally define

    a function this way const sum = (a, b) => a + b sum(a, b) { return a + b } or or then, at some point, we execute it... then, at some point, we execute it... @loige 37
  15. We generally define a function this way We generally define

    a function this way const sum = (a, b) => a + b 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
  16. A function in JS creates an isolated scope A function

    in JS creates an isolated scope @loige 38
  17. 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
  18. 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
  19. 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
  20. 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
  21. )(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
  22. )(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
  23. IIFE is a recurring pattern in IIFE is a recurring

    pattern in JavaScript modules JavaScript modules @loige 40
  24. Let's implement a module Let's implement a module that provides:

    that provides: Information hiding Information hiding exported functionalities exported functionalities @loige 41
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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 45
  36. JavaScript module systems JavaScript module systems globals CommonJS (Node.js) AMD

    (Require.js / Dojo) UMD ES2015 Modules (ESM) Many others (SystemJS, ...) @loige 47
  37. Globals Globals var $, jQuery $ = jQuery = (()

    => { return { /* ... */ } })() // ... use $ or jQuery in the global scope $.find('.button').remove() @loige 48
  38. 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 49
  39. // 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 50
  40. // 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 50
  41. // 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 50
  42. // 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 50
  43. // 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 50
  44. 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 51
  45. 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 52
  46. 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 52
  47. 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 52
  48. 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 52
  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 () { // ... } } ) module factory function used to construct the module, receives the dependencies as arguments @loige 52
  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 52
  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 }); @loige 53
  52. 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 53
  53. 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 53
  54. 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 53
  55. 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 54
  56. UMD UMD Universal Module Definition Universal Module Definition A module

    definition that is compatible with Global modules, CommonJS & AMD github.com/umdjs/umd @loige 55 . 1
  57. (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 55 . 2
  58. (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 55 . 2
  59. (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 55 . 2
  60. (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 55 . 2
  61. (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 55 . 2
  62. 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 55 . 3
  63. 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 56 . 1
  64. // 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 56 . 2
  65. // 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 56 . 2
  66. // 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 56 . 2
  67. // 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 56 . 2
  68. // 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 56 . 2
  69. // 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 56 . 3
  70. // 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 56 . 3
  71. 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 56 . 4
  72. So many options... So many options... Current most used practice:

    Use CommonJS or ES2015 & create "compiled bundles" @loige 57
  73. 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 58
  74. Let's try to use CommonJS Let's try to use CommonJS

    in the browser in the browser @loige 59
  75. 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 60
  76. 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 61
  77. 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 62
  78. A module bundler has to: A module bundler has to:

    Construct the dependency graph (Dependency Resolution) Assemble the modules in the graph into a single executable asset (Packing) @loige 63
  79. // 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 64 @loige
  80. // 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 64 (entrypoint) (1) @loige
  81. // 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 64 (entrypoint) (1) @loige
  82. // 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 64 (entrypoint) (1) (2) @loige
  83. // 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 64 (entrypoint) (1) (2) @loige
  84. // 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 64 (entrypoint) (1) (2) (3) @loige
  85. // 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 64 (entrypoint) (1) (2) (3) @loige
  86. // 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 64 (entrypoint) (1) (2) (3) (4) @loige
  87. // 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 64 (entrypoint) (1) (2) (3) (4) @loige
  88. // 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 64 (entrypoint) (1) (2) (3) (4) (5) @loige
  89. During During dependency resolution dependency resolution, , the bundler creates

    a the bundler creates a modules map modules map @loige 65
  90. During During dependency resolution dependency resolution, , the bundler creates

    a the bundler creates a modules map modules map { } @loige 65
  91. During During dependency resolution dependency resolution, , the bundler creates

    a the bundler creates a modules map modules map { } './app': (module, require) => { … }, @loige 65
  92. 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 65
  93. 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 65
  94. 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 65
  95. 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 65
  96. 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 65
  97. 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 65
  98. 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 65
  99. 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 65
  100. 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 65
  101. Packing Packing { ­­­ : ­­­ ­­­ : ­­­­ ­­­

    : ­­ } Modules map Modules map @loige 66
  102. Packing Packing { ­­­ : ­­­ ­­­ : ­­­­ ­­­

    : ­­ } Modules map Modules map @loige 66
  103. Packing Packing .js { ­­­ : ­­­ ­­­ : ­­­­

    ­­­ : ­­ } Modules map Modules map Single executable Single executable JS file JS file @loige 66
  104. 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) => { … } } ) 67 @loige
  105. 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 67 @loige
  106. 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 67 @loige
  107. 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 67 @loige
  108. 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) 67 @loige
  109. 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. 67 @loige
  110. 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 67 @loige
  111. 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 67 @loige
  112. 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 67 @loige
  113. 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) => { … } } ) 67 @loige
  114. 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 68
  115. A challenge for you! A challenge for you! If you

    do, ... I'll have 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 ! let me know acorn babel-parser resolve minipack Can you build a (simple) module bundler from scratch? Can you build a (simple) module bundler from scratch? @loige 69
  116. 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 70
  117. A state of the art module A state of the

    art module bundler for the web bundler for the web @loige 71
  118. 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 76
  119. 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 77
  120. 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 77
  121. 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 77
  122. 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 77
  123. 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 77
  124. 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 78
  125. 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 78
  126. 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 78
  127. 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 78
  128. Webpack can load any type of file Webpack can load

    any type of file As long as you specify a " As long as you specify a "loader loader" that tells " that tells how to convert the file into something the how to convert the file into something the browser understands. browser understands. This is how Webpack allows you to use Babel, TypeScript, Clojure, Elm but also to load CSSs, Images and other assets. 79
  129. { 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', 80
  130. { 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) 80
  131. { 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 80
  132. { 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 80
  133. { 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 80
  134. ...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 81
  135. 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 82
  136. Webpack in Webpack in create-react-app create-react-app yarn global add create-react-app

    create-react-app newapp cd newapp yarn yarn eject cat ./config/webpack.config.{dev,prod}.js 83
  137. 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 86
  138. 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 87
  139. THANKS! THANKS! loige.link/bundle-dublinjs Special thanks , , , (reviewers) and

    (inspirations: his and his ) @Podgeypoos79 @andreaman87 @mariocasciaro @eugenserbanescu @MarijnJH amazing book workshop on JS modules @loige Images by . Streamline Emoji pack 88