The Many Ways to Build
Modular JavaScript
Tim Perry
Tech Lead & Open-Source Champion at Softwire
@pimterry / github.com/pi...
JavaScript
 Originally designed by Brendan Eich for Netscape in mid-1995 as
LiveScript, based on his ideas from Scheme an...
JavaScript
Some great bits:
 Dynamic typing
 First-order functions & closures
Some features that just need removing:
 T...
Why?
coffeeFunctions.js:
function askAboutSugar() { … }
function askAboutMilk() { … }
function prepareMug(sugar, milk) { …...
Why?
JavaScript uses lexically-defined function scoping
Variables definitions are either in local scope or global scope
fu...
Why?
index.html:
<html><body>
<button onclick=‚makeCoffee()‛>Make Coffee</button>
<script src=‚coffeeFunctions.js‛></scrip...
Why?
index.html:
<html><body>
<button onclick=‚makeCoffee()‛>Make Coffee</button>
<script src=‚coffeeFunctions.js‛></scrip...
Why?
Global state makes systems
hard to reason about
Why?
Encapsulation breaks systems
into component parts, which
can be clearly reasoned about
index.html:
<html>
<body>
<button>Make Coffee</button>
<script src=‚beanGrinder.js‛></script>
<script src=‚hotWaterSource....
Why?
Build reusable chunks of code
Why?
beanGrinder.js:
function BeanGrinder() {
var beans = ko.observable(new BeanSupply());
var grindStrategy = new GrindSt...
Why?
beanGrinder.js:
function BeanGrinder() {
var beans = ko.observable(new BeanSupply());
var grindStrategy = new GrindSt...
index.html:
<html><body>
<script src=‚jquery.js‛></script>
<script src=‚jquery-ui.js‛></script>
<script src=‚knockout.js‛>...
Why?
Be explicit about your
component’s external
dependencies
Why?
1. Encapsulated state
2. Reusable code
3. Explicit dependency management
Immediately-Invoked
Function Expression (IIFE)
window.coffeeMachine.moduleName = (function ($, grindStrategy) {
[… some co...
Immediately-Invoked
Function Expression (IIFE)
window.coffeeMachine.moduleName = (function ($, grindStrategy) {
[… some co...
Immediately-Invoked
Function Expression (IIFE)
window.coffeeMachine.moduleName = (function ($, grindStrategy) {
[… some co...
Immediately-Invoked
Function Expression (IIFE)
window.coffeeMachine.moduleName = (function ($, grindStrategy) {
[… some co...
Immediately-Invoked
Function Expression (IIFE)
window.coffeeMachine.moduleName = (function ($, grindStrategy) {
[… some co...
IIFE Module Benefits
 Code internals are encapsulated
 Dependencies are explicitly named
 Code is reusable (in contexts...
IIFE Module Problems
 Global state has to be used to store each module exported from
an IIFE module
 Namespacing require...
Asynchronous Module
Definitions (AMD)
define([‚lib/jquery‛, ‚lib/knockout‛, ‚coffeeMachine/grinder‛],
function ($, ko, cof...
Asynchronous Module
Definitions (AMD)
define([‚lib/jquery‛, ‚lib/knockout‛, ‚coffeeMachine/grinder‛],
function ($, ko, cof...
Asynchronous Module
Definitions (AMD)
define([‚lib/jquery‛, ‚lib/knockout‛, ‚coffeeMachine/grinder‛],
function ($, ko, cof...
Asynchronous Module
Definitions (AMD)
define([‚lib/jquery‛, ‚lib/knockout‛, ‚coffeeMachine/grinder‛],
function ($, ko, cof...
Asynchronous Module
Definitions (AMD)
define([‚lib/jquery‛, ‚lib/knockout‛, ‚coffeeMachine/grinder‛],
function ($, ko, cof...
Asynchronous Module
Definitions (AMD)
require([‚font!fonts/myFavFont‛, ‚less!styles/homeStyle‛, ‚domReady!‛],
function () ...
Asynchronous Module
Definitions (AMD)
require([‚font!fonts/myFavFont‛, ‚less!styles/homeStyle‛, ‚domReady!‛],
function () ...
index.html:
<html>
<script src=‚require.js‛
data-main=‚scripts/main.js‛></script>
<body>
[ … ]
</body>
</html>
scripts/mai...
AMD Benefits
 Code internals are encapsulated, with explicitly exposed
interfaces
 Code is reusable as long as paths mat...
AMD Problems
 Lots of boilerplate (for JavaScript)
 Lots of complexity
 Can’t handle circular dependencies
 Can result...
CommonJS Modules
var $ = require(‚jquery‛);
var coffeeGrinder = require(‚./coffeeGrinder‛);
var niceBeans = require(‚./cof...
CommonJS Modules
var $ = require(‚jquery‛);
var coffeeGrinder = require(‚./coffeeGrinder‛);
var niceBeans = require(‚./cof...
CommonJS Modules
var $ = require(‚jquery‛);
var CoffeeGrinder = require(‚./coffeeGrinder‛).CoffeeGrinder;
var niceBeans = ...
CommonJS Runners
 Various non-browser platforms
 Browserify
 Require.js
CommonJS Runners
 Various non-browser platforms
 Node.JS, CouchDB, Narwhal, XULJet
 The native environment for CommonJS...
CommonJS Runners
 Various non-browser platforms
 Browserify
 CommonJS modules for the browser
 Build tool that takes C...
CommonJS Runners
 Various non-browser platforms
 Browserify
 Require.js
 Primarily an AMD script loader
 Can support ...
CommonJS Benefits
 Code internals are encapsulated
 Dependencies are explicitly named
 Code is easily reusable
 Simple...
CommonJS Problems
 Lots of magic involved
 Doesn’t follow standard JavaScript conventions
 No consideration of environm...
ES6 Modules
module ‚aCoffeeComponent‛ {
import $ from ‘jquery’;
import { NICE_BEANS as niceBeans } from ‚beanTypes‛;
impor...
ES6 Modules
module ‚aCoffeeComponent‛ {
import $ from ‘jquery’;
import { NICE_BEANS as niceBeans } from ‚beanTypes‛;
impor...
ES6 Modules
module ‚aCoffeeComponent‛ {
import $ from ‘jquery’;
import { NICE_BEANS as niceBeans } from ‚beanTypes‛;
impor...
ES6 Modules
module ‚aCoffeeComponent‛ {
import $ from ‚jquery‛;
import { NICE_BEANS as niceBeans } from ‚beanTypes‛;
impor...
ES6 Module Benefits
 Likely to be extremely well supported everywhere, eventually
 More granular & powerful module impor...
ES6 Module Problems
 Currently supported effectively nowhere
 Not even final in the spec yet
 Quite a lot of genuinely ...
Which one do I use?
IIFE:
For tiny projects
For trivial
compatibility
AMD:
For most serious
browser-based
projects
For a n...
Thank you
Tim Perry
Tech Lead & Open-Source Champion at Softwire
@pimterry / github.com/pimterry / tim-perry.co.uk
The Many Ways to Build Modular JavaScript
Upcoming SlideShare
Loading in …5
×

The Many Ways to Build Modular JavaScript

1,783 views

Published on

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,783
On SlideShare
0
From Embeds
0
Number of Embeds
54
Actions
Shares
0
Downloads
0
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

The Many Ways to Build Modular JavaScript

  1. 1. The Many Ways to Build Modular JavaScript Tim Perry Tech Lead & Open-Source Champion at Softwire @pimterry / github.com/pimterry / tim-perry.co.uk
  2. 2. JavaScript  Originally designed by Brendan Eich for Netscape in mid-1995 as LiveScript, based on his ideas from Scheme and Self, and implemented over 10 days ‘or something worse than JS would have happened’.  LiveScript ships in Netscape 2.0 that September  Renamed to JavaScript three months later to confuse as many people as possible for marketing purposes  Standardisation begins a year later (now as ECMAScript), after everybody has already implemented their own unique take.  First ECMAScript spec is published in June 1997.  16 years later, it’s the de facto standard language for all code on the largest common platform in the world (and everybody still calls it JavaScript)
  3. 3. JavaScript Some great bits:  Dynamic typing  First-order functions & closures Some features that just need removing:  The ‘with’ keyword  Much of type coercion  Automatic semicolon insertion Some fundamental structures that are hugely counter-intuitive:  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
  4. 4. Why? coffeeFunctions.js: function askAboutSugar() { … } function askAboutMilk() { … } function prepareMug(sugar, milk) { … } function requestuserEmptyGrounds() { … } function requestuserEmptyTray() { … } function grindBeans() { … } function putGroundsInFilter() { … } function heatWater() { … } function waterHotEnough() { … } function pourCoffeeInMug () { … } function serveMugToUser() { … } index.html: <html><body> <button onclick=‚makeCoffee()‛>Make Coffee</button> <script src=‚coffeeFunctions.js‛></script> <script> function makeCoffee() { var sugars = askAboutSugar(); var milk = askAboutMilk(); prepareMug(sugars, milk); while (!groundsAreEmpty) requestUserEmptyGrounds(); while (!dripTrayIsEmpty) requestUserEmptyTray(); grindBeans(); putGroundsInFilter(); heatWater(); while (!waterHotEnough()) wait(); pourCoffeeInMug(); serveMugToUser(); }; </script> </body></html>
  5. 5. Why? 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() { } } var globalVar = ‚a string‛; function g() { globalVar = ‚a string‛; } function g() { window.globalVar = ‚a string‛; } window.window.window.window === window;
  6. 6. Why? index.html: <html><body> <button onclick=‚makeCoffee()‛>Make Coffee</button> <script src=‚coffeeFunctions.js‛></script> <script> function makeCoffee() { var sugars = askAboutSugar(); var milk = askAboutMilk(); prepareMug(sugars, milk); while (!groundsAreEmpty) requestUserEmptyGrounds(); while (!dripTrayIsEmpty) requestUserEmptyTray(); grindBeans(); putGroundsInFilter(); heatWater(); while (!waterHotEnough()) wait(); pourCoffeeInMug(); serveMugToUser(); }; </script> </body></html> coffeeFunctions.js: function askAboutSugar() { … } function askAboutMilk() { … } function prepareMug(sugar, milk) { … } function requestuserEmptyGrounds() { … } function requestuserEmptyTray() { … } function grindBeans() { … } function putGroundsInFilter() { … } function heatWater() { … } function waterHotEnough() { … } function pourCoffeeInMug() { … } function serveMugToUser() { … }
  7. 7. Why? index.html: <html><body> <button onclick=‚makeCoffee()‛>Make Coffee</button> <script src=‚coffeeFunctions.js‛></script> <script> function makeCoffee() { var sugars = askAboutSugar(); var milk = askAboutMilk(); prepareMug(sugars, milk); while (!groundsAreEmpty) requestUserEmptyGrounds(); while (!dripTrayIsEmpty) requestUserEmptyTray(); grindBeans(); putGroundsInFilter(); heatWater(); while (!waterHotEnough()) wait(); pourCoffeeInMug(); serveMugToUser(); }; </script> </body></html> coffeeFunctions.js: function askAboutSugar() { … } function askAboutMilk() { … } function prepareMug(sugar, milk) { … } function requestuserEmptyGrounds() { … } function requestuserEmptyTray() { … } function grindBeans() { … } function putGroundsInFilter() { … } function heatWater() { … } function waterHotEnough() { … } function pourCoffeeInMug() { stopHeatingWater(); openCoffeeTap(); pourWaterThroughFilter(); } function serveMugToUser() { … }
  8. 8. Why? Global state makes systems hard to reason about
  9. 9. Why? Encapsulation breaks systems into component parts, which can be clearly reasoned about
  10. 10. index.html: <html> <body> <button>Make Coffee</button> <script src=‚beanGrinder.js‛></script> <script src=‚hotWaterSource.js‛></script> <script src=‚coffeeMachineUi.js‛></script> <script src=‚coffeeController.js‛></script> <script src=‚coffeeMachine.js‛></script> <script> coffeeMachine = new CoffeeMachine(); coffeeMachine.ui.show(); </script> </body> </html> Why? coffeeMachine.js: function CoffeeMachine() { var grinder = new BeanGrinder(); var hotWater = new HotWaterSource(); var ui = new CoffeeMachineUi(); var cc = new CoffeeController(grinder, hotWater, ui); } beanGrinder.js: function BeanGrinder() { … } coffeeController.js: function CoffeeController(grinder, hotWater, ui) { … } hotWaterSource.js: function HotWaterSource() { … }
  11. 11. Why? Build reusable chunks of code
  12. 12. Why? beanGrinder.js: function BeanGrinder() { var beans = ko.observable(new BeanSupply()); var grindStrategy = new GrindStrategy(); this.grindBeans = function () { … }; } coffeeMachineUi.js: function CoffeeMachineUi() { this.onMakeCoffee = function (makeCoffeeCallback) { $(‚button‛).click(makeCoffeeCallback); $(‚beanTypes‛).draggable(); }; this.askForSugar = function () { … }; this.askForMilk = function () { … }; this.confirmCancelMyCoffeePlease = function () { … }; } hotWaterSource.js: function HotWaterSource() { var dynamicsCalculator = new FluidCalc(); this.openTap = function () { … } this.startHeating = function () { … } this.stopHeating = function () { … } }
  13. 13. Why? beanGrinder.js: function BeanGrinder() { var beans = ko.observable(new BeanSupply()); var grindStrategy = new GrindStrategy(); this.grindBeans = function () { … }; } coffeeMachineUi.js: function CoffeeMachineUi() { this.onMakeCoffee = function (makeCoffeeCallback) { $(‚button‛).click(makeCoffeeCallback); $(‚beanTypes‛).draggable(); }; this.askForSugar = function () { … }; this.askForMilk = function () { … }; this.confirmCancelMyCoffeePlease = function () { … }; } hotWaterSource.js: function HotWaterSource() { var dynamicsCalculator = new FluidCalc(); this.openTap = function () { … } this.startHeating = function () { … } this.stopHeating = function () { … } }
  14. 14. index.html: <html><body> <script src=‚jquery.js‛></script> <script src=‚jquery-ui.js‛></script> <script src=‚knockout.js‛></script> <script src=‚beanSupply.js‛></script> <script src=‚grindStrategy.js‛></script> <script src=‚fluidDynamicsLib.js‛></script> <script src=‚beanGrinder.js‛></script> <script src=‚coffeeMachineUi.js‛></script> <script src=‚coffeeController.js‛></script> <script src=‚hotWaterSource.js‛></script> <script src=‚coffeeMachine.js‛></script> <script> coffeeMachine = new CoffeeMachine(); coffeeMachine.ui.show(); </script> </body></html> Why?
  15. 15. Why? Be explicit about your component’s external dependencies
  16. 16. Why? 1. Encapsulated state 2. Reusable code 3. Explicit dependency management
  17. 17. Immediately-Invoked Function Expression (IIFE) window.coffeeMachine.moduleName = (function ($, grindStrategy) { [… some code using these dependencies…] return aModuleObject; })(window.jQuery, window.coffeeMachine.grindStrategy);
  18. 18. Immediately-Invoked Function Expression (IIFE) window.coffeeMachine.moduleName = (function ($, grindStrategy) { [… some code using these dependencies…] return aModuleObject; })(window.jQuery, window.coffeeMachine.grindStrategy);
  19. 19. Immediately-Invoked Function Expression (IIFE) window.coffeeMachine.moduleName = (function ($, grindStrategy) { [… some code using these dependencies…] return aModuleObject; })(window.jQuery, window.coffeeMachine.grindStrategy);
  20. 20. Immediately-Invoked Function Expression (IIFE) window.coffeeMachine.moduleName = (function ($, grindStrategy) { [… some code using these dependencies…] return aModuleObject; })(window.jQuery, window.coffeeMachine.grindStrategy);
  21. 21. Immediately-Invoked Function Expression (IIFE) window.coffeeMachine.moduleName = (function ($, grindStrategy) { [… some code using these dependencies…] return aModuleObject; })(window.jQuery, window.coffeeMachine.grindStrategy);
  22. 22. IIFE Module Benefits  Code internals are encapsulated  Dependencies are explicitly named  Code is reusable (in contexts where the dependencies are already available)
  23. 23. IIFE Module Problems  Global state has to be used to store each module exported from an IIFE module  Namespacing requires manual initialization and management  Module loading and ordering still have to be managed manually
  24. 24. 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, }; } );
  25. 25. 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, }; } );
  26. 26. 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, }; } );
  27. 27. 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, }; } );
  28. 28. 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, }; } );
  29. 29. Asynchronous Module Definitions (AMD) require([‚font!fonts/myFavFont‛, ‚less!styles/homeStyle‛, ‚domReady!‛], function () { showPageNowThatAllPrerequisitesAreLoaded(); } );
  30. 30. Asynchronous Module Definitions (AMD) require([‚font!fonts/myFavFont‛, ‚less!styles/homeStyle‛, ‚domReady!‛], function () { showPageNowThatAllPrerequisitesAreLoaded(); } );
  31. 31. index.html: <html> <script src=‚require.js‛ data-main=‚scripts/main.js‛></script> <body> [ … ] </body> </html> scripts/main.js require([‚coffee/machine‛], function (CoffeeMachine) { coffeeMachine = new CoffeeMachine(); coffeeMachine.ui.show(); }); Asynchronous Module Definitions (AMD)
  32. 32. AMD Benefits  Code internals are encapsulated, with explicitly exposed interfaces  Code is reusable as long as paths match or are aliased  Dependencies are explicitly named  Dependency loading is asynchronous, and can be done in parallel  Implemented in vanilla JavaScript only; no fundamental new semantics
  33. 33. AMD Problems  Lots of boilerplate (for JavaScript)  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)
  34. 34. CommonJS Modules var $ = require(‚jquery‛); var coffeeGrinder = require(‚./coffeeGrinder‛); var niceBeans = require(‚./coffeeBeans‛).NICE_BEANS; [… code to do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4;
  35. 35. CommonJS Modules var $ = require(‚jquery‛); var coffeeGrinder = require(‚./coffeeGrinder‛); var niceBeans = require(‚./coffeeBeans‛).NICE_BEANS; [… code to do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4;
  36. 36. CommonJS Modules var $ = require(‚jquery‛); var CoffeeGrinder = require(‚./coffeeGrinder‛).CoffeeGrinder; var niceBeans = require(‚./coffeeBeans‛).NICE_BEANS; [… code to do something tenuously coffee related …] exports.doSomethingCoffeeRelated = function () { … }; exports.usefulNumber = 4;
  37. 37. CommonJS Runners  Various non-browser platforms  Browserify  Require.js
  38. 38. CommonJS Runners  Various non-browser platforms  Node.JS, CouchDB, Narwhal, XULJet  The native environment for CommonJS modules  Synchronous loading makes perfect sense server-side  Closer model to non-browser scripting languages  Browserify  Require.js
  39. 39. 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
  40. 40. 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.favouriteBeanType = beanTypes[0]; });
  41. 41. 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
  42. 42. 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
  43. 43. 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; }
  44. 44. 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; }
  45. 45. 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; }
  46. 46. 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; }
  47. 47. ES6 Module Benefits  Likely to be extremely well supported everywhere, eventually  More granular & powerful module import controls  New syntax, but otherwise fairly true to existing JS semantics  Fairly low on boilerplate  Handles circular references even better  Similar to other language concepts & syntax  Modules can be declared either inline, or nested, or externally
  48. 48. ES6 Module Problems  Currently supported effectively nowhere  Not even final in the spec yet  Quite a lot of genuinely new syntax  import * is included, but is frowned upon in every other language  Powerful, but thereby comparatively quite complicated
  49. 49. 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
  50. 50. Thank you Tim Perry Tech Lead & Open-Source Champion at Softwire @pimterry / github.com/pimterry / tim-perry.co.uk

×