Refactoring Large Web Applications with Backbone.js

64
From Mess to Success, Refactoring Large Applications with Backbone dev.Objective() May 14, 2015 Stacy London @stacylondoner McWay Falls, Julia Pfeiffer Burns State Park - Big Sur, CA - 2015 by Stacy London

Transcript of Refactoring Large Web Applications with Backbone.js

Page 1: Refactoring Large Web Applications with Backbone.js

From Mess to Success, Refactoring Large Applications with Backbone

dev.Objective() May 14, 2015 Stacy London

@stacylondoner

McWay Falls, Julia Pfeiffer Burns State Park - Big Sur, CA - 2015 by Stacy London

Page 2: Refactoring Large Web Applications with Backbone.js

About Me

I’ve been making things for the web since 1998.

Currently I’m a Senior Application Architect focused on Front-End Engineering at Northwestern Mutual.

@stacylondoner

Page 3: Refactoring Large Web Applications with Backbone.js

Northwestern Mutual

Northwestern Mutual has been helping families and businesses achieve financial security for nearly 160 years. Our financial representatives build relationships with clients through a distinctive planning approach that integrates risk management with wealth accumulation, preservation and distribution. With more than $230 billion in assets, $27 billion in revenues, nearly $90 billion in assets under management and more than $1.5 trillion worth of life insurance protection in force, Northwestern Mutual delivers financial security to more than 4.3 million people who rely on us for insurance and investment solutions, including life, disability and long-term care insurance; annuities; trust services; mutual funds; and investment advisory products and services.

Northwestern Mutual is the marketing name for The Northwestern Mutual Life Insurance Company, Milwaukee, WI, and its subsidiaries. Northwestern Mutual and its subsidiaries offer a comprehensive approach to financial security solutions including: life insurance, long-term care insurance, disability income insurance, annuities, investment products, and advisory products and services. Subsidiaries include Northwestern Mutual Investment Services, LLC, broker-dealer, registered investment adviser, member FINRA and SIPC; the Northwestern Mutual Wealth Management Company, limited purpose federal savings bank; and Northwestern Long Term Care Insurance Company.

Page 4: Refactoring Large Web Applications with Backbone.js

What do I mean by large application?

• a multi-page web application (MPA) built with ASP.NET MVC

• 1500+ files | 343,000+ lines of code

• UI (CSS / JS)

• 230+ files | 42,000+ lines of code

Page 5: Refactoring Large Web Applications with Backbone.js

Development Culture

• JEE/Java shop turned .NET/C#

• view JS as a bit of a “toy” language

• as JS was being added it was just being added without much thought to maintainability, extensibility, testability, patterns, etc.

• app was going to be heavy with JS yet initially on a platform that wasn’t optimized for this kind of app dev.

• path of least resistance was to just put everything in $(document).ready()

Page 6: Refactoring Large Web Applications with Backbone.js

https://www.youtube.com/watch?v=J---aiyznGQ

Page 7: Refactoring Large Web Applications with Backbone.js

http://starecat.com/kermit-typing-on-a-typewriter-like-crazy-animation/

Page 8: Refactoring Large Web Applications with Backbone.js

$(document).ready(function() {

});

http://www.wikihow.com/Make-a-Quick-Italian-Spaghetti

Page 9: Refactoring Large Web Applications with Backbone.js

Problems

• one large JavaScript file in the <head> (all code included on every page regardless if it needed it)

• all JavaScript global and in $(document).ready()

• some pages had inline JavaScript

• not commented

• no unit tests

• unclear if a function / event was used on multiple pages

Page 10: Refactoring Large Web Applications with Backbone.js

Iterative Refactoring

• big codebase

• large team working on 2 week sprints that couldn’t be slowed down

• refactors had to fit into a sprint

Page 11: Refactoring Large Web Applications with Backbone.js
Page 12: Refactoring Large Web Applications with Backbone.js
Page 13: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 1: Break Apart JS

• figure out what JS belongs with each page and move that JS into a separate file for that page

• include that JS with the partial HTML

Page 14: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 1: Start Adding Unit Tests

• start adding unit tests for original code then make sure it passed after refactoring as a safety/confidence mechanism

• to prepare for this journey of breaking code into smaller, more testable chunks it was good to start early with creating a test bed

• help make subsequent refactors less stressful

Page 15: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 1: Start Adding Unit Tests

• picked Jasmine (BDD style) - http://jasmine.github.io/

• a lot of the JS tied to DOM manipulation so not in an ideal state for unit testing with Jasmine out of the box

• needed DOM testing helper so added jquery-jasmine library to add fixtures (HTML snippets to run tests against)

• no external UI Regression tools (e.g. Selenium) in a state to do this validation throughout the refactor

