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

The Many Ways to Build Modular JavaScript

Tim Perry
December 12, 2013

The Many Ways to Build Modular JavaScript

Tim Perry

December 12, 2013
Tweet

More Decks by Tim Perry

Other Decks in Programming

Transcript

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

    View Slide

  2. 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)

    View Slide

  3. View Slide

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

    View Slide

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

    View Slide

  6. Why?
    Why?
    Modular code ≠ Dependency Injection
    Modular code ≠ Dependency Injection

    View Slide

  7. 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”;
    }

    View Slide

  8. 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);

    View Slide

  9. 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);

    View Slide

  10. 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);

    View Slide

  11. 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);

    View Slide

  12. 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);

    View Slide

  13. 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)

    View Slide

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

    View Slide

  15. 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,
    };
    }
    );

    View Slide

  16. 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)

    View Slide

  17. 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)

    View Slide

  18. 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)

    View Slide

  19. 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)

    View Slide

  20. 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)

    View Slide

  21. 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)

    View Slide

  22. index.html:

    data-main=“js/main.js”><br/>

    [ … ]


    index.html:

    data-main=“js/main.js”><br/>

    [ … ]


    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)

    View Slide

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

    View Slide

  24. 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)

    View Slide

  25. 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;

    View Slide

  26. 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;

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. 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;
    });

    View Slide

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

    View Slide

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

    View Slide

  34. 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;
    }

    View Slide

  35. 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;
    }

    View Slide

  36. 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;
    }

    View Slide

  37. 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;
    }

    View Slide

  38. 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});

    View Slide

  39. 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);
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide