TypeScript 基礎概念:編譯與轉譯(Transpiling to JS)
簡介
在前端開發的生態系統裡,TypeScript 已成為許多大型專案的首選語言。它在 JavaScript 的基礎上加入了靜態型別、介面、列舉等語法,讓程式碼更易維護、錯誤更早被捕捉。然而,瀏覽器只能直接執行 JavaScript,因此 TypeScript 必須先 編譯(transpile) 成純 JavaScript,才能在瀏覽器或 Node.js 環境中運行。
了解編譯與轉譯的流程不只是「把 .ts 檔案變成 .js」那麼簡單,還涉及 編譯設定(tsconfig.json)、目標語法版本(ES5、ES6…)、模組系統(CommonJS、ESM)以及 source map、型別檢查 等重要概念。掌握這些基礎,才能在實務開發中避免不必要的錯誤、提升建置效率。
核心概念
1. 為什麼需要轉譯?
- 瀏覽器相容性:舊版瀏覽器只支援 ES5,若直接寫 ES6+(例如
let、async/await),必須透過轉譯降級才能執行。 - 型別安全:TypeScript 會在編譯階段檢查型別,若有錯誤會直接在編譯時期阻止產出 JS,避免執行時的未預期行為。
- 開發體驗:利用編譯器的 增量編譯(incremental compilation)與 watch mode,即時看到錯誤並自動產生最新的 JavaScript 檔案。
2. tsc 基本使用
tsc(TypeScript Compiler)是官方提供的編譯工具。最簡單的編譯指令如下:
# 將單一檔案編譯成 JavaScript
tsc hello.ts
執行後會在同目錄產生 hello.js,內容與原始 TypeScript 基本相同,只是去除了型別宣告。
3. tsconfig.json:編譯設定的核心
在大型專案中,我們不會每次手動指定檔案與選項,而是透過 tsconfig.json 統一管理。以下是一個常見的範例:
{
"compilerOptions": {
"target": "ES2017", // 產出語法目標
"module": "ESNext", // 使用的模組系統
"strict": true, // 開啟所有嚴格型別檢查
"esModuleInterop": true, // 允許 CommonJS 模組的預設匯入
"sourceMap": true, // 產生 .map 供除錯
"outDir": "./dist", // 輸出目錄
"rootDir": "./src", // 原始碼根目錄
"removeComments": false, // 保留註解(方便除錯)
"noEmitOnError": true // 有型別錯誤時不產出檔案
},
"include": ["src/**/*.ts"], // 包含的檔案
"exclude": ["node_modules"] // 排除的檔案
}
重點說明
target決定最終產出的 JavaScript 版本,常見值:ES5、ES2015(ES6)、ES2020、ESNext。module控制匯入/匯出語法,CommonJS用於 Node.js,ESNext用於原生 ESM。strict開啟一系列嚴格檢查(noImplicitAny、strictNullChecks等),是 最佳實踐。
4. 常見的轉譯選項與範例
(1)降級至 ES5(支援 IE11)
{
"compilerOptions": {
"target": "ES5",
"module": "CommonJS"
}
}
// src/example.ts
const greet = (name: string): string => {
return `Hello, ${name}!`;
};
export default greet;
編譯後的 dist/example.js 會變成:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function greet(name) {
return "Hello, " + name + "!";
}
exports.default = greet;
(2)保留 ES6+ 語法(配合 Babel 或現代瀏覽器)
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext"
}
}
// src/asyncDemo.ts
export async function fetchData(url: string): Promise<any> {
const response = await fetch(url);
return response.json();
}
編譯結果保留 async/await,讓後續的 Babel 或瀏覽器直接執行:
export async function fetchData(url) {
const response = await fetch(url);
return response.json();
}
(3)產生 Source Map 方便除錯
{
"compilerOptions": {
"sourceMap": true,
"outDir": "./dist"
}
}
編譯時會同時產生 example.js.map,在 Chrome DevTools 中開啟「Enable source maps」即可看到原始的 TypeScript 程式碼。
(4)使用 incremental 與 watch 提升開發效率
# 增量編譯,僅重新編譯變更的檔案
tsc --incremental
# 監聽檔案變化,自動編譯
tsc -w
5. 與其他工具的整合
| 工具 | 角色 | 為何要配合使用 |
|---|---|---|
| Webpack | 打包、模組合併 | 讓多個 .js 檔案一次性輸出為單一 bundle,減少 HTTP 請求 |
| Babel | 語法轉譯、Polyfill | 即使 TypeScript 已降級至 ES5,仍可透過 Babel 加入最新語法的 polyfill(例如 Array.flat) |
| ESLint + @typescript-eslint | 程式碼風格、靜態檢查 | 在編譯前就捕捉潛在問題,避免不必要的編譯失敗 |
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記設定 noEmitOnError |
編譯時仍產出 .js,導致錯誤的程式碼被部署 |
在 tsconfig.json 中加入 "noEmitOnError": true |
target 與執行環境不匹配 |
目標設定過高(例如 ES2022)但執行環境僅支援 ES5,會跑出語法錯誤 | 依照部署環境(瀏覽器或 Node 版本)調整 target |
使用 any 逃避型別檢查 |
失去 TypeScript 的最大優勢,程式碼變得不安全 | 開啟 noImplicitAny,盡量使用具體型別或 unknown |
| 未產生 Source Map | 除錯時只能看到編譯後的 JS,難以定位錯誤 | 設定 "sourceMap": true,或在 Webpack 中使用 devtool: 'source-map' |
| 模組解析錯誤(CommonJS vs ESNext) | 匯入/匯出語法在不同模組系統下行為不同,可能出現 default is not a function 的錯誤 |
根據執行環境統一 module 設定,並使用 esModuleInterop: true 來兼容 CommonJS 模組 |
最佳實踐
- 始終使用
strict:即使在小型專案,也建議開啟嚴格模式,能在開發早期捕捉到潛在問題。 - 把編譯交給腳本:在
package.json中加入build、watch指令,讓團隊成員只要執行npm run build即可得到正確產物。{ "scripts": { "build": "tsc", "watch": "tsc -w" } } - 分離開發與生產設定:使用
tsconfig.dev.json(開啟sourceMap、noEmitOnError)和tsconfig.prod.json(關閉sourceMap、啟用removeComments)來優化建置速度與檔案大小。 - 結合 CI/CD:在 CI pipeline 中加入
tsc --noEmit只做型別檢查,確保每次 PR 都通過型別安全測試。
實際應用場景
1. 前端單頁應用(SPA)
在 React、Vue、Angular 等框架中,通常會使用 Webpack + ts-loader 或 Vite + @vitejs/plugin-react-swc 來即時編譯 TypeScript。設定 target: "ES2017"、module: "ESNext",讓瀏覽器直接執行 ES 模組,同時利用 Babel 處理尚未支援的語法。
2. Node.js 後端服務
Node.js 12+ 已原生支援 ES Modules,只要把 tsconfig.json 設為:
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2020",
"outDir": "./dist",
"sourceMap": true
}
}
再配合 ts-node-dev(ts-node-dev --respawn src/index.ts)即可在開發階段直接執行未編譯的 TypeScript,省去每次手動 tsc 的流程。
3. 建立共用函式庫(npm package)
發布 npm 套件時,通常會同時輸出 兩種目標:
dist/index.cjs.js(CommonJS)供 Node 使用dist/index.esm.js(ESM)供現代前端工具鏈使用
透過 tsc 的 多輸出 設定或結合 Rollup,一次完成兩種格式的編譯與打包,確保套件在不同環境都有最佳相容性。
總結
TypeScript 的編譯與轉譯 不只是把 .ts 變成 .js 那麼簡單。透過 tsc、tsconfig.json、Source Map、嚴格模式 等工具,我們可以在開發階段即時捕捉型別錯誤、產出符合目標環境的 JavaScript,並與 Webpack、Babel、ESLint 等生態系統無縫整合。
掌握以下要點,您就能在 實務專案 中自信地使用 TypeScript:
- 依需求設定
target與module,保證相容性與效能。 - 開啟
strict、noEmitOnError、sourceMap,讓型別檢查與除錯更可靠。 - 使用增量編譯與 watch mode,提升開發迭代速度。
- 將編譯流程寫入 npm script,在團隊中保持一致的建置方式。
透過正確的編譯設定與最佳實踐,您不只能寫出 型別安全、易維護 的程式碼,還能在部署時獲得 最小化、相容性佳 的 JavaScript,為專案的長期成功奠定堅實基礎。祝您在 TypeScript 的旅程中玩得開心、寫得順手!