Let’s multiply Object by a String and add an Array!
Said no sane developer ever.
Why not leave things as they are?
Once upon a time there was an
App. It was made of common
App building blocks. One of its important foundation stones was a
UtilityBeltService (let’s call it
Utils had so many methods that it was hard even for old master
App builders to grab a proper one without a few minutes of reading its code. Many small type inconsistencies lurked in huge codebase. Disaster was only a matter of time…
One of the builders spent some time commenting all those methods with some odd annotations. But it only made
Utils look ugly and did not help too much. The
App said, it wanted to be clean and beautiful, to be developed agilely and smoothly.
The builders decided to send one of them to the dark wastelands of world wide web in search for The Solution. The chosen one spent several working hours in search of salvation and he brought back three magic books. They had to decide. And they decided.
App was to be happy again and not only
Utils would change…
The Solution: Type
To help them find bugs faster.
To make their code better.
Wait, but JS has types!
Yes. It has. But JS is dynamically typed. And it’s dynamically typed with great elasticity – that’s really useful in fast prototyping and simple one-job scripts. But when application grows this elasticity becomes real pain in the ass as type bugs come out in runtime or even after several months on production. And no, unit and e2e tests are not enough.
"a" + 1 (which is adding String to Number) will go smoothly and return
"a1" (as a String). Here Number
1 is implicitly converted to a String to fit the operation. And that’s how coertion works. It smoothly hides many little and nasty bugs!
"a" + 1 expression. What if some kind of checker would test it before running application? It would say Hey! I can Add Strings or Add Numbers but I do not know how add a String to a Number? Aaargh…. And sane developer can decide now what he really needs. Maybe he wants to
getCharCode("a") + 1 or
"a" + String(1)? The first one returns number
98, the second one string
I’m sure that you remember all those JS tweaks related to coertion (and not only this). But I’m also sure that everyone makes mistakes. And few such mistakes in a huge codebase can make one spend several hours in chase of The Bug.
I have good news. Static typing can find it for you even without running an application 🙂
Of course it can be only some kind of fake static typing applied on top of JS in one or other way. Not a real thing.
There are three popular possibilities – jsDoc (with e.g. Google Closure Compiler), Facebook Flow and Microsoft TypeScript. First one uses comment like annotations – does not corrupt pure JS – while next two extend JS so they have to be transpiled. I will compare them briefly in a moment.
JsDoc is annotation-like comment system that brings some standarisation to describing code in comments (based on JavaDoc). It’s easy to start, as you do not have to change your builds, tests, etc. – you can just add few comments here, few there and you have an inline documentation. Then it can be easily compiled to human readable docs by jsDoc tools. The most interesting part, is that you can:
– define type of variable
– define type of function argument
– define type of function return
– or even define new type
.js. May be quite misleading, right?
It also supports ES6 classes and destructuring, generic types, polymorphic classes, not concrete definitions (interfaces),
.jsx React files… and much more. All it makes Flow a mature static type checker. I’d go further then it’s creators and call it a language.
.ts. I consider this a good idea, because TS code – same as with Flow – may not be a valid JS.
Converting bigger apps from JS may be a bit painful. TS uses implicit
any type (just like Flow) but its type inference is different (more strict?):
|variable assignment sets its type forever||type is “dynamically” changed on reassignment|
|more type errors for reassigned variables||less (or none) errors for reassigned variables|
TypeScript can add some pain. But this is the cost of strict typing. The cost of safety!
It also supports ES6 classes, destructuring, generators, await, async, modules, ES7 decorators and reflection and also generic types, polymorphic classes, interfaces, enums, abstract classes,
.tsx that can be compiled to
.jsx React files)… …all that Flow does and much more.
One more great feature is DefinitelyTyped – the library of interfaces and definitions for commonly used libraries.
- no need to compile/transpile to real JS
- the fastest conversion
- can be used for optimization (compressing with GCC)
- more a documenting tool than a type checker
- adds a lot of ugly bloat to code
- really painful to use with external libraries
- fast and painless conversion from JS
- mature type system
- not nullable types
- developed by conscious team and community
- support for some ES6 features
- usable with React’s
- forgiving type inference
- same file extension as plain JS (chaos warning –
- painful use with external libraries see
- is transpiled to JS
- mature type system
- strict type inference
- most ES6 features
- some ES7 features
- different file extension (
- DefinitelyTyped – easy use with most external libraries
- possibly easy conversion from JS
- support for React (
- is transpiled to JS
- in huge, complicated codebase conversion may be painful
- Microsoft (but it is only emotional)
For our projects at Evojam we have chosen TypeScript. It works fabulously in our environment because we love type safety, good code quality, strict rules and the newest technologies (ES6, ES7).
Angular team’s decision to use TypeScript had not influenced us as we started to use TS a month before they announced their move. jsDoc was not even considered – it’s too ugly. Flow is less strict and have less features – it may be a good conversion route (to TypeScript) for large systems with a really huge codebase.
The nice part is that Flow and TypeScript teams seem to learn from each other. It can make both of them even better.