TypeScript – Help or Hindrance?

At 345 we’ve been using TypeScript for a while now. This started when we began using Angular 2 during the early Betas, and we’ve been experimenting with TypeScript on some of our other Node.js applications as well.

The time has come to ask the question: Is TypeScript a help or a hindrance?

What is TypeScript?

I’m not going to go into too much detail here, you can always look at https://www.typescriptlang.org/, but it’s worth noting that TypeScript is a superset of JavaScript, which compiles to JavaScript (and you can instruct the compiler on which version of JavaScript to compile to).

Why use TypeScript?

JavaScript is an untyped, interpreted language. When you write JavaScript, your code is executed as-is. Owing to the lack of type safety, some people believe that JavaScript code suffers from errors that could be avoided in other compiled languages. Hence TypeScript allows JavaScript developers to write in a language that is more like a traditional OO language, with compile-time checks.

Although TypeScript compiles to JavaScript, variables are actually strongly typed. This allows the compiler to infer the type of a variable and hence check whether it is being used appropriately.

When TypeScript has been good

Angular 2 (https://angular.io/) runs off TypeScript. When you’re creating single page applications (SPAs) with Angular 2 the majority of the code you reference is the Angular 2 framework itself. The Angular model is consistent and well written, and as Angular provide the types for their framework the use of a typed language is natural and effortless.

TypeScript has also been good for asynchronous JavaScript. ES6 supports a promise-based callback model for async programming, and TypeScript allows you to program async methods and have them compile down to a much more verbose JavaScript implementation. With the advent of ES7, this advantage for TypeScript may not last long. There are, also, JavaScript libraries that allow a similar programming model.

The “linting” process, i.e. code checking, has been good in TypeScript. It has been easy to put in place rules that provide uniform-looking code that passes static code checks. Again, not saying tools for JS aren’t out there, but this has been good in TS.

When TypeScript has let me down

We build a lot in Node.js these days, as do many others. We rely heavily on NPM packages to pull in references, but with TypeScript you need to pull in the types for referenced packages in order to use them.   Most of the types are out there because people have needed to use them for their own projects, and they create type definitions and upload them to opensource repos.

Types have therefore become a minefield of inconsistency, with the official types often not keeping in step with the versions of the packages they are supposed to reference. Furthermore, there’s a ton of lazy typings with “any” types used all over the place, negating the benefit of type checking in the first place. TypeScript 2 tidies up the type references significantly, and includes the addition of a type search (https://microsoft.github.io/TypeSearch/) and type references, but there are still problems with the availability of type definitions and the inconsistencies therein.

The massive productivity improvements you can get from using Node and NPM are dampened considerably by the TypeScript type restraints. While you could argue that this is a “good thing” because you get additional compile-time checks, you could counter that by saying that you should concentrate on test coverage instead.

Version drift

TypeScript is very much an evolving language, and the ancillary components such as tslint (https://www.npmjs.com/package/tslint) are also moving rapidly. This can cause issues if you’re not pinning your versions and managing the transition from one to the other carefully. This is a configuration management activity that seems to bleed time over and over again if you’re not careful.

Code coverage

Remember that all TS code actually ends up being executed as JavaScript. Therefore any runtime error stack traces, code coverage stats for unit testing, logging or other debug information will refer to the JavaScript and not to the TypeScript. It’s not too much of a stretch to eyeball one against the other, but it’s something you need to be aware of.

Limitations of TypeScript

Unlike true OO languages such as C# or Java, where an object can be properly interrogated to find the interfaces it implements and the superclasses it descends from, TypeScript is much more limited in this regard. You cannot check whether an object implements an interface. Given the purpose of TypeScript, this seems slightly crazy, but it’s true. You can only check whether an object is an instance of a class, because the runtime type checks uses class constructors. There is no type attributes associated with an object.

I’m mystified why the TypeScript designers didn’t take the opportunity to tag metadata onto classes so that it can be interrogated at runtime, but that’s another story.

Loss of JavaScript flexibility

Ironically, one of the strong things about JavaScript is the very type-unsafe flexibility that you can gain. In JavaScript you can simply augment an object with properties and functions at runtime, which means that much of the inheritance you use in OO languages, or use of the decorator pattern, is actually redundant. I can tag properties and methods to an object, and hence dynamically add an interface, without having to do this at compile time. Powerful, and only to be used wisely.

In TypeScript you end up in this blind alley sometimes where you need to inherit a class just to add a new interface, and then you can’t type check your interface later. You end up spending time getting round the limitations of the TypeScript language in order to achieve things that are supported by the language you’re compiling to.

Compilation

Also, note that in JavaScript applications the code you write is the code that is executed. There isn’t a compilation stage. You can go straight from writing code to running tests with no interruption.

TypeScript by definition has the compile stage, and so you then need to sort out your source from the code you ship, and then in the case of NPM packages, you’ll probably end up keeping both in your GitHub repo anyway.  Setting up the compile and running a build process is additional activity that you don’t get with JavaScript.

Summary

TypeScript offers some neat things, and is perhaps a way of getting OO developers to feel comfortable with JavaScript applications.

The major cost in almost all software projects is man-hours. Productivity is therefore paramount, and as far as I can see you lose a ton of productivity when you use TypeScript that you don’t seem to gain anywhere else. TS is the “type-safe tax” you have to pay on your project.

Some specific scenarios, such as Angular 2 apps, you may decide are worth it. I would advise anyone making a decision on TS vs JS to look very closely at the benefits you feel you’ll get and to try to measure the productivity gap between the languages.

Don’t use TypeScript because there’s a bandwagon. Use it because you have a reason to. If you don’t have a reason to, stick to JS and concentrate on mature dev practices and test coverage.