TypeScript dự án lớn: tsc chậm do CPU hay I/O, sửa gì?

Chia sẻ bài viết

Mục lục
TypeScript dự án lớn: tsc chậm do CPU hay I/O, sửa gì?

Trả lời nhanh: tsc làm hai việc tách được: type-check, suy luận kiểu toàn dự án, ăn CPU đơn luồng là chính; và đọc/ghi, nạp vạn file nguồn + node_modules, ăn I/O. Dự án lớn chậm thường vì cả hai cộng hưởng. Bộ thuốc theo thứ tự: bật incremental (chỉ check phần đổi), skipLibCheck (bỏ check node_modules), tách transpile khỏi check (esbuild/swc dịch code trong mili-giây, tsc chỉ còn vai gác kiểu chạy nền/CI), và nền máy: CPU xung cao + ổ NVMe, đúng khẩu vị của cả hai nửa việc.

Ngày đầu tsc chạy 2 giây, một năm sau 40 giây, và không ai nhớ nó chậm dần từ lúc nào. Bài này mổ tsc thành hai nửa CPU và I/O, dạy cách đo xem dự án của bạn nghẽn nửa nào, rồi đi qua bộ giải pháp từ miễn phí (cấu hình) đến kiến trúc (tách vai), kèm góc nhìn ít bài nào nói: vì sao agent AI làm việc trên repo TypeScript lại nhân đôi tầm quan trọng của mấy thứ này.

Tóm tắt nhanh
  • tsc = type-check (CPU đơn luồng) + đọc ghi file (I/O), hai bệnh hai thuốc
  • incremental + tsBuildInfo: lần check sau chỉ làm phần đổi, công tắc lãi nhất
  • skipLibCheck: bỏ check .d.ts trong node_modules, nhanh hơn hẳn, rủi ro thấp
  • Kiến trúc chuẩn 2026: esbuild/swc lo dịch, tsc lo kiểu (watch nền + chốt ở CI)

Đo trước: dự án bạn nghẽn nửa nào

npx tsc --noEmit --extendedDiagnostics
# nhin cac dong:
#  Files / Lines: quy mo phai doc, lien quan I/O
#  I/O Read time: thoi gian doc file
#  Check time:    thoi gian suy luan kieu, CPU don luong
#  Total time:    tong

Đọc kết quả: Check time áp đảo → bệnh CPU/độ phức tạp kiểu; I/O Read cao hoặc Files hàng vạn → bệnh đọc file (hay gặp khi include quét thừa, hoặc ổ/mount chậm); cả hai cao → dự án lớn thật, cần trọn bộ thuốc. Chạy hai lần liên tiếp: lần hai nhanh hơn hẳn là cache hệ thống đang cứu bạn, dấu hiệu ổ đĩa có vai trò lớn hơn bạn tưởng.

Tầng cấu hình: ba công tắc trong tsconfig

{
  "compilerOptions": {
    "incremental": true,                    // luu .tsbuildinfo, lan sau chi check phan doi
    "skipLibCheck": true,                   // bo check node_modules, an toan voi 99% du an
    "noEmit": true                          // khi da co esbuild/swc lo phan dich
  },
  "include": ["src"],                       // quet dung cho, dung de mac dinh quet ca repo
  "exclude": ["node_modules", "dist", ".next"]
}

incremental là công tắc lãi nhất: sửa một file, chỉ nhánh liên quan được check lại, từ 40 giây về vài giây cho vòng lặp thường nhật. Lưu ý file .tsbuildinfo là cache trên ổ: CI xóa sạch mỗi lần là mất lãi, cache nó lại giữa các lần chạy (cùng bài học với node_modules).

Tầng kiến trúc: tách người dịch khỏi người gác

Insight đổi đời dev TypeScript: chạy code không cần check kiểu. esbuild/swc (nền của Vite, tsx, Next.js mới) chỉ bóc type đi và dịch, nhanh gấp 20-100 lần tsc vì bỏ hẳn phần suy luận. Kiến trúc chuẩn hiện nay:

  • Vòng dev: esbuild/swc dịch tức thì (HMR mượt), tsc --watch chạy nền chỉ để báo lỗi kiểu trong editor.
  • Chốt chặn: tsc --noEmit đứng ở CI/pre-commit, code sai kiểu không qua được cổng, nhưng không ai phải chờ nó trong lúc sáng tạo.

Với dự án có agent AI làm việc: điều này nhân đôi giá trị, agent sửa-chạy-thử hàng chục vòng mỗi phiên, mỗi vòng tiết kiệm 30 giây là mỗi phiên tiết kiệm cả chục phút; và tsc --noEmit trở thành hàng rào khách quan bắt lỗi agent trước khi bạn kịp đọc diff.

Tầng máy: khẩu vị phần cứng của TypeScript

Type-check phần lớn là một luồng, 16 nhân yếu thua 6 nhân xung cao (project references mới chia song song được phần nào). Phần đọc vạn file + .tsbuildinfo + node_modules là I/O nhỏ ngẫu nhiên, sở trường NVMe, tử huyệt của mount mạng (dev trong container mount code từ ngoài chậm gấp nhiều lần là vì đây). Cấu hình ngọt cho dev TypeScript trên server: CPU xung cao + NVMe + RAM đủ cho cache hệ thống ôm trọn node_modules, đúng công thức mà cả cụm bài này đã lặp như điệp khúc, vì mọi công cụ JS hiện đại cùng chung khẩu vị đó.

