Day 18:前端模組化開發架構思考

Share Dialog

練習題目模組化重構時,思考 修煉圈圈 這個先前 Vibe Coding 做的小專案可以重構的方式,才發現 js 和 css 內建語法模組概念的不同,記錄一下思考過程。

修煉圈圈開發日誌

📚 目錄

  1. 核心概念

  2. JavaScript 模組化

  3. CSS 模組化

  4. 對比分析

  5. 建置工具

  6. 開發 vs 生產環境

  7. 實踐指南


核心概念

什麼是模組化?

模組化 是將大的程式碼拆分成小的、獨立的、可重複使用的單元(模組),每個模組:

  • 職責單一

  • 易於維護

  • 易於測試

  • 可以獨立開發

為什麼需要模組化?

未模組化程式碼(1000+ 行)
  ↓
❌ 難以理解
❌ 難以維護
❌ 容易出錯
❌ 難以分工協作

模組化程式碼(多個 100-200 行檔案)
  ↓
✅ 清晰易懂
✅ 易於維護
✅ 測試簡單
✅ 便於團隊協作

模組化的目標

開發階段(Developer Experience)
├─ 程式碼清晰易懂
├─ 容易定位問題
├─ 方便重複使用
└─ 便於團隊協作

    ↓ 建置/合併工具 ↓

生產環境(Browser Performance)
├─ 減少 HTTP 請求
├─ 檔案大小最小化
├─ 瀏覽器快速解析
└─ 性能最優化

JavaScript 模組化

原生支援:ES6 Modules

JavaScript 有 原生的模組系統,開發者可以直接使用 importexport

基礎語法

導出模組(Export)

// 方式 1:具名導出
export function add(a, b) {
  return a + b;
}

export const PI = 3.14159;

export class Calculator {
  multiply(a, b) {
    return a * b;
  }
}

// 方式 2:預設導出
export default function subtract(a, b) {
  return a - b;
}

// 方式 3:混合導出
export function divide(a, b) {
  return a / b;
}

export default { add, subtract, divide };

導入模組(Import)

// 導入具名導出
import { add, PI } from './math.js';

// 導入預設導出
import subtract from './math.js';

// 導入所有內容
import * as math from './math.js';

// 混合導入
import subtract, { add, PI } from './math.js';

// 使用
console.log(add(2, 3));        // 5
console.log(PI);               // 3.14159
console.log(subtract(5, 2));   // 3

本專案的 JavaScript 模組結構

src/
├─ logic/                    # 純業務邏輯模組
│  ├─ calculator.js         # 進度計算邏輯
│  ├─ formatter.js          # 日期和時間格式化
│  ├─ messages.js           # 勵志訊息生成
│  ├─ timer.js              # 計時器邏輯
│  └─ date-navigation.js    # 日期導航邏輯(純函數)
│
├─ state/                    # 狀態管理模組
│  └─ store.js              # 集中式狀態儲存
│
├─ services/                 # API 服務模組
│  └─ api.js                # 後端通訊
│
├─ views/                    # UI 層模組
│  ├─ dom.js                # DOM 元素快取
│  ├─ render.js             # 協調層(統一渲染入口)
│  └─ components/           # UI 元件模組
│     ├─ day-card-view.js   # 日卡片渲染
│     ├─ month-card-view.js # 月檢視渲染
│     ├─ rings-view.js      # 進度圈渲染
│     ├─ timer-view.js      # 計時器 UI
│     ├─ history-view.js    # 歷史記錄 UI
│     └─ encourage-view.js  # 勵志訊息 UI
│
└─ constants/               # 常數模組
   └─ config.js            # 應用配置

導入鏈示例

main.js (應用入口)
  ├─ import * as render from "./src/views/render.js"
  ├─ import * as timer from "./src/logic/timer.js"
  ├─ import { getState } from "./src/state/store.js"
  └─ import * as dateNav from "./src/logic/date-navigation.js"

render.js (協調層)
  ├─ import * as dayCardView from "./components/day-card-view.js"
  ├─ import * as monthCardView from "./components/month-card-view.js"
  ├─ import * as ringsView from "./components/rings-view.js"
  ├─ import * as timerView from "./components/timer-view.js"
  ├─ import * as historyView from "./components/history-view.js"
  ├─ import * as encourageView from "./components/encourage-view.js"
  └─ import { getState } from "../state/store.js"

各元件模組
  └─ import 所需的邏輯模組 和 狀態管理

優勢

語言原生支援 - 無需額外工具
清晰的依賴關係 - 一眼看出模組間的關係
程式碼複用 - 同一函數可多次導入使用
易於測試 - 可獨立測試各個模組
優雅的語法 - 簡潔易讀


