Learn How TypeScript Compilation to JavaScript Works

Have you been using TypeScript for a while but you are not sure how the compilation works? In this article, we are going to go in deep about this works, what does it mean to compile a code as well as understanding why TypeScript needs to be compiled.

How humans communicate with computers?

As programmers, we are in charge of writing applications to take advantage of computers to execute them. In other words, we are telling the computer what to do. However, humans and computers have different languages, and failing to clearly communicate from one to another what to do will make software development complex.

Think about when you go to a South American country and don’t know any Spanish. Chances are most of the people down in South America speak Spanish and some population (Brazil) speak Portuguese. Do you think they will talk to you in English? Unless they are actively learning English, they will speak in their native language. The main challenge you will face is to communicate with them in their native language.

The same way happens with computers. Everything in computers is 0’s and 1’s. Literally, computers talk only using 0’s and 1’s. This is what is called a bit. 0’s and 1’s don’t mean much to us. It doesn’t represent a character such as ‘A’, ‘B’, ‘C’, etc.

If we want to represent a character, then we would need to have a byte. A byte is the collection of 8 bits such as, 10010100 which means ‘x’ (this is an example to represent what a byte is as it gets more complex. It doesn’t actually mean that it is ‘x’). Depending on what character is and what encoding it is in, there will be different ways to write the letter ‘x’.

You could have Unicode character in UTF-8, ASCII character, ISO-8895-1 character, ASCII character in UTF-8, etc. UTF-8, UTF-16BE, UTF-32BE, UTF-16LE or UTF-32LE, etc represent the character encoding scheme. The character encoding scheme is a way to store code units to a sequence of octets to facilitate storage. In other words, is the scheme used to store the letter ‘x’ in computer language.

If we truly want to tell a computer what to do, we would have to know a specific character standard as well as a character encoding scheme. Although humans are capable of a lot of things, learning computer language would make the programmer’s job extremely challenging. More than what already is.

You might have heard of different programming languages used to develop applications such as Python, JavaScript, PHP, C#, C, C++, etc. Well, these programming languages can be separated into two group types: high-level and low-level programming languages.

High-level programming languages

Depending on the programming language you use, have you noticed certain keywords are human-readable? For instance, if you are using JavaScript, you probably have seen keywords such as if, else, while, function, etc. They don’t mean much unless we add some code. However, it makes it easy for our brains to read code in English as much of the keywords used in several programming languages are written in English.

High-level programming languages allow developers and even non-developers to read what the program does as they are closer to human languages than to computer languages. Common examples of high-level programming languages are PHP, JavaScript, Python, Java.

Low-level programming languages

There are programming languages such as C/C++ that are used but are not as popular as they used to in the past. These programming languages have a higher learning curve because it involves a greater understanding of how the machine works.

We could also communicate to computers using machine code and assembly language. They are not the “typical” programming language as reading lines of code don’t make sense to our brains. Low-level programming languages allow communication almost in computer language and provides more complexity for humans to understand. There is a low abstraction between the language and machine language.

In theory, C could be considered a high-level programming language when compared to assembly language as it is more human-readable. On the other hand, C is considered a low-level programming language when compared to other programming languages such as Python or Java.

What does “Compile” mean?

Going back to the example of traveling to a South American country, if you don’t know Spanish or Portuguese, in case you go to Brazil, you are going to have a hard time communicate with people there. Thankfully, we have powerful technologies allowing us to translate from English to Spanish and vice-versa such as Google Translator.

In a similar way, we can do the same when developing software using a programming language, regardless of whether it is a high-level or low-level programming language, and that is when the word “compile” comes into action. Compiling means translating code written in a programming language into machine language.

Compiling is a process that happens ahead of time or just in time. If you are using a compiler, the code is already translated into machine language prior to its execution making programs run faster than those applications using programming languages needing interpreters. If you don’t know, Interpreters reads file line by line in a Just-In-Time manner or during the execution of the code. That means, the compilation process happens during the execution of a program.

Depending on what kind of compilation mechanism is used and what kind of programming language is compiled, the compiler ensures the program is typed correctly as well as syntactically correct. However, it doesn’t ensure that the logic of your program is correct. Logic is one of the most important parts of a programmers’ job and the reason why it makes it a high-paying career nowadays as everything is becoming computerized.

Why does TypeScript need to compile?

To answer this question we need to got back to the definition of TypeScript.

After all, What is Typescript?

If you check out the TypeScript website, you will find the following definition:

TypeScript is a strongly typed programming language which builds on JavaScript giving you better tooling at any scale.

TypeScript Official Website

Another common and valid answer you will find for the definition of TypeScript is: TypeScript is a superset of JavaScript.

