Skip to content

Ruairidh Codes

Generate automatic documentation with TypeScript

typescript, api, documentation4 min read

Introduction

If you've worked on a TypeScript project of any size, then after a while you start to notice that seemingly small updates break your API 'contract'. Maybe you had a method which expected certain input, but your update has broken that somewhere down the line and you don't realise.

Or maybe you forgot to export a type which means your end-user or colleague isn't sure what input is expected, and it's easy to do the opposite - exporting a type which you mean to keep private.

It's easy to make these errors, and many more of their kind. Worse, it's very easy for this kind of mistake to make it through a Pull Request and review, without being picked up.

Luckily, there is a great tool in the TypeScript toolchain which solves these issues and is generally underused. TypeScript's API Extractor (api-extractor) can save a great deal of developer time, and also provide an easier introduction to your code thanks to its documentation generation features.

Want to improve the quality of your TypeScript codebase and reduce errors? Then read on!

Impatient and want to skip ahead? You can see the finished example on the repo

What is the API Extractor in TypeScript?

api-extractor is a build-time tool which uses the TypeScript compiler engine to:

  • Generate API documentation
  • Let you know if there are changes to your API surface (i.e. your code has changed)
  • Provide different variants of your API surface to reflect release maturity. For example, you could have a 'staging' release, as well as a 'production' release.

At its core, api-extractor is a static analysis tool which can provide you with three types of output:

  • An API report which traces all the exports from your project's entry point
  • A rollup of your d.ts files. This is similar to how Webpack rolls up your JavaScript files into a single bundle for distribution. api-extractorr can bundle all your TypeScript declarations into a single d.ts file.
  • A documentation file. api-extractor generates a 'doc model' of your project in JSON containing extracted type signatures and doc comments. A companion tool called api-documenter can then use these files to build a documentation website, which could be included in your build pipeline.

So as you can see, api-extractor is a pretty powerful tool. So let's check out how we can useit.

Using api-extractor in TypeScript

Installing API Extractor

You'll need to install api-extractor with your favourite package manager. In this case I've used Yarn, but NPM works fine too:

1yarn global add @microsoft/api-extractor

Then in your TypeScript project directory, you'll need to run:

1api-extractor init

This will create a template file that shows all your available settings and their default values.

Setup api-extractor

Firstly, you'll want to set up the entry point of your project. api-extractor expects your application to have a single entry point where all of the imports come from. This is generally a good pattern for a project anyway, as you can have a deliberate public API surface.

So the default looks at <projectFolder>/lib/index.d.ts but you may want to change that depending on your application.

Version graduation for TypeScript

Want to allow users to select between a production and beta version of your API? Maybe some of your methods are private. Here is an easy way to make that explicit with api-extractor.

Check for dtsRollup in your config and ensure that it is enabled. That will run the bundling of your d.ts files into a single file.

Then, look for untrimmedFilePath and change it to:

1- // "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
2+ "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-private.d.ts",

Next, look for betaTrimmedFilePath and make the following change:

1- // "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
2+ "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",

Finally, look for publicTrimmedFilePath and do the following:

1- // "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
2+ "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts"

(Note, the removal of public isn't required but I think that it's clearer.)

So what just happened here?

This is one of the really cool features of api-extractor. It lets you mark your code as public, in beta, or private. You get to choose what shows up in autocomplete and in documentation.

If you run yarn api-extractor run --local we'll see a few declaration files appear in /dist:

  • <yourProject>.d.ts
  • <yourProject>-beta.d.ts
  • <yourProject>-private.d.ts

Then you can update your package.json to point to the public types:

1+ "types": "dist/<yourProject>.d.ts",

If a user wanted to change over to the beta version of your code, they'd simply update their tsconfig.json to something like:

1"compilerOptions": {
2+ "paths": {
3+ "<yourProject>": ["node_modules/<yourProject>/dist/<yourProject>-beta.d.ts"]
4+ }
5 }

Automatically generate documentation

A really powerful ability of api-extractor is the fact that it can automatically generate documentation from your types. It's easy to setup.

Firstly, we'll need to tell api-extractor where to get your tree of declaration files. We can do this in a new file: tsconfig.apidocs.json:

1{
2 "extends": "./tsconfig.json",
3 "compilerOptions": {
4 "outDir": ".dist-types",
5 "declaration": true,
6 "noEmit": false,
7 "emitDeclarationOnly": true,
8 "jsx": "react"
9 },
10 "include": ["src"]
11}

Next, add .dist-types to your .gitignore as we don't need these committed to our repository.

Now, let's try and build using this setup:

1# build
2tsc -P tsconfig.apidocs.json
3
4# list contents of output folder
5ls -p .dist-types

This creates a series of .d.ts files which contain all your type information.

Now we can pass this to api-extractor via a dedicated entry point. This is more the functionality that you would see in a library such as React:

1// src/publicApi.ts
2export { Foo, Bar } from "./types";
3
4export { default as CoolFunction } from "./utils/CoolFunction";

Now we can run our build again to expose that public API surface:

1tsc -P tsconfig.apidocs.json && ls -p .dist-types

You'll now see the following files generated:

1src/
2types.d.ts
3index.d.ts
4publicApi.d.ts

See the publicApi.d.ts? That acts as the entry point for our types for api-extractor.

Now open api-extractor.json and update mainEntryPointFilePath:

1+ "mainEntryPointFilePath": "<projectFolder>/.dist-types/publicApi.d.ts",

With that configured, we can extract those types!

Run:

1yarn api-extractor run --local

This will create:

  • An /etc folder containing your markdown API report.
  • A /temp folder with a .json and .md version of your API report.
  • Likely a bunch of warning messages in the console.

The /etc folder can be committed to source control, but be sure to add /temp to your .gitignore.

The /etc folder will become the approved API surface and will be used in the future to monitor changes.

Now you can update your package.json to have some helpful commands:

1+ "api-report": "tsc -b tsconfig.apidocs.json && yarn api-extractor run",
2+ "api-docs": "api-documenter markdown -i temp -o docs"

If you have any errors in your console, they're likely caused by an incorrect JSDoc tag. They should be in the format:

1@param ParamName - Description

Take a look at the completed code on the repo.

Conclusion

And that's it! Congratulations for getting this far, and hopefully you can see the value in an automated process for generating documentation, and versioning your types.

This provides a lot of flexibility in developing, especially when your code will be used across teams or put to open-source.

There's more to be discovered with api-extractor, for example you can use this as informal linting since if you forget to export a type that's used elsewhere, you'll hear about it.

Likewise, you'll be able to customise your documentation to fit your brand, and audience.

A huge thanks to Mike North at LinkedIn for his insight into this and sharing the knowledge! This is based on his work and is to consolidate my own understanding.

Found this useful? Let me know on Twitter!

Subscribe to the Newsletter

Subscribe to get my latest content by email.

    I won’t send you spam.

    Unsubscribe at any time.



    © 2021 by Ruairidh Wynne-McHardy. All rights reserved.