Type annotations in JavaScript files

Is it possible to get the benefits of type checking without TypeScript's syntax? Absolutely

Written by Rico Sta. Cruz
(@rstacruz) · 7 Apr 2019

TypeScript lets you annotate your JavaScript with type annotations. It can even check these for errors in build-time, so you can catch errors before they get deployed to production. You’ll never have to deal with another undefined is not a function error ever again!

TypeScript, by default, requires you to make a few changes to your build setup. You’ll need to rename your JavaScript files to .ts and .tsx, and either use tsc (the TypeScript Compiler) or Babel (with preset-typescript) to compile them.

In this article:
  1. TypeScript and Jsdoc
  2. TypeScript setup
  3. Basic annotations
  4. Objects and imports
  5. Using with React
  6. Recap

TypeScript and Jsdoc

TypeScript syntax

Working with TypeScript means having to use a new syntax, even if it’s a strict superset of JavaScript. Many developers don’t like having to deal with a new syntax. If this describes you, then this article is for you.

typescript-syntax-example.ts
/*
* TypeScript syntax allows you to put inline type
* annotations... but it's not really JavaScript anymore.
*/
function repeat(text: string, count: number) {
return Array(count + 1).join(text)
}

The JSDoc syntax

TypeScript can read JSDoc’s type annotations. While JSDoc is primarily used as a means of writing documentation, TypeScript types can be written with JSDoc syntax as well. This means anyone can take advantage of TypeScript’s type checking in JavaScript without having to convert JavaScript code to TypeScript.

/**
* Repeats some text a given number of times.
*
* @param {string} text - The text to repeat
* @param {number} count - Number of times
*/
function repeat(text, count) {
return Array(count + 1).join(text)
}

Why JSDoc?

It’s a great idea to use JSDoc whether you use TypeScript or not. It’s the de facto standard for documenting JavaScript, and is supported by a lot of tools and editors. If you’re using JSDoc to document your JavaScript, you might as well let TypeScript enforce the integrity of your types in your code.

TypeScript setup

  1. InstallTypeScript. We’ll need TypeScript to get started. Install the typescript npm package in your project to get started.

  2. Enable JSDoc type checking. Configure TypeScript to check your JavaScript files. (By default, TypeScript only checks .ts files.) TypeScript is configured using the tsconfig.json file. We’ll also be using the noEmit option, since we’re only going to be using TypeScript as a type checker.

  3. Try it. Run tsc to check your project’s types. It’s recommended to add this to your CI, too, so you can automatically enforce it in your project’s changes.

Terminal
npm install -D typescript
tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"noEmit": true
}
}
Terminal window
npm exec tsc

Basic annotations

Function parameters

Use @param to document types of a function’s parameters. You’ll need to put these in JSDoc comments, which are block comments that begin with two stars.

example.js
/**
* @param {string} text
* @param {number} count
*/
function repeat(text, count) {
return Array(count + 1).join(text)
}

Documenting code

JSDoc is, first and foremost, a documentation tool. Aside from adding type annotations, we might as well use it to document what your functions do.

/**
* Repeats a given string a certain number of times.
*
* @param {string} text - Text to repeat
* @param {number} count - Number of times
*/
function repeat(text, count) {
return Array(count + 1).join(text)
}

Documenting options

Properties of params can be documented. This can be used to document React props in function components, too.

example.js
/**
* @param {string} text - Text to repeat
* @param {Object} options
* @param {number} options.count
* @param {string} options.separator
*/
function repeat(text, options) {
console.log(options.count)
console.log(options.separator)
// ...
}
repeat('hello', { count: 2, separator: '-' })

Optional types

Add an equal sign at the end of a type to signify that it’s optional. In this example, number= is the same as number | null | undefined. This special syntax (“closure syntax”) is only available in JSDoc types.

example.js
/**
* @param {string} text
* @param {number=} count
*/
function repeat(text, count = 1) {
// ...
}

Variables

Use @type to provide inline types to variable declarations. This isn’t typically needed for constants, as TypeScript can usually infer types pretty well. It’s a great fit for non-constant variables, though (ie, let).

example.js
/**
* Time out in seconds.
* @type number
*/
let timeout = 3000

Function parameters

@type can also be used to provide inline type definitions to function arguments. Great for anonymous functions.

example.js
list.reduce((
/** @type number */ acc,
/** @type number */ item
) => {
return acc + item
}, 0)

Objects and imports

Importing types

Complex, reusable types are better defined in an external TypeScript file. Types can be defined in an external .d.ts file, then import them into JavaScript using @typedef {import(...)}.

example.js
/** @typedef {import('./myTypes').User} User */
/**
* @param {User} author
*/
function cite(author) {
// ...
}
myTypes.d.ts
export interface User {
name: string
email: string
}

Object types

Use @typedef to define a type. External .d.ts files are preferred to this approach, but this syntax is available should you need it.

example.js
/**
* @typedef {Object} Props
* @property {string} title - The title of the page
* @property {number} updatedAt - Last updated time
*/
/**
* A component.
*
* @param {Props} props
*/
const ArticleLink = (props) => {
console.log(props.title)
console.log(props.updatedAt)
// ...
}

Union types

Use union types (|) to signify types that can be one or another. This, along with almost any TypeScript type, can be used in a @typedef.

example.js
/** @typedef {number | string} NumberOrString */

Advanced features

The JSDoc syntax isn’t as expressive as the TypeScript syntax, but it comes very close. There are also some other advanced TypeScript features that are available in JSDoc: The official JSDoc in TypeScript documentation has details on these features and more.

  • Templates with @template
  • Return values with @returns
  • Type guards with @returns
  • Function types with @callback
  • Enums with @enum
  • …and more

Using with React

Function components

Function components are plain functions. You can document them in any of the ways we previously learned to document functions. In this example, we’ll document them using object types.

example.js
/**
* This is a React function component.
*
* @param {Object} props
* @param {string} props.title
* @param {string} props.url
* @param {string} props.image
*/
const ArticleLink = (props) => {
// ...
}

Class components

Use @extends to define the types for your props and state. You can then use @typedef (either inline or imports) to define what Props and State are.

/**
* This is a React class component.
*
* @extends {React.Component<Props, State>}
*/
class MyComponent extends React.Component {
// ...
}

Recap

Documenting functions: Write documentation as block comments that begin with a double-star. Document parameters with @param.

/**
* Multiply a number by itself.
* @param {number} input - What to square
*/
function square(input) {
// ...
}

Importing type definitions: Import type definitions with @typedef and import. This allows writing type definitions in external TypeScript files.

/** @typedef {import('./myTypes').User} User */

Optionals: Use the equal sign to denote nullable types. This is equivalent to User | null | undefined.

/** @param {User=} user */

Anonymous functions: Use @type to document parameters of an anonymous function.

numbers.map((/** @type number */ n) => {
return n * 2
})

Documenting options: You can document the properties of object parameters.

/**
* @param {Object} options
* @param {number} options.count
* @param {string} options.sep
*/
function repeat(options) {
// ... options.count, options.sep
}

Written by Rico Sta. Cruz

I am a web developer helping make the world a better place through JavaScript, Ruby, and UI design. I write articles like these often. If you'd like to stay in touch, subscribe to my list.

Comments

More articles

← More articles