TypeScript Compiler Explained

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:

let age : number = 27;
Trivial TypeScript code

and try to paste it into Google Chrome’s console in order to execute it:

TypeScript code cannot be understood by a web browser

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:

let age : number = 27;
TypeScript code before compilation

produces the following JavaScript output when processed by the TypeScript compiler:

var age = 27;
JavaScript code after TypeScript compilation

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 😀:

TypeScript compiler only protects you at runtime (during compilation)

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:

{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
Default tsconfig.json

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:

class Person {
private name: string;
private age: number;

constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

public getName(): string {
return this.name;
}

public getAge(): number {
return this.age;
}
}

const person = new Person("John", 27);
console.log(person.getName()); // Output: John
console.log(person.getAge()); // Output: 27
test.ts

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:

var Person = /** @class */ (function () {
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function () {
return this.name;
};
Person.prototype.getAge = function () {
return this.age;
};
return Person;
})();
var person = new Person("John", 27);
console.log(person.getName()); // Output: John
console.log(person.getAge()); // Output: 27
test.js

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:

TypeScript compiler will often let you go with any type

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:

16 Steps to become .NET full stack developer in 2024 - download a free guide
.NET full stack web developer & digital nomad
5 2 votes
Article Rating
Subscribe
Notify of
guest
2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
MGMNDMBR
MGMNDMBR
5 months ago

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.

Last edited 5 months ago by MGMNDMBR