In Today's Browser-Based Web Applications
Michael K Snead
!function() {
}();
Part 1
This is the "bare minimum". Avoiding global variables and namespace collisions.
//Wrap all my code in a closure
!function() {
function doWork() {
}
$(function() {
//Do something on DOM ready
});
}();
!function($, context) { //alias jQuery as a function argument
function doWork() {
}
$(function() {
//Do something on DOM ready
});
}(jQuery, window);
//You don't have to do things exactly like this, but...
//kitchen.fridge.js
!function($, kitchen) {
kitchen.fridge = kitchen.fridge || {}; //don't clobber shared namespaces
kitchen.fridge.storeFood = function storeFood(food) {
//store some food
}
$(function() {
//Do something on DOM ready
});
}(jQuery, window.kitchen = window.kitchen || {});
//kitchen.stove.js
!function($, kitchen) {
kitchen.stove = kitchen.stove || {}; //don't clobber shared namespaces
kitchen.stove.cookFood = function cookFood(food) {
//store some food
}
$(function() {
//Do something on DOM ready
});
}(jQuery, window.kitchen = window.kitchen || {});
//You don't have to do things exactly like this, but...
//kitchen.stove.js
var kitchen = window.kitchen || {};
var kitchen.stove = (function($, kitchen) {
function cookFood(food) {
//cook some food
}
function clean() {
//run cleaning routine
}
$(function() {
//Do something on DOM ready
});
//revealing module pattern - everything "public" is right here
return {
cookFood: cookFood,
clean: clean
}
})(jQuery, kitchen);
No compile-time checking if dependencies are missing.
If dependencies are loaded out of order, it could break something.
Ceremony (extra horizontal spacing, wrapping code for closure).
Sharing namespaces is possible, but clashing/overwriting is still possible too.
Sharing between modules still requires a global.
Part 2
Instead of modules living on top of namespaces, each module has a name.
Module names are either explicit (define calls), or implicit (the name of the file).
//kitchen.stove.js - module name is implicit
define([ 'jquery' ],
function($) {
//Your IDE will probably push this farther ->
function cookFood(food) {
//cook some food
}
function clean() {
//run cleaning routine
}
$(function() {
//do something at DOM ready
});
//"Revealing module pattern" ... everything "public" is right here:
return {
cookFood: cookFood,
clean: clean
};
});
//app.js - it only has a "require", so no module is exported
// require'ing this file will execute the code and just not return anything.
require([ 'scripts/kitchen.stove' ],
function(stove) {
stove.cookFood(somefood);
stove.clean();
});
Also, though require.js is solid and has been used in popular frameworks, AMD is not the future of JavaScript modules.
Part 3
Works with IIFE's, but require.js shouldn't be used alongside these methods.
Browserify and webpack allow you to write modules in AMD or CJS style.
Combine with babel (ES6 transpiler) and you can even use ES6 style modules which transpile down to CJS.
These tools use static analysis to determine which dependencies are required.
//This is the same as writing a node.js application
//baz.js
var foo = require('./foo.js'); //paths are relative to the filesystem, not the routing module
if(needsBar) {
//bar will already be loaded into memory prior to this
//(also, hoisting, ya'll!)
var bar = require('libraries/bar'); //assumes .js extension
}
//This isn't global, because all modules will be loaded in their own function scope
function bazzify() {
//baz some stuff
}
//Module names are implicit
module.exports = {
bazzify: bazzify
};
//app.js
var bazz = require('./baz.js');
bazz.bazzify();
//This is the same as writing a node.js application
//baz.js
//es6 says all 'import' statements must appear at top level!
import foo from './foo.js';
import bar from 'libraries/bar'; //assumes .js extension
function bazzify() {
//baz some stuff
}
//we have options here. We could export just the function.
//I chose to export an object that has a function.
export default {
bazzify: bazzify
}
//app.js
import bazz from './baz';
bazz.bazzify();
Also, babel + browserify/etc. will happily use both require() and ES6 simultaneously, but I suggest you stick with one.
There are modules/methods to break up your app into separate packages/bundles (ie: vendor vs. app code).
Similarly you can load some dependencies asynchronously if you really want/need to, but it wont enjoy compile-time checking. The most basic way to do this is through jQuery's getScript() or a library like $script.js.
Can build-on-save just like TypeScript/CoffeeScript using gulp. Gulp integration through Task Runner Explorer (2013), OOB in 2015