Slide 1

Slide 1 text

Unbundling the JavaScript module bundler Unbundling the JavaScript module bundler { Luciano Mammino ( Luciano Mammino ( ) ) @loige @loige ROME - APRIL 13/14 2018 loige.link/bundle-rome 1

Slide 2

Slide 2 text

loige.link/bundle-rome 2

Slide 3

Slide 3 text

Luciano... who Luciano... who Find me online: - (@loige) - (lmammino) - - (loige.co) Twitter GitHub Linkedin Blog Solution Architect at with @mariocasciaro with @andreaman87 with @Podgeypoos79 3

Slide 4

Slide 4 text

1. A sample app 2. what is a dependency 3. JavaScript module systems 4. How a module bundler works 5. Webpack in 2 minutes! Agenda Agenda 4

Slide 5

Slide 5 text

http://poo.loige.co 5

Slide 6

Slide 6 text

App features App features 6

Slide 7

Slide 7 text

Dynamic DOM manipulation React, Angular & Vue are so... overrated! 7

Slide 8

Slide 8 text

Dynamic Favicon favico.js 8

Slide 9

Slide 9 text

Custom animated tooltips tippy.js 9

Slide 10

Slide 10 text

Confetti rainfall dom-confetti 10

Slide 11

Slide 11 text

Persist state through Local Storage + UUIDs + store2 uuidjs 11

Slide 12

Slide 12 text

7 Requests only for the JS code! 12

Slide 13

Slide 13 text

current scenario current scenario zepto@1.2.0/dist/zepto.min.js uuidjs@4.0.3/dist/uuid.core.js store2@2.7.0/dist/store2.min.js tippy.js@2.2.3/dist/tippy.all.min.js confetti@0.0.10/lib/main.js favico.js@0.3.10/favico­0.3.10.min.js 13

Slide 14

Slide 14 text

Ideal scenario Ideal scenario zepto@1.2.0/dist/zepto.min.js uuidjs@4.0.3/dist/uuid.core.js store2@2.7.0/dist/store2.min.js tippy.js@2.2.3/dist/tippy.all.min.js confetti@0.0.10/lib/main.js favico.js@0.3.10/favico­0.3.10.min.js vendors.js + + + + + = 14

Slide 15

Slide 15 text

How to do this? How to do this? 15

Slide 16

Slide 16 text

./buildVendors.sh > vendors.js buildVendors.sh $ 16

Slide 17

Slide 17 text

npm install ­­global lumpy $ github.com/lmammino/lumpy 17

Slide 18

Slide 18 text

# lumpy.txt https://unpkg.com/zepto@1.2.0/dist/zepto.min.js https://unpkg.com/uuidjs@4.0.3/dist/uuid.core.js https://unpkg.com/store2@2.7.0/dist/store2.min.js https://unpkg.com/tippy.js@2.2.3/dist/tippy.all.min.js https://unpkg.com/confetti­js@0.0.11/dist/index.min.js https://unpkg.com/dom­confetti@0.0.10/lib/main.js https://unpkg.com/favico.js@0.3.10/favico­0.3.10.min.js Lumpy allows you to define all the vendors in a text file 18

Slide 19

Slide 19 text

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 19

Slide 20

Slide 20 text

7 requests 2 requests 20

Slide 21

Slide 21 text

Concatenation Concatenation + + Minification Minification 21

Slide 22

Slide 22 text

This is good... This is good... ... in 2008 ... in 2008 was was 22

Slide 23

Slide 23 text

Today... Today... We can do better! We can do better! 23

Slide 24

Slide 24 text

Updating dependencies should be easy Updating dependencies should be easy We don't want to worry about dependencies We don't want to worry about dependencies of dependencies of dependencies We don't have to worry about the order of We don't have to worry about the order of imports imports Today... Today... 24

Slide 25

Slide 25 text

Dependency Dependency (or coupling) a state in which one object uses a function of another object — Wikipedia 25

Slide 26

Slide 26 text

Reusable dependencies... Reusable dependencies... Modules! Modules! 26

Slide 27

Slide 27 text

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... 27

Slide 28

Slide 28 text

Meet my friend Meet my friend I.I.F.E. I.I.F.E. (Immediately Invoked Function Expression) loige.link/iife 28

Slide 29

Slide 29 text

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) 29

Slide 30

Slide 30 text

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 30

Slide 31

