refactor large apps with backbone

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

Upload: devobjective

Post on 08-Aug-2015

29 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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

Page 7: Refactor Large apps with Backbone

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

Page 8: Refactor Large apps with Backbone

$(document).ready(function() {

});

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

Page 9: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 12: Refactor Large apps with Backbone
Page 13: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 17: Refactor Large apps with Backbone
Page 18: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

https://gist.github.com/stacylondon

Page 20: Refactor Large apps with Backbone

;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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

;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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 27: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 30: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 34: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 36: Refactor Large apps with Backbone
Page 37: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 39: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 42: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 44: Refactor Large apps with Backbone
Page 45: Refactor Large apps with Backbone
Page 46: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone
Page 48: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

;(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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

'use strict';

var Backbone = require('backbone');

var PageOneView = Backbone.View.extend({

el: '#pageOneMainContainer',

initialize: function() {}

});

module.exports = PageOneView;

page-one-view.js

Page 58: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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

Page 60: Refactor Large apps with Backbone

"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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

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: Refactor Large apps with Backbone

Thank You!

@stacylondoner

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