Bradley Braithwaite
1 import {
2 Learning,
3 JavaScript,
4 AngularJS
5 } from 'Brad on Code.com'
6 |
Check out my online course: AngularJS Unit Testing in-depth with ngMock.

Running a Node.js Library on the Front-End with Browserify

Learn how to make a server side node.js library work on the client using Browserify.

31 Mar 2015 Free Software Tutorials by Bradley Braithwaite

One of the tenets of writing Server-Side JavaScript is that the code can be written once and re-used on the server or browser. How practical is that? This post details how I took a small node.js library and made it available as a browser package using Browserify, Bower, and a little patience.

Using a simple example library

Recently I wrote a client library for the OMDb API with node.js. I chose to do this to demonstrate how to create a simple library, that does one thing well, with unit tests, that could be made into an NPM package.

The library provides a simple client for the get and search functionality offered by the OMDb API. It has some paramter validation and makes the necessary HTTP requests to the API endpoints.

Being a good internet citizen, I extracted the code into an NPM package for all to use and shared the code on Github. The package worked well and the Node devs are happy, but what about the front-end? How can I take the node.js code I wrote and have it run in a browser?

Node.js developers ‘require’ the module and use it as follows:

var omdbApi = require('omdb-client');

var params = {
	title: 'Terminator',
	year: 2012
}
omdbApi.get(params, function(err, data) {
	// process response...
});

I wanted to support the following scenario for the front-end, ideally without having to make too many changes to the code I had already written:

<html>
	<body>
		<div id="search"></div>
		<script type="text/javascript" src="omdb-client.min.js"></script>
		<script type="text/javascript">
			// the omdb would be made available globally, for the simplest use-case
			omdb.search({
				query: 'Terminator',
				year: 1984
			}, function handleResponse(err, data) { 
				var el = document.getElementById('search');
				if (err) {
					el.innerHTML = err;
				} else {
					el.innerHTML = JSON.stringify(data);
				}
			});
		</script>
	</body>
</hmtl>

Using Browserify to make Server-Side code run on the Front-End

Browserify lets you require(‘modules’) in the browser by bundling up all of your dependencies. Browsers don’t have the require method defined, but Node.js does. With Browserify you can write code that uses require in the same way that you would use it in Node. This is exactly what I need to take the Node code and package it up for use within the browser.

I used the Browserify for Node package to do this. It can be installed via:

$ npm install -g browserify

The entry point for the npm package is the index.js file in the root, which in turn exposes the modules in the /lib folder. Here’s the code for index.js:

module.exports.get = require('./lib/get');
module.exports.search = require('./lib/search');

Browserify can start with this file and work out what other dependencies need to be bundled into the output file.

The browserify command to do this is:

$ browserify index.js > target.js --standalone 'omdb'

The –standalone option provides a name for which the bundle is made available in the browser i.e. window.omdb.get(…). It generates a UMD bundle for the supplied export name (‘omdb’ in this example). This bundle works with other module systems and sets the name given as a window global if no module system is found.

The files are correctly bundled, but the target file is now ~7,000 lines long (gist for reference). Considering the logic for this library is < 400 lines long, where did all this extra code come from?

To dig into what’s happening, let’s review the get function from the library:

// 3rd party lib for making JSON HTTP get requests
var http = require('json-http');

// other code that's here omitted for brevity.

/**
 * Get film content from the imdb http service.
 *
 * @param {Object} params
 * @param {Function} callback
 * @api public
 */
module.exports = function(params, callback) {
  
  var validate = _validateParams(params);
  var timeout = (params) ? params.timeout || 10000 : 10000;

  if (validate) {
    callback(validate.error, null);  
    return;
  }

  http.getJson(_createUrl(params), timeout, function handleResponse(err, data) {
    
    if (err) {
      callback(err, null); 
      return;
    }

    if (data.Error) {
      callback(data.Error, null);  
    } else {
      callback(null, data); 
    }

  });
};

The ‘json-http’ module is a 3rd party library (which I also wrote) that only performs an HTTP get request using JSON. Here is the code for the ‘json-http’ module:

'use strict';

/**
 * Module dependencies.
 */

var http = require('http');

/**
 * Simple wrapper for JSON HTTP get request.
 *
 * @param {string} The url to call
 * @param {number} an (optional) http timeout value
 * @param {function} the callback function
 * @api public
 */
module.exports = function() {
  
  var url = arguments[0];
  var timeoutInMs = 10000;
  var callback;

  if (typeof(arguments[1]) === 'function') {
    callback = arguments[1]; 
  }

  if (typeof(arguments[1]) === 'number') {
    timeoutInMs = arguments[1];
    callback = arguments[2];
  }

  var request = http.get(url, function handleResponse(res) {

    var body = '';
    res.setEncoding('utf8');

    res.on('data', function(chunk) {
      body += chunk;
    });

    res.on('end', function() {
      var response = JSON.parse(body);
      callback(null, response);
    });

  }).on('error', function(e) {
    callback(e, null);
  });

  // if the request takes longer than 1 second, exit
  request.setTimeout(timeoutInMs, function handleTimeout() {
    callback('timeout exceeded', null);
  });

};

