36

Is there a way to create a javascript micro-library (a library that has no dependencies), that support all of the following module formats:

  • Asynchronous Module Definition
  • CommonJS
  • exposing the library's exports as a global namespace object (no loader)
slobo
  • 761
  • 2
  • 9
  • 16

7 Answers7

52

Yes, and I owe this answer to ded and his awesome modules:

(function(name, definition) {
    if (typeof module != 'undefined') module.exports = definition();
    else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
    else this[name] = definition();
}('mod', function() {
    //This is the code you would normally have inside define() or add to module.exports
    return {
        sayHi: function(name) {
            console.log('Hi ' + name + '!');
        }
    };
}));

This can then be used:

  1. in AMD (e.g. with requireJS):

    requirejs(['mod'], function(mod) {
        mod.sayHi('Marc');
    });
    
  2. in commonJS (e.g. nodeJS):

    var mod = require('./mod');
    mod.sayHi('Marc');
    
  3. globally (e.g. in HTML):

    <script src="mod.js"></script>
    <script>mod.sayHi('Marc');</script>
    

This method needs to get more publicity - if jQuery and co. started using it life would be much easier!

Marc
  • 13,011
  • 11
  • 78
  • 98
  • 1
    I had to use `define(name, [], definition);` on line 3 to get this to work with Curl. – jcollum Feb 13 '13 at 22:40
  • How would i go about adding dependencies to the 'mod' module? – Dan Ramos May 21 '13 at 15:33
  • 2
    Would this work if the module has a dependency I want to require? If inside I do var x = require ('./x'); I would expect to work normally in amd and node, and without a loader I would want to use window.x – Hernan Rajchert Jul 20 '13 at 05:16
  • The idea is right, use a *wrapper* arround the module definition to trick the module loader. [uRequire](http://urequire.org/) liberates you from having to write these wrappers for each of your modules, which is rather painful. It also has a number of such *wrapper templates* (UMD/UMDplain/AMD/nodejs) and other goodies like `'rootExports'`, `noConflict()` etc – Angelos Pikoulas Oct 29 '13 at 11:08
  • 1
    This is great. Note that the test `typeof module != undefined` is testing for commonJS, but `module` may be defined as a global object by other scripts one might include (e.g. QUnit) thus preventing this from loading under AMD. So I find it better to switch the order and test for AMD first. – prototype Feb 08 '15 at 21:10
22

Here is a list of various cross-compatible module formats.

I suspect that the one you're looking for is what they're calling "commonjsStrict.js"

Lee
  • 13,462
  • 1
  • 32
  • 45
7

uRequire, the Universal Module & Resource Converter is the tool that does exactly that.

  • It mainly converts AMD and CommonJS to UMD / AMD / CommonJS / Plain script (no AMD loader required).

  • It allows declarative exporting of modules, with a noConflict() baked in.

  • It can manipulate modules (inject/replace/remove dependencies OR code) as you build them.

  • It converts from coffeescript, coco, Livescript, icedCoffeescript and you can add your own conversions in one liners!

Angelos Pikoulas
  • 1,002
  • 1
  • 14
  • 21
  • I couldn't get this tool to work. See my other [stackoverflow post about it here](http://stackoverflow.com/questions/35135935/cannot-run-javascript-umd-command-line-tool-typeerror-any-is-not-a-function) – sjdirect Feb 01 '16 at 17:01
1

Just to update a little bit on this answer in regards to @marc I too give credit to ded and have updated it a bit to be with the latest updates:

(function (name, definition, context, dependencies) {
  if (typeof context['module'] !== 'undefined' && context['module']['exports']) { if (dependencies && context['require']) { for (var i = 0; i < dependencies.length; i++) context[dependencies[i]] = context['require'](dependencies[i]); } context['module']['exports'] = definition.apply(context); }
  else if (typeof context['define'] !== 'undefined' && context['define'] === 'function' && context['define']['amd']) { define(name, (dependencies || []), definition); }
  else { context[name] = definition(); }
})('events', function () {
  // Insert code here
  return {
    sayHi: function(name) {
      console.log('Hi ' + name + '!');
    }
  };
}, (this || {}));

Object at the end is a reference to either the parent or the current scope, lets say you have a package you are writing and this is just a piece of the pie, well that context could be a name-spaced object and this is just a slice of that pie.

Also, if you wish to have dependencies, there is an optional parameter at the end after your scope which supports an array, in this case the definition parameter then can utilize each dependency as a argument. Also, the dependencies listed in an array will be required inside node-js platform for your convenience sake.

See: https://gist.github.com/Nijikokun/5192472 for a real example.

Nijikokun
  • 1,514
  • 1
  • 15
  • 22
0

I have solved this exact problem and managed to easily support:

  • Dojo AMD (referencing the RequireJS specs)
  • jQuery (under $/jQuery.fn.[your_library_here])
  • node.js using vanilla require('path_to.js')
  • Browser window.[your_library_here]

It's using a combination of dependency injection and IIFE to get the job done.

See Below:

/*global jQuery:false, window:false */
// # A method of loading a basic library in AMD, Node.JS require(), jQuery and Javascript's plain old window namespace.
(function(exporterFunction) {
exporterFunction('cll',
    function(a,b) {
        return a+b;
    }
);
})(
    (function() { // Gets an exportFunction to normalize Node / Dojo / jQuery / window.*

        if ((typeof module != 'undefined') && (module.exports)) { // Node Module
            return function(library_name,what_was_exported) {
                module.exports = what_was_exported;
                return;
            };
        }
        if (typeof define != 'undefined' && define.hasOwnProperty('amd') && define.amd) { // Dojo AMD
            return function(library_name,what_was_exported) {
                define(function() {
                    return what_was_exported;
                });
            };
        }
        if (typeof jQuery === 'function') { // jQuery Plugin
            return function(library_name,source) {
                jQuery.fn[library_name] = source;
                return;
            };
        }
        if (typeof window != 'undefined') { // Fall down to attaching to window...
            return function(library_name,what_was_exported) {
                window[library_name] = what_was_exported;
            };
        }

    })(),
    (function() { 
        // ## Other Parameters Here
        // You could add parameters to the wrapping function, to include extra 
        // functionalilty which is dependant upon the environment... See 
        // https://github.com/forbesmyester/me_map_reduce for ideas.
        return 'this_could_be_more_arguments_to_the_main_function'; 
    })()
);

Public Gist available at https://gist.github.com/forbesmyester/5293746

Forbesmyester
  • 936
  • 6
  • 15
0

This is based on Nijikokun's answer. Since RequireJS discourages the use of explicit module names this has been omitted in this version. The second argument to the loader describe the dependencies. Pass [] if you don't need to load any.

var loader = function(name, dependencies, definition) {
  if (typeof module === 'object' && module && module.exports) {
      dependencies = dependencies.map(require);
      module.exports = definition.apply(context, dependencies);
  } else if (typeof require === 'function') {
    define(dependencies, definition);
  } else {
    window[name] = definition();
  }
};

loader('app', ['jquery', 'moment'], function($, moment) {
   // do your thing
   return something;
}
monken
  • 116
  • 1
  • 4
0

I've posted a solution to github that should work with any browser. Its based on how jQuery and underscore.js implemented their libraries but added a private anonymous object allowing you to use this to reference its private namespace; making for easy automated extension and scoping. Of course it has an _export object to expose your public APIs.

I also posted sample of how to define an internal library that has similar private scope and public exports.

My code is posted here: https://github.com/JavaScriptDude/JSLibraryTemplate

Note: I have not tested outside of the browser but it should work in node.

Timothy C. Quinn
  • 3,739
  • 1
  • 35
  • 47