Page 16: Refactoring Large Web Applications with Backbone.js
Page 17: Refactoring Large Web Applications with Backbone.js
Page 18: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 2: Namespacing & Object Literal

• create an app namespace to get custom code out of the global namespace (window) to avoid collisions with external libs/frameworks

• introduce an Object Literal notation style in preparation for Backbone (which uses this pattern)

• helps to organize the code and parameters logically “an object containing a collection of key:value pairs with a colon separating each pair of keys and values where keys can also represent new namespaces”

• add closures with Instantly Invoked Function Expressions (IIFEs)

Page 19: Refactoring Large Web Applications with Backbone.js

https://gist.github.com/stacylondon

Page 20: Refactoring Large Web Applications with Backbone.js

;var myApp = myApp || {};

(function($, myApp) {

'use strict';

$.extend(myApp, {

init: function() { this.commonStuff(); },

commonStuff: function() { // common code across many or all pages }

});

})(window.jQuery, window.myApp);

$(document).ready(function() {

myApp.init();

});

my-app.js

Page 21: Refactoring Large Web Applications with Backbone.js

my-app.js with comments

// prefix with semi-colon as safety net against concatenated // scripts and/or other plugins that are not closed properly;// check for existence of myApp in the global namespace var myApp = myApp || {};// Use IIFE to:// * encapsulate app logic to protect it from global namespace // * pass in namespace so can be modified locally and isn't// overwritten outside of our function context// * ensure $ only refers to window.jQuery (defensive programming)// * $ passed through as local var rather than as globals and this // (slightly) quickens the resolution process and can be more // efficiently minified (especially if regularly referenced)(function($, myApp) {

Page 22: Refactoring Large Web Applications with Backbone.js

my-app.js with comments

(function($, myApp) {

// Strict mode makes several changes to normal JavaScript semantics: // * eliminates some JS silent errors by changing them to throw errors. // * fixes mistakes that make it difficult for JavaScript engines to // perform optimizations: strict mode code can sometimes be made to // run faster than identical code that's not strict mode. Add inside // the IIFE so it's defined for just the functions defined within and // doesn't flip concatenating/minified code to strict inadvertently 'use strict';

// extend the namespace with more functionality $.extend(myApp, {

Page 23: Refactoring Large Web Applications with Backbone.js

;var myApp = myApp || {};myApp.pageOne = myApp.pageOne || {};

(function($, myApp) {

'use strict';

$.extend(myApp.pageOne, {

init: function() { this.pageSpecificStuff(); },

pageSpecificStuff: function() { // code specific to this page }

});

})(window.jQuery, window.myApp);

$(document).ready(function() {

myApp.pageOne.init();

});

page-one.js

Page 24: Refactoring Large Web Applications with Backbone.js

Better but not great

• code is aligned with the screen to which is pertains

• pages now have mid-page script includes which isn’t good for performance / rendering

• still hand-wiring Ajax calls

• entire-page-JS is not very modular

• want to break screens down into smaller features and keep events neatly associated

Page 25: Refactoring Large Web Applications with Backbone.js

Backbone to the rescue

• wanted something that provided/enforced structure but in a lightweight way

• it’s a MPA not a SPA so full-featured SPA frameworks didn’t make sense (e.g. Angular, Ember)

• wanted to be able to use just a small feature of the library/framework and add more full integration over time (refactor in multiple iterations)

• for these reasons Backbone.js made sense - http://backbonejs.org/

Page 26: Refactoring Large Web Applications with Backbone.js
Page 27: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 3: Page Level Backbone Views

• reducing boilerplate code

• views enforce organization of events

• views enforce an Object Literal notation pattern

• views helped developers think about encapsulating pieces of the screen

• starting with just Views gave the team time to start planning for refactoring back-end data provided by ASP.NET MVC Controllers into more RESTful web services (Web API) which is necessary to take full advantage of Backbone.js Models/Collections

Page 28: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 3: Page Level Backbone Views

• my-app.js - is now responsible for instantiating the app namespace and setting up a super light-weight view manager for Backbone.js

• instantiate all available views on the page upon DOM ready.

• page-one-view.js - no longer namespaced individually since being added to the views object of the app namespace

Page 29: Refactoring Large Web Applications with Backbone.js
Page 30: Refactoring Large Web Applications with Backbone.js

my-app.jsvar myApp = myApp || {};

(function($, myApp) { 'use strict'; $.extend(myApp, { init: function() { this.initializeViews(); },

// view manager code });})(window.jQuery, window.myApp);

$(document).ready(function() { myApp.init();});

Page 31: Refactoring Large Web Applications with Backbone.js

my-app.js view manager

views: {}, initializeViews: function() { for (var view in this.views) { if (typeof this.views[view] === 'function') { this.views[view.substring(0, 1).toLowerCase() + view.substring(1, view.length)] = new this.views[view](); } } }, addView: function(key, view) { if (this.views.hasOwnProperty(key)) { throw (new Error('A view with that key already exists.')); } this.views[key] = view; }, getView: function(key) { if (!this.views.hasOwnProperty(key)) { throw new Error('View does not exist in views collection.'); } return this.views[key]; }

Page 32: Refactoring Large Web Applications with Backbone.js

page-one-view.js;(function($, _, Backbone, myApp) {

'use strict';

var PageOneView = Backbone.View.extend({ el: '#pageOneMainContainer',

events: { 'click .something': 'doSomething' }, initialize: function() { this.setupValidation(); }, setupValidation: function() {

}, doSomething: function() {

} });

myApp.addView('PageOneView', PageOneView);

}(window.jQuery, window._, window.Backbone, window.myApp));

Page 33: Refactoring Large Web Applications with Backbone.js
Page 34: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 4: Sub-Page Backbone Views

• start breaking the large page views into sub-page views so that you can get truly modular

• share modules/code if a module exists on more than one page

Page 35: Refactoring Large Web Applications with Backbone.js
Page 36: Refactoring Large Web Applications with Backbone.js
Page 37: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 5: Backbone Models, Collections

• move code that is getting data and doing any business logic out of the Views and into Models and Collections

• e.g. move phone number formatting out of View and into the Model

• remove hand written Ajax, use BB API (e.g. fetch)

• make sure server side controllers were written in a RESTful way

• still not complete across the application

Page 38: Refactoring Large Web Applications with Backbone.js
Page 39: Refactoring Large Web Applications with Backbone.js

var PageOneView = Backbone.View.extend({

el: '#pageOneMainContainer',

initialize: function() { this.getSomeData(); },

getSomeData: function() { var self = this; $.ajax({ type: 'GET', url: 'SomeEndpoint', data: formData, dataType: 'json', success: function(data, textStatus, jqXHR) { self.displayResults(data); }, error: function(jqXHR, textStatus, errorThrown) { self.displayError(); } }); },

displayResults: function(data) { },

displayError: function() { }

});

Hand-wiring Ajax

Page 40: Refactoring Large Web Applications with Backbone.js

Use Backbone.js API to get data

// initialize viewvar pageOneView = new PageOneView({ collection: new PageOneCollection([]),});

var PageOneView = Backbone.View.extend({

el: '#pageOneMainContainer',

template: _.template($('#page-one-template').html()),

initialize: function() {},

render: function() { this.$el.html(this.template({'collection': this.collection})); // maintain chainability return this; }

});

var PageOneCollection = Backbone.Collection.extend({

initialize: function(models, options) {},

model: PageOneModel,

// RESTful web service URL url: '/SomeEndpoint'

});

Page 41: Refactoring Large Web Applications with Backbone.js
Page 42: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 6: Mini-SPA

• treat each page of the multi-page app (MPA) like it’s a miniature single page app (SPA)

• each page has a single entry point (main.js)

• this will setup the code for a module loader / build system so that we can finally move the JS to the bottom of the page and remove mid-page scripts

• improve performance (time to first paint - how long it takes between a user entering a URL into the browser and when he/she sees visual activity on screen)

Page 43: Refactoring Large Web Applications with Backbone.js
Page 44: Refactoring Large Web Applications with Backbone.js
Page 45: Refactoring Large Web Applications with Backbone.js
Page 46: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 7: Modules

• current version of JavaScript (ECMA-262) doesn’t provide a way to import modules of code like more traditional programming languages do

• modules are proposed for the next version of JS (ES6/ES2015/Harmony)

• to use modules and manage dependencies with the current version of JS you can use community driven methodologies

• there are two popular styles with associated script loaders / build systems

Page 47: Refactoring Large Web Applications with Backbone.js
Page 48: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 7: Modules - AMD

• AMD - Asynchronous Module Definition“The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.”https://github.com/amdjs/amdjs-api/wiki/AMD

Page 49: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 7: Modules - CommonJS

• CommonJS’s module format was made popular for server-side JavaScript development (namely for Node.js/NPM)

• it’s synchronous

• syntax is a bit easier as it frees you from the define() wrapper that AMD enforces

• requires a build in a JS runtime

Page 50: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 7: Modules - RequireJS

• popular script loader written by James Burke that helps you load multiple script files and define modules with or without dependencies

• use RequireJS as first pass at module loading since async nature means no build step during dev time (least disruption to the dev team which is important because there is a learning curve)

• use almond (AMD API shim) so don’t have to add a special script tag to load RequireJS and change any HTML

• this will move the code away from IIFEs to modules with dependency management

Page 51: Refactoring Large Web Applications with Backbone.js

my-app.js// requireJS simplified commonJS wrapper// do this so can use the commonJS style & // make switch to browserify easierdefine('app', function(require, exports, module) {

'use strict';

var $ = require('jquery'); var _ = require('underscore'); var Backbone = require('backbone'); var Modernizr = require('modernizr'); var HeaderController = require('./header-controller'); var FooterController = require('./footer-controller');

var app = new Backbone.Application();

module.exports = app;

});

(function() {

var $ = require('jquery'); var app = require('app');

// dom ready $(function() { app.start(); });

}());

Page 52: Refactoring Large Web Applications with Backbone.js

;(function() {

'use strict';

var $ = require('jquery'); var PageOneController = require('./page-one-controller'); var app = require('app');

// dom ready $(function() { app.start({ ScreenController: PageOneController }); });

}());

page-one-main.js

Page 53: Refactoring Large Web Applications with Backbone.js

define('page-one-controller', function(require, exports, module) {

'use strict';

var Backbone = require('backbone'); var PageOneView = require('PageOneView');

var PageOneController = Backbone.Controller.extend({

initialize: function(options) { var pageOneView = new PageOneView(); }

});

module.exports = PageOneController;

});

page-one-controller.js

Page 54: Refactoring Large Web Applications with Backbone.js

define('page-one-view', function(require, exports, module) {

'use strict';

var Backbone = require('backbone');

var PageOneView = Backbone.View.extend({

});

module.exports = PageOneView;

});

page-one-view.js

Page 55: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 7: Modules - Browserify

• Browserify lets you require('modules') in the browser by bundling up all of your dependencies during a build step

• it’s the end-state module loader because it allows for easy bundle splitting

• figure out common/shared JS and create a common bundle then a bundle for the unique code on each page

• by having a main.js for each screen this will act as an entry point so Browserify can find all the modules required and create a screen-specific bundle

Page 56: Refactoring Large Web Applications with Backbone.js

Refactor, Iteration 7: Modules - Browserify

• means build during dev but we were already doing that for our CSS with LESS

• “It’s great the dev community has embraced compilation because it’s inevitable.” - Brendan Eich at Fluent 2015

• the code will become very clean

Page 57: Refactoring Large Web Applications with Backbone.js

'use strict';

var Backbone = require('backbone');

var PageOneView = Backbone.View.extend({

el: '#pageOneMainContainer',

initialize: function() {}

});

module.exports = PageOneView;

page-one-view.js

Page 58: Refactoring Large Web Applications with Backbone.js

Summary

• the JavaScript is now modular and easier to maintain

• Backbone.js helps devs keep consistent with coding patterns and organization (Object Literal notation, events)

• events are scoped to the smallest part of the page to which they matter (Backbone.js)

• namespacing/IIFEs and then later module loader/build removes the possibility of collisions with other frameworks/libs

• unit tests mean you can feel more confident to change things

• only sending JS to the browser that is necessary is good for performance (think mobile)

Page 59: Refactoring Large Web Applications with Backbone.js

http://ak-static.scoopon.com.au/scpn/deals/main/50000/50693_2.jpg

Page 60: Refactoring Large Web Applications with Backbone.js

"The secret to building large apps is never build large apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big

application"

- Justin Meyer, author JavaScriptMVC

Page 61: Refactoring Large Web Applications with Backbone.js

Team Shout Out

• This was over the course of several years and I worked with two other fantastic front-end engineers:

• Ryan Anklam ( @bittersweetryan )

• Zeek Chentnik ( http://ezekielchentnik.com )

Page 62: Refactoring Large Web Applications with Backbone.js

JavaScript References

• “Patterns For Large-Scale JavaScript Application Architecture” by Addy Osmani http://addyosmani.com/largescalejavascript/

• “Learning JavaScript Design Patterns” by Addy Osmani http://addyosmani.com/resources/essentialjsdesignpatterns/book/

• “Using Objects to Organize Your Code” by Rebecca Murphey http://rmurphey.com/blog/2009/10/15/using-objects-to-organize-your-code/

• “It’s time to start using JavaScript strict mode” by Nicholas Zakas http://www.nczonline.net/blog/2012/03/13/its-time-to-start-using-javascript-strict-mode/

• “Writing Modular JavaScript with AMD, CommonJS & ES Harmony” by Addy Osmanihttp://addyosmani.com/writing-modular-js/

Page 63: Refactoring Large Web Applications with Backbone.js

Backbone.js References

• “Developing Backbone.js Applications” by Addy Osmanihttp://addyosmani.github.io/backbone-fundamentals/

• “Communicating Between Views in Client-Side Apps” by Rebecca Murphey http://bocoup.com/weblog/communicating-between-views-in-client-side-apps/

• Talks from past Backbone Conferences are free/online: http://backboneconf.com/http://backboneconf.com/2013/

Page 64: Refactoring Large Web Applications with Backbone.js

Thank You!

@stacylondoner

Code Samples:https://gist.github.com/stacylondon