PhaultLines

Get the advantages of TypeScript without transpiling

Microsoft’s TypeScript programming language brings many of the advantages of static typing to JavaScript. Although it doesn’t enforce types at runtime, it enables richer static analysis, encourages more safety, and opens the door for better IDE integration. TypeScript code is typically transpiled to standards-based JavaScript so that it can run natively in browsers and Node.js. Given the attractiveness of TypeScript’s benefits, it is unsurprising that adoption is growing at a rapid pace.

Of course, using a non-standard language dialect also has its fair share of downsides. Using TypeScript in your project introduces a build step during development, diminishes compatibility with the broader ecosystem of existing tools that are designed to work with JavaScript, and requires all of your collaborators to learn the non-standard language features. Considering the rapid pace at which JavaScript is evolving, there are also some risks attached to locking yourself into a non-standard dialect. TypeScript’s creators designed the language and its tools to account for some of the potential pitfalls, but it’s still fundamentally not vanilla JavaScript.

Fortunately, even vanilla JavaScript developers can get in on the fun. TypeScript 2.3, which arrived in April, introduced support for analyzing conventional JavaScript code that has type annotations in comments. You can use a JSDoc-inspired syntax to describe function signatures and add type information. TypeScript tools read the type annotations from the comments and use them in much the same way that they use TypeScript’s own type system.

JavaScript with type annotations in comments is less succinct than writing actual TypeScript code, but it works everywhere, it eliminates the need for transpiling, and it makes the TypeScript tooling totally optional. It doesn’t cover all the same ground as the TypeScript language yet, but it is comprehensive enough to be useful.

A working example

To turn on TypeScript analysis in JavaScript, simply add a comment with the text @ts-check to the beginning of a file. Then add TypeScript’s JSDoc-like type annotations anywhere in the file. The following example demonstrates how to describe a function signature, with a return type and two parameters:

// @ts-check

/**
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
function example(a, b) {
    return a + b;
}

In Visual Studio Code, which has built-in TypeScript support, the editor automatically detects these comments and does what you would expect. There’s literally no setup—you don’t even need a TypeScript configuration file—just add the comments to any JavaScript code. If you try to call a function with parameters that don’t match the specified type, the editor displays a warning.

Type error displayed in Visual Studio Code

The editor also uses the type annotations to increase the intelligence of other features, such as autocompletion. The type analysis works as expected across files because TypeScript recognizes ES6 import statements and Node’s require.

In addition to annotating functions, you can also describe the structure of an arbitrary object. This is particularly useful when you want to get autocompletion and property access checks for JSON data that you retrieve from an API endpoint. The following example shows how to describe the structure of a JSON object fetched from a remote API:

/**
 * @typedef {Object} Issue
 * @property {string} url
 * @property {string} repository_url
 * @property {id} number
 * @property {string} title
 * @property {string} state
 * @property {bool} open
 */

const url = "https://api.github.com/repos/microsoft/typescript/issues";

(async () => {
  let response = await got(url, {json: true});
  
  /** @type {Issue[]} */
  let issues = response.body;
  for (let issue of issues)
    console.log(issue.title);
})();

The example uses the typedef annotation to define an Issue type. Inside of the async IIFE, where the response from the GitHub API is assigned to a variable called issues, there’s a type annotation that indicates that value of issues is an array of Issue objects.

You can find more examples of TypeScript’s supported JSDoc-style type annotations in the TypeScript wiki.

Library support

TypeScript automatically picks up type information for Node’s standard library, so you get type checking for much of Node’s built-in functionality out of the box. Some third-party JavaScript libraries also include TypeScript type definitions (typically a file with a .d.ts extension) in their npm packages. Enabling @ts-check in your project will get you type checking on the functionality exported from those libraries, too.

Standard library type definition displayed in Visual Studio Code

Conclusion

Over the past year, I’ve pushed to simplify my JavaScript tooling and get off the treadmill of ever-increasing bloat and complexity that plagues modern web development. Using comment type annotations aligns well with this approach: I get the advantages of TypeScript without needing a superfluous build step during development. It feels sort of like using TypeScript as a really smart linter rather than as a programming language. I don’t even need to add TypeScript as a development dependency in my project, I just treat the type checking like a text editor feature that helps me write better code.