PhoneGap/Cordova is enabling the development of mobile applications that are written using web technologies, but these applications aren’t just mobile websites; they’re fully fledged mobile applications that can leverage on native device functionality and provide smooth hardware accelerated animations using CSS3 for a native-like experience. With the advent of Node.js server-side JavaScript became a realistic, scalable option and also made structuring JavaScript far easier than we were used to in previous environments.

Sharing similar environments on client and cloud enables us to share code between both, and you’ll be surprised to see just how easy this sharing can be.

Using CommonJS design patterns and one of my favourite tools, Browserify, makes it easy to share code. If you’re skeptical then check out these projects I wrote that work on the client and server with little or no environment detection required:

  • Hype JS API - Get track lists, album art and keys required to play tracks
  • Vec2D (Vector2D) - 2D Vector library focused on flexible performance
  • fhlog - Logger with a consistent interface on client and cloud.
  • safejson - Safely parse JSON via callbacks

Benefits

So how does this sharing really benefit you?

  • Shared, consistent APIs on client and cloud
  • NPM can be used for dependencies on client and cloud
  • A single file will be created meaning less load on your website if you Browserify your JS.
  • Easily share business logic between client and cloud.
  • Remain in a consistent programming paradigm

The How

Prequisites

You’ll need Node.js, NPM and Browserify installed to accomplish this. NPM comes bundled with all recent version of Node.js so just download Node.js if you don’t already have it. To install Browserify just run the following command:

$ npm i browserify

Development

There’s not much to cover here if you know how Node.js’ CommonJS module system operates. You write your JavaScript in the usual Node.js style and can then run your code on the server just like you’re used to doing, but the real idea here is to make these operable on a client browser!

To run your module in a browser you’ll need to Browserify it. This is the easy part! As an example assume we wrote a module called sqaured and want to run it in a browser.

Here’s the module code:

/**
 * file: squared.js
 * Takes a number input and returns a squared result
 */

module.exports = function (num) {
  return Math.pow(num, 2);
};

And here’s how we can package it for a browser:

$ browserify -e ./lib/sqaured.js -s sqaure -o ./browser/sqaure.js

So what does the above command do? It takes an entry point (-e) and creates a standalone module (-s) with the name sqaure which is bound to the window variable and writes the resulting code (-o) to ./browser/sqaure.js.

Here’s how we could use it:

window.sqaure(2);

Any function that is bound to the exports object in your entry script (-e) is made accessible on the window object.

What about core Node.js modules?

So you’re probably wondering how things like require(‘http’) or require(‘util’) work if you’re running in the browser and what about UDP (dgram) and TCP (net). Right? Well that’s easy enough. Where possible Browserify will provide the original module, for example util, querystring, assert and events. In the case of others a shim is applied if available but naturally browsers can’t send UDP packets willy nilly and open TCP sockets (although WebSockets exist!). Take a look here to see shims vs inclusion of the standard Node.js core module.

So. How can I handle missing native modules?

When writing modules that will be shared across the client and cloud you need to change very little in your coding practices as demonstrated. One area that will need to change is accessing native functions such as HTTP.

You need to write a module with a common interface for accessing HTTP or whatever non-standard functionality you require. You should probably do this for most projects anyway to avoid code duplication. Because this module has a common interface you simply swap out the implementations based on environment, and this swapping won’t require extra coding, which is pretty sweet.

Below is an example of how this can be handled using Browserify.

Node.js HTTP abstraction:

/**
 * file: http-wrapper.js
 * This file will be used for HTTP when
 * running in a node env.
 */

var request = require('request');

exports.get = function (url, callback) {
  request.get(url, callback);
};

Browser HTTP abstraction:

/**
 * file: http-wrapper-browser.js
 * This file will be used for HTTP in browsers
 */

var xhr = require('xhr');

exports.get = function (url, callback) {
  xhr(
    {
      method: 'GET',
      url: url
    },
    callback
  );
};

So how do you use your HTTP abstraction? The way you always do, require it!

/**
 * file: index.js
 */

var http = require('./http-wrapper.js');

function onResponse (err, res, body) {
  if (err) {
    console.error('Oh no!');
  } else {
    console.log(body);
  }
}

http.get('http://www.google.com', onResponse);

You may have noticed we used the Node.js HTTP wrapper in index.js in the above example. We do this as the default should always be to use the Node.js version. When Browserifying this module we can substitute the Node.js wrapper with the browser version by adding a configuration in the package.json of this project using the browser field. The browser field below is read when Browserify is bundling our project and replaces all requires for http-wrapper.js with http-wrapper-browser.js. Now that’s pretty darn cool.

{
  "name": "GoogleGetter",
  "version": "0.0.0",
  "description": "A simple module sharing example",
  "main": "./index.js",
  "author": "Evan Shortiss",
  "license": "MIT",
  "browser": {
    "./http-wrapper.js": "./http-wrapper-browser.js"
  },
  "dependencies": {
    "xhr": "*",
    "request": "*"
  }
}

Now we can Browserify this module and use it in either environment!

If you’re really sharp you might say “Hey! xhr and request share a very similar API. Why not just have one http file and replace request with xhr?” Well. You’re right! We could have written a single http.js like so:

/**
 * file: http.js
 */

var request = require('request');

exports.get = function (url, callback) {
  request({
    method: 'GET',
    url: url
  }, callback);
};

With this all we need to do is update the package.json to have the following browser field, but for the sake of the example it seemed appropriate to assume the APIs would be different.

{
  "browser": {
    "request": "xhr"
  }
}

Using Client Modules via NPM

If you published this module to NPM and wanted to use it in another client-side project you wouldn’t need to target the Browserified build. Instead, Browserify will be clever enough to also build this module despite the fact it’s a dependency.

Go forth and Browserify!