Updated 25th October 2019.

Ably, a cloud infrastructure and API provider for realtime messaging, recently migrated its main project from JavaScript to TypeScript. This is a summary of why we did what we did, and how.

This article is split into two sections. In the first I zoom through the basics of TypeScript and cover only the essentials of understanding. In the second part I focus on how Ably evaluated the use of TypeScript as a way to overcome challenges, as well as the exact steps we followed for an easy and stable migration.

Section one: understanding TypeScript

What is TypeScript?

If you are a web developer, I’m sure TypeScript has created enough buzz by now for you to know about it. However, for some of us geeks living under a rock while coding away most part of the day, here’s a quick intro.

JavaScript was originally designed to build logic into small front end apps in order to make them function dynamically based on user interaction. But we soon realised it was so useful, diverse and easy-to-use that we went on to use it for everything - even large projects for both frontend and backend applications.

But JavaScript wasn’t built for this kind of use. As you’d expect, it soon started to pose problems within complex projects. Developers found themselves spending much more time on debugging and maintaining the code rather than writing new code.

The solution was twofold: use a more strongly typed language or turn JavaScript itself into one such language. Of course, anyone having spent years in building a project in JavaScript would be tempted to go with the second option if it was possible.

Then came TypeScript, donning the hat of a saviour god for all the devotees in JavaScript land!

By definition, TypeScript is a typed superset of JavaScript. This means that all existing JavaScript is also valid TypeScript. And that you can convert an existing JavaScript project to contain type definitions and other language features that make maintenance and scalability of your project much easier and efficient in the long run.

TypeScript comes with a compiler that compiles a .ts file - which cannot be run in a browser - into a .js file which is vanilla JavaScript, as if we originally wrote the whole project in JavaScript itself. Including this layer of 'convenience' over JavaScript code is hugely beneficial, as you'll see later in this article.

Key Concepts in TypeScript

There are three things that make Typescript so useful:

Types

Interfaces

Classes and OOP

Types

Types, as you’d imagine, form the very crux of TypeScript. It has the following basic types: Boolean, Number, String, Array, Enum, Any, Tuple, Never, Void, Null and Undefined. Most of these are straight forward but you can check out detailed description in TypeScript's handbook.

Basic Types in TypeScript

Interfaces

TypeScript supports duck-typing and structural typing via Interfaces. You can declare and use interfaces as follows:

Interfaces in TypeScript

Using an Interface, you can specify a structure for the types to be used as parameters to the function. Duck typing allows this function to accept any parameters that fall within the same structure i.e, both of them being numbers in this case. It’s no longer necessary for objects of this type to be instances of the Interface in question only. One thing to bear in mind is that Interfaces completely disappear when the code is transpiled to JavaScript. So make sure your optional variables are not being used within the implementation.

Classes and OOP

Classes and other object oriented features are already part of ES6 but not all browsers support these yet. Using classes within TypeScript with the correct target version gives a way to start using these features in your code without having to worry about browser compatibility.

Classes in TypeScript

Section Two: Migrating to TypeScript

Ably began with an initial focus on R&D rather than sales or funding so the project grew at a rapid pace. It inevitably ran to thousands of lines of code in a short period of time. That’s when a discussion sparked within the R&D team and a proposal to start using a strongly typed language like TypeScript or Flow emerged.

There have been many discussions, talks and articles across the web that compare Flow vs. TypeScript. A quick run through articles like this can help decide which is best for your project. Here are a couple of examples:

In Ably's case, after much research, we found that TypeScript won the war. This was largely to due it's stability, massive community support, overall better tooling, and better error handling.

Ably's approach to migration

The first step was to set up a TypeScript configuration that would build all of our existing Javascript, and any new TypeScript source files, into a brand new directory using the allowJS compiler flag.

Once the project was configured to allow TypeScript code, we began by converting the files that are used most often and have most active development. This made converting other files progressively easier since the dependencies would already have their types declared.

Thereafter, we followed the process below for the converting each file:

Change the file extension from .js to .ts Convert common.js imports/exports to ES6 style Compile the file and add types to fix any warnings generated (it’s useful to have editor extensions for this) Declare basic types for any dependencies that do not have types available Convert ES3-style function classes into Typescript classes Add types to all functions and method signatures, as well as declare interfaces or type aliases where useful Ensure unit, module and integration tests still pass Open another file and repeat the process

It’s worth noting that TypeScript classes cannot extend plain (ES3-style) JavaScript classes without TypeScript declarations. So if many classes extend a common base class, it becomes necessary to convert or find declarations for those first.

Our aim was not to convert the entire codebase in this manner, but rather to convert enough that any ongoing work could be now done in TypeScript with minimal friction.

What’s improved for us?

After migrating to TypeScript it’s not only easier to catch any errors caused mainly due to bad typing it also makes maintenance, especially further refactoring of code, extremely easy. Since TypeScript includes enough information about types within the code itself you can completely do away with JSdocs. New engineers joining the team therefore find it easier to quickly onboard themselves just by reading through the code.

Tips & Tricks: What no one told you about TypeScript

noImplicitAny : Failing to annotate function parameters and class methods will lead TypeScript to implicitly consider the type as any. This defeats the very purpose of using TypeScript for type checking as these places are mostly prone to type errors. To avoid this, you can set the noImplicityAny flag in the tsconfig file which forces you to annotate these, even if that means you explicitly specify any as a type.

: Failing to annotate function parameters and class methods will lead TypeScript to implicitly consider the type as any. This defeats the very purpose of using TypeScript for type checking as these places are mostly prone to type errors. To avoid this, you can set the flag in the file which forces you to annotate these, even if that means you explicitly specify as a type. Object vs Any: If you don’t know which parameters a type will have, prefer the object type or { [key: string]: any } to any .

If you don’t know which parameters a type will have, prefer the type or to . Wherever possible, use object type instead of any , since it is more restrictive in terms of the existence of certain constructs.

type instead of , since it is more restrictive in terms of the existence of certain constructs. Function Overloads: Use function overloads where multiple call signatures are expected. This improves safety, readability and type inference and provides better editor suggestions.

Use function overloads where multiple call signatures are expected. This improves safety, readability and type inference and provides better editor suggestions. Function and class return types: Consider not always adding return types to short functions if it’s obvious. These can generally be inferred by the compiler implicitly, while adding them would add an unnecessary visual noise.

Consider not always adding return types to short functions if it’s obvious. These can generally be inferred by the compiler implicitly, while adding them would add an unnecessary visual noise. Const variables rarely require type declarations: For some variables such as const MAX_NUM: number = 1000 skipping the type would cause no harm in terms of understanding the variable type as it is pretty obvious, so it would make more sense to declutter the code by skipping this info.

For some variables such as skipping the type would cause no harm in terms of understanding the variable type as it is pretty obvious, so it would make more sense to declutter the code by skipping this info. Make use of interfaces where possible vs. classes: Interfaces allow duck typing so they give you much more flexibility in terms of how you would like to pass on function arguments while still ensuring correct types. This is not possible with classes.