Node.js has a very nice module system that is easy to understand, yet distinguishes between the exports of a module and things that should be private to it. This post explains how the code of a Node.js module can be modified so that it works on both Node.js and web browsers. It also explains how to unit-test such code.
var privateVariable = 123; function privateFunction() { } exports.publicVariable = 345; exports.publicFunction = function () { return privateVariable * exports.publicVariable; }You add your public data to the object exports which Node.js manages for you. Additionally, Node.js ensures that all other data stays private to your module. Inside the module, you refer to private data directly, to public data via exports. Sect. 2 shows how such a module is used.
"use strict"; (function(exports) { // Your Node.js code goes here }(typeof exports === "undefined" ? (this.moduleName = {}) : exports));Let’s examine the wrapper code:
if (typeof exports !== "undefined") { var exports = {}; // (*) }Reason: JavaScript is function-scoped – a variable always exists inside the complete (inner-most) enclosing function. Additionally, variable declarations are hoisted – moved to the beginning of the function. Thus, the var declaration (but not the assignment!) at (*) is moved before the if statement and exports will always be undefined when the check is performed. Note that the code would work without hoisting, but not with block-scoping, because then the exports declared at (*) would only exist inside the then-block.
"use strict"; (function(exports) { exports.StringSet = function () { this.data = {}; // arguments is not an array; borrow forEach Array.prototype.forEach.call(arguments, function(elem) { this.add(elem); }, this); // pass "this" on to the function } exports.StringSet.prototype.add = function(elem) { if (typeof elem !== "string") { throw new TypeError("Argument is not a string: "+elem); } this.data[elem] = true; return this; // allow method chaining } exports.StringSet.prototype.contains = function(elem) { // Comparison ensures boolean result return this.data[elem] === true; } }(typeof exports === "undefined" ? (this.strset = {}) : exports));
var strset = require("./strset"); var s = new strset.StringSet();Browser:
<script src="strset.js"></script> <script> var s = new strset.StringSet(); </script>
var assert = require('assert'); var strset = require('./strset'); // constructor (function() { var sset = new strset.StringSet("a"); assert.ok(sset.contains("a")); assert.ok(!sset.contains("b")); }()); // add - illegal arguments (function() { assert.throws(function() { new strset.StringSet().add(3); }); }());These tests are run by storing them in a file strset-node-test.js and executing it via the command line:
> node strset-node-test.jsIf one of the tests fails, there will be an exception. If all tests succeed, nothing happens.
<!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>strset-test</title> <script src="http://code.jquery.com/jquery-latest.js"></script> <link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen"> <script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script> <script type="text/javascript" src="strset.js"></script> <script> $(document).ready(function(){ test("constructor", function() { var sset = new strset.StringSet("a"); ok(sset.contains("a")); ok(!sset.contains("b")); }); test("add - illegal arguments", function() { raises(function() { new strset.StringSet().add(3); }); }); }); </script> </head> <body> <h1 id="qunit-header">strset-test</h1> <h2 id="qunit-banner"></h2> <div id="qunit-testrunner-toolbar"></div> <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> <div id="qunit-fixture">test markup, will be hidden</div> </body> </html>You execute these tests by saving them in an .html file and opening it in a browser.