---
title: "Khám Phá Cách Hoạt Động Của TypeScript Compiler"
description: "Bạn đã code Typescript một khoảng thời gian, nhưng vẫn còn mơ hồ về cách mà Typescript biên dịch sang Javascript. Bài viết này sẽ giúp bạn hiểu rõ chi tiết về từng bước trong quá trình biên dịch."
canonical: "https://200lab.io/blog/cach-hoat-dong-typescript-compiler"
published: "2024-08-12T09:52:57Z"
updated: "2026-08-21T07:17:39Z"
authors: ["Tran Thuy Vy"]
tags: ["Backend"]
reading_minutes: 5
access: "public"
---

Bạn đã code Typescript một khoảng thời gian, nhưng vẫn còn mơ hồ về cách mà Typescript biên dịch sang Javascript. Bài viết này sẽ giúp bạn hiểu rõ chi tiết về từng bước trong quá trình biên dịch.

[Khoá học lập trình microservices với Typescript và Express](<https://200lab.io/khoa-hoc-lap-trinh-microservices-voi-typescript-va-express>)

## 1. Typescript Compiler là gì?

TypeScript Compiler (hay `tsc` ) là công cụ biên dịch code [TypeScript](</blog/typescript-la-gi>) (.ts) thành [JavaScript](</blog/javascript-la-gi>) (\*.js), cùng với tệp định nghĩa kiểu dữ liệu (\*.d.ts) và source maps (\*.js.map), giúp mã chạy trên trình duyệt và môi trường [Nodejs](</blog/nodejs-la-gi>).

![tsc high level](https://assets.200lab.io/images/2024/12/05/tsc-high-level-e04f1fd3fdeb9bfdc1a8.webp)

Ngoài ra, nếu như source của bạn gặp problems, Typescript compiler sẽ giúp bạn phát hiện và khắc phục nó.

## 2. Các thành phần cơ bản của compiler

### 2.1 Parser

Parser là bộ phân tích cú pháp, chia nhỏ mã nguồn thành các phần tử (tokens) như biến, hàm và tạo ra cây biểu diễn cú pháp AST (Abstract Syntax Tree) - mỗi nút đại diện cho thành phần mã.

### 2.2 Binder

Binder sẽ liên kết các thành phần như biến, hàm, lớp với định nghĩa trong source code.

Ví dụ như khi bạn khai báo biến `let a = 10;`, compiler sẽ nhận diện `a` là một thành phần cụ thể là biến, và gắn kết với `a` là kiểu dữ liệu `number`.

### 2.3 Type Checker

Type checker là một trong những tính năng mạnh nhất của Typescript. Type checker sẽ đảm bảo tất cả các thành phần trong code đều tuân theo quy tắc mà bạn định nghĩa.

Nếu bạn khai báo một biến với kiểu là `number` nhưng sau đó lại cố gắng gán cho biến đó giá trị kiểu `string`, type checker sẽ phát hiện và báo lỗi.

### 2.4 Emitter

Emitter sẽ tuần tự duyệt qua các nút của AST và phát sinh ra mã Javascript tương ứng.

Ví dụ như trong typescript: `let a:number = 200;` sẽ được phát sinh thành `var a = 200;` trong javascript.

### 2.5 Scanner

Scanner là bộ quét mã nguồn, phân tích mã để chuyển đổi thành tokens.

Ví dụ: `let a = 200;` sẽ được scanner quét thành: `let`, `a`, `=`, `200` và `;`

## 3. Quá trình hoạt động compiler

Sau khi đã hiểu hơn về các thành phần cơ bản, thì mình sẽ đề cập đến quá trình hoạt động compiler:

![Các thành phần cơ bản của Typescript Compiler](https://assets.200lab.io/images/2024/12/05/qua-trinh-compile-typescript-f9844ce18a79ca5fdfdb.webp)

Quá trình biên dịch TypeScript bắt đầu khi bạn chạy lệnh `tsc`. Để thực hiện, TypeScript Compiler cần file `tsconfig.json`:

1. **Compiler Options**: phần này chứa các cấu hình cho trình biên dịch Typescript.
2. **Input Files**: phần này chứa các file TypeScript cần biên dịch.

```
{
	"compilerOptions": {
    	...
    },

    "files": [
        "src/*.ts"
    ],
}
```

Quá trình biên dịch sẽ được tạo ra dưới dạng đối tượng `Program`, được định nghĩa trong file **`src/compiler/program.ts`.**

Khi đối tượng được tạo, nó sẽ tải tất cả các input files và các tệp phụ thuộc. Sau đó, gọi `Parser`- được định nghĩa trong tệp `src/compiler/parser.ts` để phân tích từng files thành AST (Abstract Syntax Tree).

![tsc program parser to ast](https://assets.200lab.io/images/2024/12/05/tsc-program-parser-to-ast-24e1e8801ece03f41aa0.webp)

Trong quá trình hoạt động, `Parser` tạo ra đối tượng `Scanner` được định nghĩa trong file `src/compiler/scanner.ts`, nó sẽ quét qua source code và sinh ra chuỗi `SyntaxKind` tokens.

Sau đó, AST sẽ được truyền cho `Binder` được định nghĩa trong tệp `src/compiler/binder.ts` để tạo map giữa các nút AST và Symbols.

![tsc map ast binder symbols](https://assets.200lab.io/images/2024/12/05/tsc-map-ast-binder-symbols-f9503320088733cff56d.webp)

Một `Symbol` bổ sung vào metadata, để lưu trữ thông tin kiểu của mỗi nút. `Binder` tạo ra một symbols table, sẽ được sử dụng sau này để kiểm tra kiểu dữ liệu.

Sau đó, lệnh `Program.emit` được gọi, `Emit Worker` sẽ được tạo ra để chuyển đổi AST thành code JavaScript và các tệp khác. Có hai loại emitter:

1. **JavaScript Emitter**: được định nghĩa trong `src/compiler/emitter.ts`, emitting source code JavaScript và source maps.
2. **Type Definition Emitter**: được định nghĩa trong `src/compiler/definitionEmitter.ts`, emitting các file định nghĩa kiểu dữ liệu.

Khi Emitter chạy, nó sẽ gọi hàm `getDiagnostics()` để tạo một `Type Checker`, được định nghĩa trong file `src/compiler/checker.ts`. Sau đó, Emitter sẽ duyệt qua AST để xử lý từng nút.

Trên mỗi nút, nó sẽ phân tích mã, sử dụng dữ liệu kiểu từ `Symbols Table`, và nếu mọi thứ không có vấn đề, JavaScript sources cuối cùng sẽ được sinh ra.

![tsc emitter](https://assets.200lab.io/images/2024/12/05/tsc-emitter-5762d88ad2dd68132b17.webp)

## 4. Báo lỗi khi Typescript Compiler

Nhiều loại lỗi khác nhau có thể được trả về trong quá trình biên dịch, phụ thuộc vào giai đoạn trình biên dịch tìm thấy lỗi.

Đây là cách các lỗi được định nghĩa và sử dụng trong hệ thống compiles:

```
enum BuildResultFlags {
    None = 0,
    Success = 1 << 0,
    DeclarationOutputUnchanged = 1 << 1,

    ConfigFileErrors = 1 << 2,
    SyntaxErrors = 1 << 3,
    TypeErrors = 1 << 4,
    DeclarationEmitErrors = 1 << 5,
    EmitErrors = 1 << 6,

    AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors
}
```

- Nếu có lỗi trong tệp tsconfig.json, ConfigFileErrors sẽ được trả về.
- Nếu lỗi được Scanner tìm thấy,  SyntaxErrors được trả về.
- Nếu code được viết đúng cú pháp, nhưng không đúng ngữ nghĩa, đa phần sẽ trả về TypeErrors.

## 5. Tối ưu hóa quá trình biên dịch

- Sử dụng `tsc --watch`: cho phép compiler giám sát thay đổi trong source code, tự động compiles chỉ thành phần thay đổi.
- Tối ưu cấu trúc dự án: chia dự án thành các module nhỏ hơn dễ bảo trì source code.
- Sử dụng `skipLibCheck`: Khi làm việc với các thư viện bên ngoài, bạn có thể sử dụng `skipLibCheck` để bỏ qua kiểm tra kiểu của các file `.d.ts` .

## 6. Kết luận

Hy vọng thông qua bài viết này, bạn đã hiểu rõ quá trình chi tiết compiles.

Nếu bạn quan tâm các chủ đề khác liên quan, bạn có thể tham khảo một số bài viết dưới đây:

- [Backend cơ bản: REST API Versioning - Quản lý các Version của REST API](</blog/api-versioning-la-gi>)
- [Backend cơ bản: Tìm hiểu API Gateway](</blog/api-gateway-la-gi>)
- [NestJS: Giải Pháp Toàn Diện Cho Ứng Dụng Server-Side](</blog/nestjs-giai-phap-toan-dien-cho-ung-dung-server-side>)
