歡迎來到我的技術部落格
這是一個融合靜態網站生成、區塊鏈整合和遊戲化介面的技術作品集。這篇文章將分享我的技術選型理念,以及每個技術決策背後的考量。
🎯 核心理念
問題:如何打造一個既能展示技術實力,又不會讓人覺得無聊的作品集?
答案:把履歷變成 RPG 遊戲,把技術棧變成可互動的系統。訪客不只是「閱讀」作品集,而是「探索」一個遊戲世界。
🛠️ 技術棧與選型理由
1. Jekyll - 靜態網站生成器
為什麼選擇 Jekyll?
我需要一個能快速部署、零維護成本的部落格系統。Jekyll 完美符合:
- 零成本部署:直接託管在 GitHub Pages,省去伺服器費用
- 安全性高:沒有資料庫,沒有後端,不會被 SQL injection
- 速度極快:純靜態 HTML,載入速度直接受限於 CDN
- Markdown 撰寫:專注內容,不用處理複雜的 CMS 介面
使用的插件與理由:
gem "jekyll-feed" # 自動生成 RSS,讓讀者能訂閱
gem "jekyll-seo-tag" # 自動處理 Open Graph、Twitter Card 等 SEO meta
gem "jekyll-sitemap" # 自動生成 sitemap.xml 給搜尋引擎爬
gem "jekyll-paginate" # 文章分頁,避免首頁太長
這些插件幫我自動處理了所有 SEO 與 RSS 的瑣事,讓我專注在內容撰寫上。
Liquid 模板的實際應用:
Jekyll 的 Liquid 模板讓我能在靜態網站中實現動態邏輯,例如根據語言顯示不同的導航列、根據分類過濾文章等。
2. Hardhat + Ethers.js - Web3 技術棧
為什麼需要區塊鏈整合?
我想展示 Web3 開發能力,同時為作品集加入「代幣經濟」這個實驗性功能。訪客可以用 USDT 購買我的代幣 (SGT),象徵性地「投資」我的技術服務。
為什麼選擇 Hardhat?
在 Truffle、Foundry、Hardhat 三者之間,我選擇 Hardhat 因為:
- 優秀的錯誤訊息:當合約 revert 時,能清楚看到原因
- 活躍的社群:大部分 OpenZeppelin 文件都以 Hardhat 為範例
- 內建測試框架:整合 Mocha/Chai,不需要複雜設定
- JavaScript 原生支援:使用熟悉的 JavaScript 編寫測試和腳本
為什麼選擇 Ethers.js 而非 Web3.js?
- 更小的 bundle size:Ethers.js 只有 88KB,Web3.js 超過 400KB
- 更現代的 API:async/await 風格,不用處理 callback hell
- 更好的文件:Richard Moore 的文件寫得非常清楚
依賴項與用途:
{
"@openzeppelin/contracts": "^5.4.0", // 經過審計的標準合約
"@openzeppelin/contracts-upgradeable": "^5.4.0", // UUPS 升級模式
"@openzeppelin/hardhat-upgrades": "^3.9.1", // 升級部署工具
"ethers": "^6.15.0", // 前端錢包互動
"hardhat": "^2.26.3" // 開發環境
}
為什麼選擇 UUPS?
使用 OpenZeppelin 的 UUPS (Universal Upgradeable Proxy Standard) 可升級模式,讓合約邏輯可以升級,同時保持合約地址和狀態不變。這對長期維護的代幣合約很重要。
Solidity 編譯器版本:
合約使用 Solidity 0.8.19,這是一個穩定且被 OpenZeppelin 完整支援的版本。Hardhat 配置中也設定了 0.8.20 和 0.8.22 編譯器,以確保能編譯不同版本的依賴套件。
3. SCSS - 樣式架構
為什麼用 SCSS 而非 Tailwind?
我評估了幾個選項:
方案 | 優點 | 缺點 | 為何不選 |
---|---|---|---|
CSS-in-JS | 元件化、動態樣式 | 需要 React/Vue | 這是靜態網站 |
Tailwind | 快速開發、utility-first | HTML 冗長、客製化困難 | RPG 風格需要大量客製 |
SCSS | 變數、巢狀、mixins | 需要編譯 | ✅ 選擇這個 |
SCSS 的關鍵優勢:
// 主題變數統一管理
[data-theme="dark"] {
--primary: #ffd700;
--bg: #0a192f;
}
// 響應式 mixin 重用
@mixin mobile {
@media (max-width: 768px) { @content; }
}
// 模組化匯入
@import "variables"; // 全域變數
@import "theme"; // 主題定義
@import "components"; // 元件樣式
我把樣式拆成 33 個模組,每個模組負責一個功能(header、footer、game-tabs 等),這樣修改 A 功能時不會影響到 B。
4. i18n 多語言系統
為什麼需要多語言?
我的目標受眾包括台灣、中國、日本、韓國的開發者,而且我會日文,所以決定支援 5 種語言。
為什麼用 YAML + Ruby 而非前端 i18n 庫?
我比較了幾個方案:
- i18next:功能強大但 bundle 太大(45KB)
- vue-i18n:需要 Vue 框架
- 自建系統:YAML → Ruby 腳本 → JSON → 前端載入
我選擇自建系統,因為:
- 零 runtime 依賴:所有翻譯在編譯時處理
- YAML 易讀:翻譯者不需要懂 JavaScript
- Jekyll 整合:Ruby 腳本可以直接執行
工作流程:
# 1. 編輯 YAML(人類友善格式)
_data/i18n/zh-TW.yml
# 2. 執行轉換腳本
ruby generate_i18n_json.rb
# 3. 前端載入 JSON
fetch('/assets/i18n/zh-TW.json')
為什麼要同步載入 i18n-manager.js?
如果 i18n 是 defer
載入,會出現「英文閃一下變中文」的問題(FOUC - Flash of Unstyled Content)。所以我讓 i18n-manager.js 在 DOM 解析前就載入,這樣首次渲染就是正確語言。
5. 動畫系統
為什麼同時用 Anime.js 和 GSAP?
功能 | Anime.js | GSAP | 選擇 |
---|---|---|---|
簡單進場動畫 | ✅ 9KB | ❌ 50KB | Anime.js |
複雜時間軸 | ❌ 功能有限 | ✅ 強大 | GSAP |
SVG 動畫 | ⚠️ 基礎支援 | ✅ 完整支援 | GSAP |
我用 Anime.js 處理 90% 的簡單動畫(元素進場、數字滾動),用 GSAP 處理複雜的技能樹動畫。
為什麼不全部用 GSAP?
GSAP 太大了(50KB)。對於這個專案,Anime.js 的 9KB 已經足夠大部分需求。
6. 前端架構設計
問題:如何管理 22 個 JavaScript 模組的依賴關係?
我使用了三層載入策略:
1. 同步層(阻塞載入)
i18n-manager.js
:必須在 DOM 前載入,避免翻譯閃爍- 主題腳本:避免「白色閃一下變黑色」
2. 延遲層(defer 載入)
- 所有其他腳本:使用
defer
屬性非阻塞載入 - CDN 資源:Anime.js、GSAP 都用 defer
3. 事件驅動層(Promise.all)
// 不用輪詢,用 Promise 等待依賴就緒
Promise.all([
window.walletManager?.isReady,
window.i18nManager?.isReady,
window.gameState?.isReady
]).then(() => {
console.log('系統初始化完成');
});
為什麼不用 Webpack/Vite?
- Jekyll 已經處理了靜態資源
- 我不需要 tree-shaking(腳本總共才 100KB)
- 不需要 HMR(Jekyll 已經有 auto-reload)
- 部署更簡單(不需要 build step)
7. 狀態管理
為什麼用 Cookie 而非 localStorage?
Cookie 可以設定過期時間(我設定為 7 天),這樣玩家狀態會自動過期,避免佔用過多儲存空間。而且 Cookie 在跨頁面時更穩定。
為什麼不用 Redux/Vuex?
這不是一個 SPA,每次換頁都會重新載入。用 Redux 會增加 45KB bundle size,但完全用不到 time-travel debugging 等功能。
簡單的狀態管理:
class GameStateManager {
save() {
document.cookie = `gameState=${JSON.stringify(this.state)}; max-age=604800`; // 7 天
}
load() {
const cookie = document.cookie.match(/gameState=([^;]+)/);
return cookie ? JSON.parse(cookie[1]) : this.defaultState;
}
}
🚀 效能優化決策
消除渲染阻塞
Font Awesome 的非同步載入技巧:
<link rel="stylesheet" href="font-awesome.css"
media="print" onload="this.media='all'">
這個技巧讓 CSS 以低優先級載入,不會阻塞首屏渲染。
懶載入
使用 lazy-loader.js
管理大型套件的動態載入,只有在實際需要時才載入相關功能,減少初始載入時間。
🔐 安全性考量
智能合約安全
為什麼用 OpenZeppelin?
自己寫 ERC20 容易出錯。OpenZeppelin 的合約經過:
- 多次獨立審計
- 數百個專案的實戰驗證
- 持續的安全更新
為什麼需要 ReentrancyGuard?
雖然我的合約遵循 CEI 模式(Checks-Effects-Interactions),但多加一層保護不會有壞處,Gas 成本只增加幾百 wei。
為什麼需要時間鎖(Timelock)?
關鍵操作(如變更 Treasury 地址、升級合約)需要 24 小時至 7 天的等待期,這讓用戶有時間發現異常並撤出資金。
前端安全
為什麼用 CSP(Content Security Policy)?
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
強制所有 HTTP 請求升級為 HTTPS,防止中間人攻擊。
📊 開發體驗優化
一鍵啟動腳本
為什麼需要 ./dev
腳本?
開發時需要同時啟動:
- Hardhat 本地區塊鏈(端口 8545)
- 部署智能合約
- Jekyll 開發伺服器(端口 4000)
每次都手動執行這三步很麻煩,所以我寫了一個 Bash 腳本整合所有步驟。
測試策略
為什麼寫了 52 個測試?
智能合約一旦部署就無法修改(即使用了可升級模式,升級也有風險)。所以我測試了:
- 所有正常流程
- 所有邊界條件(0 金額、最大值、溢位)
- 所有錯誤處理(餘額不足、權限不足)
- 所有安全機制(重入攻擊、時間鎖)
測試覆蓋率 100%,每次部署前都會跑完所有測試。
💡 技術取捨
不選擇 React/Vue 的原因
- 過度設計:這是個部落格,不是 Gmail
- SEO 複雜:需要 SSR 或 prerender
- Bundle size:React 16KB + ReactDOM 40KB,對於靜態網站太重
- 學習曲線:協作者需要學 JSX/Vue template
不選擇 Next.js/Nuxt 的原因
- 需要 Node.js 伺服器:失去靜態部署的優勢
- 複雜度:為了部落格功能引入全端框架
- 成本:Vercel 免費額度有限,GitHub Pages 完全免費
結語
這個專案的技術選型圍繞三個原則:
- 簡單優先:能用靜態就不用動態,能用 CSS 就不用 JS
- 效能優先:每增加一個依賴都要問「值得嗎?」
- 安全優先:智能合約絕不省測試
技術棧總結:
- 前端:Jekyll + SCSS + Vanilla JS(零框架)
- 區塊鏈:Hardhat + Ethers.js + OpenZeppelin
- 動畫:Anime.js(輕量) + GSAP(複雜場景)
- 多語言:YAML → Ruby → JSON(零 runtime)
如果你對技術選型有任何疑問,歡迎透過 GitHub Issues 討論!