Let's Make a Framework: Node and Modules

2011-07-21 00:00:00 +0100 by Alex R. Young
*Let's Make a Framework* is an ongoing series about building a JavaScript framework from the ground up. These articles are tagged with [lmaf](http://dailyjs.com/tags.html#lmaf). The project we're creating is called [Turing](http://github.com/alexyoung/turing.js). Documentation is available at [turingjs.com](http://turingjs.com/).


There's a growing collection of Node modules that started life as
front-end frameworks but have found a new lease of life on the
server-side through Node and npm. A good example is
Underscore which includes many functional-style methods that work well in both environments.

There are different ways to make a project like Turing available to
CommonJS environments. Some front-end code might work best as modules
for Connect or
Express -- imagine HTTP middleware that automatically enhances features using server-side code (nib/stylus do
this to great effect).

Turing has components that would suit distribution through npm, but
other parts of the framework are clearly browser-specific. Some Node
modules actually make jQuery available for conveniently querying markup
using selectors. This is incredibly useful, particularly for testing or
screen scraping.

That gives us three possible avenues for exploration:

  1. Allow people to require modules that provide features suitable
    for Node. For example: functional helpers, promises, enumerable
  2. Provide a version of the DOM code that can be used outside of a
  3. Look into what Connect middleware could be created

Supporting CommonJS Modules

Underscore (GitHub: documentcloud / underscore,
License, npm: underscore) from DocumentCloud has a simple package.json that is
used to distribute the project through npm:

  "name" : "underscore",
  "description" : "JavaScript's functional programming helper library.",
  "homepage" : "http://documentcloud.github.com/underscore/",
  "keywords" : ["util", "functional", "server", "client", "browser"],
  "author" : "Jeremy Ashkenas ",
  "contributors" : [],
  "dependencies" : [],
  "repository" : {"type": "git", "url": "git://github.com/documentcloud/underscore.git"},
  "main" : "underscore.js",
  "version" : "1.1.7"

The main property points at the same file used by browsers.
How does this work? Underscore needs to be exported as a CommonJS

// Export the Underscore object for **CommonJS**, with backwards-compatibility
// for the old `require()` API. If we're not in CommonJS, add `_` to the
// global object.
if (typeof module !== 'undefined' && module.exports) {
  module.exports = _;
  _._ = _;
} else {
  // Exported as a string, for Closure Compiler "advanced" mode.
  root['_'] = _;

Setting a value to module.exports like this will
effectively make it return when using require(). Make a
file called a.js:

module.exports = function() {
  return 'Hello from a';

Now load node and require it:

$ node
> var a = require('./a');
> a()
'Hello from a'

Backbone does things slightly differently:

var Backbone;
if (typeof exports !== 'undefined') {
  Backbone = exports;
} else {
  Backbone = root.Backbone = {};

The difference here is Backbone exports an Object rather than an
instance of something. This is documented in Node's Modules

Wrapping DOM Code

Supporting client-side JavaScript in an environment like Node is far
from trivial. Fortunately there's an extremely popular solution in the
form of JSDOM (GitHub: tmpvar /
License, npm: jsdom) by Elijah Insua. This is a fully native JavaScript implementation of the W3C DOM.

Most projects use it by running jsdom.env over some HTML,
but it doesn't just parse HTML, it'll load scripts from URLs or files:

var jsdom = require('jsdom');

  // Some HTML

Amazing, right? Are you reconsidering your career now you can apply your
client-side skills straight to the server?

Using JSDOM will allow us to offer most (if not all) of Turing's
functionality in some form on the server.

Connect Middleware

Connect modules take this form:

module.exports = function myModule() {
  return function(req, res, next) {
    if (some condition) {
    } else {
      // End the request

This is a function that returns a function that accepts the standard
request and response objects found in all Express (and Connect-based)
apps. The next parameter is called when the request should
continue on to other middleware.

What can we do with this with a client-side framework? If Turing
included something like Raphaƫl it could
generate bitmap versions of SVG graphics. Or maybe it could simply load
the client-side JavaScript so people can use npm to manage Turing rather
than manually downloading the scripts.


Client-side JavaScript is often suitable for use by server-side code.
Also, in some cases it's desirable to run DOM code outside of a browser,
particularly for creating fast unit tests.

Next week I'll explain how to apply some of these techniques.