The Browserify tool very cleverly understands that to run the get function, it also needs the ‘json-http’ module, which in turn needs the node.js HTTP module, and so on. The browser doesn’t have an equivalent Node.js HTTP module so Browserify includes one which is where the additional code comes from. You can see a full list of the Node.js to browser shims on the Browserify project README.

It’s not perfect, but with minor tweaks (to fix this problem and this one) I was able to run the code in the browser.

So, back to the ~7k lines of JavaScript! Considering that I only want to make a simple HTTP get request, do I really need the full HTTP node.js implementation? I like to keep my websites fast so the ~7k lines of JavaScript (albeit un-minfied) was not ideal. The solution was to write a front-end version of ‘json-http’. Here’s the code:

module.exports.getJson = function() {  
  var xhr = new XMLHttpRequest();
  var url = arguments[0];
  var timeoutInMs = 10000;
  var callback;

  if (typeof(arguments[1]) === 'function') {
    callback = arguments[1]; 
  }

  if (typeof(arguments[1]) === 'number') {
    timeoutInMs = arguments[1];
    callback = arguments[2];
  }

  xhr.onreadystatechange = function() {
    this.timeout = timeoutInMs;
    if (this.readyState === 4) {
      if (this.status === 200) {
        callback(null, this.response);
      } else {
        // timeouts are handled by ontimeout callback
        if (this.status !== 0) { 
          callback(this);
        }
      }
    }
  };
  xhr.open('GET', url);
  xhr.responseType = 'json';
  xhr.timeout = timeoutInMs;
  xhr.ontimeout = callback;
  xhr.send();
};

Now I need a way of telling Browserify to use a specific version of the ‘json-http’ module. I did this by using the aliasify tool. Install this via:

$ npm install aliasify --save-dev 

Then I added the following to the package.json to map the module ‘json-http’ to the new, front-end specific version:

{
	"browserify": {
    "transform": [ "aliasify" ]
  },
  "aliasify": {
    "aliases": {
      "json-http": "./client/json-http.js"
    }
  }
}

The output file after re-running the browserify command is now less than <400 lines long (un-minified).

Using Browserify and Grunt

To run the bundling as a grunt task, see the grunt-browserify package. It can be installed via:

$ npm install grunt-browserify --save-dev

See the grunt configuration file from the OMDB project to reference the browserify and minification configuration.

Bower and Front-End Package Management

Now that the bundling and minification is taken care of, the next step is to make the package easily available for front-end devs. At the time of writing, Bower is the most commonly used package manager for front-end code (with NPM being the common choice for the server-side).

You can reference the creating packages documentation from the bower website or see the simple setup instructions that follow.

To get started, first install bower:

$ npm install -g bower

Next run the initialisation command to create a default bower.json file for the project:

$ bower init

Tagging and Versioning

Versioning with bower differs a little from NPM. You can install from the master branch of Github via:

$ bower install https://github.com/bbraithwaite/omdb-client.git

But it’s better to use tags for versioning, so that specific versions can be requested:

$ bower install https://github.com/bbraithwaite/omdb-client/releases/tag/v1.0.4

You can create a Github tag as follows:

$ git tag -a v1.0.0 -m 'version 1.0.0 stable'
$ git push --tags

Note: Bower uses semver versioning and the tag number should correspond to the version in the bower.json file.

Publishing the Package

To make the package publicly available it needs to be registered with Bower. The command to register the package is:

bower register omdb-client https://github.com/bbraithwaite/omdb-client.git

Once published it can be installed via the given package name:

bower install omdb-client

To publish new releases of the package, you just need to update the version number in the bower.json file and create a new Github tag that matches the version number e.g. 1.0.1.

For more depth on creating bower packages, reference this excellent how-to on publishing bower packages.

Summary

Lessons to be taken away from this exercise it that this process isn’t flawless, and in some cases it would be preferable to create custom abstractions for functionality that relies on node.js modules that are not available in the browser. If you are writing node.js code with an idea that one day it may be used in the browser, carefully consider native node.js module dependencies and how they are used across the application.

Source Code

The source code and packages of the omdb-client example can be found at:

SHARE
Don't miss out on the free technical content:

Subscribe to Updates

CONNECT WITH BRADLEY

Bradley Braithwaite Software Blog Bradley Braithwaite is a software engineer who works for search engine start-ups. He is a published author at pluralsight.com. He writes about software development practices, JavaScript, AngularJS and Node.js via his website . Find out more about Brad. Find him via: