Shareable Code for Node and Browser

I had a simple problem along the way of exploring Node.js, which involves writing modules that work for both server and client sides. Just like many programmers nowadays, I google’d out a solution (which was written by the person who authored async, so I guess I need to thank him at least twice).

However this doesn’t solve all my problem since I am way too lazy to get rid of all those amazing open source libraries. And:

Of course, the browser doesn’t support many other node features like require(), so you’ll need to test that your code is suitable for use in the browser first.
Caolan McMahon

Realizing dependencies can be passed into the exposed module as its arguments, I have finally landed a solution that is simple (for me) to grasp.

Say I needed to have a shared utility module for both Node.js and browser to use, one that rely on underscore.js and moment.js, which fortunately have fairly consistent interfaces for both platforms.

shared-utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// my module that relies on underscore.js and moment.js
var utils = function(_, moment) {
// private stuff
var getNow = function(utcOffset) {
return moment.utc().utcOffset(utcOffset || 0);
};
// ... more private stuff ...

// public stuff
return {
sum: function(values) {
return _.reduce(values, function(memo, num) {
return memo + num;
}, 0);
},
dateTimeRange: function(past, unit, utcOffset) {
var dates = [];
var now = getNow(utcOffset || 0);

for (var i = 0; i < past; i++) {
dates.push(moment(now).subtract(i, unit || 'day'));
}

return dates;
},
// ... more public stuff ...
};
}; // but NOT actually "self-excuting" this yet

// "sync" the behavior of node require() and browser <script> tag
if (typeof module !== 'undefined' && module.exports) {
// we're on node.js, where we have the sweet module system to support exports and require
module.exports = utils(
require('underscore'),
require('moment')
); // given that both packages are installed
} else {
// we're on browser, so let's "call" the defered self-executing function
utils = utils(
_,
moment
); // similarly, given that both libraries are included already
}

So now I could use this sweet little module in node:

app.js
1
2
3
4
5
6
7
8
9
10
11
// expose public directory to client side
// here I'm using express
var express = require('express');
var app = express();
var server = require('http').createServer(app);
app.use(express.static(__dirname + '/public'));

// test it out on server side
var utils = require('./public/js/shared-utils');

console.log(utils.sum([1, 2, 3, 4, 5, 6, 7]));

and in browser:

index.html
1
2
3
4
5
6
<script src="js/underscore.min.js"></script>
<script src="js/moment.min.js"></script>
<script src="js/shared-utils.js"></script>
<script>
console.log(utils.sum([1, 2, 3, 4, 5, 6, 7]));
</script>

For now, I am satisfied.