— typescript, api, documentation — 4 min read
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
api-extractor
is a build-time tool which uses the TypeScript compiler engine to:
At its core, api-extractor
is a static analysis tool which can provide you with three types of output:
d.ts
files. This is similar to how Webpack rolls up your JavaScript files into a single bundle for distribution. api-extractor
r can bundle all your TypeScript declarations into a single d.ts
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.
api-extractor
in TypeScriptYou'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.
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.
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 }
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# build2tsc -P tsconfig.apidocs.json34# list contents of output folder5ls -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.ts2export { Foo, Bar } from "./types";34export { 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.ts3index.d.ts4publicApi.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:
/etc
folder containing your markdown API report./temp
folder with a .json
and .md
version of your API report.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.
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!