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

Patterns of Large-Scale JavaScript Applications

Patterns of Large-Scale JavaScript Applications

JavaScript has come a long way in few years. As we no longer need to battle DOM differences we can focus on building our applications instead. Now, however, as more and more application logic move from the server to the client our main problem is that we need to unlearn our earlier DOM-centric approach to JavaScript. Tools such as Backbone and Angular help, but before we are able to use the effectively we have to change some of our neural pathways. In this talk I will look at a couple of patterns that will help you move away from jQuery spaghetti and get you started on a foundation for building large-scale JavaScript applications.

6c51c14716e24bc1f1a3fb5ad234e773?s=128

Kim Joar Bekkelund

June 12, 2013
Tweet

More Decks by Kim Joar Bekkelund

Other Decks in Programming

Transcript

  1. of large-scale JavaScript applications patters

  2. Kim Joar Bekkelund I work here @kimjoar <- Twitter @kjbekkelund

    <- GitHub kimjoar.net
  3. We’re going to talk about large JavaScript apps

  4. We’re going to talk about large JavaScript apps where “large”

    really doesn’t need to be that large
  5. Most of this will apply to the JavaScript apps we

    write
  6. $('#my-button').click(function() { $.get('https://api.github.com', function(data) { $('#res').html(data.emojis_url); }); }); we always

    start with a small feature … The problem with JavaScript is that
  7. suddenly 5k lines in one file “how did this happen?”

  8. None
  9. Unobtrusive JavaScript cirka 2006

  10. Unobtrusive JavaScript + cirka 2006

  11. $

  12. $ DOM MANIPULATION

  13. $ $.fn.everything $(“.test”).parent().parent(); DOM MANIPULATION $(document).ready(everything)

  14. $ DOM MANIPULATION Too often, the business logic is hidden

    in a mess of code
  15. jQuery(document).ready(function() { $(".user form").submit(function(e) { e.preventDefault(); $.ajax({ // ... success:

    function(data) { var name = data.name || "Unknown"; $(".user .info").append("<h1>" + name + "</h1>"); } }) }); });
  16. jQuery(document).ready(function() { $(".user form").submit(function(e) { e.preventDefault(); $.ajax({ // ... success:

    function(data) { var name = data.name || "Unknown"; $(".user .info").append("<h1>" + name + "</h1>"); } }) }); }); Page event User event Network I/O Network event Parsing response Templating Callback hell https://speakerdeck.com/bkeepers/the-plight-of-pinocchio
  17. Considerable increase in the amount of JavaScript

  18. Complex Single page apps now

  19. backend --> frontend

  20. New capabilities: geolocation, local storage, canvas, web workers, WebSockets backend

    --> frontend
  21. The biggest problem is no longer the browser it’s the

    size and complexity of our apps
  22. None
  23. We want testable, scalable, maintainable JavaScript

  24. This is difficult! actually

  25. we need to unlearn

  26. we need to unlearn our DOM-centric approach

  27. we need to unlearn our DOM-centric approach The DOM is

    Inherently global and stateful hard to test and maintain
  28. we need to unlearn how we write JavaScript

  29. we need to unlearn how we write JavaScript No more

    5k lines of code in a single file Watch out for global variables Learn from the backend
  30. we need to unlearn how we use jQuery

  31. we need to unlearn how we use jQuery “Our experience

    is that, when code is difficult to test, the most likely cause is that our design needs improving.” – Growing Object-Oriented Software, Guided by Tests
  32. – Justin Meyer “The secret to building large apps is

    never build large apps. Break up your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application.”
  33. None
  34. There’s so much choice that you end up feeling like

    this guy
  35. There’s so much choice that you end up feeling like

    this guy We need a foundation
  36. we need to move state away from the DOM most

    importantly
  37. MVC

  38. MVC Google Trends for “JavaScript mvc”

  39. MVC A clean separation between data and the DOM

  40. MVC Mediates inputs and manipulates the model

  41. MVC NOT THE ONLY ONE. MANY VARIATIONS.

  42. MV*

  43. MV* everyone has a different take

  44. MVP all presentation logic is pushed to the presenter

  45. MVVM exposes model so it can be easily managed and

    consumed
  46. “I hereby declare AngularJS to be MVW framework – Model-View-Whatever.

    Where Whatever stands for ‘whatever works for you’.” – Igor Minar
  47. The most important aspect is to separate STATE and the

    DOM
  48. First step ✓

  49. However, MV* is not enough

  50. None
  51. “Lots of monolithic single files that contained too much code.”

  52. “Actually how you partition your code has a lot to

    do with how you organize your project source code and how much easier it is to maintain as your project grows.” – Jim Lavin
  53. # files ++ filesize --

  54. Package by feature Package by layer vs

  55. Package by feature Package by layer vs

  56. Package by feature I prefer

  57. Package by feature I prefer I call them Modules

  58. Module

  59. Module

  60. Module

  61. Modules

  62. Modulescontain business logic

  63. Modules small are

  64. Modules focused are

  65. Modulesare decoupled from other modules

  66. Modulesare preferably reusable

  67. Modulesare easily testable

  68. “A system is easier to change if its objects are

    context-independent; that is, if each object has no built-in knowledge about the system in which it executes.” – Growing Object-Oriented Software, Guided by Tests
  69. Module rules http://www.slideshare.net/nzakas/scalable-javascript-application-architecture-2012

  70. Module rules Never access the DOM outside the module http://www.slideshare.net/nzakas/scalable-javascript-application-architecture-2012

  71. Module rules Never access the DOM outside the module Don’t

    create global variables http://www.slideshare.net/nzakas/scalable-javascript-application-architecture-2012
  72. Module rules Never access the DOM outside the module Don’t

    create global variables Don’t access global, non-native objects http://www.slideshare.net/nzakas/scalable-javascript-application-architecture-2012
  73. Keeping this in mind should get you a long way

  74. Next up: dependencies

  75. We depend on other code dependencies We depend on other

    files We depend on Third-party libraries
  76. We depend on other code

  77. We usually depend far too much on globals We depend

    on other code
  78. We usually depend far too much on globals JavaScript makes

    this really easy We depend on other code
  79. $('#my-button').click(function() { $.get('https://api.github.com', function(data) { $('#res').html(data.emojis_url); }); });

  80. $('#my-button').click(function() { $.get('https://api.github.com', function(data) { $('#res').html(data.emojis_url); }); }); Cannot be

    reused Globals DIFFICULT TO TEST
  81. var downloadEmojis = function() { $.get('https://api.github.com', function(data) { $('#res').html(data.emojis_url); });

    }; $('#my-button').click(downloadEmojis);
  82. var downloadEmojis = function() { $.get('https://api.github.com', function(data) { $('#res').html(data.emojis_url); });

    }; $('#my-button').click(downloadEmojis); Globals DIFFICULT TO TEST
  83. var downloadEmojis = function(ajax, $el) { ajax.get('https://api.github.com', function(data) { $el.html(data.emojis_url);

    }); }; $('#my-button').click(function() { downloadEmojis($, $('#res')); });
  84. var downloadEmojis = function(ajax, $el) { ajax.get('https://api.github.com', function(data) { $el.html(data.emojis_url);

    }); }; $('#my-button').click(function() { downloadEmojis($, $('#res')); }); Now we can control the dependencies
  85. None
  86. var UserView = function(el, user) { this.el = el; this.user

    = user; }; // let's just create a super simple user object var user = { image: 'http://example.com/image.png' }; // initialize with the element the view owns and a user var view = new UserView($('.user'), user);
  87. var UserView = function(el, user) { this.el = el; this.user

    = user; }; UserView.prototype.showImage = function() { this.el.append('<img src=' + this.user.image + '>'); }; // let's just create a super simple user object var user = { image: 'http://example.com/image.png' }; // initialize with the element the view owns and a user var view = new UserView($('.user'), user);
  88. var UserView = function(el, user) { this.el = el; this.user

    = user; }; UserView.prototype.showImage = function() { this.el.append('<img src=' + this.user.image + '>'); }; // let's just create a super simple user object var user = { image: 'http://example.com/image.png' }; // initialize with the element the view owns and a user var view = new UserView($('.user'), user); // ... and now we can do stuff which changes the DOM view.showImage();
  89. Always inject dependencies

  90. has embraced this

  91. var MyController = function($scope, $http) { $http.get('https://api.github.com') .success(function(data) { $scope.emojis_url

    = data.emojis_url; }); } An example using AngularJS
  92. var MyController = function($scope, $http) { $http.get('https://api.github.com') .success(function(data) { $scope.emojis_url

    = data.emojis_url; }); } the ordering of these does not matter An example using AngularJS
  93. var MyController = function($http, $scope) { $http.get('https://api.github.com') .success(function(data) { $scope.emojis_url

    = data.emojis_url; }); } the ordering of these does not matter An example using AngularJS
  94. // Create an AngularJS module with no dependencies var ndc

    = angular.module('ndc', []);
  95. // Create an AngularJS module with no dependencies var ndc

    = angular.module('ndc', []); // Register a function ndc.factory('awesome', function() { return 'awesome conference'; });
  96. // Create an AngularJS module with no dependencies var ndc

    = angular.module('ndc', []); // Register a function ndc.factory('awesome', function() { return 'awesome conference'; }); // Get the injector (happens behind the scenes in AngularJS apps) var injector = angular.injector(['ndc', 'ng']);
  97. // Create an AngularJS module with no dependencies var ndc

    = angular.module('ndc', []); // Register a function ndc.factory('awesome', function() { return 'awesome conference'; }); // Get the injector (happens behind the scenes in AngularJS apps) var injector = angular.injector(['ndc', 'ng']);
  98. // Create an AngularJS module with no dependencies var ndc

    = angular.module('ndc', []); // Register a function ndc.factory('awesome', function() { return 'awesome conference'; }); // Get the injector (happens behind the scenes in AngularJS apps) var injector = angular.injector(['ndc', 'ng']); // Call a function with dependency injection injector.invoke(function(awesome) { console.log('awesome() == ' + awesome); });
  99. // Create an AngularJS module with no dependencies var ndc

    = angular.module('ndc', []); // Register a function ndc.factory('awesome', function() { return 'awesome conference'; }); // Get the injector (happens behind the scenes in AngularJS apps) var injector = angular.injector(['ndc', 'ng']); // Call a function with dependency injection injector.invoke(function(awesome) { console.log('awesome() == ' + awesome); });
  100. http://docs.angularjs.org/guide/dev_guide.templates.databinding

  101. http://docs.angularjs.org/guide/dev_guide.templates.databinding

  102. {{#if isExpanded}} <div class='body'>{{body}}</div> <button {{action contract}}>Contract</button> {{else}} <button {{action

    expand}}>Show More...</button> {{/if}} App.PostController = Ember.ObjectController.extend({ body: 'body', isExpanded: false, expand: function() { this.set('isExpanded', true); }, contract: function() { this.set('isExpanded', false); } });
  103. {{#if isExpanded}} <div class='body'>{{body}}</div> <button {{action contract}}>Contract</button> {{else}} <button {{action

    expand}}>Show More...</button> {{/if}} App.PostController = Ember.ObjectController.extend({ body: 'body', isExpanded: false, expand: function() { this.set('isExpanded', true); }, contract: function() { this.set('isExpanded', false); } }); no jquery!
  104. dependencies We depend on other code We depend on other

    files We depend on Third-party libraries
  105. We depend on other files

  106. The order of <script> elements

  107. The order of <script> elements

  108. The order of <script> elements Lots of globals Weakly stated

    dependencies
  109. CommonJS AMD

  110. CommonJS AMD var $ = require('jquery'); exports.myFn = function ()

    {};
  111. CommonJS AMD var $ = require('jquery'); exports.myFn = function ()

    {}; define(['jquery'] , function ($) { return function () {}; });
  112. CommonJS AMD var $ = require('jquery'); exports.myFn = function ()

    {}; define(['jquery'] , function ($) { return function () {}; }); works better in current browsers
  113. AMD

  114. dependencies We depend on other code We depend on other

    files We depend on Third-party libraries
  115. We depend on Third-party libraries

  116. Package manager

  117. Bower // bower.json { "dependencies": { "angular": "~1.0.7", "angular-resource": "~1.0.7",

    "jquery": "1.9.1" } }
  118. Bower // bower.json { "dependencies": { "angular": "~1.0.7", "angular-resource": "~1.0.7",

    "jquery": "1.9.1" } } $ bower install
  119. None
  120. … and that’s as far as we’ll go today

  121. is all of this necessary? … but seriously,

  122. Want to learn more?

  123. None
  124. Questions?