Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Packaging-up Browser/Server CommonJS modules with dependancies

Lets say I'm writing a module in JavaScript which can be used on both the browser and the server (with Node). Lets call it Module. And lets say that that Module would benefit from methods in another module called Dependancy. Both of these modules have been written to be used by both the browser and the server, à la CommonJS style:

module.js

if (typeof module !== 'undefined' && module.exports)
  module.exports = Module; /* server */
else
  this.Module = Module; /* browser */

dependancy.js

if (typeof module !== 'undefined' && module.exports)
  module.exports = Dependancy; /* server */
else
  this.Dependancy = Dependancy; /* browser */

Obviously, Dependancy can be used straight-out-of-the-box in a browser. But if Module contains a var dependancy = require('dependency'); directive in it, it becomes more of a hassle to 'maintain' the module.

I know that I could perform a global check for Dependancy within Module, like this:

var dependancy = this.Dependancy || require('dependancy');

But that means my Module has two added requirements for browser installation:

  • the user must include the dependency.js file as a <script> in their document
  • and the user must make sure this script is loaded before module.js

Adding those two requirements throws the idea of an easy-going modular framework like CommonJS.

The other option for me is that I include a second, compiled script in my Module package with the dependency.js bundled using browserify. I then instruct users who are using the script in the browser to include this script, while server-side users use the un-bundled entry script outlined in the package.json. This is preferable to the first way, but it requires a pre-compilation process which I would have to run every time I changed the library (for example, before uploading to GitHub).

Is there any other way of doing this that I haven't thought of?

like image 661
shennan Avatar asked Apr 06 '16 14:04

shennan


2 Answers

The two answers currently given are both very useful, and have helped me to arrive at my current solution. But, as per my comments, they don't quite satisfy my particular requirements of both portability vs ease-of-use (both for the client and the module maintainer).

What I found, in the end, was a particular flag in the browserify command line interface that can bundle the modules and expose them as global variables AND be used within RequireJS (if needed). Browserify (and others) call this Universal Module Definition (UMD). Some more about that here.

By passing the --standalone flag in a browserify command, I can set my module up for UMD easily.

So...

Here's the package.js for Module:

{
  "name": "module",
  "version": "0.0.1",
  "description": "My module that requires another module (dependancy)",
  "main": "index.js",
  "scripts": {
    "bundle": "browserify --standalone module index.js > module.js"
  },
  "author": "shennan",
  "devDependencies": {
    "dependancy": "*",
    "browserify": "*"
  }
}

So, when at the root of my module, I can run this in the command line:

$ npm run-script bundle

Which bundles up the dependancies into one file, and exposes them as per the UMD methodology. This means I can bootstrap the module in three different ways:

NodeJS

var Module = require('module');
/* use Module */

Browser Vanilla

<script src="module.js"></script>
<script>
  var Module = module;
  /* use Module */
</script>

Browser with RequireJS

<script src="require.js"></script>
<script>
  requirejs(['module.js'], function (Module) {
    /* use Module */
  });
</script>

Thanks again for everybody's input. All of the answers are valid and I encourage everyone to try them all as different use-cases will require different solutions.

like image 132
shennan Avatar answered Sep 19 '22 00:09

shennan


Of course you could use the same module with dependency on both sides. You just need to specify it better. This is the way I use:

(function (name, definition){
    if (typeof define === 'function'){ // AMD
        define(definition);
    } else if (typeof module !== 'undefined' && module.exports) { // Node.js
        module.exports = definition();
    } else { // Browser
        var theModule = definition(), global = this, old = global[name];
        theModule.noConflict = function () {
            global[name] = old;
            return theModule;
        };

        global[name] = theModule;
    }
})('Dependency', function () {
    // return the module's API
    return {
        'key': 'value'
    };
});

This is just a very basic sample - you can return function, instantiate function or do whatever you like. In my case I'm returning an object.

Now let's say this is the Dependency class. Your Module class should look pretty much the same, but it should have a dependency to Dependency like:

function (require, exports, module) {
    var dependency = require('Dependency');
}

In RequireJS this is called Simplified CommonJS Wrapper: http://requirejs.org/docs/api.html#cjsmodule

Because there is a require statement at the beginning of your code, it will be matched as a dependency and therefore it will either be lazy loaded or if you optimize it - marked as a dependency early on (it will convert define(definition) to define(['Dependency'], definition) automatically).

The only problem here is to keep the same path to the files. Keep in mind that nested requires (if-else) won't work in Require (read the docs), so I had to do something like:

var dependency;
try {
    dependency = require('./Dependency'); // node module in the same folder
} catch(err) { // it's not node
    try {
        dependency = require('Dependency'); // requirejs
    } catch(err) { }
}

This worked perfectly for me. It's a bit tricky with all those paths, but at the end of the day, you get your two separate modules in different files which can be used on both ends without any kind of checks or hacks - they have all their dependencies are work like a charm :)

like image 24
Andrey Popov Avatar answered Sep 18 '22 00:09

Andrey Popov