TypeScript Isolated Declarations: เพิ่มความเร็วในการ Build Monorepo
การ Build Monorepo มักจะช้าลงเนื่องจากวิธีการที่ TypeScript จัดการกับไฟล์ declaration โดยปกติแล้ว TypeScript จะวิเคราะห์ทุก import chain ก่อนที่จะสร้างไฟล์ .d.ts เพียงไฟล์เดียว สิ่งนี้ทำให้เกิดคอขวดที่แพ็กเกจต่างๆ ต้องรอซึ่งกันและกัน
คุณสามารถแก้ไขปัญหานี้ได้ด้วย flag --isolatedDeclarations
flag นี้ช่วยให้สามารถ build แบบขนาน (parallel builds) ได้ แทนที่จะต้องรอ dependency แต่ละแพ็กเกจจะสร้าง declaration ของตัวเองได้อย่างเป็นอิสระ ทีมพัฒนาจะพบว่าความเร็วในการ build เพิ่มขึ้นถึง 3 เท่า ถึง 15 เท่า
The Problem:
โดยปกติแล้ว TypeScript จะทำการ infer type ข้ามขอบเขตของ module หาก Package A ขึ้นอยู่กับ Package B ตัว compiler จะต้องประมวลผล B ให้เสร็จสมบูรณ์ก่อนจึงจะเริ่มทำ A ได้ ใน monorepo ขนาดใหญ่ แพ็กเกจส่วนใหญ่จึงต้องหยุดรอในขณะที่ compiler ทำงานทีละแพ็กเกจ
The Solution:
flag --isolatedDeclarations จะเปลี่ยนกฎเกณฑ์ใหม่ โดยกำหนดให้คุณต้องเขียน type annotation แบบชัดเจน (explicit) สำหรับทุกอย่างที่คุณ export ออกไป
The Tradeoff:
โค้ดของคุณจะมีจำนวนบรรทัดมากขึ้น คุณต้องเพิ่ม return type ให้กับฟังก์ชัน และเพิ่ม type ให้กับ constant ด้วยตัวเอง คุณไม่สามารถพึ่งพาให้ compiler คาดเดา type เหล่านี้จากโค้ดของคุณได้อีกต่อไป
The Result:
เนื่องจากทุกการ export มี type ที่ชัดเจน compiler จึงไม่จำเป็นต้องไปดูแพ็กเกจอื่นเพื่อทำความเข้าใจพวกมัน ทำให้สามารถประมวลผลทุกแพ็กเกจได้พร้อมกัน
Real World Results:
- Monorepo ที่มี 18 แพ็กเกจ สามารถลดเวลา build จาก 47 วินาที เหลือเพียง 3.2 วินาที
- Monorego ที่มี 32 แพ็กเกจ พบว่าความเร็วใน CI pipelines เพิ่มขึ้นถึง 8 เท่า
- ประสิทธิภาพจะเพิ่มขึ้นตามจำนวน CPU core ยิ่งมี core มากขึ้น ก็ยิ่ง build แพ็กเกจได้พร้อมกันมากขึ้น
How to Migrate:
- เปิดใช้งาน flag ใน
tsconfig.jsonที่ root และใน config ของทุกแพ็กเกจ - ใช้ flag
"composite"เพื่ออนุญาตให้ใช้ project references - เพิ่ม explicit return types ให้กับฟังก์ชันที่ export:
// ก่อน
export function add(a: number, b: number) {
return a + b;
}
// หลัง
export function add(a: number, b: number): number {
return a + b;
}
- เพิ่ม explicit types ให้กับ constant ที่ export:
// ก่อน
export const SETTINGS = { port: 3000 };
// หลัง
export const SETTINGS: { port: number } = { port: 3000 };
หากคุณไม่เพิ่ม type เหล่านี้ compiler จะแจ้ง error ซึ่งสิ่งนี้จะช่วยให้มั่นใจได้ว่า type ของคุณมีความแน่นอน (deterministic) และทำงานได้อย่างรวดเร็ว
