How to Make Simple Node.js Modules Work in the Browser

Node.js is all about writing small, simple modules that do one thing, and do it well. This can be taken to extremes by crazy people. There’s even a module for multi-line strings!

Some people can’t resist writing frameworks either. This post is not for them.

Actually, it is. I wrote the first version of Seneca, my micro-services framework, suffering from Java withdrawal. It’s too big and needs to be refactored into small modules. That’s what I’m doing now. As it happens some of these modules seem like they could be useful on the front-end, so I decided to see if I could make them browser compatible.

Now, the right way to do this is to use browserify. But where there’s a right way, there’s always a wrong way too. This is it. Sometimes, you just need things to be standalone. It’s not every coder that’s lucky enough to use Node.js, you know.

Instead of reinventing the wheel, I went straight to my number one module crush, underscore, the utility belt of JavaScript. I’m so much in love with this module I automatically bang out npm install underscore at the start of a new project. It feels like part of the language. Underscore started out as just a browser library, and then had to support Node.js. What you have here is exactly what you need:

  • Main file has to work in the browser, and be a Node module;
  • Tests have to be run in the browser, and for Node;
  • You need to provide a minified version;
  • Good choices (one assumes!) for third party tools to do all this.

Go take a look at the github repo. You’re going to copy the approach. This post just documents it so you don’t have to figure it out for yourself.

Let’s start with the final product. Here are three small modules, each of which can run in the browser or Node. They can be installed with npm, or bower.

  • jsonic – A JSON parser for Node.js that isn’t strict
  • gex – Glob Expressions for JavaScript
  • patrun – A fast pattern matcher on JavaScript object properties.

You can use any of these as a template. They all follow the same approach. Here’s what to do.

Browserize/Nodize your Code

You may be starting from either a Node module, or a browser library. In both cases, you need to wrap your code up into a single file. Assuming you’re calling your module mymodule, here’s the start of that file, which you’ll call mymodule.js:

"use strict";

