TypeScript
編譯與建構工具整合 — Webpack + ts‑loader
簡介
在現代前端開發中,TypeScript 已成為大型專案的事實標準,而 Webpack 則是最常見的模組打包工具。將兩者結合,能讓開發者在寫程式時即享有靜態型別檢查的好處,同時在建構階段自動完成檔案轉譯、模組解析與資源壓縮。
如果只使用 tsc 直接編譯 TypeScript,最終產出的檔案仍需要手動整合、設定別名或處理 CSS、圖片等非 JavaScript 資源。Webpack + ts‑loader 則提供了一條「一站式」的解決方案:從原始的 .ts、.tsx 檔案開始,經過 loader 轉譯,再交給 Webpack 完成打包、分割、優化,最後輸出可直接在瀏覽器執行的 bundle。
本篇文章將從 概念說明、實作範例、常見陷阱 到 最佳實踐,一步步帶你建立一個可在開發與生產環境皆適用的 TypeScript + Webpack 工作流程。即使你是剛接觸 TypeScript 的新手,也能在閱讀完後自行在專案中套用。
核心概念
1. 為什麼選擇 Webpack?
- 模組化管理:支援 ES6、CommonJS、AMD 等多種模組規範,讓不同來源的程式碼可以無縫整合。
- 資源管線:除了 JavaScript,還能處理 CSS、圖片、字型等靜態資源,全部走同一條 pipeline。
- 開發體驗:配合
webpack-dev-server或webpack serve,提供即時重載 (Hot Module Replacement) 與 source map,除錯更方便。
2. ts‑loader 是什麼?
ts-loader 是一個 Webpack loader,負責把 TypeScript 檔案交給官方的 tsc(TypeScript Compiler)編譯,然後把編譯後的 JavaScript 傳回給 Webpack。它的特點包括:
| 特性 | 說明 |
|---|---|
與 tsconfig.json 完全同步 |
只要在 tsconfig.json 中設定好編譯選項,ts-loader 會自動讀取。 |
增量編譯 (transpileOnly) |
只做語法轉換,不做型別檢查,配合 fork-ts-checker-webpack-plugin 可在另一執行緒做型別檢查,提升編譯速度。 |
| 支援 Babel | 若想同時使用 Babel 轉換新語法,可在 ts-loader 之後接上 babel-loader。 |
3. 基本設定步驟
下面示範一個最小化的專案結構與設定檔,讓你快速跑起來:
my-project/
├─ src/
│ ├─ index.ts
│ └─ utils.ts
├─ tsconfig.json
├─ webpack.config.js
└─ package.json
3.1 package.json
{
"name": "webpack-ts-demo",
"version": "1.0.0",
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production"
},
"devDependencies": {
"typescript": "^5.4.0",
"webpack": "^5.90.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.0",
"ts-loader": "^9.5.1",
"fork-ts-checker-webpack-plugin": "^9.0.2"
}
}
提示:使用
npm i -D安裝上表的 devDependencies,或改用yarn add -D。
3.2 tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"sourceMap": true,
"outDir": "./dist",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
3.3 webpack.config.js
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
entry: './src/index.ts', // 入口檔案
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'), // 輸出目錄
clean: true // 每次 build 前清空 dist
},
resolve: {
extensions: ['.ts', '.js'] // 讓 import 時可省略副檔名
},
module: {
rules: [
{
test: /\.ts$/, // 只處理 .ts 檔案
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true // 交給 ForkTsChecker 做型別檢查
}
}
],
exclude: /node_modules/
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin() // 型別檢查外掛
],
devtool: 'source-map', // 產生 source map 方便除錯
devServer: {
static: './dist',
hot: true // 啟用 HMR
}
};
3.4 程式碼範例
src/utils.ts
/** 計算兩個數字的總和 */
export function add(a: number, b: number): number {
return a + b;
}
/** 產生隨機字串 */
export function randomId(length: number = 8): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
src/index.ts
import { add, randomId } from './utils';
// 使用 TypeScript 的型別推斷
const sum = add(12, 30);
console.log(`12 + 30 = ${sum}`);
// 產生一個 12 位的隨機 ID
const id = randomId(12);
console.log(`Generated ID: ${id}`);
// 示範 DOM 操作(webpack 會自動把這段 JS 包進 bundle)
const el = document.createElement('div');
el.textContent = `Result: ${sum} | ID: ${id}`;
el.style.fontFamily = 'monospace';
document.body.appendChild(el);
執行:
npm run dev # 啟動開發伺服器,瀏覽器自動開啟 http://localhost:8080 npm run build # 產出 production 版的 bundle.js(已壓縮、去除註解)
4. 進階設定:支援 React、CSS 與環境變數
4.1 加入 React (.tsx)
npm i -D @babel/preset-react @babel/core babel-loader
webpack.config.js 中加入:
{
test: /\.(ts|tsx)$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-react', // 轉換 JSX
['@babel/preset-env', { targets: "defaults" }]
]
}
},
{
loader: 'ts-loader',
options: { transpileOnly: true }
}
],
exclude: /node_modules/
}
tsconfig.json 需加:
{
"compilerOptions": {
"jsx": "react-jsx"
}
}
4.2 處理 CSS(style-loader + css-loader)
npm i -D style-loader css-loader
{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
}
4.3 使用環境變數 (DefinePlugin)
const webpack = require('webpack');
module.exports = {
// ... 其他設定
plugins: [
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify(process.env.API_URL || 'http://localhost:3000')
})
]
};
在 TypeScript 中:
const api = process.env.API_URL as string;
console.log('API endpoint:', api);
5. 程式碼範例彙總
| 範例 | 目的 | 相關檔案 |
|---|---|---|
| 基本 ts-loader 設定 | 讓 .ts 檔案走 TypeScript 編譯流程 |
webpack.config.js、tsconfig.json |
| ForkTsChecker 加速編譯 | 分離型別檢查與轉譯,提高開發時的 rebuild 效率 | webpack.config.js |
| React + ts-loader | 支援 .tsx 與 JSX 語法 |
webpack.config.js、tsconfig.json |
| CSS Loader | 直接在程式碼中 import .css 檔案 |
webpack.config.js |
| DefinePlugin 環境變數 | 在不同環境注入全域變數 | webpack.config.js |
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記在 resolve.extensions 加入 .ts |
import './foo' 會找不到對應的檔案,導致 Module not found。 |
在 webpack.config.js 中加入 extensions: ['.ts', '.js']。 |
ts-loader 與 babel-loader 順序錯誤 |
先執行 Babel 再執行 ts-loader 會導致型別資訊遺失。 | 先 ts-loader → 後 babel-loader(或使用 babel-loader + @babel/preset-typescript)。 |
transpileOnly: true 卻忘記安裝 fork-ts-checker-webpack-plugin |
只跑語法轉換,卻失去型別檢查,錯誤會在瀏覽器跑時才顯現。 | 同時安裝並在 plugins 中掛載 ForkTsCheckerWebpackPlugin。 |
sourceMap 沒開 |
除錯時只能看到編譯後的 bundle,無法對應到原始 TS 檔案。 | 在 tsconfig.json 設 sourceMap: true,同時在 webpack 設 devtool: 'source-map'。 |
| 產出目錄未清空 | 每次 build 後舊檔案仍留在 dist,可能造成舊程式碼被誤載入。 |
使用 Webpack 5 的 output.clean: true,或在 package.json 加 rimraf dist && webpack。 |
最佳實踐
- 分離開發與生產設定:利用
webpack.dev.js、webpack.prod.js兩套配置,分別設定 HMR、source‑map 與代碼壓縮、tree‑shaking。 - 開啟增量編譯:在開發模式下
transpileOnly: true+ForkTsCheckerWebpackPlugin,可把編譯速度提升 2–3 倍。 - 使用別名 (
alias):在resolve.alias中設定@指向src,讓 import 更簡潔。resolve: { alias: { '@': path.resolve(__dirname, 'src') }, extensions: ['.ts', '.js'] } - 保持
tsconfig.json單一來源:所有 TypeScript 相關設定都放在tsconfig.json,Webpack 只負責讀取,避免兩邊設定不一致。 - 加入 Lint:配合
eslint+@typescript-eslint/parser,在編譯前就捕捉潛在錯誤。
實際應用場景
| 場景 | 為何選擇 Webpack + ts‑loader |
|---|---|
| 單頁應用 (SPA) – React + TypeScript | 可同時處理 .tsx、CSS modules、圖片資產,並在開發時提供 HMR,提升開發效率。 |
| 大型企業內部工具 | 需要嚴格型別檢查與代碼分割 (code splitting) 以減少首屏載入時間,Webpack 能自動產生懶加載的 chunk。 |
| Node.js 後端服務(使用 ts‑node) | 雖然不一定需要打包,但若想把多個 Lambda 函式或微服務合併成單一 bundle,Webpack + ts‑loader 能把 tree‑shaking 與壓縮帶入。 |
| 多語系前端專案 | 結合 i18next-loader 或自訂 JSON loader,Webpack 能一次性把翻譯檔案匯入,並透過 TypeScript 定義語系型別。 |
| Progressive Web App (PWA) | Webpack 的 WorkboxPlugin 可自動產生 service worker,配合 ts‑loader 把 TypeScript 轉譯後的程式碼納入緩存策略。 |
總結
- Webpack + ts‑loader 為 TypeScript 專案提供了從 編譯、資源整合、開發體驗 到 生產優化 的完整流水線。
- 只要掌握 基本設定(
tsconfig.json、webpack.config.js)與 增量編譯(transpileOnly+ForkTsCheckerWebpackPlugin),即可在開發階段享有即時重載與完整型別檢查。 - 進階需求(React、CSS、環境變數)只需要在 Webpack 中額外加入相應的 loader 或 plugin,保持 設定單一來源(
tsconfig.json)的原則,就能避免設定衝突。 - 常見的陷阱大多與 loader 順序、source‑map、清理輸出目錄 有關,依照本文的表格檢查即可快速定位問題。
把上述步驟實踐在自己的專案裡,你將會發現開發速度提升、程式碼品質更穩定,且在部署到生產環境時也能自動得到最佳化的 bundle。祝你玩得開心,寫出更安全、更高效的 TypeScript 應用!