TypeScript in JavaScript using JSDoc comments
Recently I was working on a JavaScript project where I wanted a type checker to help me out, but I didn’t want to make any changes or additions to the project’s current build system. Luckily, TypeScript has a way to provide and check types using JSDoc comments. I’d like to show you how to get started with a few examples below.
I’ve created a repository with all the examples I’m going to reference and scripts to help one get up and running at github.com/shareup/ts-in-js-blog-posts.
(You’ll need to have node
installed to work through the examples in this blog post. Checkout the README in the repo for instructions if you need them.)
TypeScript’s compiler (tsc
) can be used to check files instead of compiling them. One needs to install tsc
.
We can use npx
to run tsc
. npx
will fetch typescript
if it’s not yet installed.
$ npx tsc --version
Version 4.0.5
If you have a JavaScript project already, then I recommend installing typescript
into the project with npm
:
$ npm i --save-dev typescript
Example JavaScript code
The example I’d like to use is a function to average numbers along with a test function to exercise it. I’ll insert a mistake into the test function so we can clearly see when the type checking starts working. Create an average.js
file:
export function average (numbers) {
let sum = numbers.reduce((acc, number) => acc + number)
return sum / numbers.length
}
export function testAverage () {
console.debug(average([3, 3, 3, 4]))
console.debug(average([1, 1, 1, 4]))
// ↓ mistake
console.debug(average(['a', 1, 1]))
}
Checking types with tsc
By default tsc
will read a file.ts
and then output a file.js
. For this example there are no .ts
files so I’ll tell tsc
to not “emit” anything when it’s run.
$ npx tsc --noEmit *.js
error TS6504: File 'average.js' is a JavaScript file. Did you mean to enable the 'allowJs' option?
By default TypeScript ignores JavaScript files, so one needs to also specify --allowJs
to allow JavaScript to be checked.
$ npm tsc --noEmit --allowJs *.js
Which outputs zero errors. Technically, there aren’t any errors because JavaScript will happily let you add a string
and a number
together…
JSDoc comments
Let’s make sure TypeScript knows that we only ever intend to average numbers.
The best way to think about the JSDoc comments that TypeScript can read is they specify the types of whatever code is about to be next. So to specify the types of the arguments and return values of a function, we write comments to specify the @param
s and the @return
:
/**
* @param {number[]} numbers
* @return {number}
*/
export function average (numbers) {
let sum = numbers.reduce((acc, number) => acc + number)
return sum / numbers.length
}
The syntax can take some time to get used to if you are unfamiliar (it did for me). Almost always things are in the order @instruction {type} name
with the type always between curly braces. In this instance I’ve written that the argument numbers
has the type number[]
which can also be written as Array<number>
or array of numbers.
All the possible JSDoc annotations TypeScript understands are documented on a single page. I reference this page often.
Does tsc
now pickup the mistake where 'a'
is provided instead of a number? Well, no.
$ npx tsc --noEmit --allowJs *.js
# …no output…
One not only has to provide the --allowJs
flag to tsc
, but one also has to add a special comment to the top of every .js
file where we intend to have types:
// @ts-check
/**
* @param {number[]} numbers
* @return {number}
*/
export function average (numbers) {
let sum = numbers.reduce((acc, number) => acc + number)
return sum / numbers.length
}
export function testAverage () {
console.debug(average([3, 3, 3, 4]))
console.debug(average([1, 1, 1, 4]))
console.debug(average(['a', 1, 1]))
}
Now, tsc
can notice there is a problem:
$ npx tsc --noEmit --allowJs *.js
average.js:15:26 - error TS2322: Type 'string' is not assignable to type 'number'.
15 console.debug(average(['a', 1, 1]))
~~~
Found 1 error.
Super cool 🎉
Something else interesting to try is to remove the return
keyword from the average
function and then tsc
will complain since we told it @return {number}
:
$ npx tsc --noEmit --allowJs *.js
average.js:5:13 - error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
5 * @return {number}
~~~~~~
Super cool again 🎉
Where to go from here?
This can be very helpful without requiring one to rewrite an entire project in TypeScript. One can opt-in each file so it’s easy to incrementally add types to a project in small steps. And it doesn’t change the final output or bundle of the project at all.
I recommend giving TypeScript in JavaScript using JSDoc comments a try. Checkout the JS Projects Utilizing TypeScript, the big page of all JSDoc annotations supported by TypeScript, and I will definitely write more about this in the future.