Why we love TypeScript
Delve and TypeScript are a lot like this:
Why are we such good friends? Because TypeScript forces us to fall into the Pit of Success more than all of our other infrastructure combined. TypeScript helps prevent the simplest of mistakes as soon as they’re made:
Sometimes the above example isn’t fully understood as super valuable unless you’ve shipped as part of a large team, worked on a complex product or had high quality requirements. TypeScript performs a very important function in our engineering process: it automatically runs an entire test suite every time a file is saved. Unit tests are very valuable to us, but the automatic coverage we get from TypeScript is beyond what you could reasonably do with manually created tests. And besides, there’s only so many times you can write this:
A counter argument to the above is that we are writing extra code with TypeScript, which is true! Every member, argument, variable and function needs a type annotation. What we’ve seen that is that the extra cost of adding the type annotations is paid back many times over with the above protections, plus all of the great intellisense features that many editors like VSCode provide.
TSLint as a First Class Citizen
I have to admit: we’re probably the most dogmatic users of TSLint. We’ve turned on basically every rule and written a few more that TSLint doesn’t provide out of the box. When talking with Ryan and the TypeScript team, their reaction is half envy and half “you guys really want C#.”
One of TypeScript’s main priorities is to allow teams and codebases to transition gracefully to type annotations. The TypeScript compiler is perfectly fine if you use any for all of your annotations or omit them completely. The problem is that the type any tends to infect the rest of the codebase. And when that happens you’ve lost many of the benefits of using TypeScript.
For this reason we’ve been very harsh about using any in our codebase. TSLint helps enforce this with the “no-any” rule, which we’ve enabled. But this isn’t enough because TypeScript is happy to allow you to leave off type annotations which result in an “any” type unless direct inferencing can be done:
Even though we’re adding a number to arg, TypeScript can’t definitely say what type arg is, so it’s still typed as any. To prevent this spreading of any, we’ve enabled all of the “typedef” rules in TSLint, with one exception: we allow direct inferencing of types as in the case above for x.
In addition to the standard TSLint rules, we’ve written several additional rules. Many of these are similar to the level of enforcement you might see in StyleCop. What we’ve learned is that it’s much easier to write a rule than to make the same comment 50 times in code reviews!
- camelCaseMethodName: enforces consistent method names
- noEmptyLineAfterOpeningBrace: aka the Øystein Space Rule
- singleLineComment: comments above code, not to the right
- typedefRelaxed: a fork of typedef that allows for non-null initialization without a type annotation.
Let us know if these are useful and we’ll see about creating PRs to TSLint. We also have a wish list that has ~15 more ideas…we need more time!
We also use the AirBNB style guide when TSLint isn’t prescriptive enough.
React and TypeScript
React and JSX are first-class citizens to TypeScript. When we first did our hackathon React prototype ~18months ago a big concern was the lack of TypeScript support for React. We were very concerned about using runtime checks (a.k.a proptypes) since those errors are surfaced much later during the development cycle than static typing. And sometimes those errors can still sneak through to production!
James Brantly’s talk at React Conf 2015 gave us hope about TypeScript integration. François de Campredon did a fantastic job proving value with his TypeScript fork that included JSX support. And Ryan Cavanaugh has been super great about getting JSX shipped as part of TypeScript.
We’ve been using JSX + TypeScript since it first landed in a nightly build. It’s been a great solution and Ryan and the team have been very responsive about keeping up with React. For example, functional react components landed in TypeScript within two months of being shipped by the React team…and those two months included Thanksgiving and Christmas!
TypeScript and Flux
We’re going to write more about our Flux pattern and how we approach it shortly, but I wanted to drop a few notes about how TypeScript has enabled our Flux pattern. Our basic desire is that everything should be typed correctly, which means we avoid this common Flux example that you’ll find:
The problem with this example is that actions and state are completely untyped. It would be trivial for a developer to accidentally misspell “completed” and ship a bug to production. TypeScript allows us to have compile time safety against this kind of bug. Here’s an example of how our actions are defined:
Let’s break this down into smaller pieces:
- All actions implement the IAction interface.
- The IAction interface demands a toLogEntry method. This ensures that we have great telemetry associated with all of our actions. We’ll talk more about our telemetry in a future article.
Now our stores can consume an action in a completely type safe way:
Full type safety through our Flux stack ensures that when someone changes an action they can immediately know about all of the consumers and where additional changes need to be made. And because it’s done at compile time, we’re assured that we don’t have silly mistakes. We believe that this is a much safer and more productive environment than the default untyped action pattern that you’ll typically find.
Delve ❤ TypeScript
I hope you’ve enjoyed a whirlwind tour of how we use TypeScript. To quickly conclude: we use TypeScript not because we’re part of Microsoft, but because we find tremendous value by improving our productivity and keeping our quality high which together allow us to move much faster. I sincerely hope that you’ll give TypeScript a deeper look and integrate it into your engineering process!