Đo trước: nghẽn ở vi xử lý hay ở ổ đĩa

# 1. Xem thoi gian phan bo cho tung khau
npx tsc --diagnostics --noEmit

# Doc ba dong quan trong nhat:
#   Files:            so tep phai doc  -> cao la nghen o dia
#   I/O Read time:    thoi gian doc tep
#   Check time:       thoi gian kiem kieu -> cao la nghen vi xu ly

# 2. Do bang dong ho thuc te
time npx tsc --noEmit

# 3. Xem trong luc chay: cho o dia hay chay vi xu ly
vmstat 2 30   # cot wa cao la cho o dia; cot us cao la chay vi xu ly

# 4. Dem so tep thuc su duoc doc
npx tsc --listFiles --noEmit | wc -l
Kết quả đoNghĩa làSửa gì
Số tệp rất lớn, thời gian đọc caoNghẽn ổ đĩaThu hẹp phạm vi, loại thư mục thừa, dùng ổ NVMe
Thời gian kiểm kiểu chiếm phần lớnNghẽn vi xử lýĐơn giản hóa các kiểu phức tạp, tách dự án thành nhiều phần
Cả hai đều caoDự án đã quá lớn cho một lần kiểmTách thành nhiều dự án con và kiểm từng phần

Ba công tắc trong tệp cấu hình

  • Bật chế độ kiểm tăng dần, để lần chạy sau chỉ kiểm phần đã đổi. Đây là thay đổi cho hiệu quả lớn nhất và ít rủi ro nhất.
  • Bỏ qua việc kiểm kiểu của các tệp khai báo trong thư viện. Với dự án có nhiều thư viện, đây là phần chiếm rất nhiều thời gian mà giá trị thấp.
  • Thu hẹp danh sách tệp được đưa vào phạm vi. Rất nhiều dự án vô tình kéo cả thư mục kết quả dựng và thư mục kiểm thử vào.

Tách người dịch khỏi người gác

Cách tổ chức hiệu quả với dự án lớn: dùng một công cụ dịch nhanh để tạo ra kết quả chạy được, và chạy việc kiểm kiểu riêng ở một tiến trình khác, hoặc chỉ chạy trong quy trình kiểm tự động trước khi ghép mã.

Lợi ích: vòng lặp phát triển nhanh hơn hẳn, vì bạn không phải chờ kiểm kiểu toàn bộ mỗi lần lưu tệp. Đánh đổi: lỗi kiểu phát hiện muộn hơn một chút, nên bù lại bằng việc chạy kiểm kiểu trong quy trình tự động và trong trình soạn thảo.

Khẩu vị phần cứng

  • Xung nhịp một luồng quan trọng hơn số nhân, vì phần kiểm kiểu chủ yếu chạy một luồng.
  • Ổ NVMe tạo khác biệt rõ với dự án có nhiều tệp, vì mỗi lần chạy phải đọc hàng nghìn tệp nhỏ.
  • Bộ nhớ đủ để giữ cả dự án trong bộ nhớ đệm của hệ thống tệp. Thiếu bộ nhớ là mỗi lần chạy phải đọc lại từ ổ.

Câu hỏi thường gặp

tsc chậm có phải tại TypeScript dở không?

Không, nó đang làm việc thật sự nặng: chứng minh kiểu đúng cho cả dự án. Cái dở là bắt nó làm lại từ đầu mỗi lần (thiếu incremental) và làm cả việc dịch (đã có người nhanh hơn lo). Cấu hình đúng vai, tsc là người gác đáng tin chứ không phải kẻ cản đường.

skipLibCheck có nguy hiểm không?

Rủi ro thấp: nó bỏ check file .d.ts của thư viện, vốn đã được tác giả thư viện check. Xung đột kiểu giữa các thư viện hiếm gặp sẽ bị che; đổi lại tốc độ đáng kể. Đa số dự án bật và không ngoái lại.

Project references có đáng dùng không?

Đáng với monorepo/dự án chia module rõ: check song song từng phần, cache từng phần. Giá là công cấu hình ban đầu, dự án đơn khối cứ incremental + tách vai là đủ xa.

Vòng lặp với Claude Code nên cấu hình thế nào?

Ghi vào CLAUDE.md: lệnh dev dùng tsx/vite (nhanh), lệnh nghiệm thu là npm run typecheck && npm test. Agent sẽ tự chạy đúng cổng chốt sau mỗi mảnh việc, bạn được cả tốc độ lẫn hàng rào kiểu.

Hạ tầng NVMe Enterprise của TND
VPS ổ Enterprise U.2 NVMe, CPU xung cao, đặt tại Việt Nam
Cả hai nửa của tsc đều có thuốc phần cứng tại TND: Xeon Platinum xung cao cho nửa type-check đơn luồng, Enterprise U.2 NVMe RAID 10 cho nửa vạn-file và cache tsBuildInfo. Dev TypeScript qua SSH + agent trong tmux: vòng sửa-check-chạy ngắn lại thấy rõ từng phiên. Bàn giao 5 phút.
Xem bảng giá VPS NVMe Xeon Platinum

Bài viết liên quan