Having fun with ESDoc plugins
My team uses ESDoc to generate the documentation of JS projects. We use custom plugins to fit the generated docs to our needs. In this post I’ll share some of our plugins to show that the ESDoc output can easily be fine tuned.
To follow the examples it’s best to have some prior knowledge about how ESDoc plugins work. I wrote a post about this, but you can skip it an go into the examples if you feel confident enough.
Importing from the root package
ESDoc will generate a recommended import line for a method using the full path:
import {sum} from "awesome-math/src/sum.js";
In our code we prefer to reexport everything from the main entry point to avoid problems with future refactorings, so we encourage the use of:
import {sum} from ‘awesome-math’
We can fix the ESDOC output by creating a custom plugin that uses the onHandleDocs
handler to rewrite the importPath
of variables, functions and classes.
Disguise a variable as a function
ESDoc infers the documentation using the code’s AST. Only function expressions and declarations are considered functions. This will not play nicely when functions are wrapped by high order functions like curry.
To solve this we use a custom tag: function
, to mark the code.
On the onHandleDocs
step we look for the tag. If present, the doc entry is set to kind
function
, effectively fooling ESDoc. Notice that unsupported tags can be found in the doc.unknown
array.
Injecting references
It’s useful to link external sources to the documentation but it’s tedious to write full urls each and every time we add a new link. To ease this, we use a plugin to inject references from a table in the code comments.
We grab the table via config at the onStart
handler to modify the ESDoc comments of each source file using simple text replacement at onHandleCode
.
Injecting code examples
Sometimes both code comments and tutorials use the same examples. By injecting this examples using a plugin we can share the code snippets. Also we can treat them as normal code so they can be linted, prettified and even executed.
We add examples to the source code by replacing text in the code comments as we did in the previous plugin. To handle the tutorial files we use onHandleDocs
and replace text in the markdown files. We decorate the injected examples with comments in the source files and code fences in the tutorial markdowns.
Gotcha: dependencies
The examples plugin has a little problem. If you try it you’ll notice that it’s not working with the tutorials. The problem is subtle. The tutorial files are generated at runtime by the esdoc-integrate-manual-plugin (included in the esdoc-standard-plugin). Our plugin depends on the esdoc-integrate-manual-plugin but, depending on the execution order it may be executed before. To ensure the correct order we use another plugin.
Using the onHandlePlugins
it’s possible to reorder the plugins collection to ensure that our custom plugins are executed after the ESDoc ones. Magic!
Adding markup to the layout: Google analytics
Sometimes the ESDoc generated markup must be modified. For example, adding extra markup, removing unneeded pieces, changing texts, translating, etc. In this case we want to track some publicly available docs using a Google Analytics Tracking code.
Using the onHandleContent
handler we can modify the head
tag and append the Analytics tracking snippet. The tracking code will be passed as a plugin option.
In this example we used a simple text replacement but I encourage you to use some library like cheerio if you are going to heavily modify the HTML.
Zipping the documents
Sometimes we distribute our documentation as a zip package. Instead of using a custom npm script, we can use a plugin to let ESDoc handle the zipping after generating all the documentation.
We use the onComplete
handler for this task and rely on the archiver library to do the compression. The zip file is named using the package.json
name
an version
keys.
Gotcha: your plugin is broken
If you play with the above examples or write your own plugins you can run into this error:
../esdoc-plugins-example/node_modules/esdoc/out/src/Util/InvalidCodeLogger.js:72const start = Math.max(error.loc.line — 3, 1);TypeError: Cannot read property ‘line’ of undefined
Do not despair, your plugin is broken. You just have a syntax or runtime error. ESDoc has some bugs and cannot output where the error is.
Sum up
The examples shown in this post can be tailored to fit your own needs. Most of them can be approached differently and implemented using other lifecycle steps. Just try new things and see what happens. With some effort the ESDoc output can be fine tuned and most of its shortcomings avoided.