cover

概念概覽

flowchart LR
    subgraph 輸入["原始檔案"]
        JS["JS / TS"]
        CSS["CSS / SCSS"]
        IMG["圖片資源"]
        HTML["HTML"]
    end

    subgraph 打包工具["Bundler"]
        Webpack["Webpack"]
        Vite["Vite"]
    end

    subgraph 輸出["優化結果"]
        Bundle["bundle.js<br/>壓縮合併"]
        Styles["styles.css<br/>前綴 + 壓縮"]
        Assets["optimized assets<br/>圖片壓縮"]
    end

    JS --> 打包工具
    CSS --> 打包工具
    IMG --> 打包工具
    HTML --> 打包工具
    打包工具 --> Bundle
    打包工具 --> Styles
    打包工具 --> Assets

前端開發面臨的問題

1. 模組化問題

早期 JavaScript 沒有模組系統,所有變數都在全域作用域:

<!-- 傳統方式:手動管理載入順序 -->
<script src="jquery.js"></script>
<script src="utils.js"></script>      <!-- 依賴 jquery -->
<script src="component.js"></script>  <!-- 依賴 utils -->
<script src="app.js"></script>        <!-- 依賴全部 -->
 
<!-- 問題:
  1. 順序錯誤就壞掉
  2. 全域變數污染
  3. 依賴關係不明確
-->

現代模組化:

// utils.js
export function formatDate(date) { ... }
 
// app.js
import { formatDate } from './utils.js';
import React from 'react';  // npm 套件

2. 瀏覽器相容性

// 新語法
const sum = (a, b) => a + b;
const user = { ...defaultUser, name: 'John' };
const result = await fetchData();
 
// 舊瀏覽器不支援!需要轉換成:
var sum = function(a, b) { return a + b; };
var user = Object.assign({}, defaultUser, { name: 'John' });

3. 效能問題

<!-- 太多 HTTP 請求 -->
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="module3.js"></script>
<!-- ... 50 個檔案 -->
 
<!-- 檔案太大 -->
<script src="bundle.js"></script>  <!-- 5MB 未壓縮 -->

打包工具解決的問題

┌─────────────────────────────────────────────────────────┐
│                     打包工具                             │
│                                                          │
│  輸入                          輸出                      │
│  ┌─────────────┐              ┌─────────────┐           │
│  │ ES6+ 語法   │              │ ES5 相容    │           │
│  │ JSX/TSX     │    ──→      │ 純 JS       │           │
│  │ SCSS/Less   │              │ 純 CSS      │           │
│  │ 多個模組    │              │ 單一 Bundle │           │
│  │ 原始圖片    │              │ 優化圖片    │           │
│  └─────────────┘              └─────────────┘           │
└─────────────────────────────────────────────────────────┘

主要打包工具比較

工具特點適用場景
Webpack功能最完整、生態系成熟大型專案
Vite開發速度極快、設定簡單現代專案(推薦)
Rollup打包效果最佳套件開發
esbuild速度最快簡單打包
Parcel零設定小型專案

Webpack 基礎

核心概念

// webpack.config.js
module.exports = {
  // 入口:從哪開始分析依賴
  entry: './src/index.js',
 
  // 輸出:打包後的檔案
  output: {
    filename: 'bundle.[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },
 
  // Loader:處理非 JS 檔案
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: 'babel-loader',  // 轉換 ES6+
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.(png|jpg|gif)$/,
        type: 'asset/resource',
      },
    ],
  },
 
  // Plugin:擴充功能
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
    }),
  ],
};

打包流程

入口分析 → 依賴解析 → Loader 轉換 → 程式碼優化 → 輸出 Bundle

src/
├── index.js        ──→  dist/
├── App.jsx              ├── bundle.js
├── styles.css           ├── styles.css
└── logo.png             └── logo.abc123.png

Vite - 現代開發體驗

為什麼 Vite 這麼快?

Webpack 開發模式

啟動 → 打包所有檔案 → 啟動 Dev Server → 等待...(可能數分鐘)

Vite 開發模式

啟動 → 直接啟動 Dev Server → 按需編譯(毫秒級)

Vite 利用瀏覽器原生 ES Modules:

<!-- Vite 開發時直接使用 ESM -->
<script type="module" src="/src/main.js"></script>

Vite 設定

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [react()],
  server: {
    port: 3000,
    proxy: {
      '/api': 'http://localhost:8080',
    },
  },
  build: {
    outDir: 'dist',
    minify: 'esbuild',
  },
});

常見優化功能

1. 程式碼分割(Code Splitting)

// 動態載入,按需載入
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
 
// 打包結果
dist/
├── main.js          // 主要程式碼
├── Dashboard.js     // 獨立 chunk
└── Settings.js      // 獨立 chunk

2. Tree Shaking

// utils.js
export function used() { ... }
export function unused() { ... }  // 不會被打包
 
// app.js
import { used } from './utils';  // 只引入 used

3. 壓縮與優化

// 原始碼
function calculateTotal(items) {
  let total = 0;
  for (const item of items) {
    total += item.price * item.quantity;
  }
  return total;
}
 
// 壓縮後
function calculateTotal(t){let e=0;for(const n of t)e+=n.price*n.quantity;return e}

開發 vs 生產

階段目標設定
開發快速反饋Source Map、HMR、不壓縮
生產效能優先壓縮、Tree Shaking、快取
// package.json
{
  "scripts": {
    "dev": "vite",           // 開發模式
    "build": "vite build",   // 生產打包
    "preview": "vite preview" // 預覽生產結果
  }
}

常見問題

什麼時候不需要打包工具?

  • 簡單的靜態網頁
  • 使用 CDN 載入函式庫
  • 小型原型專案

該選 Webpack 還是 Vite?

新專案 → Vite(更快、更簡單)
舊專案維護 → 繼續用 Webpack
需要高度自訂 → Webpack
開發套件 → Rollup

延伸閱讀

Vite 官方文件 Webpack 官方文件 工程化 npm 基本指令 前後端分離 - 序章