Slide 1

Slide 1 text

The Many Ways to Build Modular JavaScript The Many Ways to Build Modular JavaScript Tim Perry Tech Lead & Open-Source Champion at Softwire @pimterry / github.com/pimterry / tim-perry.co.uk Tim Perry Tech Lead & Open-Source Champion at Softwire @pimterry / github.com/pimterry / tim-perry.co.uk

Slide 2

Slide 2 text

JavaScript JavaScript  First designed & implemented by Brendan Eich over 10 days ‘or something worse than JS would have happened’  Renamed to JavaScript three months later to confuse as many people as possible for marketing purposes  Everybody implements subtly different versions  First spec published in June 1997 (now as ECMAScript)  First designed & implemented by Brendan Eich over 10 days ‘or something worse than JS would have happened’  Renamed to JavaScript three months later to confuse as many people as possible for marketing purposes  Everybody implements subtly different versions  First spec published in June 1997 (now as ECMAScript)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

JavaScript JavaScript Some great bits:  Dynamic typing  First-order functions & closures  Asynchronous everything Some ‘features’ that need removing:  The ‘with’ keyword  Much of type coercion  Automatic semicolon insertion Some great bits:  Dynamic typing  First-order functions & closures  Asynchronous everything Some ‘features’ that need removing:  The ‘with’ keyword  Much of type coercion  Automatic semicolon insertion Some hugely counter-intuitive fundamentals:  How ‘this’ and variable scope work  Prototypes Some clearly relevant features that don’t exist:  Simple class definitions  Tail-call optimizations  A mechanism to allow structured modular code Some hugely counter-intuitive fundamentals:  How ‘this’ and variable scope work  Prototypes Some clearly relevant features that don’t exist:  Simple class definitions  Tail-call optimizations  A mechanism to allow structured modular code

Slide 5

Slide 5 text

Why? Why? 1. Encapsulated state 2. Reusable code 3. Explicit dependency management 1. Encapsulated state 2. Reusable code 3. Explicit dependency management

Slide 6

Slide 6 text

Why? Why? Modular code ≠ Dependency Injection Modular code ≠ Dependency Injection

Slide 7

Slide 7 text

Why? Why? JavaScript uses lexically-defined function scoping Variables definitions are either in local scope or global scope JavaScript uses lexically-defined function scoping Variables definitions are either in local scope or global scope function f() { var localVar = “a string”; } function f(localParameter) { localParameter = “a string”; } function f() { function localFunction() { } } function f() { var localVar = “a string”; } function f(localParameter) { localParameter = “a string”; } function f() { function localFunction() { } } var globalVar = “a string”; function g() { globalVar = “a string”; } function g() { window.globalVar = “a string”; } var globalVar = “a string”; function g() { globalVar = “a string”; } function g() { window.globalVar = “a string”; }

Slide 8

Slide 8 text

Immediately-Invoked Function Expression (IIFE) Immediately-Invoked Function Expression (IIFE) window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum); window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum);

Slide 9

Slide 9 text

Immediately-Invoked Function Expression (IIFE) Immediately-Invoked Function Expression (IIFE) window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum); window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum);

Slide 10

Slide 10 text

Immediately-Invoked Function Expression (IIFE) Immediately-Invoked Function Expression (IIFE) window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum); window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum);

Slide 11

Slide 11 text

Immediately-Invoked Function Expression (IIFE) Immediately-Invoked Function Expression (IIFE) window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum); window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum);

Slide 12

Slide 12 text

Immediately-Invoked Function Expression (IIFE) Immediately-Invoked Function Expression (IIFE) window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum); window.softwire.coffeeMachine.grinder = (function ($, beansEnum) { [… some code using these dependencies …] return aModuleObject; })(window.jQuery, window.softwire.coffeeMachine.beansEnum);

Slide 13

Slide 13 text

IIFE Module Benefits IIFE Module Benefits Code internals are encapsulated Dependencies are explicitly defined (if only by the global variables they manage) Code is reusable (in contexts where dependencies are already available globally) Code internals are encapsulated Dependencies are explicitly defined (if only by the global variables they manage) Code is reusable (in contexts where dependencies are already available globally)

Slide 14

Slide 14 text

IIFE Module Problems IIFE Module Problems Global state has to be used to store each module Namespacing requires manual initialization and management Module loading and ordering have to be managed manually Moderate amount of boilerplate Global state has to be used to store each module Namespacing requires manual initialization and management Module loading and ordering have to be managed manually Moderate amount of boilerplate

Slide 15

Slide 15 text

Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD) define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } );

Slide 16

Slide 16 text

define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD)

Slide 17

Slide 17 text

define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD)

Slide 18

Slide 18 text

define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD)

Slide 19

Slide 19 text

define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); define([“lib/jquery”, “lib/knockout”, “coffeeMachine/grinder”], function ($, ko, coffeeGrinder) { [… make coffee or build some private state or something …] return { “doSomethingCoffeeRelated” : coffeeMakingFunction, “usefulNumber” : 4, }; } ); Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD)

Slide 20

Slide 20 text

