Why we chose and why we don’t switch away from TypeScript?
Written by Dmitri Gabbasov
In this post we are going to explore the reasons why we have chosen TypeScript as our main development language at Sixfold and why we are happy with it.
Thank JavaScript
The seeming omnipresence and the undeniable popularity of TypeScript no doubt owe it to the pre-existing popularity of the language that TypeScript is built upon — JavaScript.
Even before TypeScript came into existence, JavaScript had already been, for a very long time, the only language natively supported by one of the most important interactive content delivery channels — the Web. Or rather by the programs that people use to access it — web browsers.
JavaScript implements a specification that defines a language called ECMAScript. For all practical purposes, the two names refer to the same thing. JavaScript was simply the name of the original implementation, and ECMAScript was the name chosen later when a specification was produced. Brendan Eich — the creator of JavaScript — has called ECMAScript “an unwanted trade name”.
The ECMAScript standard, first published in 1997, reached its 5th edition in 2009. The development of this edition took a long time and involved disputes between the two major contributors — Netscape and Microsoft.
Throughout the process, features like classes, modules and even static typing were already discussed, however, none of them actually made it into ES5. If you are interested in the history of ECMAScript then I suggest you read this paper by Brendan Eich and Allen Wirfs-Brock.
The next major update (6th edition) came in 2015, finally introducing classes, arrow functions, modules and some other things. It took web browsers considerable time to catch up with the specification, and so JavaScript transpilers entered the scene.
One could write modern JavaScript and have it transformed into ES5 as part of the build process. However, people wanted to use the new language constructs even before ES2015 was published. This lead to the creation of multiple alternative languages that could be compiled to ES5. Among them were for example CoffeeScript (released in 2010) and TypeScript (released in 2012).
Since 2015, the ECMAScript standard has continuously evolved, releasing a new edition every year. Similarly, other web-related technologies and standards were also evolving rapidly. Various APIs were being added that gave web pages more and more power (e.g. <canvas>
, <video>
, History, Storage, File and Blob, Crypto, WebRTC).
There were also significant improvements in JavaScript engines. In 2008 three major JavaScript runtimes started compiling JavaScript to native code. V8 (Chrome) came out that year with Just-In-Time (JIT) compilation support from the get-go. SpiderMonkey (Firefox) gained JIT compilation support through a new engine called TraceMonkey. And WebKit (Safari) started generating native code in its next generation engine called SquirrelFish Extreme. Opera and Internet Explorer followed suit in 2009. These improvements constituted a landmark advancement in the history of JavaScript engine performance.
Lastly, Node.js was released in 2009, eventually becoming a popular server-side stack choice.
All this naturally lead to larger and more sophisticated JavaScript codebases. Nobody could any longer cast a shadow on the fact that JavaScript was one of the most popular languages at the time. You can also see this from the StackOverflow developer survey of 2013:
Also, here is some reasoning provided by Anders Hejlsberg — one of the creators of TypeScript — back when TypeScript was released in 2012:
Over the last five years we’ve increasingly heard from customers that writing application-scale JavaScript is just too hard.
JavaScript was created as a scripting language. It wasn’t designed to structure medium- to large-scale code bases such as classes or modules. JavaScript is an entirely dynamic language that has no static typing, and static typing is really the thing that powers today’s rich IDEs.
What we like about it
Coming back to our original question — why did we choose TypeScript? We had already chosen to use the Node.js stack for our backend, meaning that we’d need to write or generate JavaScript in any case.
Why Node.js? Well, that question warrants an entire blog post of its own. Maybe we’ll write about that in the future. For now, here are some of the key things that our engineers like about TypeScript.
IDE support
TypeScript is well integrated into the major IDEs. There’s good support for navigation and refactoring provided by the compiler toolchain itself, and some IDEs, like WebStorm, build further on top of that to support even more advanced use cases.
Actually, the fact that TypeScript ships with a language-server-like module, which allows clients to query information about symbols and perform refactorings by sending messages over a TCP socket, has probably made it easier for various IDEs and text editors to so quickly provide full-fledged support for the language. Compare this with older languages where an IDE would need to essentially reimplement the compiler for every language it wants to support.
The available type information also makes it easier to explore new APIs, as well as use familiar ones, by way of showing auto-complete and parameter information popups.
Type checking
The type checking process is able to prevent many silly errors from happening at runtime. Plain JavaScript is particularly prone to this as, being dynamically typed, there is no way to ensure that a value, that is passed as an argument to a function or assigned to some property, has the type that is expected from it by the rest of the code.
It is a well known idea, that the earlier a bug is discovered, the cheaper it is to fix it. Being able to detect errors during compilation, or right inside your IDE as you type, can therefore help avoid many costly mistakes.
Gradual typing
TypeScript allows one to introduce types gradually, so to speak. It is fine to leave out type annotations when development speed is important and you only need to prototype something. You can later add more type information to your code and make things as strict as necessary. Similarly, not all your dependencies need to supply type information, you can still use them even if they don’t, although without the benefits that types bring.
It is also possible to migrate an existing JavaScript codebase to TypeScript one file at a time. So you don’t have to rewrite all your code at once, instead you can start by ensuring that all new code is written in TypeScript, and then, piece-by-piece, migrate the old.
Familiarity
TypeScript is merely JavaScript with additional syntax for type declarations and type annotations. This makes it easy for someone who is familiar with JavaScript to get started with TypeScript and can possibly open up the talent pool for a startup looking to build up a team.
Readability
Having type annotations present in certain places makes the code more readable. Type inference also helps a lot here, as you don’t need to litter your code with types. Mostly, you only need to put them in specific relevant places, like field and function declarations.
Additionally, types also serve as documentation. Indeed, it is common in JavaScript to include type information inside JSDoc comments. The TypeScript compiler can even make use of those comments and extract type information from them, making the whole experience even smoother.
Typed libraries
Many third-party libraries already ship with type information. Those that don’t, very likely have type definitions created by the community as part of the wonderful DefinitelyTyped project.
It is also one of the features that set TypeScript apart — being able to create “library type definitions” for existing JavaScript code. This means the typing information can live independently from the actual library code, allowing types to be defined even for abandoned or otherwise inaccessible codebases.
Documentation
TypeScript has good documentation and due to its popularity also has great community resources available.
Big company backing
TypeScript’s development is backed by a large organization (Microsoft), which is an important fact when choosing a technology to run your business and assessing its longevity. At the same time, the team’s development process is very much open to the community. Even the their meeting notes are available in GitHub for everyone to read.
What makes it harder to like
We also asked what our engineers think are some of the shortcomings of TypeScript. Following three things stood out. Note that they are not necessarily bad things, but rather can present obstacles to some people in certain situations.
Strictly JavaScript
TypeScript generally refrains from adding new syntax to the language that is not specifically related to typing and does not already exist in JavaScript. So things like custom operators, operator overloading and other syntactic sugar are not available and have little chance of being implemented.
The proper way to get such features is to propose (and champion) them in the ECMAScript technical committee (TC39). In other words, these features should be added to the JavaScript language first. A good example of this are the nullish coalescing (??
) and the optional chaining (?.
) operators. The latter in particular had been a requested feature in TypeScript since 2014, but was not implemented until November 2019, after it had become a stage 3 proposal in TC39. The proposal was then accepted into ES2020.
Advanced type system
For people coming from languages like Java or C#, TypeScript’s type system might be somewhat surprising at first. The big difference here is that TypeScript has a structural type system, as opposed to a nominal one. Two types with different names (or even without a name) can still be considered to be the same type, if they have the same structure. This is not the case in many other languages.
Also, TypeScript’s type system is quite advanced and allows to do various “tricks” that are not possible elsewhere. For example, union types make it possible for a property or variable to have multiple types, which can be discerned, if necessary, via various operations. Mapped types allow transforming one type into a different looking one, by applying certain transformations to it. All this can initially stump developers who have worked with other languages in the past.
Unsoundness
No real programming language has a type system that is 100% sound. Having such a property would mean that any valid program in that language is guaranteed to never throw an unexpected error or crash. However, some languages are better than others at isolating areas of code that can misbehave from code that can not. Being more sound usually comes at the expense of having to write more verbose code.
Since the beginning TypeScript has tried to strike a good balance in this matter — being as sound as possible, without forcing users to write complex code. In fact, soundness in TypeScript is highly configurable through various “strictness” flags, which turn certain language features on or off. Ideally, one would write TypeScript code with maximum strictness turned on, which we also do here at Sixfold.
A few times however, we have encountered errors at runtime, that we wish were preventable at the type level. These issues typically have something to do with the more “forgiving” aspects of the type checker, such as indexed property accessor types (think someArray[i]
not including undefined
in its type), and the lack of distinction between optional properties and required properties with undefined
in their type.
Final words
Ultimately, we can’t quite imagine writing Node.js or front-end code in anything other than TypeScript. It is really a no-brainer at this point. Even if you don’t want to be concerned with declaring your own types, you could at least make use of the type definitions of other libraries and built-in objects, it is still beneficial.
Taking the plunge and adding your own types leads to code that is more robust, often easier to read and faster to work with in modern IDEs, all the while remaining familiar to anyone with JavaScript experience.