CSS 模組化

沒有原生支援

CSS 沒有原生的模組系統,無法直接使用 @import 模組語法(它只能導入 CSS 檔案)。

為什麼 CSS 沒有模組系統?

  1. 歷史原因 - CSS 創建於 1994 年,當時無模組化概念

  2. 設計理念 - CSS 是聲明式語言,不適合傳統模組系統

  3. 演進緩慢 - CSS 標準更新速度遠低於 JavaScript

CSS 模組化的替代方案

方案 A:單一檔案 + 清晰註解(推薦用於小型專案)

/* style.css */

/* ================================================ */
/* 1. BASE STYLES - 基礎樣式和變數定義 */
/* ================================================ */

:root {
  --dora-blue: #009eff;
  --dora-red: #e92024;
  --dora-white: #ffffff;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Huninn', sans-serif;
  background-color: var(--dora-bg);
}

/* ================================================ */
/* 2. TIMER SECTION - 計時器相關樣式 */
/* ================================================ */

.timer-section {
  /* ... */
}

.timer-display {
  /* ... */
}

/* ================================================ */
/* 3. RINGS SECTION - 進度圈相關樣式 */
/* ================================================ */

.rings-section {
  /* ... */
}

.ring-circle {
  /* ... */
}

/* ... 以此類推 */

優點:

  • 簡單直接

  • 無需額外工具

  • 瀏覽器快速解析(單一檔案)

缺點:

  • 檔案越來越大難以維護

  • 無法自動優化

方案 B:多檔案 + @import(無建置工具)

/* style.css - 主入口檔案 */

@import url('styles/base.css');
@import url('styles/timer.css');
@import url('styles/rings.css');
@import url('styles/history.css');
@import url('styles/modal.css');
@import url('styles/responsive.css');
styles/
├─ base.css           # 變數、Reset、基礎
├─ timer.css          # 計時器相關
├─ rings.css          # 進度圈相關
├─ history.css        # 歷史記錄相關
├─ modal.css          # Modal 相關
└─ responsive.css     # 手機版回應式

優點:

  • 模組化程式碼

  • 易於維護

  • 無需建置工具

缺點:

  • 多個 HTTP 請求(性能)

  • 需要手動管理依賴

方案 C:Sass/SCSS(需要編譯工具)

// main.scss
@import 'variables.scss';
@import 'base.scss';
@import 'timer.scss';
@import 'rings.scss';
@import 'history.scss';
@import 'responsive.scss';

功能:

  • 變數、嵌套、混入

  • 自動編譯為 CSS

  • 支援導入其他 SCSS 檔案

// variables.scss
$primary-color: #009eff;
$border-radius: 15px;

// timer.scss
.timer-section {
  color: $primary-color;
  border-radius: $border-radius;
}

優點:

  • 高級 CSS 特性

  • 程式碼複用

  • 編譯為最小化 CSS

缺點:

  • 需要編譯工具

  • 額外的學習成本

方案 D:CSS Modules(需要建置工具如 Webpack)

// Button.js
import styles from './Button.module.css';

export function Button() {
  return <button className={styles.primary}>點擊</button>;
}
/* Button.module.css */
.primary {
  background-color: #009eff;
  color: white;
  padding: 10px 20px;
}

編譯後會生成唯一的 class 名稱,避免衝突。

優點:

  • 避免樣式衝突

  • 組件級別的樣式作用域

  • 與 JavaScript 框架整合

缺點:

  • 需要建置工具

  • 學習框架(React 等)

本專案的 CSS 狀態

目前:style.css (1003 行)
├─ 1. BASE STYLES
├─ 2. TIMER SECTION
├─ 3. RINGS SECTION
├─ 4. HISTORY SECTION
├─ 5. MODAL / DAY CARD
├─ 6. MONTH VIEW
└─ 7. RESPONSIVE

問題:
❌ 檔案過大,難以維護
❌ 無法自動優化
✅ 但對於當前規模足夠

對比分析

JavaScript vs CSS 模組化

面向

JavaScript

CSS

原生支援

ES6 Modules

無原生系統

導入語法

import { x } from "..."

無模組語法

開發方式

模組化(import/export)

手動組織

語言特性

函數、類、對象

聲明式

依賴管理

自動追蹤

手動管理

程式碼複用

容易

⚠️ 需要工具

導入方式對比

/* ===== JavaScript ===== */

// 具名導出和導入
export function add(a, b) {}
import { add } from './math.js';

// 預設導出和導入
export default subtract;
import subtract from './math.js';

