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

Build & Emit

โมดูลนี้เปิดด้วยไอเดียว่า tsc ทำสองงาน — check กับ emit ใน setup สมัยใหม่งานสองอย่างนี้มักถูกทำโดย เครื่องมือคนละตัว และการเข้าใจว่าทำไมทำให้ config ชัดเจนขึ้น

flowchart LR
  src["source .ts"] --> bundler["bundler (esbuild/swc/vite)
strips types, emits JS fast"]
  src --> tsc["tsc --noEmit
checks types"]
  bundler --> out["shipped JavaScript"]
  tsc --> ci["pass/fail in CI"]
build แบบสองราง
  • bundler (esbuild, swc, Vite) transpile ไฟล์แต่ละไฟล์ โดย strip type ออก เร็วมากเพราะไม่เคย check อะไรเลย — แค่ดูทีละไฟล์แล้วลบ type ทิ้ง
  • tsc --noEmit ทำ type check ทั้งโปรแกรมและไม่ produce output ออกมา นี่คือตัวที่จับ bug ของคุณจริง ๆ และคุณควรรันใน CI (และ editor ก็รันให้คุณตลอดเวลา)

ship สิ่งที่ bundler emit; เชื่อสิ่งที่ tsc รายงาน noEmit บอก tsc ให้เป็น checker ล้วน ๆ

เพราะ bundler transpile ทีละไฟล์ จึงมองไม่เห็น type จากไฟล์อื่น มี TypeScript construct ไม่กี่อย่างที่กำกวมภายใต้ single-file transpilation — ที่สำคัญที่สุดคือ การ re-export type ดูเหมือนกับการ re-export value เป๊ะ isolatedModules: true ทำให้ tsc flag อะไรก็ตามที่ single-file transpiler อาจทำพลาด เพื่อให้ code ของคุณเข้ากันได้กับ esbuild และ swc

// with isolatedModules, tsc forces you to be explicit here:
export type { User } from "./user"; // ✅ clearly a type re-export
export { User } from "./user"; // ❌ ambiguous: is User a type or a value?

การแก้ข้างบนเป็นส่วนหนึ่งของ feature ที่กว้างกว่า: type-only import และ export การ mark import เป็น type-only บอก compiler ว่ามีอยู่เพื่อ type information ล้วน ๆ และต้องถูก erase — ไม่ emit เป็น runtime import เด็ดขาด

import type { User } from "./user"; // erased entirely at emit
import { type User, save } from "./db"; // User erased, save kept

verbatimModuleSyntax: true บังคับวินัยนี้: import ตัวไหนที่ ไม่ ถูก mark type จะถูก emit ตรง ๆ เป็น runtime import setting นี้กำจัด class ของเรื่องเซอร์ไพรส์ที่ compiler แอบ drop หรือเก็บ import ไว้ และเป็น setting ที่แนะนำสำหรับโปรเจกต์ใหม่

ใน repo ใหญ่ที่มีหลาย package การ type-check ทุกอย่างใหม่หมดทุกครั้งที่แก้จะช้า project references ให้คุณแบ่ง codebase เป็นโปรเจกต์ที่ build แยกกันได้พร้อม dependency ที่ประกาศไว้:

{
"references": [{ "path": "../core" }],
"compilerOptions": { "composite": true }
}

เมื่อเปิด composite: true tsc --build จะ check แต่ละโปรเจกต์ที่ reference ครั้งเดียว, cache ผลไว้ และ recheck เฉพาะสิ่งที่เปลี่ยน วิธีนี้เปลี่ยนการ check แบบ O(ทั้ง repo) ให้เป็นแบบ incremental — วิธีมาตรฐานในการทำให้ type-checking เร็วเมื่อ monorepo โตขึ้น

ใน setup สมัยใหม่ เครื่องมือตัวไหนที่จับ type error จริง ๆ?
ทำไม `isolatedModules` ถึงแนะนำเมื่อใช้ bundler?
`import type { User }` การันตีอะไร?
project references พร้อม `composite: true` แก้ปัญหาอะไร?