This is the first spike for the Testable Documentation project.
I call this first spike the "code only" concept. I'm trying to combine code and documentation in a way that we can test and lint the code that we're using as an example. One way to do that is to just keep everything as code with the documentation as comments.
There are a number of code-only examples out there to draw inspiration from:
- The Auth0 Express OIDC SDK has runnable examples for a number of use cases
- We also have a number of run-able sample applications, like this Ruby on Rails one, that serve as compliments to a Quickstart tutorial
- My WordPress Unit Testing post uses a functional, linted GitHub repo
All of the samples above have something in common: they are code written as educational tools. They are not meant to be deployed but they are fully-functional and, in some cases, under test. The only thing they are missing are the comments that turn them into more of a document than a programming artifact.
If we added comments to these samples, keeping the code functional, we could do some kind of treatment to improve the readability. What comes immediately to mind is the annotated jQuery and Backbone source code. You can read through the code on the right and, as soon as you need a bit more information, the description is on the left.
The tool to make this happen is Docco. The Docco docs themselves are generated from the Docco source code. The last 4 links are a little long in the tooth but, dated styling aside, I think they are still an effective learning tool.
The point here is to write code with comments that serve as the whole example, extra points if they can be compiled into something that looks nice.
Sample Code
First, we need some code examples to test, lint, and document. We don't need anything complex, just enough to make sure we know we're representing the real world.
For this sample, we'll pull in a library, make up some data, and use that library to do something. I'll use this as the code for all the spikes to keep things consistent. Code is here.
I wrote the above out in an editor and ran prettier
. Then I wrote a simple test that would simply require the code above, making sure it ran properly. If you look close at the code above, you'll see that it caught something right away:
"expiresIn" should be a number of seconds or string representing a timespan
7 | };
8 |
> 9 | jwt.sign(payload, "TOKEN_SECRET", {
> | ^
10 | expiresIn: this.tokenExpiresIn,
11 | });
12 |
I copy-pasted that block from a running module and accidentally left in the class reference. Turns out, I could benefit from a system that helps prevent dumb mistakes. Who knew?
Write the Docs
We have the tested, linted code sample, now it's time to write the documentation. In this case, we're writing it as in-line code comments that are Markdown-capable. I did not dig enough into Docco to find the exact Markdown flavor or specifics on how the comments are parsed beyond the library that's used (Marked). We'll just give it a shot and see what happens.
I made a copy of the sample code above and started commenting away. I give almost every line a comment to see how the documentation flow worked (see the code here).
Writing the documentation was not a terrible experience but the multi-line comments were not as natural as the single-line ones. I could definitely see this process interrupting the writing flow quite a bit if there needed to be a lot of explanation. I tried with the //
at the beginning of each line but VS Code was not auto-adding that on new lines (probably a setting somewhere). I switched to doc-block style and that seemed to help a bit.
Building the HTML
I've got a well-documented block of code, now it's time to see what processing looks like.
I installed Docco as directed and ran it on the code written in the section above:
❯ npm i -g docco
+ docco@0.8.0
updated 3 packages in 2.449s
❯ docco *.js
docco: sample-1-use-module.js -> docs/sample-1-use-module.html
That was easy! Without passing any options, we generated something that looked like the Backbone docs from above. You can see that here.
While this looked good for a first pass, I was writing the comments visualizing the code and documentation all being the in same column so I added --layout linear
and tried again.
That's a bit more like it but I'm not sure about the design details. This method of generation also came with 590 KB of fonts, images, and CSS. The final HTML generated from this process will be included or concentenated somehow with an existing site so the less included the better.
Docco allows for a --template
flag that will accept a .jst
template file, a format I'm not familiar with. Not really interested to dig too deep into a templating language that time has forgotten, I looked for and found the .jst
file used in the "linear" style and stripped that down to the bare minimum. The command complained that I did not indicate a CSS sheet so I added a dummy value.
❯ docco --template docco.jst --css none *.js
docco: sample-1-use-module.js -> docs/sample-1-use-module.html
That was finally outputting something that could be included somewhere. You can see the output here. I was a bit worried that the build process for this would involve stripping or parsing HTML but, thankfully, it does not look like that will be necessary.
Trying it out
With our tested and linted JS getting parsed into somewhat clean HTML, I wanted to see what it would look like imported. I'm using Nunjucks templates processed with Eleventy for my blog so an include
directive was all I needed.
Everything between the pointer fingers is the procesed HTML.
👇👇👇
First, install the jsonwebtoken
package (npmjs.com):
$ npm install jsonwebtoken
Require it at the top of your module:
const jwt = require("jsonwebtoken");
Now, we export a function to do the heavy lifting.
module.exports = function makeToken() {
The session token payload includes claims typical of an OpenID Connect ID token.
- The
aud
claim indicates who is meant to consume the token - The
iss
claim indicates who generated the token - The
sub
claim indicates who the token is meant to represent
const payload = {
aud: "AUDIENCE_ID",
iss: "https://example.com/",
sub: "SUBJECT_ID",
};
This function will return a signed JSON Web Token - JWT - using the second parameter below as the signing key. This token will need that same value to check the token signature when validated. It will be obvious to some but the "TOKEN_SECRET"
value below should be long, random, and never stored in the code.
Note that the jsonwebtoken
library can accept plain English time representations.
return jwt.sign(payload, "TOKEN_SECRET", {
expiresIn: "1 day",
});
};
And there you have it, a function that returns a signed token!
👆👆👆
Two problems I ran into:
- I had a surrounding
<div>
originally but that was causing the HTML to get a bit mangled. I investigated a bit but settled on simply removing it. I want the included HTML to blend into the surrounding content so the additional markup was not necessary. - I am using basic Eleventy syntax highlighting (PrismJS under the hood), which uses different classes and formatting than the processed HTML. If we go this route, we'll probably look into adding a modular code highlighting processer to Docco or forking and replacing ourselves. I tweaked my blog CSS a bit to get the code blocks in acceptable shape.
Overall, not a terrible workflow. Eventually, I had a combination script to run Docco, prettier the output, then copy into the include directory for my blog all at once. Eleventy picked it up and processed the pages, and, viola, the new documentated code was displayed. Magic.
Analysis
This spike was definitely a success. I got something working in under a day's worth of work and seeing testable code go to styled output was great, regardless of whether it's the right solution or not.
Original criteria:
- How much work would it take to convert existing code samples? We can ignore the work to convert the samples to testable files as that will be the same regardless of the processing. I would say the work here is medium as we need to add a step to the existing build pipeline to handle this.
- How much work would it take to handle the output? - Minimal if the HTML can be included in existing documentation rendering. With the stripped-down template, styles would cascade from the main document.
- How nicely does it play with existing documentation platforms? - It plays nicely with Markdown processed by Nunjucks. I think it would work fine in the Markdown files we're using but I'm not at all sure about inclusion in a cloud CMS, that would need more research.
- How easy is it to write code samples? - Not great but not terrible. As long as the comments between the code blocks is minimal, then it's acceptable.
- What languages does it support? - Lots and you can add additional language processing as well.
Here are a few additional thoughts on this system:
👍 Portable
I like that these examples could be produced into their own pages and served from GitHub pages or Netlify or another static file host. You could have one build process that creates include-able HTML and another that creates servable pages.
👎 Extra build step
We're generating HTML first, which is then included in the documentation. This extra build step adds complexity, both in terms of extra processing and things to go wrong but also a fairly opaque way of indicating your output. The HTML used above is stripped down to nearly nothing in the template file that was used but there is still plenty of HTML in variables that is not configurable.
👎 Forced cadence and order
The order of the comments and code is a limitation. You might find yourself structuring the code in order to make the documentation more clear, which does not feel like the right way to do things.
Or maybe it is if you buy into Literate Programming:
I believe that the time is ripe for significantly better documentation of programs, and that we can best achieve this by considering programs to be works of literature.
This type of documentation-first thinking feels better-suited to the source code itself rather than example code like what we're writing here.
👎 Old technology
The library I'm using here does the trick but it is quite old. Would we need to adopt it at some point? Would PRs be responded to? It's not abandoned entirely but it's worth considering.
So, that wraps up this spike! Stay tuned for the next one, which will explore including code directly into templates.
< Take Action >
Comment via:
Subscribe via:
< Read More >
Tags
Newer
Dec 31, 2020
Protect your WordPress REST API with OAuth 2 using Auth0
In this post, we are going to add the ability to use Auth0-generated access tokens for WP REST API endpoints that require an account and certain capabilities.
Older
Oct 28, 2020
Technical Research: Testable Documentation
I've been thinking about documentation quite a bit lately, especially the code-centric type, and how to avoid punishing myself for spending time writing it. Here's the start of my research project to that end.