Mar 4, 2019

I love using YUIDoc for my JavaScript projects because the setup is dead simple and the default theme has everything I need, including search and good markup for SEO. It’s no secret it’s a little outdated, though (does Yahoo! even still exist?). If you’re using more modern JavaScript practices like CommonJS modules, you’ll probably run into issues with its interpretation of a “module” versus a “class”–two concepts that have evolved wildly in their practical usage in JavaScript since YUIDoc was created.

Suffice to say, “module” in YUIDoc doesn’t mean “module” in the import/export/require sense, especially when you’re splitting your code out into smaller files or using something like Browserify to bundle your project. Classes didn’t even technically exist (at least in name) until ECMAScript 2015. So how do you document a module such that it shows up nicely in your documentation?

Static vs. Constructor Classes

What you’re probably looking for is a “static class.” YUIDoc’s syntax reference notes that whenever you define a @class you should include a @static or @constructor tag. The @constructor tag’s use is self-evident in the traditional sense of a class, whereas the @static tag “indicates that you should not instantiate the class with new. You can call all of the class’s methods statically.” That sounds kind of like what we’re doing when requiring a module!

Assuming you’re exporting an object with methods, you can document the module as a static class and then write up each of its methods with the @method tag. Place a block like so at the top of your module:

/**
 * Provides sorting tools for Posts instances
 *
 * @class sort
 * @static
 * @uses validatePostArray
 */

Then document each of the methods of the returned object like so:

/**
 * Sorts posts in a Posts instance by their
 * pubDate property, descending.
 *
 * @example
 * 	sort.pubDateNewest(Posts);
 *
 * @method pubDateNewest
 *
 * @param {Posts} posts Posts instance
 * @return {Posts} sorted Posts instance
 */
Methods indexed at same level of importance in module documentation

But what if you’re not exporting an object with methods?

Exporting a Function

If you’re exporting a single function (with or without @private methods in the module) you’ll need a workaround, though. A @class tag will not respect the usual @method tags like @param and @return.

In this case, what I recommend is placing your @class block, then immediately below it placing an @method block with the method name matching your module name. Document the function you’re returning as a normal method, then in your class block, write in the description that the class is a module that exports that method, and link to the method with an anchor tag.

/**
 * CommonJS module - exports <a href="#method_fetchXML">fetchXML</a>
 *
 * @class fetchXML
 * @static
 */

/**
 * Fetches an XMLDocument instance from a URL.
 * 
 * @example
 * 	let xmlDoc = await require("path/fetchXML.js")("https://example.com/feed");
 *
 * @method fetchXML
 * @async
 * @chainable
 * @param {String} url URL of feed to retrieve
 * @return {XMLDocument} traversable XMLDocument instance
 */

This ensures that the exported function has a clear, expected name and takes a place front-and-center in your module’s documentation. Clicking the link in the class description will immediately open the documentation for your exported function.

Exported function takes a priority spot in the “class” description

Using this formatting will allow you to document any range of CommonJS modules and point clearly to your exports while ensuring they show up in the index on the left.

The future of YUIDoc

YUIDoc has been around for a while and has a lot of competitors like JSDoc, ESDoc, Doxx, Docco, Document! X, and Natural Docs. While its functionality is probably not going anywhere any time soon, I’m interested to hear what documentation tools you use and how they address modern JavaScript. Feel free to leave a comment or send me an email on this topic!

Fork me on GitHub