require([“font!fonts/myFavFont”, “less!styles/homeStyle”, “domReady!”], function () { showPageNowThatAllPrerequisitesAreLoaded(); } ); require([“font!fonts/myFavFont”, “less!styles/homeStyle”, “domReady!”], function () { showPageNowThatAllPrerequisitesAreLoaded(); } ); Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD)

Slide 21

Slide 21 text

require([“font!fonts/myFavFont”, “less!styles/homeStyle”, “domReady!”], function () { showPageNowThatAllPrerequisitesAreLoaded(); } ); require([“font!fonts/myFavFont”, “less!styles/homeStyle”, “domReady!”], function () { showPageNowThatAllPrerequisitesAreLoaded(); } ); Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD)

Slide 22

Slide 22 text

index.html: [ … ] index.html: [ … ] js/main.js require([“coffee/machine”], function (CoffeeMachine) { coffeeMachine = new CoffeeMachine(); coffeeMachine.start(); }); js/main.js require([“coffee/machine”], function (CoffeeMachine) { coffeeMachine = new CoffeeMachine(); coffeeMachine.start(); }); Asynchronous Module Definitions (AMD) Asynchronous Module Definitions (AMD)

Slide 23

Slide 23 text

AMD Benefits AMD Benefits Code internals are encapsulated Code is reusable Explicitly named dependencies Dependency loading is asynchronous Implementable in vanilla JavaScript only Powerful plugin architecture Code internals are encapsulated Code is reusable Explicitly named dependencies Dependency loading is asynchronous Implementable in vanilla JavaScript only Powerful plugin architecture

Slide 24

Slide 24 text

AMD Problems AMD Problems Lots of boilerplate Lots of complexity Can’t handle circular dependencies Can result in code that requires many HTTP requests to pull down its large dependency network (solvable with R.js or similar) Lots of boilerplate Lots of complexity Can’t handle circular dependencies Can result in code that requires many HTTP requests to pull down its large dependency network (solvable with R.js or similar)

Slide 25

Slide 25 text

