ข้ามไปยังเนื้อหา

Migration Strategy

การ rewrite JS codebase จริงไป TypeScript ทีเดียวคือวิธีที่ migration ตายกัน จุดสำคัญของ TypeScript คือเป็น type system แบบ gradual: JS และ TS อยู่ด้วยกันได้ทีละไฟล์ คุณจึงแปลงทีละนิดขณะที่ยัง ship ได้ตลอด

flowchart LR
  js["allowJs: TS builds
alongside JS"] --> checkjs["checkJs: type-check
the JS too"]
  checkjs --> convert["rename .js to .ts
file by file"]
  convert --> strict["ratchet strict flags
one at a time"]
ratchet การ migrate แบบค่อยเป็นค่อยไป
  1. เปิด allowJs ตอนนี้ TypeScript compile project แบบผสมได้ — ไฟล์ .js build เหมือนเดิม และคุณเริ่มเพิ่มไฟล์ .ts ข้าง ๆ ได้
  2. เพิ่ม checkJs (และ // @ts-check) TypeScript type-check JavaScript ของคุณด้วย inference และ JSDoc คุณได้ error จริงก่อนแปลงไฟล์สักไฟล์ — first pass ฟรี ๆ
  3. แปลงทีละไฟล์ rename .js เป็น .ts, แก้ error ที่โผล่, commit เริ่มจาก leaf (utility ที่ dependency น้อย) แล้วไล่เข้าหา core
  4. ratchet strictness อย่าเริ่มด้วย strict เต็ม เปิด flag ทีละตัว — noImplicitAny ก่อน แล้ว strictNullChecks แล้วที่เหลือ แก้แต่ละ wave ให้เสร็จก่อนเปิดตัวถัดไป

แต่ละ step ship ได้ คุณไม่เคยติดอยู่ในสภาพ migrate ค้างครึ่ง ๆ นาน ๆ

ระหว่าง migrate any เป็นวิธีที่ถูกต้องในการบอกว่า “ยังไม่ type — ไปต่อก่อน” อันตรายคือปล่อยทิ้งไว้ วินัยสองข้อช่วยให้ any ซื่อสัตย์:

  • ทำให้ explicit และ grep เจอ เลือก any ที่ explicit (หรือ alias // TODO: type this) แทน implicit เพื่อให้หาและกำจัดทีหลังได้
  • ratchet ด้วย noImplicitAny พอเปิด compiler เลิกแอบใส่ any — code ใหม่ต้อง type หนี้จึงมีแต่ลดลง

เมื่อต้อง suppress error ให้ใช้ @ts-expect-error ไม่ใช่ @ts-ignore:

// @ts-expect-error — legacy shape, tracked in TICKET-123
legacyCall(weirdValue);

ทั้งคู่ silence error ในบรรทัดถัดไป แต่ @ts-expect-error จะ error ด้วยถ้าบรรทัดนั้นเลิกมี error ดังนั้นเมื่อคุณแก้ type ที่เป็นต้นเหตุทีหลัง suppression ที่ไม่จำเป็นแล้วจะทำให้ build พัง และบอกให้คุณลบทิ้ง ส่วน @ts-ignore แค่เน่าเงียบ ๆ

ถ้าคุณ publish package มีกฎเพิ่มอีกไม่กี่ข้อ:

  • ship declaration file (.d.ts) ตั้ง "declaration": true เพื่อให้ผู้ใช้ได้ type ของคุณ ชี้ "types" ใน package.json ไปที่ entry .d.ts
  • อย่าให้ internal type leak เฉพาะ type ของ public API เท่านั้นที่เป็น contract เก็บ type ของ implementation ไว้ไม่ export เพื่อให้เปลี่ยนได้อย่างอิสระ
  • มอง type เป็น semver การเปลี่ยนที่ทำให้ type checking ของผู้ใช้พังคือ breaking change แม้ runtime behavior จะเหมือนเดิม การ widen parameter มักปลอดภัย; การ narrow parameter หรือเปลี่ยน return type ทำให้ build ของ downstream พังได้
วิธีที่แนะนำในการ migrate JS codebase ไป TypeScript คืออะไร?
`checkJs` (พร้อม @ts-check) ให้อะไรก่อนแปลงไฟล์?
ทำไมควรเลือก `@ts-expect-error` มากกว่า `@ts-ignore`?
สำหรับ library ที่ publish ทำไมต้องมอง type เป็นส่วนหนึ่งของ semver?