本文 AI 產出,尚未審核

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-serverwebpack 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.jstsconfig.json
ForkTsChecker 加速編譯 分離型別檢查與轉譯,提高開發時的 rebuild 效率 webpack.config.js
React + ts-loader 支援 .tsx 與 JSX 語法 webpack.config.jstsconfig.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-loaderbabel-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.jsonsourceMap: true,同時在 webpack 設 devtool: 'source-map'
產出目錄未清空 每次 build 後舊檔案仍留在 dist,可能造成舊程式碼被誤載入。 使用 Webpack 5 的 output.clean: true,或在 package.jsonrimraf dist && webpack

最佳實踐

  1. 分離開發與生產設定:利用 webpack.dev.jswebpack.prod.js 兩套配置,分別設定 HMR、source‑map 與代碼壓縮、tree‑shaking。
  2. 開啟增量編譯:在開發模式下 transpileOnly: true + ForkTsCheckerWebpackPlugin,可把編譯速度提升 2–3 倍。
  3. 使用別名 (alias):在 resolve.alias 中設定 @ 指向 src,讓 import 更簡潔。
    resolve: {
      alias: { '@': path.resolve(__dirname, 'src') },
      extensions: ['.ts', '.js']
    }
    
  4. 保持 tsconfig.json 單一來源:所有 TypeScript 相關設定都放在 tsconfig.json,Webpack 只負責讀取,避免兩邊設定不一致。
  5. 加入 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.jsonwebpack.config.js)與 增量編譯transpileOnly + ForkTsCheckerWebpackPlugin),即可在開發階段享有即時重載與完整型別檢查。
  • 進階需求(React、CSS、環境變數)只需要在 Webpack 中額外加入相應的 loader 或 plugin,保持 設定單一來源tsconfig.json)的原則,就能避免設定衝突。
  • 常見的陷阱大多與 loader 順序source‑map清理輸出目錄 有關,依照本文的表格檢查即可快速定位問題。

把上述步驟實踐在自己的專案裡,你將會發現開發速度提升、程式碼品質更穩定,且在部署到生產環境時也能自動得到最佳化的 bundle。祝你玩得開心,寫出更安全、更高效的 TypeScript 應用!