Modular
Javascript:
Today
September, 2013
Espen Hovlandsdal
Who?
Espen Hovlandsdal
What?
Modular JS.
"Modularity is the degree to which a system's components may be separated and recombined."
What does this mean for JS?
Stop doing this:
$(document).ready(function(){
$("ul.menu li span").click(function() {
$(this).parent().find("ul.level2").slideToggle('slow').show();
});
startAutoRefresh();
document.cookie = 'hasvisited=1';
$('.ad').each(function() {
OAS_AD($(this).attr('data-pos'));
});
$.getJSON('some.thing', function(res) {
$('.random-dom').text(res.numDays);
});
// [...]
});
Instead; write modules.
or...
-
Components
-
Packages
-
Classes
Write programs that do one thing and do it well. Write programs to work together.
- Doug McIlroy
Write modules that do one thing and do it well. Write modules to work together.
- Me
Benefits:
- Reuse of code!
- Separation of concerns
- Easier testing
- Packaging and distribution
- Maintainability
Separation of concerns
Web developers (should) already know this!
- HTML
- CSS
- Javascript
Is this the best we can do?
// app-specific code...
if (parseInt(document.cookie.replace(/(?:(?:^|.*;\s*)numVisits\s*\=\s*([^;]*).*$)|^.*$/, "$1"), 10) == 0) {
// first visit
$('.annoying-we-have-app-dialog')
.removeClass('hidden')
.addClass('annoying-slide-in-effect');
document.cookie = 'numVisits=1; path=/; domain=.your-domain.com';
}
// More app-specific code
Does every app need solve cookie-reading?
... and implement the same (or different) bugs?
NO.
Write it once.
Test it.
Reuse it.
Better?
var Cookies = require('cookie-module')
, appPrompt = require('app-prompt');
var numVisits = Cookies.get('numVisits');
if (numVisits == 0) { // First visit
appPrompt.show();
}
Cookies.set('numVisits', ++numVisits, {
path: '/',
domain: '.your-domain.com'
});
Test it!
With modularity comes ease of testing.
var Cookie = {
set: function(name, value) {
document.cookie = name + '=' + escape(value);
},
get: function(name) {
var regex = new RegExp(
"(?:(?:^|.*;\\s*)" + name
+ "\\s*\\=\\s*([^;]*).*$)|^.*$"
);
return unescape(document.cookie.replace(regex, '$1'));
}
};
Cookie.set('foo', 'bar');
assert.equal(Cookie.get('foo'), 'bar');
Distribution
- Put a version number on that module
- Distribute it using a package manager
- There are plenty to choose from!
CommonJS
A group with a goal of building up the JavaScript ecosystem for web servers, desktop, command line apps and the browser.- CommonJS.org
AMD
(Asynchronous Module Definition)
define(['cookies'], function(Cookies) {
var cookieName = 'numVisits';
return {
getNumberOfVisits: function() {
var visits = parseInt(Cookies.get(cookieName), 10);
return isNaN(visits) ? 0 : visits;
},
isFirstVisit: function() {
return this.getNumberOfVisits() === 0;
},
incrementVisitCount: function() {
var numVisits = this.getNumberOfVisits() + 1;
Cookies.set(cookieName, numVisits);
return numVisits;
}
};
});
AMD (cont.)
- Works in the browser today
- Require.JS
- curljs
- Kinda sorta almost works in Node.JS
- Asynchronous!
- Widely adopted today
- jQuery
- (even internally, as of 1.11/2.1 betas)
-
r.js can build single-file package for production
CommonJS modules
var Cookies = require('cookies')
, cookieName = 'numVisits';
exports.getNumberOfVisits = function() {
var visits = parseInt(Cookies.get(cookieName), 10);
return isNaN(visits) ? 0 : visits;
};
exports.isFirstVisit = function() {
return this.getNumberOfVisits() === 0;
};
exports.incrementVisitCount = function() {
var numVisits = this.getNumberOfVisits() + 1;
Cookies.set(cookieName, numVisits);
return numVisits;
};
CommonJS modules (cont.)
- In Node.JS: Works flawlessly, today.
- In the browser: Not today (well...)
-
- Arguably prettier API
- Synchronous requires
- Build tool for CommonJS packages/apps
-
- Specify entry point
- Walks dependency graph
- Generates single-file package
-
- Adds polyfills for CommonJS/node modules
- querystring
- http
- etc.
- What about development?
-
Auto-package on change
- Browserify modules on request
- Always building: Build works.
-
- What about debugging?
- Sourcemaps through --debug
Harmony Modules
import { set as setCookie, get as getCookie } from 'cookies';
export var getNumberOfVisits = function() {
var visits = parseInt(getCookie(cookieName), 10);
return isNaN(visits) ? 0 : visits;
};
export var isFirstVisit = function() {
return this.getNumberOfVisits() === 0;
};
export var incrementVisitCount = function() {
var numVisits = this.getNumberOfVisits() + 1;
setCookie(cookieName, numVisits);
return numVisits;
};
Harmony Modules (cont.)
- Does not work in any browsers
- Does not work in Node.JS
- Even with --harmony
- Is the module definition stabilized yet?
- Give it a go, use:
- grunt-es6-module-transpiler
- grunt-traceur
- Transpiles to CommonJS or AMD
CoffeeScript? TypeScript?
- Both can build to AMD
- You are already building...
UMD
Universal Module Definition
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['cookies'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('cookies'));
} else {
root.VisitCounter = factory(root.Cookies);
}
}(this, function (Cookies) {
var cookieName = 'numVisits';
return {
getNumberOfVisits: function() { /* ... */ },
isFirstVisit: function() { /* ... */ },
incrementVisitCount: function() { /* ... */ }
};
}));
UMD (cont).
- Pros:
- Works in node (CommonJS)
- Works in the browser (AMD)
-
Cons:
- Boilerplate code (build step?)
- Less readable
- Many variations
- check umdjs on github
Managing modules
- Package managers are great
- Search before you create
- Easy to share between projects
- Consider open-sourcing modules
- Lock onto specific versions
- Avoids breaking API changes
What's the best format?
Well....
- Browserify (with UMD) for non-DOM modules
- (Arguably) easier to write node/browser
- AMD for applications
- Speed of development (no build step)
- Easier to set up for multiple developers
- Make modules, don't worry about format
The Future™
- ES6: Not in browsers anytime soon
- Guess we are stuck with AMD/CommonJS
- Is that really a bad thing?
- Does it really bring much to the table?
Questions?
- Feedback?
- Let me know!
Modular Javascript, today (2013)
By Espen Hovlandsdal
Modular Javascript, today (2013)
- 1,666