Thoughts on TypeScript

For the past few months, I have been working at Continuum Analytics and we use TypeScript to build the libraries and widgets of PhosphorJS along with the applications built on top of them. As a long-time JavaScript programmer, here are some of my thoughts about switching to TypeScript.

What is TypeScript?

TypeScript is a language designed by Microsoft, which describes itself as “a typed superset of JavaScript that compiles to plain JavaScript”. And that’s pretty accurate. It looks a lot like ES6 with type annotations. And as long as you choose to allow the implicit any type (which is what the compiler assumes anything without a type annotation or inferable type must be), all valid JavaScript is valid TypeScript.

TypeScript is different from other compile-to-JavaScript languages in that it explicitly tries to adhere to the latest and greatest JS syntax, and in fact is a major influence on the ES6 standard. For example, the ES6 class notation is identical to TypeScript classes except for type annotations.

The good parts

A richer toolchain

I’m not a fan of IDEs but having autocompletion and error detection in Sublime Text with the TypeScript plugin has been great. Especially by setting the noImplicitAny flag to true, Sublime reliably warns me whenever I add a property that doesn’t exist or pass an argument that is incompatible. This ends up being far less cumbersome than I would have imagined, partly because the compiler is good at type inference.

TypeDoc is the documentation generator we use. To users of JavaDoc or JSDoc (in all of its incarnations), it’ll be instantly familiar. But unlike JSDoc, which feels as natural as writing Java documentation for a Ruby library, TypeDoc makes sense, because all the types are already right there in function signatures and interface declarations.

Interfaces

Interfaces are a zero-cost abstraction for when you don’t necessarily want or need a full class to define an object being passed around, but you want to have guarantees about what it is and what it contains. Here’s an example:

/**
 * A three-dimensional point
 */
interface IPoint {
    x: number;
    y: number;
    z: number;
}


/**
 * Calculate the Euclidean distance between two points.
 *
 * @param a - The first point
 *
 * @param b - The second point
 *
 * @returns the distance between a and b.
 */
function distance(a: IPoint, b: IPoint): number {
    // Must check because null and undefined satisfy all types.
    if (!a || !b) return NaN;
    let dX = a.x - b.x;
    let dY = a.y - b.y;
    let dZ = a.z - b.z;
    return Math.sqrt(dX**2 + dY**2 + dZ**2);
}


console.log(distance({ x: 1, y: -1, z: 2 }, { x: 16, y: 9, z: 8 }));
// Output: 19

Interfaces only exist for compile-time type checking; they are removed along with type annotations in the compiled output:

/**
 * Calculate the Euclidean distance between two points.
 *
 * @param a - The first point
 *
 * @param b - The second point
 *
 * @returns the distance between a and b.
 */
function distance(a, b) {
    // Must check because null and undefined satisfy all types.
    if (!a || !b)
        return NaN;
    var dX = a.x - b.x;
    var dY = a.y - b.y;
    var dZ = a.z - b.z;
    return Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2) + Math.pow(dZ, 2));
}
console.log(distance({ x: 1, y: -1, z: 2 }, { x: 16, y: 9, z: 8 }));
// Output: 19

The TypeScript compiler distinguishes between documentation that ought to be kept (i.e. the documentation for distance()) and documentation that can be discarded (i.e. the documentation for the removed IPoint interface) during compilation.

Compile-time error detection

I love that many of the most common JavaScript errors, namely TypeErrors, are caught by the TypeScript compiler before I even load the page. Any time I’m passing some unknown blob into some function, having type enforcement becomes my friend.

Generics help prevent errors, too, particularly in asynchronous code, because if a function returns a Promise<number>, that’s a lot more meaningful than just a plain Promise.

Real private members

Having private methods and attributes is really useful for larger projects where many developers will be using components they have not seen before or written themselves. It’s an essential ingredient to keeping API surface areas as small as possible. The compiled JavaScript obviously does not have private methods and attributes, but in TypeScript land, privacy is respected.

The bad parts

Slow compiler

The compiler feels slow. If you come from the Java or C++ world, maybe a slow compilation phase is just the cost of doing business, but my most recent experiences have been with interpreted languages that have zero compilation delay or with Go, which has an exceptionally fast compiler. To be fair, though, the issue is not simply that tsc is slow, it’s also that the build process of any non-trivial web application is by turns arcane, byzantine, and infuriating.

.d.ts files

All JavaScript is technically TypeScript, but without having type definitions by writing libraries in TypeScript to define a public API’s types and interfaces, the compiler can’t provide the benefits of meaningful type error prevention. For off-the-shelf libraries that are written in JavaScript or a language that compiles down to JavaScript, the way to add type information is to write .d.ts files that are basically analogous to .h header files in C. This is fine and there’s an incredible community resource called DefinitelyTyped that provides .d.ts files for all of the major JavaScript libraries.

But if you need a library that isn’t included in DefinitelyTyped, then you need to write your own. It’s not the end of the world, but it’s a pain. And it’s definitely time spent building something other than your application.

Conclusion

I like TypeScript. It has a whole raft of features like simple inheritance, abstract classes, and a sane module system, among others. It compiles down to very readable JavaScript. In a way, it’s different from learning, say, Go or Java in that it’s still better to know JavaScript well before using TypeScript, because having an informed intuition about the JavaScript that the compiler will output for any given TypeScript input is really useful. But for any project where there are more than a handful of programmers, or for libraries that may have many consumers, TypeScript is a safer language than JavaScript.

Posted by Afshin T. Darian