5

I'm creating my first AngularJS module intended for open source distribution. I'd like to package it in a way that's easy for others to consume.

The UMD project provides a pattern for exporting JavaScript modules that are compatible with AMD, CommonJS (or at least Node) and browser globals:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['b'], factory); // AMD
  } else if (typeof exports === 'object') {
    module.exports = factory(require('b')); // Node
  } else {
    root.returnExports = factory(root.b); // browser global (root is window)
  }
}(this, function (b) {
  // use b in some fashion
  return {}; // return a value to define the module export
}));

However, since AngularJS has its own internal module system, registering a module is done by simply calling a method on the angular object, i.e. angular.module(). Thus, a UMD module wouldn't need to export anything; it would just need to require and act on angular. In terms of the previous example, I think that would look something like this:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    factory(require(['b'])); // AMD
  } else if (typeof exports === 'object') {
    factory(require('b')); // Node
  } else {
    factory(root.b); // browser global (root is window)
  }
}(this, function (b) {
  // use b in some fashion
}));

Or, specific to my case:

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    factory(require(['angular'])); // AMD
  } else if (typeof exports === 'object') {
    factory(require('angular')); // Node
  } else {
    factory(root.angular); // browser global (root is window)
  }
}(this, function (angular) {
  angular.module( ... );
}));

Is this no big deal, or does it go against the spirit of UMD? I ask because I couldn't find any UMD patterns that don't export anything.

Bungle
  • 19,392
  • 24
  • 79
  • 106

2 Answers2

2

I don't see anything wrong with using this pattern. As you've probably discovered, Angular doesn't play very nice with existing module systems as it uses it's own, but a module doesn't have to export anything. How often does the main module for a project export anything? Usually it just gets dependencies.

If you are looking for an example that does something similar, you could look at the jQuery mousewheel plugin, which uses UMD but depends on jQuery.

Excerpt from jquery.mousewheel.js:

(function (factory) {
    if ( typeof define === 'function' && define.amd ) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node/CommonJS style for Browserify
        module.exports = factory;
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function ($) {

The one difference is that it return a constructor for the Node/CommonJS export, so that you can pass in the jQuery object you wish to extend.


On a side note, there is one issue with your UMD implementation, in the AMD section.

You have:

factory(require(['b'])); // AMD

Where you would need:

define(['b'], factory); // AMD

Since AMD is asynchronous, you wouldn't be able to synchronously require the angular module.

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(['b'], factory); // AMD
  } else if (typeof exports === 'object') {
    factory(require('b')); // Node
  } else {
    factory(root.b); // browser global (root is window)
  }
}(this, function (b) {
  // use b in some fashion
}));
Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
1

As an angular plugin, it might be nicer to instead offer a module that exports the builder, so that people stay responsible for "what angular means". For instance, say I am using a customised debug version of angular, but want to use your code: right now, I can't, because you've decided to require angular in for me. That seems like a great idea, but really isn't:

var myangular = require('debug-angular/ext/symbolised');
var yourmodule = require('yourmodule');
yourmodule.register(myangular);

Now I know, as developer, that your code will be using my angular, rather than now having loaded two different versions of angular, with all the hilarious version mismatching bugs that arise from that.

So a much nicer umd factory would be this:

(function (root, factory) {
  if (typeof define !== 'undefined' && define.amd) {
    define(['angular'] , function (angular) {
      factory(angular);
    });
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = {
      register: function(angular) {
        factory(angular);
      }
    };
  } else {
    factory(root.angular); // browser global (root is window)
  }
}(this, function (angular) {
  angular.module( ... );
}));
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
  • 1
    I'd argue that usually it's up to the build tool to decide which version of the library to include (debug vs. compressed). Also, if he's developing the library against angular it's quite reasonable to assume and require THAT angular and not someone else's. Just my two cents. – icfantv Dec 22 '15 at 23:20
  • I'm not talking about just "minified or not", think of jquery vs. zepto: completely different libraries, but 99% same API, so that virtually any plugin that relies on jQuery will work just fine with zepto... provided it doesn't blinding load its own jquery. I could have a library with the same API as angular, in which case the module "helpfully" loading angular for me will be the opposite of useful. – Mike 'Pomax' Kamermans Dec 22 '15 at 23:24
  • In that case I would argue the right implementation would be an interface that said third-party library would implement to provide a consistent API. But this is all moot since @Bungle indicated he wanted to write an angular JS module. Thus, he shouldn't have to support another library if he doesn't want to - especially since angular is the foundation on which his module is being built and is arguably THE foundation for angular web applications. If we were talking about dates, however, I would agree with you 100%. – icfantv Dec 22 '15 at 23:33