Large Scale JS

I watched a really interesting course by Shawn Wildermuth on Pluralsight recently and learned a thing or two about writing large scale JavaScript applications.

The material is very useful when applied to large applications, but can of course benefit small projects as well.

What is large Scale JavaScript?

How to determine what is large (subjective)

What's Wrong with JavaScript?

These flawds often lead to code that is hard to read and maintain.

Frameworks and Libraries

Scalable JavaScript is not a product of a framework but frameworks can make it simpler to build large scale apps.

What's the Solution?

The key is to have a codebase that is:

SPAs do not solve these problems but can make them worse. Apps should include as many pages as necessary. Rich JavaScript applications would be a better name.

SPAs have brought some goodness:

Good development practices can solve many of the problems with the JavaScript language

Meta Languages

Using a meta language can help but will need to be backed by good development practices to solve our problems building large scale apps.

Maintainable JavaScript

Application Frameworks

Avoid Polluting the Global Scope

In JS there is no block scope. Instead we must use function scope to avoid global objects.

(function () {
	// Your Code Here
	var a = {}; // The a object is only global inside the function
})();

Enclosing a function inside parenteses creates a function object. The last two parenteses tells the parser to execute the function object. Any variable declared inside this function will not leak to the global scope. You can pass parameters between the last two parenteses to prevent global object lookup.

(function ($) {
    // Your Code Here
    $.each(...)
})(jQuery);

Strictness in JavaScript

Using strict mode improves code quality by providing early detection of problematic code.

In strict mode exceptions are raised when these bad practices are used in code. Using strict mode is not a replacement for JSLint.

(function () {
	x = 0; // works
	var y = '';
	y = 123; // works too
})();
(function () {
	'use strict';

	x = 0; // raises exception
	var y = '';
	y = 123; // raises exception
})();

Modular JavaScript

Small discrete units of work pieced together to make code base more maintainable

each of a set of standardized parts or independent units that can be used to construct a more complex structure

Rules for Modules:

The Module Pattern

Returns a Singleton representing the module.

var destinationsModule = function() {
    "use strict";

    var _cache = {};

    function _fillCache(callback) {
        //...
    }

    return {
        fillCache: _fillCache,
        cache: _cache
    };
})();

The JavaScript Class Pattern

Useful when you need multiple instances of objects.

function Animal() {
	'use strict';

	this.cache = {};
}

Animal.prototype.walk = function () {
	//...
};
Framework Types of Modularity
JavaScript Namespaces, Module Pattern, Class Pattern
AngularJS Modules, Services, Factories, Controllers, Directives
Backbone Namespaces and Objects. More with extensions
EmberJS Extend built-in objects or use ES6
Durandal Asynchronous Module Definition
EcmaScript 6 CommonJS Compatible

Dependency Management

The degree to which each program module relies on each one of the other modules

A system for handling dependencies across an application. Using the dependency injection pattern (a.k.a. inversion of control) is typical.

Allows passing dependencies without relying on global scope. Cascading dependencies are handled automatically

Asynchronous Module Definition (AMD)

Making a Module

// someModule.js - default module name is file name if no name provided
// The array param is for dependencies required for this module
define('myModule', [], function () {
	function _init() {}

	return {
		init: _init
	};
});

Using a Module

require(['myModule', 'jQuery'], function (myModule, $) {
	//use the dependencies
});

CommonJS Spec

The module must use the exports function to export the facade of the module.

exports.getMeSomething = function () {
    ...
}

The code using the module requires the exported facade by assigning it to a variable that can then be used.

var my = require('./myModule');
my.getMeSomething();

AngularJS Sample

var myModule = angular.module('myModule', []);

myModule.factory('dataFactory', [], function () {
	var _myData = {};
	return {
		myData: _myData
	};
});

myModule.controller('controller', ['dataFactory'], function (dataFactory) {
	//...
});

Smart Asynchrony

$(document).ready(function() {
    $.get("/api/destinations", function(result) {
        if (result.success) {
            if ($("#userName").length > 0) {
                $.get("/api/user/" + userId, function (result) {
                    if (result.success) {
                        ...
                    }
                });
            }
        } else {
            alert("Failed to get destinations");
        }
    });
});

Promises

// using Q.js
someModule.makeAsyncCall()
    .then(function () { ... })     // runs first
    .then(function () { ... })     // runs second
    .fail(function () { ... })     //runs if something fails
    .finally(function () { ... })  //runs after either success or fail
    .done();                       //completes the promises chain

Async Library

// using Async

async.parallel([
    function(cb) {
        ...
        cb(1);
    },
    function (cb) {
        ...
        cb(2);
    }
], function (err, results) {
    ...
    // results = [1,2]
});

Loose Coupling

jQuery Events

JQuery has a global event object on the jQuery object.

//Publish event
$.event.trigger("our.event.name", ["some", "Context"]);

//Subscribe (requires DOM element)
$(document).on("our.event.name"),
    function (event, some, context) {
        //...
    });

AmplifyJS

//Publish event
amplify.publish("our.event.name", "some", "context");

//Subscribe
amplify.subscribe("our.event.name"),
    function (some, cnx) {
        //...
    });

AngularJS

//Publish event
app.controller("myCtrl", function ($rootScope) {
     $rootScope.$broadcast("our.event.name", "some", "context");
});

//Subscribe

app.controller("myCtrl", function ($scope) {
     $scope.$on("our.event.name", function(some, ctx { ... })
});

Scalable JavaScript

JavaScript code can run on many different types of devices. You want your code to run on both high-end and low-end devices.

JavaScript typically has the following scalability problems:

Improve Code Quality

Entails writing less code, improving your app architecture and smarter UI coding.

Optimize

Minification

The process of removing all unnecessary characters from source code without changing its functionality

GruntJS is a nodejs based JavaScript Task Runner. The Contrib-Uglify plugin allows you to do minification.

Minifying is helpful, but removing code that is only used for debugging is also crucial in delivering only required JavaScript to the browser.

Conditional Compilation

You can use GruntJS and UglifyJS to accomplish conditional compiling:

if (typeof DEBUG === undefined) DEBUG = true; //Force

DEBUG && console.log('some info');
if (DEBUG) {
	console.log('Other info');
}

function foo() {
	console.log('initial');
}
foo();
//gruntfile.js
...
uglify: {
    options: {
        compress: {
            global_defs: {
                DEBUG: false
            },
            dead_code: true //remove code never called
        }
    },
    ...
}

Composition

You can easily minify to separate JavaScript modules using GruntJS + UglifyJS.

LazyLoadJS allows you to lazy load javascript. It works well with frameworks that already handle dependencies like AngularJS:

// main.js
// include lazyload.js

$('#loadButton').on('click', function () {
	LazyLoad.js(['js/build/module1.min.js'], function () {
		// Now module is loaded
	});
});

RequireJS mixes script loading and dependency management. Scripts needed are defined in the html page as usual but will not be loaded until they are required.

// index.html
...
<script src="js/vendor.require.min.js"
    data-main="js/main"</script>

//main.js (or main.min.js)
require(["myModule"], function (myModule) {
    //use myModule
});

//myModule.js
define([], function () { //module pattern
    ...

    return {
        cache: _cache
    }
});

Testable JavaScript

Why is testing JS hard?

Types of tests

Testability in JS is typically relative to the amount of effort you put into creating maintainable and scalable code. Good separation and modularization of your code makes it more testable.

If you can unit test it's a good indicator that you have a good structure or architecture in your project.

Unit Testing

A method by which individual units of source code together with associated control data, usage procedures, and operating procedures are tested to determine if they are fit for use.

A few useful links