Using JSDoc with AMD/require.js modules

For quite some time I’ve been searching for just the right formula to get JSDoc 3 to work with my AMD/require.js modules. Thankfully, the next version of JSDoc (3.3.0) finally seems ready for the job and I wanted to take the time to explain how it can be done.

Now, you might be thinking that you can just go ahead and install the latest alpha release from npm, unfortunately, that is not how it works (at least until 3.3.0-alpha5). If the current version of JSDoc is 3.3.0-alpha5 or later, go ahead and install it from npm. Otherwise, you need to clone the master branch from Github.

Once you’ve done that, switch to its directory and run npm postinstall to let it setup some symlinks that allow JSDoc to run at all. For the remainder of this article, I’ll assume that you’ve installed JSDoc to ~/jsdoc and that the project you want to document sits in ~/projects/todoc. You’ll need to change the path for the commands later on if you have done it differently.

Great, you’ve downloaded and installed JSDoc so let’s take a look at the code you want to document.

We’ll start with the foo/bar/baz module:

/**
 * This is the description for the foobar module.
 * @module foo/bar/foobar
 */

define([], function() {
  /**
   * This description belongs to the constructor function that we want to export as a module.
   * @constructor
   * @alias module:foo/bar/foobar
   */

  var exports = function() {};
  Class(exports).def(/** @lends module:foo/bar/foobar# */ {
  /**
   * Says hello to the world!
   */

  sayHello: function() {}
  });
  return exports;
});

That might look a little strange to you but unfortunately, it seems to be the best way to do it. Let’s get a short overview of what kind of special cases we have here:

  • We’re documenting a module that defines a constructor
  • We’re using a class framework (in this case jidejs/base/Class) to define the instance methods of our constructor

And to get JSDoc to support these use cases, we had to do a couple of special things:

  • The constructor function is specified as an alias for module:foo/bar/foobar.
  • We need to precede the object that defines the prototype of our object with a @lends tag that points to module:foo/bar/foobar#, which is the prototype of the module (and, because the constructor is an alias for the module, of the constructor)
  • We’ve given the constructor function the name exports.

Most of these things are probably obvious but the strangest thing is actually highly important: You need to name the thing you want to define export (at least judging from my tests). If you fail to use the exact syntax shown above, you’ll run into trouble.

Great, now let’s take a look at the second module, which will show how to document constructor parameters, events and inheritance.

/**
 * This event is fired whenever the user invokes the button, usually through a click or tap within
 * the bounds of the button. If the Button currently has the focus, it might also be fired through keyboard
 * events such as the spacebar or the enter key.
 *
 * It can be observed by listening to the `action` event of a Control.
 *
 * @event module:foo/bar/baz#goodbye
 */


/**
 * An improved version of {@link module:foo/bar/foobar} that can say goodbye!
 * @module foo/bar/baz
 */

define(['foo/bar/foobar'], function(foobar) {
  /**
   * Create a new `baz` object.
   * @constructor
   * @alias module:foo/bar/baz
   * @extends module:foo/bar/foobar
   *
   * @fires module:foo/bar/baz#goodbye Fired when the module says goodbye.
   *
   * @param {string} name The name of the baz.
   */

  var exports = function(name) {
  this.name = name;
  };
  Class(exports).extends(foobar).def(/** @lends module:foo/bar/baz# */ {
  /**
   * Says goodbye to the world!
   */

  sayGoodbye: function() {}
  });
  return exports;
});

This module is similar to the previous one but has a few more tags. The first doc comment describes the event fired by the module. I experienced some problems with placing this comment within the module so I settled for putting it as the first comment in the module which seems to work.

When extending another module, you need to extend from the module itself, which is why we use @extends module:foo/bar/baz and not something like @extends foo/bar/baz.

From what I’ve seen, the @fires tag must be specified on the constructor. Anything else will make the event disappear.

Another thing to watch out for is the order of the tags. With a large project (such as jide.js) I have experienced a large variety of errors that don’t seem to relate to anything. Sticking to this order (documenting @param after @alias and so on) was the only thing that worked reliable. To be honest, I think this order makes quite some sense, too, so it won’t hurt to stick to it.

To generate your documentation, you need to create your JSDoc configuration like usual and then invoke your manually installed jsdoc version:

node ../../jsdoc/jsdoc.js -c conf.json

Now, if you take a look at your generated documentation, you’ll see that inherited members show up just fine, including events.
What you’ll probably also notice is that events show up for each inheriting module in the sidebar, resulting in lots of event entries. You’ll also notice that even though inherited members show up, they are not listed on the page and that you can’t see the inheritance chain of your module. I’ve reported this issue and if you get lucky, it’ll be resolved by the time you try it out.

Also, please note that the Rhino version is not really up to date. If you intend to use ’use strict;’ in your project and try to document your code with JSDoc, you’re waiting for a disaster to happen. So, use the node version if you can. It has the additional benefit of being a lot faster.

If you have any tips on how to use JSDoc for documenting AMD/require.js module, please share them by commenting.

Leave a Reply

Your email address will not be published. Required fields are marked *