// 萬用導入
import * as math from './math.js';
math.add(2, 3);
/* ===== CSS ===== */

/* 只能導入 CSS 檔案,無模組概念 */
@import url('base.css');
@import url('timer.css');

/* 結果:所有樣式會疊加到全局作用域 */
/* 無法像 JS 一樣「接收特定導出」 */

為什麼 CSS 沒有像 JavaScript 那樣的模組系統?


建置工具

什麼是建置工具?

建置工具 是自動化開發流程的軟體,它在開發環境和生產環境之間起橋樑作用:

開發環境 (模組化程式碼)
    ↓
  建置工具 (Webpack/Vite)
    ↓ 合併 + 壓縮 + 最小化
    ↓
生產環境 (最優化單一檔案)

常見建置工具

1. Webpack (最成熟)

// webpack.config.js
module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: __dirname + '/dist',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

特點:

  • 功能全面

  • 生態龐大

  • 配置複雜

  • 學習曲線陡峭

2. Vite (現代推薦)

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    outDir: 'dist',
  }
})

特點:

  • 配置簡單

  • 開發速度快

  • 現代化設計

  • 預設最優化

3. Parcel (零配置)

# 無需配置檔案!
parcel build index.html

特點:

  • 零配置

  • 自動偵測

  • 速度快

建置工具做了什麼?

輸入(開發環境)
├─ src/main.js
├─ src/logic/timer.js
├─ src/views/render.js
├─ src/styles/base.css
├─ src/styles/timer.css
└─ src/styles/rings.css

    ↓ 建置工具處理 ↓

1. 解析 import/export
2. 遞迴收集所有依賴
3. 合併所有 JS 檔案
4. 合併所有 CSS 檔案
5. 壓縮 + 最小化
6. 生成 Source Map(偵錯用)
7. 添加指紋(cache busting)

輸出(生產環境)
├─ dist/bundle.abc123.js (合併+壓縮)
├─ dist/style.def456.css  (合併+壓縮)
└─ dist/index.html        (指向新檔案)

CSS 合併工具

除了完整的建置工具,還有專門的 CSS 合併工具:

PostCSS

  • 自動添加瀏覽器前綴

  • 壓縮 CSS

  • 處理兼容性

Sass/SCSS

  • 編譯 SCSS 為 CSS

  • 自動合併 @import

CSS Minifier

  • 線上或命令行工具

  • 壓縮和合併 CSS


開發 vs 生產環境

無建置工具的情況

開發環境

<!-- index.html -->
<link rel="stylesheet" href="style.css">
<script src="main.js"></script>

<!-- 瀏覽器處理 -->
網路請求:
  GET /style.css (1003 行)
  GET /main.js
  GET /src/state/store.js
  GET /src/logic/timer.js
  GET /src/views/render.js
  ... (多個 HTTP 請求)

優點:易於開發調試
缺點:性能較差

生產環境

<!-- 與開發環境完全相同 -->
<link rel="stylesheet" href="style.css">
<script src="main.js"></script>

<!-- 問題:無法自動優化 -->
- CSS 未壓縮
- JS 未合併
- 文件大小較大
- 多個 HTTP 請求

有建置工具的情況

開發環境

npm run dev
開發伺服器啟動
  ├─ 熱模組更新 (HMR)
  ├─ 即時編譯
  ├─ Source Map(原始碼對應)
  └─ 快速刷新

開發者寫程式碼時:
  file: src/logic/timer.js (修改)
    ↓ 自動編譯 ↓
  瀏覽器自動刷新
    ↓
  即時看到改動 (無需手動刷新)

生產環境

npm run build
建置流程:
  1. 解析依賴
  2. 合併模組
  3. 程式碼最小化
  4. CSS 壓縮
  5. 生成指紋版本

輸出結果:
dist/
├─ bundle.a1b2c3d4.js (合併+壓縮,50 KB)
├─ style.e5f6g7h8.css (合併+壓縮,15 KB)
├─ index.html
└─ assets/

HTML 自動指向新檔案名:
<link rel="stylesheet" href="style.e5f6g7h8.css">
<script src="bundle.a1b2c3d4.js"></script>

優點:
✅ 最小化 HTTP 請求(2 個)
✅ 最小化檔案大小
✅ 瀏覽器快速解析
✅ 指紋版本便於快取更新

性能對比

無建置工具:
  CSS: 1003 行 → 無壓縮 → ~30 KB
  JS: 分散在多個檔案 → ~200 KB
  HTTP 請求: 10+ 個
  解析時間: 較長

