lexicore lexicore - 3 months ago 7
Node.js Question

How to write a module that works with Node.js, RequireJS as well as without them

I am working on a JavaScript library for JSON/XML processing. My library works in browser as well as Node.js (with

xmldom
and
xmlhttprequest
modules).

One of the users recently asked for RequireJS support. I have taken a look at the RequireJS/AMD thing and think it is a good approach so I'd like to provide this.

However I'd like to retain the portability: my library must work in browsers (with and without RequireJS) as well as Node.js. And in the browser environment I don't depend on
xmldom
or
xmlhttprequest
since these things are provided by the browser itself.

My question is: how can I implement my library so that it works in browsers as well as in Node.js with an without RequireJS?

A bit of historyand my current solution

I initially wrote my library for browsers. So it just created a global-scope object and put everything inside it:

var Jsonix = { ... };


Later on users asked for Node.js support. So I added:

if(typeof require === 'function'){
module.exports.Jsonix = Jsonix;
}


I also had to import few modules mentioned above. I did it conditionally, depending on whether the
require
function is available or not:

if (typeof require === 'function')
{
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
return new XMLHttpRequest();
}


Now there's this story with RequireJS. If RequireJS is present then the
require
function is present as well. But module loading works differently, I have to use the
define
function etc. I also can't just
require
things since
require
has an async API in RequireJS. Moreover, if my library is loaded via RequireJS, it seems to process the source code and detects
require('something')
even if I do it conditionally like

if (typeof require === 'function' && typeof require.specified !== 'function) ...


RequireJS still detects
require('xmlhttprequest')
an tries to load the corresponding JS file.

Currently I'm coming to the following solution.

// Module factory function, AMD style
var _jsonix = function(_jsonix_xmldom, _jsonix_xmlhttprequest, _jsonix_fs)
{
// Complete Jsonix script is included below
var Jsonix = { ... };
// Complete Jsonix script is included above
return { Jsonix: Jsonix };
};

// If require function exists ...
if (typeof require === 'function') {
// ... but define function does not exists, assume we're in the Node.js environment
// In this case, load the define function via amdefine
if (typeof define !== 'function') {
var define = require('amdefine')(module);
define(["xmldom", "xmlhttprequest", "fs"], _jsonix);
}
else {
// Otherwise assume we're in the RequireJS environment
define([], _jsonix);
}
}
// Since require function does not exists,
// assume we're neither in Node.js nor in RequireJS environment
// This is probably a browser environment
else
{
// Call the module factory directly
var Jsonix = _jsonix();
}


And this is how I check for dependencies now:

if (typeof _jsonix_xmlhttprequest !== 'undefined')
{
var XMLHttpRequest = _jsonix_xmlhttprequest.XMLHttpRequest;
return new XMLHttpRequest();
}


If I have
require
but not
define
then I assume this is a Node.js environment. I use
amdefine
to define the module and pass the required dependencies.

If I have
require
and
define
thet I assume this is a RequireJS environment, so I just use the
define
function. Currently I also assume this is a browser environment so dependencies like
xmldom
and
xmlhttprequest
are not available and don't require them. (This is probably nor correct.)

If I don't have the
require
function then I assume this is a browser environment without RequireJS/AMD support so I invoke the module factory
_jsonix
directly and export the result as a global object.

So, this is my approach so far. Seems a little bit awkward to me, and as a newbie to RequireJS/AMD I'm seeking advise. Is it the right approach? Are there better ways to address the problem? I'd be grateful for your help.

Answer

Take a look at how underscore.js handles it.

// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object.
if (typeof exports !== 'undefined') {
  if (typeof module !== 'undefined' && module.exports) {
    exports = module.exports = _;
  }
  exports._ = _;
} else {
  root._ = _;
}

...

// AMD registration happens at the end for compatibility with AMD loaders
// that may not enforce next-turn semantics on modules. Even though general
// practice for AMD registration is to be anonymous, underscore registers
// as a named module because, like jQuery, it is a base library that is
// popular enough to be bundled in a third party lib, but not be part of
// an AMD load request. Those cases could generate an error when an
// anonymous define() is called outside of a loader request.
if (typeof define === 'function' && define.amd) {
  define('underscore', [], function() {
    return _;
  });
}
Comments