CommonJS Modules CommonJS Modules var $ = require(“jquery”); var coffeeGrinder = require(“./coffeeGrinder”).CoffeeGrinder; var niceBeans = require(“./coffeeBeans”).NICE_BEANS; [… do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4; var $ = require(“jquery”); var coffeeGrinder = require(“./coffeeGrinder”).CoffeeGrinder; var niceBeans = require(“./coffeeBeans”).NICE_BEANS; [… do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4;

Slide 26

Slide 26 text

CommonJS Modules CommonJS Modules var $ = require(“jquery”); var coffeeGrinder = require(“./coffeeGrinder”).CoffeeGrinder; var niceBeans = require(“./coffeeBeans”).NICE_BEANS; [… do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4; var $ = require(“jquery”); var coffeeGrinder = require(“./coffeeGrinder”).CoffeeGrinder; var niceBeans = require(“./coffeeBeans”).NICE_BEANS; [… do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4;

Slide 27

Slide 27 text

CommonJS Modules CommonJS Modules var $ = require(“jquery”); var CoffeeGrinder = require(“./coffeeGrinder”).CoffeeGrinder; var niceBeans = require(“./coffeeBeans”).NICE_BEANS; [… do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4; var $ = require(“jquery”); var CoffeeGrinder = require(“./coffeeGrinder”).CoffeeGrinder; var niceBeans = require(“./coffeeBeans”).NICE_BEANS; [… do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4;

Slide 28

Slide 28 text

CommonJS Runners CommonJS Runners  Various non-browser platforms  Browserify  Require.js  Various non-browser platforms  Browserify  Require.js

Slide 29

Slide 29 text

CommonJS Runners CommonJS Runners  Various non-browser platforms  Node.JS, CouchDB, Narwhal  The native environment for CommonJS modules  Synchronous loading makes perfect sense server-side  Closer model to non-browser scripting languages  Browserify  Require.js  Various non-browser platforms  Node.JS, CouchDB, Narwhal  The native environment for CommonJS modules  Synchronous loading makes perfect sense server-side  Closer model to non-browser scripting languages  Browserify  Require.js

Slide 30

Slide 30 text

CommonJS Runners CommonJS Runners  Various non-browser platforms  Browserify  CommonJS modules for the browser  Build tool that takes CommonJS modules and compiles the whole app into a single script file  Lets node.js modules work directly in a browser  Require.js  Various non-browser platforms  Browserify  CommonJS modules for the browser  Build tool that takes CommonJS modules and compiles the whole app into a single script file  Lets node.js modules work directly in a browser  Require.js

Slide 31

Slide 31 text

CommonJS Runners CommonJS Runners  Various non-browser platforms  Browserify  Require.js  Primarily an AMD script loader  Can support CommonJS style modules, hackily, with: define(function(require, exports) { var beanTypes = require(“coffeeMachine/beanTypes”); exports.usefulNumber = 4; });  Various non-browser platforms  Browserify  Require.js  Primarily an AMD script loader  Can support CommonJS style modules, hackily, with: define(function(require, exports) { var beanTypes = require(“coffeeMachine/beanTypes”); exports.usefulNumber = 4; });

Slide 32

Slide 32 text

CommonJS Benefits CommonJS Benefits  Code internals are encapsulated  Dependencies are explicitly named  Code is easily reusable  Simple clean syntax and conceptual model  Basically no boilerplate  Handles circular references better than AMD  Code internals are encapsulated  Dependencies are explicitly named  Code is easily reusable  Simple clean syntax and conceptual model  Basically no boilerplate  Handles circular references better than AMD

Slide 33

Slide 33 text

CommonJS Problems CommonJS Problems  Lots of magic involved  Doesn’t follow standard JavaScript conventions  No consideration of environment where loads are expensive  Ignores JavaScript’s inherent asynchronicity  Dependencies aren’t necessarily all obvious upfront  Lots of magic involved  Doesn’t follow standard JavaScript conventions  No consideration of environment where loads are expensive  Ignores JavaScript’s inherent asynchronicity  Dependencies aren’t necessarily all obvious upfront

Slide 34

Slide 34 text

ES6 Modules ES6 Modules module “aCoffeeComponent” { import $ from ‘jquery’; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; } module “aCoffeeComponent” { import $ from ‘jquery’; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; }

Slide 35

Slide 35 text

ES6 Modules ES6 Modules module “aCoffeeComponent” { import $ from ‘jquery’; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; } module “aCoffeeComponent” { import $ from ‘jquery’; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; }

Slide 36

Slide 36 text

ES6 Modules ES6 Modules module “aCoffeeComponent” { import $ from ‘jquery’; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; } module “aCoffeeComponent” { import $ from ‘jquery’; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; }

Slide 37

Slide 37 text

ES6 Modules ES6 Modules module “aCoffeeComponent” { import $ from “jquery”; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; } module “aCoffeeComponent” { import $ from “jquery”; import { NICE_BEANS as niceBeans } from “beanTypes”; import ‘coffeeMachine/coffeeGrinder’ as grinder; export default function doSomethingCoffeeRelated() { … }; export var usefulNumber = 4; }

Slide 38

Slide 38 text

ES6 Modules Bonus Features ES6 Modules Bonus Features // Remote modules module JSON from 'http://json.org/modules/json2.js'; // Wildcard imports import * from myModule; // Dynamic module definition var myModule = new Module({ “a”: b, “c”: d}); // Remote modules module JSON from 'http://json.org/modules/json2.js'; // Wildcard imports import * from myModule; // Dynamic module definition var myModule = new Module({ “a”: b, “c”: d});

Slide 39

Slide 39 text

ES6 Modules Bonus Features ES6 Modules Bonus Features // AMD-style ‘dynamic loading’ System.import([“jquery”, “coffeeBeans”], function($, beans) { [ … ] }); // Redefine load process System.translate = function (source, options) { return compiledAsCoffeescript(source); } // AMD-style ‘dynamic loading’ System.import([“jquery”, “coffeeBeans”], function($, beans) { [ … ] }); // Redefine load process System.translate = function (source, options) { return compiledAsCoffeescript(source); }

Slide 40

Slide 40 text

ES6 Module Benefits ES6 Module Benefits  Likely to be well supported everywhere (eventually)  Extremely powerful  New syntax, but otherwise true to existing JS semantics  Low on boilerplate  Handles circular references excellently  Similar to other language concepts & syntax  Semi-synchronous  Likely to be well supported everywhere (eventually)  Extremely powerful  New syntax, but otherwise true to existing JS semantics  Low on boilerplate  Handles circular references excellently  Similar to other language concepts & syntax  Semi-synchronous

Slide 41

Slide 41 text

ES6 Module Problems ES6 Module Problems  Currently supported effectively nowhere  Not even final in the spec yet  Lots of genuinely new syntax  import * is included, but frowned upon in every other language  Powerful, but thereby potentially very complicated  Currently supported effectively nowhere  Not even final in the spec yet  Lots of genuinely new syntax  import * is included, but frowned upon in every other language  Powerful, but thereby potentially very complicated

Slide 42

Slide 42 text

Which one do I use? Which one do I use? IIFE: For tiny projects For trivial compatibility AMD: For most serious browser-based projects For a no-build pure-JS solution If you need to depend on non-JS content/events CommonJS: For anything outside a browser environment For anything in a browser where you might want Node modules ES6: If you yearn for the extremely bleeding edge And you live way in the future where it has real support IIFE: For tiny projects For trivial compatibility AMD: For most serious browser-based projects For a no-build pure-JS solution If you need to depend on non-JS content/events CommonJS: For anything outside a browser environment For anything in a browser where you might want Node modules ES6: If you yearn for the extremely bleeding edge And you live way in the future where it has real support

Slide 43

Slide 43 text

Thank you Thank you Tim Perry Tech Lead & Open-Source Champion at Softwire @pimterry / github.com/pimterry / tim-perry.co.uk Tim Perry Tech Lead & Open-Source Champion at Softwire @pimterry / github.com/pimterry / tim-perry.co.uk