Cross - platform wrapper function for JavaScript libraries

Thursday, October 25, 2012 Posted by Ruslan Matveev
In old times, when JavaScript libraries had to work only in the web - browsers, common pattern for exporting your stuff into the global namespace was looking like this:

(function() {
    window['MyLibraryMethod'] = function() {
        alert('HELLO WORLD!');
    };
})();

We were just attaching our methods and properties to the global namespace, and this was all fine before somebody come up with the idea to run JavaScript code in non - browser environment. There was no window object so we had to find global object ourselves:

(function(global) {
    global['MyLibraryMethod'] = function() {
        alert('HELLO WORLD!');
    };
})(function() { return this; }.call(null));

Then things started to change very quickly, CommonJS standard and it's implementations against AMD (Asynchronous Module Definition), RequireJS and so on, but how should I package my library so the end - users won't have to repackage it in order to use it?

Here I'll try to solve the problem in my own way by creating an universal wrapper that can be used for wrapping your library code so it can work as:

  1. - CommonJS module for Node.js and Mozilla Rhino
  2. - RequireJS module
  3. - old - school JavaScript library that attaches itself to the global object
All in one pack, so you don't have to think which one is better and which one is going to be more popular among your users.

function moduleHeader(definition, namespace, global) {

    // returns true if we can work as CommonJS module
    function useExports() {
        return (
            // Node.js
            typeof process === 'object' ||
            // Mozilla Rhino
            typeof Packages === 'object' &&
            typeof JavaImporter === 'function' &&
            // check if our module is loaded using require
            typeof module !== "undefined" &&
            global.module.id !== module.id
        );
    }

    // returns true if we can work as RequireJS module
    function useDefine() {
        // check if we have RequireJS in place
        if (typeof requirejs === 'function' &&
            typeof define === 'function' && define.amd) {
            // check if our module is loaded using require
            var script = document.head.getElementsByTagName('script');
            script = Array.prototype.pop.call(script);
            return script.hasAttribute('data-requiremodule');
        }
    }

    if (useDefine()) {
        // define RequireJS module
        define(definition);
    } else if (useExports()) {
        // export CommonJS module
        module.exports = definition();
    } else {
        // attach to global namespace
        global[namespace] = definition();
    }

}

And here is how you can use it in the real life:

// call module header
moduleHeader(
    // library definition
    function() {
        // return your interface
        return {
            myProperty: 'myProperty',
            myMethod: function() {
                return 'myMethod';
            }
        };
    },
    // namespace
    'MYLIBRARY',
    // global object
    function() { return this; }.call(null)
);

To run the example, put the code into a file library.js and load it in Node.js or Mozilla Rhino (with CommonJS enabled):

// loading a library
var myLibrary = require('./library.js');
// using a library
console.info(myLibrary.myProperty);
console.info(myLibrary.myMethod());

Mozilla Rhino without CommonJS:

// loading a library
load('./library.js');
// using a library
print(MYLIBRARY.myProperty);
print(MYLIBRARY.myMethod());

Web - brower, using RequireJS:

<script type="text/javascript" src="http://requirejs.org/docs/release/2.1.0/minified/require.js"></script>
<script type="text/javascript">
    require(['library.js'], function(myLibrary) {
        alert(myLibrary.myProperty);
        alert(myLibrary.myMethod());
    });
</script>

Web - browser, global namespace:

<script type="text/javascript" src="library.js"></script>
<script type="text/javascript">
    alert(MYLIBRARY.myProperty);
    alert(MYLIBRARY.myMethod());
</script>

There might be a chance, that you have RequireJS included into your page, but still you want to export your library into a global namespace, this also works:

<script type="text/javascript" src="http://requirejs.org/docs/release/2.1.0/minified/require.js"></script>
<script type="text/javascript" src="library.js"></script>
<script type="text/javascript">
    alert(MYLIBRARY.myProperty);
    alert(MYLIBRARY.myMethod());
</script>

Now your library can be used in multiple different environments without repackaging it for each of them, one JavaScript file for several JavaScript environments. I use this technique to package JavaScript version of Histone template engine. Enjoy it!

Post a Comment