(function() {
  var root = this
  var previous_mymodule = root.mymodule

Let’s dissect the code. You should always “use strict”;. No excuses.

Oh. as an aside, I’m an inveterate avoider of semi-colons. Why can’t we all just, …, get along?

The (function() { isolates your code from anything else in the browser, and has no effect on the Node module. You’re going to call this anonymous function with the current context at the end of the module, which in the browser makes this equal to the window object:

}).call(this);

OK, back to the top. The next thing you’re doing is storing the context (this) in a variable name root, and keeping a reference to any previous variable with the same name as your module. This lets you provide the noConflict feature, like jQuery.

Your module is either going to be a JavaScript object or function (not much point otherwise!). Define it like so, inside the isolation function:

var mymodule = function() {
  ...
}

mymodule.noConflict = function() {
  root.mymodule = previous_mymodule
  return mymodule
}

Now users of your module, in the brower, can call noConflict to avoid conflicts over the mymodule name:

var othername = mymodule.noConflict()
// the variable mymodule is back to its old value from here

How do you export your module into the browser? Or for Node.js? By checking for the existence of module.exports, and acting accordingly. Again, this goes inside your isolation function.

  if( typeof exports !== 'undefined' ) {
    if( typeof module !== 'undefined' && module.exports ) {
      exports = module.exports = mymodule
    }
    exports.mymodule = mymodule
  } 
  else {
    root.mymodule = mymodule
  }

If you’re in a Node.js context, you’ll end up exporting in the normal way: module.exports = mymodule. If you’re in the browser, you’ll end up setting the mymodule property on the window object.

There’s one final niggle. What if you depend on other modules, such as, say, underscore? Place this near the top of your isolation function:

  var has_require = typeof require !== 'undefined'

  var _ = root._

  if( typeof _ === 'undefined' ) {
    if( has_require ) {
      _ = require('underscore')
    }
    else throw new Error('mymodule requires underscore, see http://underscorejs.org');
  }

This code checks for the existence of the require function, and uses require if it exists. Otherwise it assumes you’ve loaded the dependency via a script tag (or a build process), and complains with an exception if the dependency can’t be found.

That’s pretty much all you need on the code front. Check out the source code of the jsonic, gex, and patrun modules on github for real-world examples.

Also, don’t forget to do

npm init

if your project needs a package.json file for Node.

Test Your Code

The jasmine framework is the one to use here. It works nicely both for Node and the browser. Create a test folder and write a mymodule.spec.js file containing your tests:

if( typeof mymodule === 'undefined' ) {
  var mymodule = require('..')
}

describe('mymodule', function(){

  it('something must be done', function(){
    expect( mymodule() ).toBe( 'doing something' )
  })

})

The test file should check to see if the module itself needs to be required in. This is for the Node case, where the module package.json is in the parent folder. In the browser case, you’ll load the module using a script tag before you load this test file, so it’s already defined.

It’s a good idea to get your project built automatically by the Travis-CI service. Follow the Travis-CI instructions to get this working. You’ll need to ensure that Travis can run the jasmine tests, so you’ll need to do

$ npm install jasmine-node --save-dev

This makes sure that jasmine get’s installed locally inside the projects node_modules folder. Which means that in your package.json, you can say:

  "scripts": {
    "test": "./node_modules/.bin/jasmine-node ./test",
  }

and then the command line

npm test

will run your jasmine tests for you, and they will be run by Travis-CI for each commit, which is what you really want.

This setup only tests your code in a Node.js context. Now you need to test in a browser context too. Actually, you need two types of browser test. One you can automate, and one you can run manually in browsers on your machine. The automated test comes courtesy of phantomjs, which is a “headless” browser environment (there’s no visible GUI). This runs your tests in a WebKit browser engine, so it’s pretty much like testing in Chrome or Safari (to a first approximation).

Install phantomjs globally. You don’t need a local copy, as you don’t need it for Travis-CI, and you’ll probably use it for other projects too:

$ sudo npm install phantomjs@1.9.1-0 -g

(At the time of writing, Sep 2013, the latest version of phantomjs does not install due to a bug, so I’m explicitly specifying a known-good version.)

To actually run the tests, you’ll need some supporting infrastructure. This consists of the jasmine library code, a “runner” script, and a HTML file to load it all. Your best bet is to copy the files from one of my example repos (see above). The files are:

  • test/jasmine.html
  • test/run-jasmine.js
  • test/jasmine-1.3.1/*

In jasmine.html, you load up everything you need, and execute the test using the jasmine API. It’s all boilerplage, just replace mymodule anywhere it occurs with the name of your module. If you have additional dependencies, add them under the script tag that loads underscore (this assumes you’ve done an npm install underscore –save already):

<!DOCTYPE html>
<html>
<head>
  <title>mymodulejasmine test</title>

  <link href="jasmine-1.3.1/jasmine.css" rel="stylesheet">

  <script src="../node_modules/underscore/underscore.js"></script>

  <script src="../mymodule.js"></script>

  <script src="jasmine-1.3.1/jasmine.js"></script>
  <script src="jasmine-1.3.1/jasmine-html.js"></script>

  <script src="mymodule.spec.js"></script>

  <script type="text/javascript">
    (function() {
      var jasmineEnv = jasmine.getEnv();

      ... jasmine boilerplate ... 

      function execJasmine() {
        jasmineEnv.execute();
      }

    })();
  </script>
</head>
<body></body>
</html>

Now you add a custom script to your package.json:

  "scripts": {
    "test": "./node_modules/.bin/jasmine-node ./test",
    "browser": "phantomjs test/run-jasmine.js test/jasmine.html",
   }

And run it with:

$ npm run-script browser

And your tests should execute as before.

To test in a real browser, just load the test/jasmine.html file directly. This will execute the tests immediately and display some nicely formatted results.

Publish your Module

Time to inflict your code on the world! Muhaha, etc.

First, you’ll want to minify it for use in browsers. Use uglifyjs2 for this. Note: use version 2! Again, no need to install this locally, as it’s a development tool you’ll want to use for other projects:

sudo npm install uglify-js -g

And then in your package.json:

  "scripts": {
    ...
    "build": "uglifyjs mymodule.js -c \"evaluate=false\" -m --source-map mymodule-min.map -o mymodule-min.js"
  },

This generates mymodule-min.js, and also mymodule-min.map, a source map, which makes debugging in the browser easier.

Now you’re ready. Make sure you’ve filled in all the details in package.json, and, in particular, have chosen a version number, say 0.1.0 to start. Then publish!

$ npm publish

If you’re not yet registered with npm, you need to do so first.

Next, you should also publish your module onto bower.io, which is like npm for browser libraries. Bower uses your github repository tags to generate versions.

So, first, generate a tag using the same version number (unless you are evil):

$ git tag 0.1.0
$ git push --tags

Make sure you’ve committed everything before you do this!

And then register with bower:

$ bower register mymodule git://github.com/your-account/mymodule.git

And now you can do bower install mymodule, as well as npm install mymodule.

If you have improvements to this approach, let me know in the comments!




This entry was posted in Node.js. Bookmark the permalink.

10 Responses to How to Make Simple Node.js Modules Work in the Browser

  1. Pingback: Using node.js modules/code from the JavaScript console of geth - Finance Money

Leave a Reply

Your email address will not be published. Required fields are marked *