Slide 1

Slide 1 text

of large-scale JavaScript applications patterns

Slide 2

Slide 2 text

Kim Joar Bekkelund kimjoar.net @kimjoar

Slide 3

Slide 3 text

We’re going to talk about large-scale JavaScript apps

Slide 4

Slide 4 text

We’re going to talk about large-scale JavaScript apps where “large” really doesn’t need to be that large

Slide 5

Slide 5 text

Unknown unknowns Complex domain Many developers Testing Long-lived Adapt to changes

Slide 6

Slide 6 text

$('#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

Slide 7

Slide 7 text

suddenly 5k lines in one file “how did this happen?”

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Unobtrusive JavaScript circa 2006

Slide 10

Slide 10 text

Unobtrusive JavaScript + circa 2006

Slide 11

Slide 11 text

$

Slide 12

Slide 12 text

jQuery Anti-patterns

Slide 13

Slide 13 text

jQuery Anti-patterns $(“.test”).parent().parent().find(“.open”).fi $(“ul.test li p > a.open”)

Slide 14

Slide 14 text

jQuery Anti-patterns $(“.test”).parent().parent().find(“.open”).fi $(document).ready(everything) $(“ul.test li p > a.open”)

Slide 15

Slide 15 text

$(“.test”).data(“foo”, { some: “state” }) $(document).ready(everything) jQuery Anti-patterns $(“.test”).parent().parent().find(“.open”).fi $(“ul.test li p > a.open”)

Slide 16

Slide 16 text

jQuery Anti-patterns making the DOM the source of truth is a bad idea

Slide 17

Slide 17 text

Too often, the business logic is hidden in a mess of code

Slide 18

Slide 18 text

What happens when the requirements change?

Slide 19

Slide 19 text

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

" + name + "

"); } }) }); }); Inspired by https://speakerdeck.com/bkeepers/the-plight-of-pinocchio and @searls

Slide 20

Slide 20 text

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

" + name + "

"); } }) }); }); Page event User event Hitting the server Nested callback Parsing response Templating Callback hell Inspired by https://speakerdeck.com/bkeepers/the-plight-of-pinocchio and @searls

Slide 21

Slide 21 text

Considerable increase in the amount of JavaScript

Slide 22

Slide 22 text

Highly interactive frontends now

Slide 23

Slide 23 text

backend --> frontend

Slide 24

Slide 24 text

New capabilities: geolocation, local storage, canvas, web workers, WebSockets backend --> frontend

Slide 25

Slide 25 text

The biggest problem is no longer the browser it’s the size and complexity of our apps

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

We want testable, scalable, maintainable JavaScript

Slide 28

Slide 28 text

This is difficult! actually

Slide 29

Slide 29 text

we need to unlearn

Slide 30

Slide 30 text

we need to unlearn our DOM-centric approach

Slide 31

Slide 31 text

we need to unlearn our DOM-centric approach The DOM is Inherently global and stateful hard to test and maintain

Slide 32

Slide 32 text

we need to unlearn how we write JavaScript

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

we need to unlearn how we use jQuery

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Structure We need

Slide 37

Slide 37 text

– 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.” http://bitovi.com/blog/2010/11/organizing-a-jquery-application.html

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

We need a foundation

Slide 40

Slide 40 text

move state and business logic away from the DOM First of all

Slide 41

Slide 41 text

MVC

Slide 42

Slide 42 text

MVC Google Trends for “JavaScript mvc”

Slide 43

Slide 43 text

MVC A clean separation between data and the DOM

Slide 44

Slide 44 text

MVC Mediates inputs and manipulates the model

Slide 45

Slide 45 text

MVC NOT THE ONLY WAY. MANY VARIATIONS.

Slide 46

Slide 46 text

MV*

Slide 47

Slide 47 text

MV* everyone has a different take

Slide 48

Slide 48 text

MVP all presentation logic is pushed to the presenter

Slide 49

Slide 49 text

MVVM exposes model so it can be easily managed and consumed Bi-directional binding

Slide 50

Slide 50 text

“I hereby declare AngularJS to be MVW framework – Model-View-Whatever. Where Whatever stands for ‘whatever works for you’.” – Igor Minar https://plus.google.com/app/basic/stream/z13vitkjpya0zxcee23ztzmqcor3fzen2

Slide 51

Slide 51 text

The most important aspect is to separate STATE and the DOM

Slide 52

Slide 52 text

First step ✓

Slide 53

Slide 53 text

However, MV* is not enough

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

“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 http://codingsmackdown.tv/blog/2013/04/19/angularjs-modules-for-great-justice/

Slide 57

Slide 57 text

# files ++ filesize --

Slide 58

Slide 58 text

Package by feature Package by layer vs

Slide 59

Slide 59 text

Package by feature Package by layer vs

Slide 60

Slide 60 text

Package by feature I prefer

Slide 61

Slide 61 text

Package by feature I prefer I call them Modules

Slide 62

Slide 62 text

Module

Slide 63

Slide 63 text

Module

Slide 64

Slide 64 text

Module

Slide 65

Slide 65 text

Modules

Slide 66

Slide 66 text

Modulescontain business logic

Slide 67

Slide 67 text

Modules small are

Slide 68

Slide 68 text

Modules focused are

Slide 69

Slide 69 text

Modulesare decoupled from other modules

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

Modulesare preferably reusable

Slide 72

Slide 72 text

Modulesare easily testable

Slide 73

Slide 73 text

“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

Slide 74

Slide 74 text

Module rules http://www.slideshare.net/nzakas/scalable-javascript-application-architecture-2012

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Keeping this in mind should get you a long way http://www.flickr.com/photos/lrargerich/4999906554/

Slide 79

Slide 79 text

Next up: dependencies

Slide 80

Slide 80 text

We depend on other code dependencies We depend on other files We depend on Third-party libraries

Slide 81

Slide 81 text

We depend on other code

Slide 82

Slide 82 text

We usually depend far too much on globals We depend on other code

Slide 83

Slide 83 text

We usually depend far too much on globals JavaScript makes this really easy We depend on other code

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

No content

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

var UserView = function(el, user) { this.el = el; this.user = user; }; UserView.prototype.showImage = function() { this.el.append(''); }; // 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);

Slide 93

Slide 93 text

var UserView = function(el, user) { this.el = el; this.user = user; }; UserView.prototype.showImage = function() { this.el.append(''); }; // 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();

Slide 94

Slide 94 text

var UserView = function(el, user) { this.el = el; this.user = user; }; UserView.prototype.showImage = function() { this.el.append(''); }; // 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(); $(‘
’) In tests

Slide 95

Slide 95 text

Always inject dependencies

Slide 96

Slide 96 text

has embraced this

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

{{#if isExpanded}}
{{body}}
Contract {{else}} Show More... {{/if}}

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

{{#if isExpanded}}
{{body}}
Contract {{else}} Show More... {{/if}} App.PostController = Ember.ObjectController.extend({ body: 'body', isExpanded: false, expand: function() { this.set('isExpanded', true); }, contract: function() { this.set('isExpanded', false); } }); no jquery!

Slide 111

Slide 111 text

dependencies We depend on other code We depend on other files We depend on Third-party libraries

Slide 112

Slide 112 text

We depend on other files

Slide 113

Slide 113 text

The order of elements

Slide 114

Slide 114 text

The order of elements

Slide 115

Slide 115 text

The order of elements Lots of globals Weakly stated dependencies

Slide 116

Slide 116 text

CommonJS AMD ECMAScript 6

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

CommonJS AMD ECMAScript 6 var $ = require('jquery'); exports.myFn = function() {}; define(['jquery'] , function($) { return function() {}; }); import $ from 'jquery'; var myFn = function() {} export { myFn };

Slide 120

Slide 120 text

CommonJS AMD ECMAScript 6 var $ = require('jquery'); exports.myFn = function() {}; define(['jquery'] , function($) { return function() {}; }); import $ from 'jquery'; var myFn = function() {} export { myFn }; works better in current browsers

Slide 121

Slide 121 text

AMD

Slide 122

Slide 122 text

Slide 123

Slide 123 text

// scripts/main.js requirejs.config({ paths: { 'jquery': 'lib/jquery', 'underscore': 'lib/underscore', 'backbone': 'lib/backbone', } }); define(['app', 'jquery'], function(App, $) { var app = new App({ el: $('body') }); app.start(); });

Slide 124

Slide 124 text

main.js app.js jQuery backbone underscore ...

Slide 125

Slide 125 text

dependencies We depend on other code We depend on other files We depend on Third-party libraries

Slide 126

Slide 126 text

We depend on Third-party libraries

Slide 127

Slide 127 text

Package manager

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

No content

Slide 131

Slide 131 text

… and that’s as far as we’ll go today

Slide 132

Slide 132 text

is all of this necessary? … but seriously,

Slide 133

Slide 133 text

Want to learn more?

Slide 134

Slide 134 text

No content

Slide 135

Slide 135 text

Questions?