(Updated: )
/ #javascript #jsdoc #typescript 

How to get type-checking and generate TypeScript Typing declaration (types.d.ts) from JSDoc annotations

How to achieve TypeScript-like behaviour in Vanilla JavaScript using JSDoc and @ts-check in VSCode: that’s the purpose of this post. TypeScript is a JavaScript superset with types. It solves one of the big problems with JavaScript, which is “what parameters does this function expect?” TypeScript is great for the JavaScript ecosystem, code written in TypeScript (or with a type declaration file) has better IDE tooling (autocomplete, intellisense) and static checks can be performed.
Table of Contents

How to achieve TypeScript-like behaviour in Vanilla JavaScript using JSDoc and @ts-check in VSCode: that’s the purpose of this post.

TypeScript is a JavaScript superset with types. It solves one of the big problems with JavaScript, which is “what parameters does this function expect?”

TypeScript is great for the JavaScript ecosystem, code written in TypeScript (or with a type declaration file) has better IDE tooling (autocomplete, intellisense) and static checks can be performed.

The great news is that you can achieve the bulk of this without adopting TypeScript at all. If you write JSDoc’s for your JavaScript code, you can get VSCode to typecheck your code and generate TypeScript Typing declaration file (usually with a file name like types.d.ts).

The full GitHub template repository is available at github.com/HugoDF/jsdoc-type-d-ts-node-pkg.

Type-checking JSDoc-annotated JavaScript code with VSCode and @ts-check

In VSCode you can add a // @ts-check comment to the top of a file that contains JSDoc annotations and you’ll get type-checking.

For example given the following function/file, you would get an error “Expected 0 arguments, but got 1.” for the ping('foo') call. Which is explained since ping’s type declaration/JSDoc doesn’t include any arguments. Similar errors would be generated if the JSDoc included a parameter of a type but the function was called with a parameter of another type.

// @ts-check
/**
 * @returns {Promise<string>}
 */
async function ping() {
  return 'pong';
}

ping('foo');

module.exports = {
  ping
}

You can see the error in VSCode as per the following screenshot.

TypeScript error: “Expected 0 arguments, but got 1.” for ping('foo')

We’ve now seen how to enable type-checking in a JSDoc-annotated JavaScript source file.

For more advanced JSDoc typings you can check out the JSDoc for the render function in alpine-test-utils main.js file.

Next we’ll see how to generate TypeScript typing/type declaration file (types.d.ts) from the JSDoc annotations.

Generating TypeScript typing declaration (types.d.ts) from JSDoc annotations

In order to generate a TypeScript typing/type declaration file (types.d.ts) from JSDoc annotations, we’ll use the JSDoc CLI (github.com/jsdoc/jsdoc) and tsd-jsdoc (github.com/englercj/tsd-jsdoc).

You can install them using npm/Yarn:

npm i --save-dev jsdoc tsd-jsdoc
# or with Yarn
yarn add --dev jsdoc tsd-jsdoc

To get them working together we can use the jsdoc CLI as follows.

We use the -t (or --template) option to point the JSDoc CLI to tsd-jsdoc -t node_modules/tsd-jsdoc/dist.

The other options are more straightforward, -r means we want to recursively parse the files in the src directory, src is the origin directory and -d . mean the destination should be the current directory: .. We can the run whole thing with npx.

npx jsdoc -t node_modules/tsd-jsdoc/dist -r src -d .

This command is reasonably unwieldy, we’ll want to alias it in our package.json as build in order to run it more easily. See the following package.json update:

{
  "// other": "keys",
  "scripts": {
    "// other": "scripts",
    "build": "jsdoc -t node_modules/tsd-jsdoc/dist -r src -d .",
    "// more ": "scripts"
  }
}

We can then run yarn build or npm run build to generate a types.d.ts file, see the following output (this is what it looks like when your type annotations have changed or types.d.ts does not exist).

yarn build output when the JSDoc types and TypeScript typing declaration file types.d.ts don’t match

We can also look at the contents of the types.d.ts file which contains a function declaration for our ping function and the function signature is: no parameters and a Promise<string> return value.

declare function ping(): Promise<string>;

We’ve now seen how to generate a TypeScript typing declaration file (types.d.ts) using JSDoc and tsd-jsdoc.

Next we’ll look at what’s required to publish an npm module with a TypeScript typing declaration file.

Referencing TypeScript typing/type declaration (types.d.ts) in an npm module

To start off with let’s clarify something that might have been glossed over in the previous section: what’s a typing declaration file?

A typing declaration file is a TypeScript file that defines the interface of the module. It includes only type information. It’s usually called types.d.ts.

In our example, types.d.ts declares that our module has a function called ping and that ping takes 0 parameters and has a return type of Promise<string> ie. it’s an async function that takes no parameters and resolves to a string.

When your npm module gets published, for type declarations to be used, you need to leave a reference to the types.d.ts in your package.json. The field used to reference type declaration files is types (although apparently typings can also be used).

As a quick reminder, we have the following folder structure you can see the full code at github.com/HugoDF/jsdoc-type-d-ts-node-pkg, where the types.d.ts

├── LICENSE
├── README.md
├── node_modules
│   └── ...
├── package.json
├── src
│   └── main.js
├── tests
│   └── ping.js
├── types.d.ts
└── yarn.lock

To reference the types.d.ts file in package.json, we can update it to include a "types" field:

{
  "// other": "fields",
  "types": "./types.d.ts",
  "// more": "fields",
}

That’s it, now when your package (npm module) gets installed, your users will get TypeScript-level support for IDEs (eg. autocomplete, intellisense) and typechecking.

You can find more information about publishing a module with a TypeScript declaration file in TypeScript Documentation - Declaration Files - “Publishing” Section.

We’ve now seen how to publish an npm module with types.d.ts TypeScript type declaration file that’s generated from JSDoc annotations.

We’ve also seen how to enable type-checking in a JSDoc annotated file.

The full GitHub template repository is available at github.com/HugoDF/jsdoc-type-d-ts-node-pkg.

Photo by Christian Fregnan on Unsplash

Author

Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Get The Jest Handbook (100 pages)

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.