Types for JavaScript without TypeScript

August 23, 2020

The other day I was working on a side project and I wanted a way to define the properties an object being passed into a function was supposed to have so that I could get good intellisense when working within the function.

I instinctively thought of using TypeScript, afterall isn’t needing types a good reason to use TypeScript?

After much deliberation (5 minutes of thinking about it) I decided TypeScript was a bit overkill and went with simple JSDOC comments.

JSDOC comments are great, you put a simple comment above whatever it is you’re typing and you’ll magically get intellisense, it also documents whatever you’ve typed too!

As an example, let’s say we have a function that takes a list of tasks. Here’s how you could type it using JSDOC comments:

/** @typedef {{
 *  id: number,
 *  name: string,
 *  due: { date: (string | null), time: (string | null) },
 *  isRepeating: boolean,
 *  subTasks: Task[]
 * }} Task */

/**
 * Takes a list of tasks and does something with them
 * @param {Task[]} tasks
 */
function exampleFunction(tasks) {}

We’ve defined the type Task as being an object with all of those properties. The function has a simple comment above it telling it that the parameter tasks has a type of Task[], meaning an array of Task objects.

Atleast when using VSCode, when we use tasks inside that function, it will give us proper intellisense. These kind of comments also work outside of vanilla JavaScript, I’ve used them in both Svelte and React projects without any issues.

Using JSDoc @typedef comments from one file in another file

So this works, but what happens if we wanted to use this Task type somewhere else in the codebase? Right now we’d have to copy and paste the @typedef comment wherever we want to use it as our editor only knows about what it can see.

Fortunately this is quite easy to solve, we can do this by putting our typedefs in a folder (either in separate js files or in a single js file) and then, at the root of our project, create a jsconfig.json file that points our editor to those files.

So assuming we have the following file:

src/types.js
/** @typedef {{
 *  id: number,
 *  name: string,
 *  due: { date: (string | null), time: (string | null) },
 *  isRepeating: boolean,
 *  subTasks: Task[]
 * }} Task */

we would create a jsconfig.json file in the root of the project that looks like the following:

jsconfig.json
{
  "include": ["src/types.js"]
}

If you had your types in separate files, you could put them in a folder and, in the jsconfig.json file reference like: src/types/*.js

Now we can use these types anywhere in our codebase just like we did in the example above, except we don’t need to define the type in the same file:

/**
 * Takes a list of tasks and does something with them
 * @param {Task[]} tasks
 */
function exampleFunction(tasks) {}

and that’s essentially it! I could have used TypeScript, but for something so simple TypeScript seemed overkill.

You can do so much more with JSDOC comments than what I’ve shown here, for more information their documentation is great.