Tom MacWright

tom@macwright.com

In defense of JSDoc

Update: This post was published in 2015. As of 2017,
  • I still believe in mixing documentation into code and writing lots of API documentation, but I tire of JSDoc's approach, and am therefore plotting to create a JSDoc successor, which would hopefully update its approach for modern JavaScript.

JSDoc is untrendy and corporate. The documentation that people write in the format can be weak and formulaic. The implementations leave much to be desired, relative to other language-level tooling like babel. For people allergic to documentation authoring, JSDoc feels even more like a chore.

JSDoc is an API documentation standard that is written as code comments that start with /** and are structured by standardized tags like @param or @throws. It’s a descendent of JavaDoc, and the cousin of Python docstrings and Ruby’s RDoc.

Here’s what it looks like:

/**
 * Add one to a number
 * @param {number} input
 * @returns {number} that number plus one
 */

Most JavaScript projects have given up on embedded API documentation. Even the node.js project manages documentation as a series of Markdown files.

I think JavaScript needs JSDoc, and that it is the future of API documentation.

Checklists are writing tools

In elementary school, writing assignments had clear requirements: an introduction, the thesis, and a conclusion. Some teachers had specific paragraph counts with expectations around the content of every paragraph.

Freeform API documentation is the opposite of a checklist, and that is a problem.

JavaScript is a wonderfully flexible and expressive language, but it is not ineffable. Every JavaScript function takes input and returns a value. These two things should be documented, even if the returned value is undefined or there are no required arguments.

Input and output are two fundamental requirements of API documentation, and they are forgotten often. What does domain.run return?

Examples are not API documentation

var addOne = require('add-one');
addOne(1);

Examples are essentially video game speedruns: you watch someone winning the game, and watch the perfect way. It’s impressive how much they achieve in so little time, and how they never run into a tough spot or lose points.

But if you’re actually playing the game, a map is more useful.

Super Mario Map

The problem with examples is that they inherently avoid pitfalls and typically simplify their task to make the code approachable. But in day-to-day programming, the promise of documentation is to show the whole picture: every possibility, every outcome, every failure.

Failure especially. JSDoc has a wonderful @throws tag that you lets you document corners in the software: under what conditions is what kind of error propagated? Almost no software does this well, despite errors being a large part of their API surface, and having many different forms.

/**
 * @throws Will throw an error if the argument is null.
 */
function bar(x) {}

Markdown is a semantic dead end

You can try to standardize the format of Markdown documentation. I tried to do this with Mapbox.js’s DOCUMENTATION.md document that described how to write API.md.

This barely works, and is going nowhere. Markdown is well-suited for narrative text, but within the realm of documentation there simply isn’t a real difference between ## and ### that enables any sort of uniformity or real structure. Markdown isn’t structured data, much in the way that Wikitext isn’t structured data.

JSDoc is structured, and its connection to code is powerful. Tools like Tern.js are able to parse JSDoc comments and provide inline documentation as you type. ESLint has a valid-jsdoc rule that can require the existence of documentation. Documentation generators like my nascent documentationjs project and many others can transform JSDoc comments into Markdown, HTML, and many other formats.

Modular documentation for modular APIs

I started taking JSDoc very seriously in the context of the Turf.js project. Turf is a geospatial analysis system that follows the tiny module philosophy: when you require('turf') really you’re including over 50 tiny modules that all do one thing well.

While this is a great approach on a code level, how should it work for documentation? Surely we don’t require people to read 50 different README.md files to use a single library.

The solution we decided on was JSDoc: each module is documented with JSDoc, and all of the docs are aggregated into a single API website. But since JSDoc can easily be pushed into other forms, every module also gets a README.md with content autogenerated by doxme.

This is something that wouldn’t be possible with API.md or any other semi-standard convention: we’re able to document a modular library with modular documentation, so that every part is documented in its own repo, and the user-facing library is documented completely on one page.

The future of JSDoc

I’m working on tools for a second generation of Mapbox’s documentation: making it possible to document Mapbox GL JS the same way we do Turf, and making conventions that can be applied across projects. A big part of this is the documentationjs project that aims to build better tooling than the default JSDoc documentation generator, but supporting the same syntax. We’re currently in the golden age of JavaScript tooling: browserify and webpack solve the problems of dependencies, babel conquers transpilation, Flow enforces type constraints, and eslint finds errors and style problems. We need tools for documentation that are just as robust and friendly, and that’s what documentationjs aims to be.

JavaScript needs to shrug off its reputation of sub-par documentation culture. JSDoc is one strong bet to get there, starting with robust API docs that narrative documentation and tutorials can be built around.