What is a superset in a programming language?

A quick google search will tell us, a superset is a programming language that contains all the features of a given language and has been expanded or enhanced to include other features as well, found in this encyclopedia.

Understanding why TypeScript needs to compile

Now that we have clear the definition of TypeScript, let’s go back to the question: Why does TypeScript need to compile?

If you read what the definition of the word “compile” is, you could say TypeScript needs to compile as any other programming language to translate it into machine language. That’s a good guess. However, it is a little bit different.

Since TypeScript is a superset of JavaScript, it is like saying that the real programming language behind TypeScript is JavaScript. This sounds confusing as TypeScript in itself is a programming language. The reality is, if JavaScript hadn’t existed with all its flexibility and lack of typing, TypeScript wouldn’t be what it is today. In the end, the TypeScript executed is JavaScript.

Therefore, TypeScript needs to compile because it needs to translate the code from a strongly typed version of JavaScript into JavaScript’s original weakly typed or untyped language. Hence, it doesn’t translate the code to machine language. The one in charge of this compilation process is JavaScript.

This makes it more complex to understand as some people have a different understanding of whether JavaScript is compiled or interpreted. What they are asking is whether JavaScript uses a compiler or an interpreter to compile the code. In the end, the code needs to be translated into machine language regardless of the approach used to convert the code. For example, JavaScript frameworks such as Angular offer two ways to compile an application: Just-in-Time and Ahead-of-time or two different ways to convert the code into machine language.

How TypeScript compilation process work

TypeScript provides a command-utility interface, or CLI known as tsc, which compiles or translates the TypeScript code to JavaScript. This process happens locally before the code is executed based on the closest tsconfig.json file.

“Hello World” app example

We can see one simple example of how this works in a simple ‘Hello World’ project with the following structure:

hello-world
├───index.ts
└───tsconfig.json

To compile the code that lives in the index.ts file, we need to use the command tsc as well as the path of the TypeScript file to compile.

npx tsc index.ts

If there are no errors, you won’t see a response from the terminal, but you will see a new generated index.js file.

hello-world
├───index.js
├───index.ts
└───tsconfig.json

In case your TypeScript code has errors, the compiler will display the error in the terminal with the location of the line of code failing as it won’t generate index.js file:

$ npx tsc index.ts
index.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'number'.

It is common for many developers to have a package.json file to configure all their project scripts.

hello-world
├───index.ts
├───package.json
└───tsconfig.json

In which the package.json can have the build script configuration to use the tsc CLI to compile the code.

{
  "name": "typescript-playground",
  "version": "1.0.0",
  "scripts": {
    "build": "npx tsc index.ts"  
  },
 "author": "Andres Reales",
  "devDependencies": {
    "typescript": "^4.4.3"
  }
}

In this way, we can use yarn or npm to execute the build command and still compile the code.

yarn build
// or
npm run build

This in itself won’t execute the program. Developers are still in charge of running that JavaScript code. Therefore, they will probably have to implement another script. In the following example, we added the execute script to run the code:

{
  "name": "typescript-playground",
  "version": "1.0.0",
  "scripts": {
    "build": "npx tsc index.ts"
    "execute": "node index.js",
  },
  "author": "Andres Reales",
  "devDependencies": {
    "typescript": "^4.4.3"
  }
}

Packages such as ts-node provide a way to execute TypeScript code, or in other words, translate TypeScript to JavaScript and execute TypeScript at the same time behind the scenes. In that way, developers no longer have to execute two scripts to see their code running. If we set up a new script configuration to our project, it will look like this if using ts-node.

{
  "name": "typescript-playground",
  "version": "1.0.0",
  "scripts": {
    "start": "npx ts-node index.ts",
    "build": "npx tsc index.ts",
    "execute": "node index.js",
  },
  "author": "Andres Reales",
  "devDependencies": {
    "ts-node": "^10.2.1",
    "typescript": "^4.4.3"
  }
}

Now, we can use one command to run our project.

yarn start

What is a tsconfig.json file?

The tsconfig.json file is used to establish a set of standards in which the TypeScript compiler, or tsc will follow. For example, if you don’t want to allow any developer in the team to use the type any, you can set that as part of the tsconfig.json file.

At the moment of compiling the code using tsc, the compiler will check the rules established and will generate a JavaScript code in case the code successfully follows all the rules. If the code fails to follow the guidelines, the tsc compiler will throw an error and no JavaScript code will be generated.

The tsconfig.json plays an important role as it allows to adjust how strict the TypeScript code can be based on each project. Some projects can be more flexible than others. Hence, the set of rules defined on these kinds of projects in the tsconfig.json file tend to be more forgiving to poor development practices.