Ba điều người ta gọi là “biên dịch TypeScript”
Khi các nhà phát triển tìm cách biên dịch TypeScript thành binary, họ thường gặp ba kỹ thuật rất khác nhau nhưng dùng chung một từ:
- Transpiling.
tsc, SWC và esbuild biến TypeScript thành JavaScript. Đầu ra vẫn cần Node.js, Bun hoặc trình duyệt để chạy. Không có binary nào ở đây. - Nhúng runtime.
bun build --compile,deno compile, và Node.js Single Executable Applications (SEA) nối JavaScript đã đóng gói của bạn với một bản sao đầy đủ của runtime. Bạn có được một tệp duy nhất, nhưng engine vẫn đi kèm bên trong nó và mã của bạn vẫn được parse và biên dịch JIT mỗi lần tiến trình khởi động. - Biên dịch gốc ahead-of-time. Đây là những gì Perry làm. TypeScript được parse bằng SWC, kiểu được giải quyết, generic được monomorphize, và LLVM sinh ra mã máy. Linker tạo ra một tệp thực thi bình thường — cùng loại artifact mà toolchain Rust, Go hay C++ tạo ra. Không hề có engine JavaScript nào trong binary.
Vì không có engine nào cần khởi động và không có gì cần parse lúc khởi chạy, một binary Perry khởi động trong khoảng một mili giây. Bản thân pipeline này được mô tả chi tiết hơn trên trang trình biên dịch TypeScript gốc và trong phần cấu trúc bên trong trình biên dịch.
Binary lớn cỡ nào?
Kích thước phụ thuộc vào những gì bạn đưa vào, vì chỉ mã bạn thực sự sử dụng mới được biên dịch và liên kết:
- Một hello world có kích thước khoảng 330 KB.
- Các công cụ CLI điển hình rơi vào khoảng 2–5 MB.
- Ứng dụng đầy đủ liên kết các framework lớn (Fastify, mysql2, và tương tự) có kích thước khoảng 48 MB.
Để so sánh: một tệp thực thi Node SEA chính là một bản sao của binary node, nên nó đã ở khoảng 88–118 MB tùy nền tảng trước cả khi thêm mã của bạn, còn một hello world biên dịch bằng Bun có kích thước khoảng 60 MB trên macOS arm64 và khoảng 100 MB trên Linux x64, vì toàn bộ runtime Bun được nhúng vào.
Perry vs bun build --compile vs Node SEA
Cả ba đều cho bạn một tệp duy nhất có thể đưa cho người khác. Ngoài ra chúng là những công cụ rất khác nhau, và mỗi công cụ là câu trả lời đúng cho một nhu cầu khác nhau:
| Perry | bun build --compile | Node SEA | |
|---|---|---|---|
| Nó tạo ra gì | Mã máy biên dịch AOT (LLVM) | JS đóng gói + runtime Bun nhúng kèm | Bản sao của binary node với script đã đóng gói của bạn được tiêm vào |
| Mô hình thực thi | Mã gốc, không có engine JS | JIT (JavaScriptCore) lúc runtime | JIT (V8) lúc runtime |
| Kích thước hello-world | ~330 KB | ~60 MB (macOS arm64) đến ~100+ MB (Linux/Windows) | ~88–118 MB (kích thước của binary node) |
| Khởi động | ~1 ms | ~10 ms | ~30 ms |
| Biên dịch chéo | 10 mục tiêu, bao gồm Windows/macOS/iOS từ Linux | Có — Linux, Windows, macOS qua --target | Không — thay vào đó sao chép binary node theo từng nền tảng |
| Khả năng tương thích JS/npm | Đang mở rộng: axios, zod v4, express, fastify, hono biên dịch gốc; phần còn lại có tùy chọn V8 dự phòng | Đầy đủ — vì đó chính là runtime Bun | Đầy đủ ngữ nghĩa Node; yêu cầu đóng gói trước, chỉ CommonJS trên Node 24 LTS |
| Trạng thái | Trước 1.0 | Ổn định | Độ ổn định “Đang phát triển tích cực” trong Node 24 LTS |
Nhìn nhận thẳng thắn: nếu ứng dụng của bạn dựa nhiều vào toàn bộ hệ sinh thái npm và bạn muốn rủi ro tương thích bằng không, Bun và Node SEA chạy đúng ngữ nghĩa engine mà bạn đã phát triển theo — đó là thế mạnh của chúng, và cái giá về kích thước có thể không quan trọng với việc triển khai của bạn. Perry là một sự đánh đổi khác. Bạn có được biên dịch ahead-of-time thực sự, binary nhỏ và khởi động tính bằng mili giây; đổi lại bạn chấp nhận một trình biên dịch trước 1.0 mà độ tuân thủ JavaScript được đo lường và công bố (test262: String 79%, Array 72% tính đến v0.5.1146) thay vì được kế thừa từ V8.
So sánh chi tiết: Perry vs Bun và Perry vs Deno. Về cách các gói npm biên dịch, xem Các package npm thực tế và một lượt quét conformance.