Slide 31 text

)(arg1, arg2) IIFE allows you to define an isolated IIFE allows you to define an isolated scope that executes itself scope that executes itself (arg1, arg2) => { // do stuff here const iAmNotVisibleOutside = true } ( 31

Slide 32

Slide 32 text

IIFE is a recurring pattern in IIFE is a recurring pattern in JavaScript modules JavaScript modules 32

Slide 33

Slide 33 text

Let's implement a module Let's implement a module that provides: that provides: Information hiding Information hiding exported functionalities exported functionalities 33

Slide 34

Slide 34 text

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 34

Slide 35

Slide 35 text

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! 35

Slide 36

Slide 36 text

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) 36

Slide 37

Slide 37 text

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 37

Slide 38

Slide 38 text

xkcd.com/927 38

Slide 39

Slide 39 text

JavaScript module systems JavaScript module systems globals CommonJS (Node.js) AMD (Require.js / Dojo) UMD ES2015 Modules (ESM) Many others (SystemJS, ...) 39

Slide 40

Slide 40 text

Globals Globals var $, jQuery $ = jQuery = (() => { return { /* ... */ } })() // ... use $ or jQuery in the global scope $.find('.button').remove() 40

Slide 41

Slide 41 text

Globals Globals Might generate naming collisions (e.g. $ overrides browser global variable) Import order is important Cannot import parts of modules 41

Slide 42

Slide 42 text

// 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 42

Slide 43

Slide 43 text

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 43

Slide 44

Slide 44 text

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 44

Slide 45

Slide 45 text

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 45

Slide 46

Slide 46 text

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™) 46

Slide 47

Slide 47 text

UMD UMD Universal Module Definition Universal Module Definition A module definition that is compatible with Global modules, CommonJS & AMD github.com/umdjs/umd 47

Slide 48

Slide 48 text

(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 48

Slide 49

Slide 49 text

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 49

Slide 50

Slide 50 text

// 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 50

Slide 51

Slide 51 text

// index.html import { add } from 'calculator.js' console.log(add(2,2)) // 4 ES2015 modules ES2015 modules "works" in some modern browsers 51

Slide 52

Slide 52 text

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 52

Slide 53

Slide 53 text

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! 53

Slide 54

Slide 54 text

So many options... So many options... Current most used practice: Use CommonJS or ES2015 & create "compiled bundles" 54

Slide 55

Slide 55 text

Let's try to use Let's try to use CommonJS in the CommonJS in the browser browser 55

Slide 56

Slide 56 text

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! 56

Slide 57

Slide 57 text

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! 57

Slide 58

Slide 58 text

Dependency graph Dependency graph A graph built by connecting every module with its direct dependencies. app dependency A dependency B dependency A2 shared dependency 58

Slide 59

Slide 59 text

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) 59

Slide 60

Slide 60 text

// 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 60 (entrypoint) (1) (2) (3) (4) (5)

Slide 61

Slide 61 text

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)) 61

Slide 62

Slide 62 text

Packing Packing .js { ­­­ : ­­­ ­­­ : ­­­­ ­­­ : ­­ } Modules map Modules map Single executable Single executable JS file JS file 62

Slide 63

Slide 63 text

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) => { … } } ) 63

Slide 64

Slide 64 text

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 to a single file that works in the browser that works in the browser 64

Slide 65

Slide 65 text

A challenge for you! A challenge for you! If you do, ... I'll have a prize for you! TIP: you can use to parse JavaScript files (look for require and module.exports) and to map relative module paths to actual files in the filesystem. let me know acorn resolve Can you build a (simple) module bundler from scratch? Can you build a (simple) module bundler from scratch? 65

Slide 66

Slide 66 text

A state of the art module A state of the art module bundler for the web bundler for the web 66

Slide 67

Slide 67 text

npm install webpack webpack­cli 67

Slide 68

Slide 68 text

webpack app.js yep, recent versions of Webpack work without config! 68

Slide 69

Slide 69 text

webpack ­­mode=development app.js Do not compress the code and add annotations! 69

Slide 70

Slide 70 text

loige.link/sample-webpacked-file 70

Slide 71

Slide 71 text

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) 71

Slide 72

Slide 72 text

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. 72

Slide 73

Slide 73 text

...And you can do more! ...And you can do 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 73

Slide 74

Slide 74 text

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! 74

Slide 75

Slide 75 text

Grazie! Grazie! loige.link/bundle-rome Special thanks to: , , (reviewers) and (inspiration - check out his and his ) @Podgeypoos79 @andreaman87 @mariocasciaro @MarijnJH amazing book workshop on JS modules 75