Max's Coding Blog

Thoughts on TypeScript

TypeScript vs plain JavaScript conversation seems to never end, so I thought I'd share my thoughts on why I dearly love TS while also passionately hating it.

People usually tend to fall into two camps

  • TypeScript is our lord and savior, praise be TypeScript
  • TypeScript is a plague and all traces of it should be expunged

I'm the weirdo who's kinda in both camps and I think most people on both sides of the argument misunderstand what TypeScript is. So what is it? Well, I'll tell you one thing it's not:

TypeScript is not a programming language

Put the gun down. Hear me out.

When you're writing TypeScript, you aren't actually writing TypeScript. You're writing JavaScript with types merely as suggestions. I know we all like to forget that Flow used to be a thing, but Flow comments is a good illustration of what TypeScript fundamentally is:

/*::
type User = {
  name: string,
  verified: boolean,
  email: string,
};
*/

function isUserVerified(user /*: User */) /*: boolean */ {
  return user.verified
}

isUserVerified({name: 1, verified: true, email: 69 }) // error

The TypeScript "compiler" essentially acts as a linter upon these suggestions. In fact I'm pretty sure it's possible for a really good ESLint parser+plugin combo to do the entirety of the job TS does.

JavaScript is a bad language

I said put the damn gun down, James. Listen.

The issue with TypeScript that is unsolvable by design is the fact that it has to compile down to JS. There's nothing you can do about it. This thing doesn't count cause you're not going to use it.

This Javascript compatibility requirement heavily hinders the evolution of TypeScript's type system and language features, as they must not conflict with those of JavaScript. As much as we might want a `do { try? await } catch {}` type flow control feature in TS, it's just not going to happen until it's either in the language or in Babel (which can be an entire topic of it's own).

Nowhere is this more apparent as with TS enums, which I have always said are a complete joke and should be avoided. Enums are a perfect example of a language feature that TypeScript attempted to implement which simply does not work due to it having to be compatible with the dynamic language it has been bolted onto.

Consider this example in Swift, a language actually designed with static typing in mind:

enum Permission {
  case viewer
  case editor
  case admin
}

enum UnrelatedShit {
  case something
  case weDont
  case careAbout
}

// ...

let value1: Permission = .viewer // syntax, because the language knows what enums are
let value2: UnrelatedShit = .something

print(value1 == value2) // will always be false because duh-doy, obviously

Now, let's see how the same code written in TypeScript would fare:

enum Permission {
  Viewer,
  Editor,
  Admin,
}

enum UnrelatedShit {
  case Something
  case WeDont
  case CareAbout
}

const value1 = Permission.Viewer // ew
const value2 = UnrelatedShit.something // ewww

console.log(value1 === value2) // will evaluate to 'true', because both "enums" desugar into [0, 1, 2]
console.log(value1 === 0) // 'true', lmao what

Desugaring into JS also requires types to be nuked at build time, making the actual code that runs in the browser/node runtime ultimately unaware of types. Which means that as soon as you use an `any`, you render the entire point of TS moot, because your precious "type safety" doesn't exist in the code you're running.

This is very clearly observed when working with JSON, say, from API responses you don't control. In Swift, if an object you're decoding doesn't properly conform to the `Codable` struct defined for it, it throws at runtime. In JS, your code will execute normally in all of its `undefined` glory, because types do not exist at runtime and you're using a dynamic language.

The language you're writing is Javascript. You can convince yourself of it not being the case because "uhm the compilation step is abstract, I'm totally writing a language with types", you just aren't.

TS?.is?.optional

It can be a good thing, it can be a bad thing but the unquestionable thing is the fact that there is no universally agreed upon approach to using static types in JavaScript. Teams will always have config disparities and use TS differently. Here, try a little experiment. Open a few open source projects on GitHub written in TypeScript and compare their TS configs. What do you see?

The fact that there is a concept of `tsconfig` at all is a huge issue, because what's an error in one codebase is totally fine in another. This is not an issue in real languages with type systems because the type system is coupled to the language itself and the way it works is universal. There's no `// @golang-expect-error`, and while there is an `Any` in Swift, it doesn't mean "yeah idk, runtime will probably figure it out, our language can multiply an array by string, something will happen, maybe" (no, seriously, how on earth is `[] - 2 * "2"` even legal syntax), it still requires you to handle it properly at runtime.

Library Support

Remember how JS is still the language you're actually writing? Yeah well, not everyone is as enlightened and some people just write JS as is. Many of your dependency authors do, in fact.

There's nothing wrong with their code, it's perfectly functional, but for TypeScript to know what to do with it, someone must tell it. If it's not done by the library developer, it has to be done either by the community via DefinitelyTyped, or you.

Those last two are not a thing in real typed languages because, once again, the type system isn't haphazardly bolted onto the language.

Enums

I will literally never shut up about this. Probably the biggest joke in modern software engineering is "enums" that desugar into an array of integers. It should be extremely apparent why this is horrendously bad.

TypeScript Is a Documentation/Refactoring Tool

What TS is, and what it's actually really good for, is documenting your code. Think of it as JSDoc with a linter built-in, because, and once again, put the gun down, it's exactly what it is.

TypeScript helps to understand what the code does/expects after you haven't looked at the code for a while. It helps to jump into an unfamiliar codebase and get around quickly. It does everything JSDoc does in a more concise fashion, unless you get too ridiculous with it.

TypeScript is also very good at telling you which function call you forgot to refactor after you changed its argument structure. Not as good as languages with named arguments, but good nonetheless.

I absolutely love TS for these aspects. It is helpful and it does save time. When it works. The problem is, very often it doesn't because it simply isn't as smart as a real typed language.

And that's fine

What I would really love for us to do as web developers is to stop religiously obsessing over the tools we use. My approach is not more gooder than your approach and your approach isn't less worser than DHH's approach. Some tools are good, some tools are shit. Some tools are good at some things and shit at others.

There's a metric ton of extremely large, extremely successful software written in Ruby, JS and many other languages with no typing at all. And that's fine.

If you find yourself spending an hour trying to get past a TS error when you absolutely know better and actual Javascript code works, use an `any`. It's fine.

Web dev would be a better place if people who call themselves "TypeScript developers" and look down upon the typeless plebs got off their fucking high horse, because once your "compiler" strips types away, you're no different from everybody else.

You write Javascript, like all of us do. Accept it and get over it.