有建置工具:
  CSS: 1003 行 → 壓縮 → ~15 KB (-50%)
  JS: 合併+壓縮 → ~50 KB (-75%)
  HTTP 請求: 2-3 個 (-80%)
  解析時間: 較短

結論:性能提升 3-5 倍!

實踐指南

本專案當前狀態評估

JavaScript 模組化成熟度:★★★★★ - 使用 ES6 import/export - 清晰的目錄結構 - 邏輯分離完善 ⚠️ CSS 模組化成熟度:★★☆☆☆ - 單一檔案(1003 行) - 有清晰註解分組 - 尚未拆分成多個檔案 ⚠️ 建置工具成熟度:★☆☆☆☆ - 無建置工具 - 無自動優化 - 開發體驗簡單但性能有限

升級路徑

第 1 階段:改進 CSS 組織(無工具,立即可做)

現在就可以做的:

  1. 改進 CSS 註解分組

  2. 使用更清晰的分隔符

  3. 添加 CSS 變數(已有)

  4. 整理選擇器順序

/* style.css */

/* ================================================= */
/*              1. BASE STYLES                       */
/* ================================================= */
/* 功能:CSS 變數、Reset、基礎樣式 */

:root { }
* { }
body { }

/* ================================================= */
/*              2. TIMER SECTION                     */
/* ================================================= */
/* 功能:計時器顯示、按鈕、模式標籤 */

.timer-section { }
.timer-display { }

/* ... */

第 2 階段:CSS 檔案分割(可選)

如果 CSS 繼續增長:

src/styles/
├─ index.css (主入口,只做 @import)
├─ _variables.css
├─ _base.css
├─ _timer.css
├─ _rings.css
├─ _history.css
├─ _modal.css
└─ _responsive.css
/* index.css */
@import url('_variables.css');
@import url('_base.css');
@import url('_timer.css');
@import url('_rings.css');
@import url('_history.css');
@import url('_modal.css');
@import url('_responsive.css');
<!-- index.html -->
<link rel="stylesheet" href="src/styles/index.css">

第 3 階段:引入建置工具(長期規劃)

當專案變複雜時考慮:

# 初始化 Vite
npm create vite@latest . -- --template vanilla

# 或 Webpack
npm install webpack webpack-cli --save-dev

優點:

  • 自動合併 JS + CSS

  • 自動壓縮最小化

  • 開發時熱更新

  • 性能優化

決策樹

CSS 超過多少行?
  │
  ├─ < 800 行  → 保持現狀 + 改進註解
  │
  ├─ 800-1500 行 → 考慮 @import 拆分
  │
  └─ > 1500 行  → 引入建置工具或 Sass

JavaScript 複雜度如何?
  │
  ├─ 低複雜度  → 無需建置工具
  │
  ├─ 中複雜度  → 可考慮 Vite(簡單配置)
  │
  └─ 高複雜度  → 引入框架 + 建置工具

推薦方案組合

小型專案(當前階段)

JavaScript: ✅ ES6 Modules (無工具)
CSS:        ✅ 單一檔案 + 清晰註解
工具:       ❌ 無
維護成本:   低
性能:       可接受

中型專案(6-12 個月後)

JavaScript: ✅ ES6 Modules
CSS:        ✅ SCSS 或多檔案 @import
工具:       ✅ Vite
維護成本:   中等
性能:       優秀

大型專案(1+ 年後)

JavaScript: ✅ ES6 Modules + 框架 (React/Vue)
CSS:        ✅ CSS Modules 或 CSS-in-JS
工具:       ✅ Webpack / Vite + 框架配置
維護成本:   中等
性能:       最優

關鍵知識點總結

1️⃣ JavaScript 有原生模組系統

import/export 語法 ✅ 瀏覽器原生支援(現代瀏覽器) ✅ 清晰的依賴追蹤 ✅ 易於維護和測試

2️⃣ CSS 沒有原生模組系統

無 import/export 語法 ❌ 所有樣式應用到全局作用域 ✅ 但有多種替代方案(註解、@import、Sass、CSS Modules) ❌ 需要外部工具進行最佳化

3️⃣ 建置工具的作用

開發環境:保持模組化(易於理解)
         ↓
建置工具:自動合併 + 壓縮 + 最小化
         ↓
生產環境:單一最優化檔案(性能最佳)

4️⃣ 升級時機

現在:  專注核心功能開發
未來:  當程式碼複雜度增加時考慮工具
遠期:  引入框架和完整工具鏈

相關資源

學習資料

工具

  • Vite - 現代建置工具

  • Sass - CSS 預處理器

  • PostCSS - CSS 轉換工具

  • Webpack - 成熟的模組打包工具