As a frontend developer, one of the things you should know is how TypeScript compiler works. Sooner or later you will work with this language (which I sincerely wish you!), so it’s good to know your stuff 😉
In this article, I will explain TypeScript compiler to you in simple terms. We will avoid complex stuff – only what you need for your everyday frontend developer’s work. We will not explore the inner workings of the TypeScript compiler Instead, we’ll see some practical implications of its workings for TypeScript developer. Let’s dive in 🙂
Few basic facts about TypeScript
For starters, let’s establish some quick facts about TypeScript as a language.
TypeScript is a superset of JavaScript
You may have heard that TypeScript is a superset of JavaScript. In other words, every JavaScript code is a valid TypeScript code. In case of TypeScript, being a “superset” means adding types information on top of perfectly legit JavaScript code.
Why is TypeScript only adding its types on top of JavaScript? In order to be able to remove them easily 🙂 But why would it do that?!
Browser doesn’t understand TypeScript
The answer is that TypeScript cannot be run directly by a web browser (or NodeJS).
Let’s take this trivial TypeScript code:
and try to paste it into Google Chrome’s console in order to execute it:
As you can see, it fails. The number
type annotation is unknown to the browser. It means that before any TypeScript code is run in the browser, the information about types must be stripped out. In the end, browsers and NodeJS environments can only execute JavaScript. This is in short what TypeScript compiler does, and we’ll get into the details soon. For now, this leads us to another conclusion, that…
TypeScript is for developers
As we have just discovered, types must be removed by the TypeScript compiler before the code can be executed by the browser. In fact, this line of TypeScript:
produces the following JavaScript output when processed by the TypeScript compiler:
This is effectively the same code we wrote in TypeScript, but without type annotations.
Wrong data types assignments will only be detected at compile time:
but as soon as it’s compiled, this protection is not there anymore 🤷♂️
This discovery means that TypeScript is for developers. Its job is to make our lives easier. Of course, in the end it also improves the end users’ experience, because programming in TypeScript is much better than using pure JS. But you can already see that TypeScript it not present at runtime at all. It only protects us until the compilation step by static types checking.
The philosophy is more or less as follows 😀:
What’s worth mentioning, as the language’s specification says, “TypeScript never changes the runtime behavior of JavaScript code”.
TypeScript compiler in practice
tsc
TypeScript compiler is actually a CLI tool called tsc
. It can be installed globally on your machine and used directly or built into your IDE. Refer to official documentation for installation details.
The compiler can also be installed locally in your project. It can then be set up as part of the build pipeline, which is supported by many bundlers. That’s why, when working on a TypeScript project, you may never encounter any direct usages of the tsc
command.
tsconfig.json
In order to make working with TypeScript compiler easier, you should use a tsconfig.json
file. It’s a JSON file containing all compilation settings. This is quite important, because, in opposite to C# or Java compilers, the TypeScript’s one allows for quite extensive (and flexible) configuration.
When you use the tsc
command, it looks for tsconfig.json
file in the current directory or any of the parent directories until it finds one. You can also provide a custom tsconfig.json
location via --project
parameter.
The simplest tsconfig.json
file you get after initializing a new TypeScript project with tsc --init
looks as follows:
We will not explore all the individual settings in this article. The official documentation does a great job.
TypeScript compiler – example
Let’s now see how it works.
Having a bit more complex TypeScript file as an example:
Notice the .ts
extension. This tells the compiler that this file contains TypeScript code.
Having tsc
installed globally, we can compile it by executing the following command:
tsc test.ts
For the file given above, the output of this command is the following JavaScript file:
Few things to note:
- there is no information about types in the
.js
file. As we discussed before, this is intentional – only pure JavaScript code can be understood by the browser - notice there is no
class
keyword used in the compiled JS code, even though classes were introduced in ES2015. This is one of TypeScript compiler’s features – to produce code which is supported in most of the browsers and JS engines, without having to wait for support of new JavaScript versions - comments are left untouched
This explains how the TypeScript compiler works. It also confirms what we stated before, that TypeScript is for programmers. As soon as your code is deployed to production, the types you added in .ts
files are absent. The way this process is built gives a lot of advantages, but it may also be a source of confusion – let’s see how exactly.
TypeScript compilation flaws
Finally, knowing how TS -> JS compilation works, let’s quickly explore some of its common flaws.
No runtime protection
As you now know, TypeScript stripes out the types during compilation. It means that the information about types is not present at runtime (when our code executes). Because of that, we are not protected by TypeScript at runtime. Consequently, if an input comes from a user and the inputs themselves are not well validated, we may still get runtime type errors. Imagine that your code expects a number
, but the user enters a string
because of lack of proper input validation. TypeScript will not protect you here.
The same applies to validating API responses. Most data from HTTP APIs comes in a form of JSON. In TypeScript, we can represent such data as type
or interface
. Based on those expected shapes of data, we use objects of a given type and assume that given properties are present on them or not. However, APIs may change, and TypeScript will again not protect us at runtime. We still need to resort to alternative solutions for runtime types validation.
To be completely clear – I don’t think that no runtime protection is something TypeScript lacks. On the contrary – I love the flexibility of TypeScript which this approach gives. It’s just how the language has been designed, and we should be aware of that 🙂
Debugging complexity
When working on a TypeScript project, the code you see on production is different from the one in your IDE. Sometimes not only types are stripped out by TypeScript compiler, but as you saw earlier it may also convert some constructs to the other (like changing a class
to a function
). It makes debugging, especially directly on production, more complex.
In order to place breakpoints and actually have them hit in your TypeScript code, you need source maps. It does the job in most cases, but adds to the complexity at the same time, not always working seamlessly.
TypeScript is sometimes too strict
If you decide to migrate your JS project to TypeScript, you will quickly get frustrated by how strict this new language can be. Especially if you are coming from JavaScript world where everything is allowed 😅
There are countless memes about that, but here is the one I like:
Joking apart, most things TypeScript complaints about can be configured/turned off in tsconfig.json
file. This is actually what I like about TypeScript. It brings static typing into JavaScript world, but lets you control how strict you want it to be. So, if you get frustrated with TypeScript compiler’s complaints, remember about configuration options 🙂
Summary
I hope that now you feel more comfortable working with TypeScript compiler 🙂
If you’re a .NET developer, and you enjoyed this article, I think you may also find my free guide useful:
I really don’t like when people saying “TypeScript is a superset of JavaScript.”.
Hell no man, TypeScript is a different language which happens (was designed to […]) have many similarities to JS. But because is AT LEAST as expressive (actually way more) than JS so we can safely transpile TS to JS.
take it easy, man 🙂 This is just a metaphor, I even put it in double quotation (“superset”). This is only to illustrate a concept, you don’t have to take everything literally 🙂