系列文章: 前端工程師的 Modern Web 實踐之道 - Day 7
預計閱讀時間: 10 分鐘
難度等級: ⭐⭐⭐⭐☆
經過前六天的學習,我們已經了解了現代化前端開發的各個重要組件:從開發環境設定、套件管理、TypeScript、框架選擇到 CSS 解決方案。今天我們要將這些工具串聯起來,打造一套完整的現代化前端工作流,讓整個開發流程更加順暢和高效。
想像一下這個場景:你剛加入一個新專案,需要設定開發環境。你發現專案使用了 Vite + Vue 3 + TypeScript + Tailwind CSS + pnpm,但是沒有統一的設定和工作流程。結果你花了整整一天在各種設定檔案之間來回奔波,最後還是無法正常啟動專案。
這就是缺乏工具鏈整合所帶來的痛點。現代前端開發涉及眾多工具,如果沒有良好的整合,就會產生:
一個完整的現代化前端工作流應該包含以下幾個層面:
Node.js 版本管理 → 套件管理器選擇 → 開發伺服器設定 → 程式碼編輯器整合
程式碼風格檢查 → 類型檢查 → 單元測試 → 建構驗證
Git Hook 整合 → CI/CD 設定 → 自動化部署 → 監控告警
開發規範 → 程式碼審查流程 → 文件維護 → 知識分享
我們以 React + TypeScript + Vite 的組合為例,建立一個完整的工作流:
# 使用 Vite 建立專案
npm create vite@latest modern-web-workflow --template react-ts
cd modern-web-workflow
# 安裝相依套件
npm install
# 安裝開發工具依賴
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
npm install -D eslint eslint-plugin-react eslint-plugin-react-hooks
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional
npm install -D vitest @testing-library/react @testing-library/jest-dom
npm install -D tailwindcss postcss autoprefixer
建立 ESLint 設定檔案 .eslintrc.cjs
:
module.exports = {
env: {
browser: true,
es2020: true,
node: true,
},
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:prettier/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'prettier'],
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'prettier/prettier': ['error', { endOfLine: 'auto' }],
},
settings: {
react: {
version: 'detect',
},
},
};
建立 Prettier 設定檔案 .prettierrc
:
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}
建立 TypeScript 設定檔案 tsconfig.json
:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/utils/*": ["src/utils/*"],
"@/hooks/*": ["src/hooks/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
建立 package.json
腳本:
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"type-check": "tsc --noEmit",
"test": "vitest",
"test:coverage": "vitest --coverage",
"test:ui": "vitest --ui",
"prepare": "husky install",
"pre-commit": "lint-staged",
"validate": "npm run type-check && npm run lint && npm run format:check && npm run test -- --run"
}
}
設定 Husky Git Hook,建立 .husky/pre-commit
:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run pre-commit
建立 .lintstagedrc.json
:
{
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{js,jsx,json,css,md}": [
"prettier --write"
]
}
建立 commit 訊息規範 .commitlintrc.json
:
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"chore",
"revert"
]
]
}
}
建立 Vitest 設定檔案 vitest.config.ts
:
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
建立測試設定檔案 src/test/setup.ts
:
import '@testing-library/jest-dom';
// 全域測試設定
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
建立 scripts/dev-setup.sh
:
#!/bin/bash
echo "🚀 設定現代化前端開發環境..."
# 檢查 Node.js 版本
NODE_VERSION=$(node --version)
echo "📦 當前 Node.js 版本: $NODE_VERSION"
# 安裝依賴
echo "📥 安裝專案依賴..."
npm install
# 初始化 Husky
echo "🐕 設定 Git Hook..."
npm run prepare
# 執行初始檢查
echo "🔍 執行程式碼品質檢查..."
npm run validate
echo "✅ 開發環境設定完成!"
echo ""
echo "常用指令:"
echo " npm run dev # 啟動開發伺服器"
echo " npm run build # 建構生產版本"
echo " npm run test # 執行測試"
echo " npm run lint # 檢查程式碼品質"
echo " npm run format # 格式化程式碼"
echo " npm run validate # 完整驗證流程"
建立工具鏈健康檢查腳本 scripts/health-check.js
:
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
class ToolchainHealthChecker {
constructor() {
this.checks = [];
this.results = [];
}
addCheck(name, checkFn) {
this.checks.push({ name, checkFn });
}
async runAllChecks() {
console.log('🔍 開始工具鏈健康檢查...\n');
for (const check of this.checks) {
try {
const result = await check.checkFn();
this.results.push({
name: check.name,
status: 'pass',
message: result
});
console.log(`✅ ${check.name}: ${result}`);
} catch (error) {
this.results.push({
name: check.name,
status: 'fail',
message: error.message
});
console.log(`❌ ${check.name}: ${error.message}`);
}
}
this.generateReport();
}
generateReport() {
const passCount = this.results.filter(r => r.status === 'pass').length;
const failCount = this.results.filter(r => r.status === 'fail').length;
console.log(`\n📊 檢查完成: ${passCount} 通過, ${failCount} 失敗`);
if (failCount > 0) {
console.log('\n🚨 需要關注的問題:');
this.results
.filter(r => r.status === 'fail')
.forEach(r => console.log(` - ${r.name}: ${r.message}`));
}
}
}
// 建立檢查器實例
const checker = new ToolchainHealthChecker();
// 添加各種檢查
checker.addCheck('Node.js 版本', () => {
const version = process.version;
const major = parseInt(version.slice(1));
if (major < 16) {
throw new Error(`需要 Node.js 16+,當前版本: ${version}`);
}
return `${version} (符合要求)`;
});
checker.addCheck('套件管理器', () => {
const packageManager = process.env.npm_config_user_agent || 'unknown';
return `使用 ${packageManager.split('/')[0]}`;
});
checker.addCheck('TypeScript 設定', () => {
if (!fs.existsSync('tsconfig.json')) {
throw new Error('缺少 tsconfig.json');
}
return 'TypeScript 設定檔存在';
});
checker.addCheck('ESLint 設定', () => {
const configs = ['.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json'];
if (!configs.some(config => fs.existsSync(config))) {
throw new Error('缺少 ESLint 設定檔');
}
return 'ESLint 設定檔存在';
});
checker.addCheck('Git Hook', () => {
if (!fs.existsSync('.husky')) {
throw new Error('Husky 未設定');
}
return 'Git Hook 已設定';
});
// 執行檢查
checker.runAllChecks().catch(console.error);
建立 DEVELOPMENT.md
:
# 開發規範指南
## 🛠️ 開發環境要求
- Node.js >= 16.0.0
- npm >= 8.0.0 或 pnpm >= 7.0.0
- Git >= 2.0.0
## 📝 程式碼規範
### Git Commit 規範
():
feat: 新功能
fix: 錯誤修復
docs: 文件更新
style: 程式碼格式調整
refactor: 重構
perf: 效能最佳化
test: 測試相關
chore: 建構和工具相關
### 分支命名規範
feature/功能名稱
bugfix/問題描述
hotfix/緊急修復
release/版本號
### 程式碼審查檢查點
- [ ] 程式碼風格符合 ESLint 規範
- [ ] TypeScript 類型定義完整
- [ ] 包含適當的錯誤處理
- [ ] 添加必要的單元測試
- [ ] 效能考量和最佳化
- [ ] 無障礙設計考慮
- [ ] 安全性檢查
建立品質監控腳本 scripts/quality-dashboard.js
:
const fs = require('fs');
const { execSync } = require('child_process');
class QualityDashboard {
constructor() {
this.metrics = {};
}
async collectMetrics() {
console.log('📊 收集品質指標...\n');
// 程式碼覆蓋率
try {
const coverage = execSync('npm run test:coverage -- --reporter=json', { encoding: 'utf8' });
const coverageData = JSON.parse(coverage);
this.metrics.coverage = coverageData.total?.statements?.pct || 0;
} catch (error) {
this.metrics.coverage = 0;
}
// ESLint 問題統計
try {
const lintOutput = execSync('npm run lint -- --format=json', { encoding: 'utf8' });
const lintData = JSON.parse(lintOutput);
this.metrics.lintErrors = lintData.reduce((total, file) =>
total + file.errorCount, 0
);
this.metrics.lintWarnings = lintData.reduce((total, file) =>
total + file.warningCount, 0
);
} catch (error) {
this.metrics.lintErrors = 0;
this.metrics.lintWarnings = 0;
}
// TypeScript 錯誤統計
try {
execSync('npm run type-check', { encoding: 'utf8' });
this.metrics.typeErrors = 0;
} catch (error) {
const errorCount = (error.stdout.match(/error TS/g) || []).length;
this.metrics.typeErrors = errorCount;
}
// 建構大小分析
try {
const buildStats = execSync('npm run build -- --reporter=json', { encoding: 'utf8' });
// 解析建構統計
this.metrics.bundleSize = '估算中';
} catch (error) {
this.metrics.bundleSize = '無法取得';
}
this.generateDashboard();
}
generateDashboard() {
const dashboard = `
# 專案品質看板
## 📈 品質指標
| 指標 | 數值 | 狀態 |
|------|------|------|
| 測試覆蓋率 | ${this.metrics.coverage}% | ${this.getStatus(this.metrics.coverage, 80)} |
| ESLint 錯誤 | ${this.metrics.lintErrors} | ${this.getStatus(this.metrics.lintErrors, 0, true)} |
| ESLint 警告 | ${this.metrics.lintWarnings} | ${this.getStatus(this.metrics.lintWarnings, 5, true)} |
| TypeScript 錯誤 | ${this.metrics.typeErrors} | ${this.getStatus(this.metrics.typeErrors, 0, true)} |
| 建構大小 | ${this.metrics.bundleSize} | - |
## 🎯 品質目標
- 測試覆蓋率 ≥ 80%
- ESLint 錯誤 = 0
- ESLint 警告 ≤ 5
- TypeScript 錯誤 = 0
---
*更新時間: ${new Date().toLocaleString()}*
`;
fs.writeFileSync('QUALITY_DASHBOARD.md', dashboard);
console.log('✅ 品質看板已更新: QUALITY_DASHBOARD.md');
}
getStatus(value, threshold, reverse = false) {
if (reverse) {
return value <= threshold ? '✅' : '❌';
}
return value >= threshold ? '✅' : '❌';
}
}
const dashboard = new QualityDashboard();
dashboard.collectMetrics().catch(console.error);
整合思維: 現代化工作流不是單純的工具堆疊,而是有機整合的生態系統,需要考慮工具間的協同效應和開發者體驗。
自動化優先: 透過 Git Hook、CI/CD 和腳本自動化,將品質檢查和重複性工作自動化,讓開發者專注於創造價值。
標準化管理: 建立統一的設定檔案、開發規範和工作流程,確保團隊協作的一致性和效率。
✅ 推薦做法: 建立一鍵設定腳本,新團隊成員可以快速上手
✅ 推薦做法: 使用統一的程式碼品質標準,包含 ESLint、Prettier、TypeScript
✅ 推薦做法: 設定 Git Hook 在提交前自動檢查程式碼品質
✅ 推薦做法: 建立品質看板定期監控專案健康狀況
❌ 避免陷阱: 過度設定導致開發效率下降,要在品質和效率間找到平衡
❌ 避免陷阱: 工具版本不統一導致團隊環境不一致的問題
❌ 避免陷阱: 忽略工具鏈的維護更新,使用過時的設定和依賴
❌ 避免陷阱: 只關注工具本身而忽略開發者體驗和學習成本
工具選擇權衡: 在引入新工具時,如何評估其帶來的價值是否大於學習和維護成本?
團隊適配性: 如何根據團隊規模和技術水平調整工具鏈的複雜度?
持續演進: 隨著新技術的出現,如何漸進式地升級工具鏈而不影響開發效率?