<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds/rss-style.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Nevermind</title>
        <link>https://blog.m1ng.space/zh-tw</link>
        <description>m1ngsama 的部落格</description>
        <lastBuildDate>Thu, 13 Aug 2026 08:51:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro-Theme-Retypeset with Feed for Node.js</generator>
        <language>zh-tw</language>
        <copyright>Copyright © 2026 m1ngsama</copyright>
        <atom:link href="https://blog.m1ng.space/zh-tw/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[如何打造屬於你的作業系統]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/myarch/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/myarch/</guid>
            <pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Arch Linux 秉持極簡主義，讓使用者能自由打造所需的任何功能。以下以實機部署為例，簡單介紹如何構建屬於你的 Arch Linux。需...]]></description>
            <content:encoded><![CDATA[<p>Arch Linux 秉持<a href="https://wiki.archlinux.org/title/Arch_Linux">極簡主義</a>，讓使用者能自由打造所需的任何功能。以下以實機部署為例，簡單介紹如何構建屬於你的 Arch Linux。</p>
<h2>準備工作</h2>
<p>需要準備：電腦、USB 隨身碟（或任何可移動儲存媒介）、網路連線、基本的資訊檢索能力</p>
<ul>
<li>不論使用何種安裝映像檔方案，即使是離線版映像檔，建議準備好網路環境，以確保核心和工具的更新。若你非常熟悉 Arch Linux，也可自行決定是否需要網路。</li>
<li>如果使用無線網路，請確保 Wi-Fi 名稱使用英文，因為在 tty 環境下無法顯示中文，中文名稱會顯示為無法辨識的方塊字元。</li>
<li>如果計劃在同一顆硬碟上安裝雙系統，請為 Arch Linux 預留足夠的硬碟空間，建議至少 100GB 以便安裝其他軟體；並確保 EFI 分區容量不少於 256MB，或<a href="https://wiki.archlinux.org/title/EFI_system_partition">新增額外的掛載點</a>。</li>
<li>檢查 Windows 10 分區是否啟用 BitLocker 加密，務必提前取得恢復金鑰，並關閉電源選項中的「快速啟動」功能！</li>
</ul>
<blockquote>
<p>在操作前，請務必仔細閱讀並針對不理解的部分進行搜尋學習，謹慎操作，及時備份，資料無價。</p>
</blockquote>
<h2>安裝媒介的製作</h2>
<ol>
<li>建議僅從 <a href="https://archlinux.org/download/">Arch Linux 官方映像檔來源</a> 下載，請注意，Arch Linux 是滾動更新發行版。</li>
<li>若要自行編譯核心，請參考<a href="https://wiki.archlinux.org/title/Kernel/Traditional_compilation">「核心/傳統編譯」</a>。</li>
<li>若使用官方提供的安裝映像檔，建議使用 <a href="https://www.ventoy.net/">Ventoy</a> 燒錄。</li>
</ol>
<h2>基礎安裝</h2>
<h3>1. 使用 Arch Linux 引導碟</h3>
<blockquote>
<p>關機，插入 USB 隨身碟後開機，進入 BIOS 選擇從 USB 啟動，選取第一個選項並按 Enter，進入 Arch Linux 安裝環境。</p>
</blockquote>
<h3>2. UEFI 檢查</h3>
<pre><code>systemctl stop reflector.service
# 禁用自動更新軟體來源，因為地理因素可能影響網路環境，建議關閉。
</code></pre>
<pre><code>ls /sys/firmware/efi/efivars
# 若顯示一串 EFI 變數，表示已使用 UEFI 模式啟動。2025 年的大多數機器皆為 UEFI 引導。
</code></pre>
<h3>3. 連線網路</h3>
<blockquote>
<p>Arch Linux 安裝需要網路環境，離線安裝步驟較為繁瑣，可參考社群的 <a href="https://wiki.archlinux.org/title/Offline_installation">離線安裝指南</a>。</p>
<p>有線網路連線無需多述，接上網路線後檢查介面指示燈是否閃爍，等待數秒後即可完成連線。</p>
<p>若在校園網路環境中，需由上層路由器完成認證，可參考 <a href="https://github.com/nbtca/nbtverify">nbtverify</a> 專案。</p>
<p>無線網路則使用 <code>iwctl</code> 進行連線。</p>
</blockquote>
<pre><code>lspci -k | grep Network
# 檢查無線網卡是否正常運作，若確認無問題可跳過此步。
</code></pre>
<blockquote>
<p>檢查核心是否載入無線網卡驅動程式。</p>
<p>通常會顯示類似：<code>00:14.3 Network controller: Intel Corporation Wi-Fi 6 AX201 (rev 20)</code>。</p>
<p>若無顯示，檢查無線連線是否被禁用（blocked: yes）。</p>
</blockquote>
<pre><code>rfkill list
# 無線網卡通常命名為 wlan0。
</code></pre>
<pre><code>ip link set wlan0 up
# 若出現類似「Operation not possible due to RF-kill」錯誤，則執行：
rfkill unblock wifi
</code></pre>
<pre><code># 使用 iwctl 連線網路
iwctl # 進入互動式命令列
device list # 列出無線網卡設備名稱，例如 wlan0
station wlan0 scan # 掃描網路
station wlan0 get-networks # 列出所有 Wi-Fi 網路
station wlan0 connect wifi-name # 連線至指定網路，注意無法輸入中文名稱。輸入密碼後按 Enter
exit # 連線成功後退出

ping www.google.com # 測試網路連通性
</code></pre>
<blockquote>
<p>若網路設定遇到問題，可參考 [網路設定.wpi</p>
</blockquote>
<p>System: 設定/無線網路設定](<a href="https://wiki.archlinux.org/title/Network_configuration/Wireless)%E3%80%82">https://wiki.archlinux.org/title/Network_configuration/Wireless)。</a></p>
<h3>5. 同步系統時間</h3>
<pre><code>timedatectl set-ntp true # 將系統時間與網路時間同步
timedatectl status # 檢查服務狀態
</code></pre>
<h3>6. 更換台灣或國際軟體來源（若在國際網路環境可忽略）</h3>
<pre><code>vim /etc/pacman.d/mirrorlist # 準備更換軟體來源，若已透過代理連線國際網路可忽略
Server = https://free.nchc.org.tw/archlinux/$repo/os/$arch # 國家高速網路與計算中心
Server = https://archlinux.cs.nctu.edu.tw/$repo/os/$arch # 國立交通大學
Server = https://mirror.liquidtelecom.com/archlinux/$repo/os/$arch # Liquid Telecom
</code></pre>
<h3>7. 建立 Btrfs 分區</h3>
<h4>查看磁碟資訊</h4>
<pre><code>lsblk
</code></pre>
<p>顯示當前分區情況。請<strong>仔細檢查你要安裝 Arch Linux 的目標硬碟名稱</strong>。</p>
<p>硬碟命名規則：</p>
<ul>
<li><strong>SATA 協議</strong>：<code>sda</code>、<code>sdb</code>、<code>sdc</code> …… 分區為 <code>sda1</code>、<code>sda2</code> 等</li>
<li><strong>NVMe 協議</strong>：<code>nvme0n1</code>、<code>nvme1n1</code> …… 分區為 <code>nvme0n1p1</code>、<code>nvme0n1p2</code> 等</li>
</ul>
<blockquote>
<p>示例以 SATA 硬碟為例，請根據實際情況替換 <code>/dev/sdx</code>。</p>
</blockquote>
<pre><code>cfdisk /dev/sdx
</code></pre>
<p>是否看到一個友好的 TUI 分區介面？😄</p>
<h4>分區操作步驟</h4>
<h5>1. 建立 Swap 分區</h5>
<ul>
<li>使用方向鍵選中 <strong>Free space</strong>。</li>
<li>按下 <code>[New]</code> 並按 Enter，輸入大小（建議為記憶體大小的 60%~100%）。</li>
<li>按 <code>[Type]</code>，選擇 <strong>Linux swap</strong>。</li>
</ul>
<h5>2. 建立根分區（用於 Btrfs）</h5>
<ul>
<li>選中剩餘的 Free space，按 <code>[New]</code> 並按 Enter。</li>
<li>輸入大小（預設使用全部剩餘空間）。</li>
<li>保持類型為預設的 <strong>Linux filesystem</strong>。</li>
</ul>
<h5>3. 寫入分區表</h5>
<ul>
<li>選中 <code>[Write]</code>，輸入 <code>yes</code> 並按 Enter。
<blockquote>
<p>⚠️ <strong>注意：若未寫入，之前的操作不會生效！</strong></p>
</blockquote>
</li>
</ul>
<h4>格式化分區</h4>
<h5>再次檢查磁碟</h5>
<pre><code>fdisk -l
</code></pre>
<h5>格式化 EFI 分區（若需新建）</h5>
<pre><code>mkfs.fat -F32 /dev/sdxn
</code></pre>
<blockquote>
<p>💡 若為雙系統使用者，可共用 Windows 的 EFI 分區，無需格式化，但需確保空間足夠，詳見 <a href="https://wiki.archlinux.org/title/Dual_boot_with_Windows">與 Windows 雙系統</a>。</p>
</blockquote>
<h5>格式化 Swap 分區</h5>
<pre><code>mkswap /dev/sdxn
</code></pre>
<h5>格式化 Btrfs 分區</h5>
<pre><code>mkfs.btrfs -L myArch /dev/sdxn
</code></pre>
<h4>建立並掛載 Btrfs 子卷</h4>
<pre><code>mount -t btrfs -o compress=zstd /dev/sdxn /mnt

# 建立子卷
btrfs subvolume create /mnt/@        # 根目錄子卷
btrfs subvolume create /mnt/@home    # /home 子卷

umount /mnt
</code></pre>
<h4>⚠️ 最後提醒</h4>
<ul>
<li>請再次檢查命令和操作的正確性！</li>
<li><strong>誤操作可能導致資料遺失，尤其是誤刪 Windows 分區 😥</strong></li>
</ul>
<h3>8. 掛載，從根目錄依序掛載</h3>
<pre><code>mount -t btrfs -o subvol=/@,compress=zstd /dev/sdxn /mnt # 掛載 / 目錄
mkdir /mnt/home # 建立 /home 目錄
mount -t btrfs -o subvol=/@home,compress=zstd /dev/sdxn /mnt/home # 掛載 /home 目錄
mkdir -p /mnt/boot # 建立 /boot 目錄
mount /dev/sdxn /mnt/boot # 掛載 /boot 目錄
swapon /dev/sdxn # 掛載交換分區
</code></pre>
<pre><code>df -h # 檢查掛載
free -h # 檢查 Swap 分區掛載
</code></pre>
<h3>9. 安裝系統</h3>
<pre><code>pacstrap /mnt base base-devel linux linux-firmware btrfs-progs
# 若使用 Btrfs 檔案系統，需額外安裝 btrfs-progs 套件
</code></pre>
<pre><code>pacman -S archlinux-keyring
# 若提示 GPG 證書錯誤，可能是因為使用的不是最新映像檔，可透過更新 archlinux-keyring 解決
</code></pre>
<pre><code>pacstrap /mnt networkmanager vim sudo zsh zsh-completions
# 使用 pacstrap 指令碼安裝必要的功能性軟體
</code></pre>
<h3>10. 生成 fstab 檔案</h3>
<blockquote>
<p>生成 fstab 以定義磁碟分區，會受到當前掛載情況影響。</p>
</blockquote>
<pre><code>genfstab -U /mnt &gt; /mnt/etc/fstab
</code></pre>
<h3>11. 進入新系統</h3>
<pre><code>arch-chroot /mnt
# 程式碼高亮消失了？別擔心，這表示你已成功切換到新系統！
</code></pre>
<h3>12. 設定主機名稱與時區</h3>
<pre><code>vim /etc/hostname
# 為你的電腦取個名字吧 XD（避免使用特殊字元和空格，否則可能出問題；不設定主機名稱也可能導致某些 GUI 程式異常終止，建議務必設定）
</code></pre>
<pre><code>vim /etc/hosts
# 編輯主機 hosts 檔案
</code></pre>
<blockquote>
<p>填入以下內容（將 myarch 替換為你設定的主機名稱，注意中間間隔使用 Tab 對齊）：</p>
</blockquote>
<pre><code>127.0.0.1   localhost
::1         localhost
127.0.1.1   myarch.localdomain  myarch
</code></pre>
<pre><code>ln -sf /usr/share/zoneinfo/Asia/Taipei /etc/localtime
# 在台北時區建立符號連結
</code></pre>
<pre><code>ls /usr/share/zoneinfo/
# 檢查可用時區，必要時更換上述命令中的時區路徑
</code></pre>
<h3>13. 硬體時間設定</h3>
<pre><code>hwclock --systohc
# 將系統時間同步至硬體時間
</code></pre>
<h3>14. 設定語系</h3>
<pre><code>vim /etc/locale.gen
# 編輯 /etc/locale.gen，移除 en_US.UTF-8 UTF-8 和 zh_TW.UTF-8 UTF-8 行前的註解符號（#）
# 此步驟決定軟體使用的語言和字元集
</code></pre>
<pre><code>locale-gen
# 生成語系
</code></pre>
<pre><code>echo 'LANG=en_US.UTF-8' &gt; /etc/locale.conf
# 設定 locale.conf，不建議使用中文語系，否則 tty 可能出現亂碼
</code></pre>
<h3>15. 設定 root 密碼</h3>
<pre><code>passwd root
# 輸入密碼時不會顯示，屬正常現象，非鍵盤故障 XD
</code></pre>
<h3>16. 安裝微碼</h3>
<pre><code>pacman -S intel-ucode # Intel 處理器
Джpacman -S amd-ucode # AMD 處理器
</code></pre>
<h3>17. 安裝 Grub 引導程式</h3>
<pre><code>pacman -S grub efibootmgr os-prober
# grub 為啟動引導程式，efibootmgr 用於向 NVRAM 寫入啟動項，os-prober 用於引導 Windows 10
</code></pre>
<pre><code>grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=ARCH
# 將 grub 安裝至 EFI 分區
</code></pre>
<pre><code>vim /etc/default/grub
# 編輯啟動參數
</code></pre>
<pre><code># 將 "loglevel=3 quiet" 修改為 "loglevel=5 nowatchdog"
# 在檔案末尾新增一行：GRUB_DISABLE_OS_PROBER=false
</code></pre>
<ul>
<li>移除 GRUB_CMDLINE_LINUX_DEFAULT 行中的 quiet 參數。</li>
<li>將 loglevel 的數值從 3 改為 5，以便在系統錯誤時方便排錯。</li>
<li>加入 nowatchdog 參數，可顯著提升開關機速度。</li>
<li>加入 os-prober 參數，用於引導 Windows 10。</li>
</ul>
<pre><code>grub-mkconfig -o /boot/grub/grub.cfg
# 生成 grub 所需的設定檔
# 若檢測到 Windows 10，會顯示一行類似「Found Windows Boot Manager on /dev/nvme0n1p1@/EFI/Microsoft/Boot/bootmgfw.efi done」的回顯
# 若 Windows 10 位於另一顆硬碟，可能不會顯示，進入系統後重新掛載並再次執行即可
</code></pre>
<blockquote>
<p>有關全部參數的說明，可參考 <a href="https://wiki.archlinux.org/title/GRUB">Arch Wiki</a>。</p>
</blockquote>
<h3>18. 完成安裝</h3>
<pre><code>exit # 返回安裝環境
umount -R /mnt # 卸載新分區
reboot # 重新啟動
</code></pre>
<blockquote>
<p>重新啟動後使用 root 帳戶登入。</p>
</blockquote>
<pre><code>systemctl enable --now NetworkManager # 設定開機自啟並立即啟動 NetworkManager 服務
ping www.google.com # 測試網路連通性
</code></pre>
<blockquote>
<p>若使用無線網路：</p>
</blockquote>
<pre><code>nmcli dev wifi list # 顯示附近的 Wi-Fi 網路
nmcli dev wifi connect "Wi-Fi 名稱（SSID）" password "網路密碼" # 連線至指定的無線網路
</code></pre>
<pre><code>nmtui
# 個人較推薦 nmtui，介面較為友好 XD
</code></pre>
<pre><code>pacman -S fastfetch
fastfetch
# 安裝 fastfetch，檢查系統資訊
# 喜聞樂見的 neofetch 時刻 XD
</code></pre>
<pre><code>shutdown 0
shutdown -h now
poweroff
# 以上三個命令皆為關機，🤣 記得關機，因為電源策略尚未設定
</code></pre>
<h2>恭喜 🎉</h2>
<blockquote>
<p>至此，你已完成一個基礎的無圖形介面的 Arch Linux 安裝！</p>
<p>圖形化介面的安裝指南將在下次更新發布，還是那句老話：多閱讀官方手冊！</p>
<p>本文旨在拋磚引玉，希望吸引更多同好加入技術交流的行列！</p>
</blockquote>
<hr />
<blockquote>
<p>友情連結：<a href="https://nbtca.space/">NBTCA</a></p>
</blockquote>
<ul>
<li>📧 NBTCA 電子郵件：<a href="mailto:contact@nbtca.com">contact@nbtca.com</a></li>
<li>🌐 NBTCA GitHub：<a href="https://github.com/nbtca">https://github.com/nbtca</a></li>
</ul>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[深入理解 Hydration：現代前端框架的「必要之惡」？]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/frameworks/deep-dive-into-hydration/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/frameworks/deep-dive-into-hydration/</guid>
            <pubDate>Sat, 10 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[服務端渲染 (SSR) 帶來了快速的首頁載入，但也引入了 Hydration 的概念及其效能開銷。它是什麼？為何會產生「互動延遲」？以及社群正在探索哪些替代方案？]]></description>
            <content:encoded><![CDATA[<h2>1. SSR 的美好與煩惱</h2>
<p>服務端渲染 (SSR) 極大地改善了 Web 應用程式的「首屏載入速度」 (FCP)。瀏覽器直接接收到完整的 HTML 內容並立刻渲染，使用者能很快看到頁面內容，這對 SEO 和使用者感知都非常友善。</p>
<p>但此時的頁面只是一個「靜態的空殼」。雖然看起來已經載入完畢，但頁面上的按鈕點擊、輸入框互動都毫無反應。為了讓這個靜態頁面「活」過來，我們需要一個過程，這個過程就是 <strong>Hydration</strong>（注水／啟用）。</p>
<h2>2. 什麼是 Hydration？</h2>
<p>Hydration 是指客戶端的 JavaScript 框架「接管」由服務端渲染的靜態 HTML 的過程。</p>
<p>這個過程大致如下：</p>
<ol>
<li>瀏覽器下載並執行頁面所需的 JavaScript 套件（例如 React、Vue 的執行階段和業務程式碼）。</li>
<li>框架在記憶體中重新建構元件樹。</li>
<li>框架遍歷服務端渲染的 DOM，並將事件監聽器（如 <code>onClick</code>）附加到對應的 DOM 節點上。</li>
<li>框架確保客戶端的元件狀態與服務端渲染時的狀態一致。</li>
</ol>
<p>完成之後，頁面才真正變得可互動。可以把 Hydration 想像成：你收到一個已經拼好的樂高模型（HTML），但為了讓模型的某個元件能動，你必須把整個模型的拼裝說明書（JS）從頭到尾讀一遍，並檢查一遍所有積木的位置，然後才給那個元件裝上電池（附加事件監聽器）。</p>
<h2>3. Hydration 的問題：「非互動的互動介面」</h2>
<p>Hydration 解決了 SSR 頁面無法互動的問題，但它自身也帶來了新的效能瓶頸，即所謂的 <strong>「非互動幽谷」 (Uncanny Valley)</strong> 或 <strong>「注水延遲」 (Hydration Gap)</strong>。</p>
<p>這個問題指的是從使用者看到頁面內容 (FCP) 到頁面可以真正回應使用者互動 (TTI) 之間存在一個時間差。在這個時間差內，頁面看起來是可用的，但實際上是「假死」狀態。</p>
<p>造成這個問題的原因是：</p>
<ul>
<li><strong>JS 下載和執行阻塞</strong>: 瀏覽器必須下載、解析並執行大量的 JavaScript 程式碼，才能開始 Hydration 過程。</li>
<li><strong>昂貴的啟動成本</strong>: 框架需要在客戶端執行大量工作來重新建構元件樹和附加事件監聽器，即使頁面上 90% 的內容都是非互動的靜態內容。</li>
</ul>
<h2>4. Hydration 的最佳化與替代方案</h2>
<p>為了解決 Hydration 的效能問題，社群探索出了多種更先進的模式。</p>
<h4>a. 局部注水 (Partial Hydration)</h4>
<p><strong>Astro</strong> 框架推廣的 <strong>島嶼架構 (Islands Architecture)</strong> 是局部注水的典型實作。其核心思想是：預設情況下，所有元件都只輸出純靜態 HTML（零 JS）。對於需要互動的元件，你可以將其明確標記為一個「島嶼」。</p>
<p>建置時，Astro 只會為這些「島嶼」元件打包並傳送 JavaScript。這樣，瀏覽器只需對頁面上的幾個孤立部分進行 Hydration，而不是整個頁面，極大地減少了啟動時需要載入和執行的 JS 量。</p>
<h4>b. 漸進式注水 (Progressive Hydration)</h4>
<p>這是一種更細粒度的最佳化策略，它按照一定的優先順序來 Hydrate 元件。例如，可以優先 Hydrate 視口內或使用者即將互動的元件，而延遲處理頁面底部的、非關鍵的元件。</p>
<h4>c. 可恢復性 (Resumability)</h4>
<p><strong>Qwik</strong> 框架提出了一個革命性的概念——<strong>Resumability</strong>，旨在完全消滅 Hydration。</p>
<p>它的運作方式是：</p>
<ol>
<li><strong>序列化</strong>: 在服務端，Qwik 將應用程式的所有狀態、元件關係、事件監聽器等資訊全部序列化，並嵌入到 HTML 中。</li>
<li><strong>恢復</strong>: 在客戶端，Qwik 的極小執行階段（約 1KB）不需要在啟動時重新建構元件樹或附加所有事件。它透過一個全域的事件監聽器，可以精確地知道當使用者點擊某個按鈕時，應該去下載並執行哪一小塊程式碼。</li>
</ol>
<p>Qwik 的目標是實現 <strong>即時互動 (Instant-on)</strong>。它不是「重新執行」一遍服務端的工作，而是從服務端停止的地方「恢復」執行。</p>
<h2>結論</h2>
<p>Hydration 是連接服務端渲染和客戶端互動的橋樑，但在傳統實作中，它是一個昂貴且可能損害使用者體驗的過程。現代前端框架正在透過局部注水（Astro）和可恢復性（Qwik）等創新模式，努力擺脫 Hydration 這個「必要之惡」，向著更極致的效能和更快的互動速度邁進。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[WebAssembly（WASM）正在如何改變前端開發]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/wasm/wasm-is-changing-frontend/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/wasm/wasm-is-changing-frontend/</guid>
            <pubDate>Sun, 30 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[JavaScript 不再是瀏覽器中唯一的「玩家」。WebAssembly（WASM）正透過提供接近原生的效能，將桌面級的複雜應用程式（如 Photoshop、Figma）帶到 Web 上。]]></description>
            <content:encoded><![CDATA[<h2>1. 瀏覽器裡的「第二語言」</h2>
<p>長久以來，JavaScript 幾乎是 Web 前端開發的唯一語言。儘管它非常成功，但作為一種動態直譯語言，其效能在處理 CPU 密集型工作（如 3D 算繪、影片編解碼、複雜運算）時始終存在瓶頸。</p>
<p>WebAssembly（簡稱 WASM）的出現，正是為了打破這一局面。它不是要取代 JavaScript，而是作為一種強大的補充，為 Web 平台帶來前所未有的效能和可能性。</p>
<h2>2. 什麼是 WebAssembly？</h2>
<p>WebAssembly 是一種為堆疊式虛擬機器設計的<strong>二進位指令格式</strong>。它是一種低階、類似組合語言的語言，但並不是讓開發者直接撰寫的。</p>
<p>相反地，它被設計為 C、C++、Rust、Go 等高階語言的<strong>編譯目標</strong>。你可以用這些高效能語言撰寫程式碼，再將它們編譯成 <code>.wasm</code> 檔案，在瀏覽器中以接近原生的速度執行。</p>
<p><strong>關鍵點：</strong></p>
<ul>
<li><strong>它不是 JavaScript 的替代品</strong>：WASM 與 JavaScript 是合作夥伴關係。</li>
<li><strong>它是一種編譯目標</strong>：你撰寫 C++／Rust，再編譯成 WASM。</li>
<li><strong>它快速、高效且可攜</strong>：設計之初就以效能為核心。</li>
</ul>
<h2>3. JavaScript 與 WASM 的協作模式</h2>
<p>WASM 模組本身執行於沙箱環境中，無法直接存取 DOM、呼叫 Web API 或發出網路請求。所有這些操作都需要由 JavaScript 擔任「膠水」層加以仲介。</p>
<p>典型的協作模式是：</p>
<ol>
<li><strong>JavaScript 負責編排</strong>：JS 程式碼負責應用程式的整體邏輯、處理使用者事件、更新 DOM。</li>
<li><strong>WASM 負責運算</strong>：遇到運算密集型工作時，JS 會呼叫從 <code>.wasm</code> 模組匯出的函式。</li>
<li><strong>資料交換</strong>：JS 和 WASM 之間可以高效地交換資料，主要是數字型別和線性記憶體區塊。</li>
</ol>
<p>可以把 JavaScript 想像成「經理」，而 WebAssembly 則是「專家級工程師」。經理負責溝通和協調，工程師則負責解決最棘手的技術難題。</p>
<h2>4. 現實世界中的應用案例</h2>
<p>WASM 已經不再是一項實驗性技術，許多頂尖 Web 應用程式都在使用它驅動核心功能：</p>
<ul>
<li><strong>Figma</strong>：這款熱門線上設計工具的核心算繪引擎以 C++ 撰寫並編譯為 WASM，因此能提供流暢的圖形編輯體驗。</li>
<li><strong>Adobe Photoshop &amp; Lightroom</strong>：Adobe 成功地透過 WASM，將其旗艦桌面應用程式的 C++ 核心程式碼庫移植到 Web 上，讓使用者可以在瀏覽器裡使用功能強大的 Photoshop。</li>
<li><strong>Google Earth</strong>：新版 Google Earth 完全在瀏覽器中執行，其複雜的 3D 地球算繪正是由 WASM 驅動。</li>
<li><strong>AutoCAD Web App</strong>：Autodesk 將其龐大的 C++ CAD 引擎編譯為 WASM，實現在瀏覽器中執行完整的 AutoCAD。</li>
</ul>
<p>這些案例證明，WASM 已經有能力將過去被認為只有桌面應用程式才能勝任的複雜軟體帶到 Web 平台。</p>
<h2>5. 未來：WASI 與瀏覽器之外</h2>
<p>WASM 的雄心不只在瀏覽器。<strong>WASI（WebAssembly System Interface）</strong> 是一項新興標準，旨在為 WASM 提供一套標準的系統級 API（如檔案系統、網路存取等）。</p>
<p>這代表未來 <code>.wasm</code> 檔案可能成為一種通用、跨平台且安全的二進位格式，可以在任何地方執行——從伺服器（挑戰 Docker）、邊緣運算節點，到物聯網裝置，真正實現「一次編譯，到處執行」。</p>
<h2>結論</h2>
<p>WebAssembly 正在深刻改變我們對 Web 應用程式能力邊界的認知。它讓 JavaScript 能專注於最擅長的領域——UI 互動和應用程式邏輯編排，並將效能的「天花板」交給由 Rust、C++ 等系統級語言編譯而來的 WASM 模組。對前端開發者來說，理解 WASM 的能力與適用情境，將是建構下一代高效能 Web 應用程式的關鍵。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[使用 View Transitions API 打造無縫的頁面過渡動畫]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/css/view-transitions-api/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/css/view-transitions-api/</guid>
            <pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[網頁跳轉時的「白屏閃爍」一直以來都是影響使用者體驗的頑疾。現在，瀏覽器原生提供了 View Transitions API，讓我們能用幾行程式碼就實現電影般的頁面過渡效果。]]></description>
            <content:encoded><![CDATA[<h2>1. 頁面跳轉的體驗鴻溝</h2>
<p>在 Web 開發中，我們長期面臨一個體驗上的兩難選擇：</p>
<ul>
<li><strong>多頁應用程式 (MPA)</strong>: 架構簡單、穩定，但每次頁面跳轉都會有一次完整的載入，帶來「白屏」或「閃爍」，體驗不連貫。</li>
<li><strong>單頁應用程式 (SPA)</strong>: 透過前端路由實現了流暢的「無重新整理」體驗，但開發者需要手動處理複雜的過渡動畫、狀態管理和程式碼分割，開發成本高昂。</li>
</ul>
<p>有沒有一種方法，既能保持 MPA 的簡單性，又能獲得 SPA 的流暢過渡體驗？View Transitions API 正是為此而生。</p>
<h2>2. View Transitions API 是什麼？</h2>
<p>View Transitions API 是一個瀏覽器原生 API，它提供了一種機制，讓我們能夠輕鬆地在兩個不同的 DOM 狀態之間建立動畫過渡。</p>
<p>它的核心工作流程非常簡單：</p>
<ol>
<li>當你觸發一個轉換時，API 會對目前頁面（舊狀態）進行「截圖」。</li>
<li>然後，你用 JavaScript 更新 DOM 到新狀態。</li>
<li>API 會對新頁面（新狀態）也進行「截圖」。</li>
<li>最後，瀏覽器會在「舊截圖」和「新截圖」之間建立一個平滑的預設過渡動畫（通常是淡入淡出）。</li>
</ol>
<p>這一切都由瀏覽器在底層高效處理，我們只需要呼叫一個簡單的函式。</p>
<h2>3. 如何使用？</h2>
<p>在單頁應用程式中，使用 View Transitions 非常簡單。只需將你的 DOM 更新邏輯包裹在 <code>document.startViewTransition</code> 函式中即可。</p>
<pre><code>// 假设你有一个更新页面内容的函数
async function updatePageContent(url) {
  const response = await fetch(url);
  const newHtml = await response.text();
  // 用新内容替换旧内容 (具体实现取决于你的架构)
  document.body.innerHTML = newHtml;
}

// 在导航链接的点击事件中调用
document.querySelector('a').addEventListener('click', (event) =&gt; {
  event.preventDefault();
  const url = event.target.href;

  // 检查浏览器是否支持
  if (!document.startViewTransition) {
    updatePageContent(url); // 不支持则直接更新
    return;
  }

  // 使用 View Transition
  document.startViewTransition(() =&gt; updatePageContent(url));
});
</code></pre>
<p>僅僅這樣，你的頁面跳轉就會帶上一個預設的淡入淡出效果！</p>
<h2>4. 自訂過渡動畫</h2>
<p>預設的淡入淡出效果很棒，但 API 的真正威力在於其可自訂性。當 <code>startViewTransition</code> 執行時，瀏覽器會建立一個包含以下偽元素的 DOM 結構：</p>
<ul>
<li><code>::view-transition</code>: 根元素，包含了整個過渡。</li>
<li><code>::view-transition-old(root)</code>: 舊檢視的「截圖」。</li>
<li><code>::view-transition-new(root)</code>: 新檢視的「截圖」。</li>
</ul>
<p>我們可以透過 CSS <code>animation</code> 來覆蓋預設效果，例如實現一個滑入動畫：</p>
<pre><code>@keyframes slide-in {
  from { transform: translateX(100%); }
}

::view-transition-new(root) {
  animation: slide-in 0.5s ease-out;
}
</code></pre>
<h2>5. 元素間的「變形」：<code>view-transition-name</code></h2>
<p>API 最令人驚豔的功能是能夠讓兩個頁面中的 <strong>不同元素</strong> 被識別為 <strong>同一個物件</strong>，並在這兩個元素之間建立平滑的「變形」動畫。</p>
<p>這透過 <code>view-transition-name</code> 這個 CSS 屬性實現。</p>
<p><strong>頁面 A（列表頁）:</strong></p>
<pre><code>&lt;img src="thumbnail.jpg" style="view-transition-name: hero-image;" /&gt;
</code></pre>
<p><strong>頁面 B（詳細頁）:</strong></p>
<pre><code>&lt;img src="full-size.jpg" style="view-transition-name: hero-image;" /&gt;
</code></pre>
<p>當從頁面 A 跳轉到頁面 B 時，瀏覽器看到兩個元素的 <code>view-transition-name</code> 相同，它就會自動計算它們在位置、尺寸、形狀上的差異，並產生一個平滑的過渡動畫，而不是簡單的淡入淡出。這對於圖片、卡片等元素的過渡效果極佳。</p>
<h2>結論</h2>
<p>View Transitions API 為 Web 帶來了期待已久的原生過渡能力。它極大地簡化了過去需要複雜 JavaScript 動畫函式庫才能實現的互動效果，讓打造電影般流暢的應用程式體驗變得前所未有的簡單。隨著 Astro、Nuxt 等框架的整合，以及未來在多頁應用程式中的普及，它必將成為現代 Web 開發者的標準技能之一。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[深入理解 React Server Components]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/react/deep-dive-into-rsc/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/react/deep-dive-into-rsc/</guid>
            <pubDate>Fri, 15 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[React Server Components（RSC）是 React 近年來最重要的範式轉變。本文將深入探討其核心概念、解決的問題，以及它如何重塑我們建構應用程式的方式。]]></description>
            <content:encoded><![CDATA[<h2>1. React Server Components（RSC）是什麼？</h2>
<p>React Server Components（RSC）是一種新的 React 元件類型，它在<strong>建置時或在伺服器上</strong>被渲染。與傳統的、在客戶端瀏覽器中渲染的元件（現在稱為「Client Components」）不同，RSC 的程式碼永遠不會被傳送到客戶端。瀏覽器接收到的僅僅是渲染好的 HTML 或一種特殊的串流格式。</p>
<p>這標誌著 React 從一個純粹的客戶端函式庫，演變為一個可以在伺服器和客戶端之間無縫運作的全端框架。</p>
<h2>2. RSC 解決了哪些問題？</h2>
<p>RSC 的出現主要是為了解決以下幾個核心痛點：</p>
<ul>
<li><strong>巨大的 JavaScript 套件體積</strong>: 傳統 React 應用程式會將所有元件的 JS 程式碼（包括那些不直接互動的）打包傳送到客戶端，導致初始載入緩慢。RSC 允許我們將大量元件（如文字、版面配置、資料展示）保留在伺服器端，從而實現<strong>零 JS 體積</strong>。</li>
<li><strong>請求瀑布（Request Waterfalls）</strong>: 在客戶端取得資料的典型模式是：渲染元件 -&gt; <code>useEffect</code> -&gt; 發起請求 -&gt; 等待資料 -&gt; 再次渲染。如果元件巢狀層級很深，就會形成請求瀑布，拖慢頁面呈現速度。RSC 可以在伺服器端直接以 <code>async/await</code> 取得資料，將資料和元件一起串流傳輸到客戶端，從根本上解決這個問題。</li>
<li><strong>直接存取後端資源</strong>: RSC 執行在伺服器端環境中（如 Node.js），這意味著它們可以直接、安全地存取資料庫、檔案系統或內部 API，無需再向客戶端暴露額外的 API 端點。</li>
</ul>
<h2>3. Server Components vs. Client Components</h2>
<p>理解兩者的區別是掌握 RSC 的關鍵。</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>Server Components（伺服器端元件）</th>
<th>Client Components（客戶端元件）</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>執行環境</strong></td>
<td>伺服器（Node.js 等）</td>
<td>客戶端（瀏覽器）</td>
</tr>
<tr>
<td><strong>JS 傳送到客戶端</strong></td>
<td>否</td>
<td>是</td>
</tr>
<tr>
<td><strong>狀態（State）</strong></td>
<td>不支援（例如 <code>useState</code>）</td>
<td>支援</td>
</tr>
<tr>
<td><strong>生命週期/Hooks</strong></td>
<td>不支援（例如 <code>useEffect</code>）</td>
<td>支援</td>
</tr>
<tr>
<td><strong>互動（Events）</strong></td>
<td>不支援（例如 <code>onClick</code>）</td>
<td>支援</td>
</tr>
<tr>
<td><strong>資料取得</strong></td>
<td>支援 <code>async/await</code></td>
<td>透過 <code>useEffect</code> 或資料取得函式庫</td>
</tr>
<tr>
<td><strong>匯入規則</strong></td>
<td>不能匯入客戶端元件</td>
<td>可以匯入伺服器端元件（作為 <code>children</code> 或 <code>prop</code>）</td>
</tr>
</tbody>
</table>
<p><strong>經驗法則</strong>: 預設將所有元件視為 Server Components。只有當你的元件需要使用 <code>useState</code>、<code>useEffect</code> 或處理使用者事件（<code>onClick</code> 等）時，才在檔案頂部加入 <code>"use client";</code> 指令，將其標記為 Client Component。</p>
<h2>4. 它們如何協同運作？</h2>
<p>一個現代 React 應用程式會是兩者的混合體。例如，一個部落格文章頁面：</p>
<ul>
<li><code>PageLayout</code>（Server Component）: 負責整體版面配置。</li>
<li><code>ArticleContent</code>（Server Component）: 從資料庫或 Markdown 檔案中取得文章資料並渲染。</li>
<li><code>LikeButton</code>（Client Component）: 包含 <code>useState</code> 和 <code>onClick</code> 事件來處理按讚互動。</li>
<li><code>Comments</code>（Client Component）: 取得並顯示留言，包含表單送出等互動。</li>
</ul>
<p>伺服器端元件可以在伺服器端「持有」客戶端元件的「位置」，最終在瀏覽器中將兩者無縫地拼接在一起。</p>
<h2>結論</h2>
<p>React Server Components 是一次深刻的範式轉變，它將 React 的能力從瀏覽器擴充套件到了伺服器，帶來了顯著的效能優勢和更優的開發體驗。雖然它引入了新的心智模型，但透過 Next.js App Router 等框架的實踐，RSC 正在快速成為建構高效能、可擴充 Web 應用程式的新標準。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[告別 process.env.UNDEFINED：在專案中實作型別安全的環境變數]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/typescript/typesafe-environment-variables/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/typescript/typesafe-environment-variables/</guid>
            <pubDate>Sun, 25 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[環境變數缺失或格式錯誤，是常見的執行階段 Bug 來源。透過 Zod 等驗證函式庫，我們可以在應用程式啟動時確認所有環境變數都存在且型別正確，從而根除此類問題。]]></description>
            <content:encoded><![CDATA[<h2>1. <code>process.env</code> 的「陷阱」</h2>
<p>在 Node.js 應用程式中，我們透過 <code>process.env</code> 存取環境變數。這是一套簡單有效的機制，但它存在一個固有問題：<strong>型別不安全</strong>。</p>
<p><code>process.env</code> 物件的值不是 <code>string</code>，就是 <code>undefined</code>。這會造成幾個常見問題：</p>
<ul>
<li><strong>意外的 <code>undefined</code></strong>：如果你忘了在 <code>.env</code> 檔案或伺服器上設定某個變數，<code>process.env.MY_VAR</code> 在執行階段會是 <code>undefined</code>，可能在程式碼深層邏輯中導致 <code>TypeError</code>。</li>
<li><strong>型別不符</strong>：你可能預期連接埠號是 <code>number</code> 型別，但 <code>process.env.PORT</code> 始終是字串，因此必須在使用它的地方手動呼叫 <code>parseInt</code>。</li>
<li><strong>驗證邏輯分散</strong>：我們常在程式碼各處撰寫防禦性程式碼，例如 <code>const port = process.env.PORT || 3000;</code>，使環境變數的管理變得混亂。</li>
</ul>
<h2>2. 核心原則：啟動時失敗（Fail-Fast）</h2>
<p>對環境變數這類關鍵設定而言，最佳實務是 <strong>「啟動時失敗」（Fail-Fast）</strong>。</p>
<p>這代表應用程式應在啟動最初階段，就檢查所有必要環境變數是否均已提供且格式正確。若有任何問題，應用程式應立即擲回錯誤並停止執行，而不是在未來某個不確定的時間點因設定錯誤而崩潰。</p>
<p>這樣能讓我們在部署或開發時立即發現設定問題，而不是等到使用者存取時才發現。</p>
<h2>3. 使用 Zod 實作型別安全驗證</h2>
<p>Zod 是一個以 TypeScript 為優先的 schema 宣告與驗證函式庫，非常適合用來解決環境變數的型別安全問題。</p>
<p>我們的策略是：</p>
<ol>
<li>為所有環境變數定義一個 Zod schema。</li>
<li>在應用程式啟動時，用這個 schema 解析 <code>process.env</code>。</li>
<li>如果解析失敗，也就是驗證未通過，Zod 會擲回錯誤，應用程式啟動失敗。</li>
<li>如果解析成功，我們會得到一個完整型別化的物件，並在整個應用程式中使用它。</li>
</ol>
<h4>實作範例</h4>
<p>首先安裝 Zod：<code>pnpm add zod</code></p>
<p>接著，建立一個專門處理環境變數的檔案，例如 <code>src/env.ts</code>：</p>
<pre><code>// src/env.ts
import { z } from 'zod';

// 1. 定义 Schema
const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  DATABASE_URL: z.string().min(1, 'DATABASE_URL is required.'),
  PORT: z.coerce.number().int().positive().default(3000),
  // z.coerce 会尝试将字符串转换为数字
});

// 2. 解析和导出
// .parse 会在验证失败时抛出错误，实现 "Fail-Fast"
export const env = envSchema.parse(process.env);
</code></pre>
<p>現在，你可以在應用程式的任何其他地方從 <code>src/env</code> 匯入 <code>env</code> 物件。</p>
<pre><code>// src/server.ts
import { env } from './env'; // 导入经过验证和类型化的 env 对象

// env.PORT 的类型是 `number`，而不是 `string | undefined`
const port = env.PORT;

// env.NODE_ENV 的类型是 'development' | 'production' | 'test'
if (env.NODE_ENV === 'development') {
  console.log('Running in development mode');
}

// 如果 DATABASE_URL 未设置，应用在启动时就已经崩溃了，
// 所以在这里我们可以放心地认为它是存在的，并且类型是 `string`。
connectToDatabase(env.DATABASE_URL);
</code></pre>
<h2>4. 帶來的好處</h2>
<p>這種模式能立即帶來好處：</p>
<ul>
<li><strong>完整的型別安全</strong>：在程式碼中存取 <code>env.PORT</code> 時，TypeScript 知道它是一個 <code>number</code>。</li>
<li><strong>集中的文件和驗證</strong>：<code>env.ts</code> 檔案本身就成了環境變數的權威文件，所有驗證邏輯也都集中於此。</li>
<li><strong>可靠的執行階段</strong>：杜絕因環境變數拼寫錯誤、缺失或格式不正確而造成的執行階段 Bug。</li>
<li><strong>Fail-Fast</strong>：任何設定問題都會在部署或開發的第一時間被發現。</li>
</ul>
<h2>結論</h2>
<p>將環境變數的驗證提前到應用程式啟動時，並使用 Zod 這類工具保證型別安全，是一項投入產出比極高的工程實務。它能顯著提升應用程式的健全性與開發者信心，是現代 TypeScript 專案不可或缺的一環。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Vue 3.4 更新：v-bind 寫法更簡潔，效能再提升]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/vue/vue-3-4-v-bind-updates/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/vue/vue-3-4-v-bind-updates/</guid>
            <pubDate>Mon, 15 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Vue 3.4「🏀 Slam Dunk」正式發布，帶來更高效的響應式系統重構，並穩定了 defineModel API，讓雙向綁定元件的開發體驗大幅提升。]]></description>
            <content:encoded><![CDATA[<h2>1. Vue 3.4「Slam Dunk」簡介</h2>
<p>2024 年初，Vue 團隊發布了 3.4 版本，代號為「Slam Dunk」。這次更新雖然不像 3.0 那樣具有顛覆性，卻在兩個關鍵領域帶來重要改進：<strong>內部效能最佳化</strong>和<strong>開發者體驗提升</strong>。</p>
<p>其中最引人注目的變化，包括重寫的響應式系統、穩定的 <code>defineModel</code> API，以及更簡潔的 <code>v-bind</code> 語法。</p>
<h2>2. 響應式系統重構</h2>
<p>Vue 3.4 對核心響應式系統進行了大規模重構。這次重構的主要目標，是提升 <code>computed</code> 計算屬性的效率。</p>
<p>在舊版本中，即使計算屬性的相依項目沒有實際變化，也可能會進行不必要的重新計算。新版本透過更智慧的相依性追蹤，確保只在真正需要時才觸發計算，從而減少不必要的元件重新算繪。</p>
<p>對大部分開發者來說，這是一頓「免費的午餐」——你不需要修改任何程式碼，升級到 3.4 版本後便能自動享有效能提升。</p>
<h2>3. <code>defineModel</code>：優雅的元件雙向綁定</h2>
<p>在 3.4 版本之前，要在元件上實作類似 <code>v-model</code> 的雙向綁定，需要撰寫不少樣板程式碼：</p>
<p><strong>之前 👎：</strong></p>
<pre><code>&lt;script setup&gt;
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

// 通常需要一个计算属性来包装
const value = computed({
  get: () =&gt; props.modelValue,
  set: (val) =&gt; emit('update:modelValue', val)
})
&lt;/script&gt;

&lt;template&gt;
  &lt;input v-model="value" /&gt;
&lt;/template&gt;
</code></pre>
<p>Vue 3.4 將 <code>defineModel</code> API 從實驗階段轉為正式穩定。現在，實作上述功能只需要一行程式碼：</p>
<p><strong>現在（Vue 3.4）👍：</strong></p>
<pre><code>&lt;script setup&gt;
const model = defineModel()
&lt;/script&gt;

&lt;template&gt;
  &lt;input v-model="model" /&gt;
&lt;/template&gt;
</code></pre>
<p><code>defineModel</code> 巨集會自動註冊 <code>modelValue</code> prop 和 <code>update:modelValue</code> 事件，並回傳一個可直接讀寫的 <code>ref</code>。這大幅簡化了支援 <code>v-model</code> 的元件開發。</p>
<h2>4. <code>v-bind</code> 同名屬性簡寫</h2>
<p>另一項提升開發體驗的語法糖，是 <code>v-bind</code> 的同名簡寫。當傳給元件的 prop 與你在 <code>&lt;script setup&gt;</code> 中定義的變數同名時，現在可以省略屬性值。</p>
<p><strong>之前 👎：</strong></p>
<pre><code>&lt;script setup&gt;
const id = 'my-id'
const title = 'Hello Vue'
&lt;/script&gt;

&lt;template&gt;
  &lt;MyComponent :id="id" :title="title" /&gt;
&lt;/template&gt;
</code></pre>
<p><strong>現在（Vue 3.4）👍：</strong></p>
<pre><code>&lt;script setup&gt;
const id = 'my-id'
const title = 'Hello Vue'
&lt;/script&gt;

&lt;template&gt;
  &lt;MyComponent :id :title /&gt;
&lt;/template&gt;
</code></pre>
<p>這個小改動讓範本程式碼變得更加清爽易讀。</p>
<h2>結論</h2>
<p>Vue 3.4 是一次紮實的迭代。它透過底層的效能最佳化和上層的 API 簡化，進一步鞏固了 Vue 作為一個「漸進式」框架的理念——既為大型應用程式提供強大的效能保障，也為日常開發帶來實實在在的便利。特別是 <code>defineModel</code> 的穩定，解決了長久以來元件封裝中的一個痛點，是本次更新中最值得稱讚的亮點。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[再見 Rome，你好 Biome：一體化前端工具鏈的新選擇]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/build-tools/intro-to-biome/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/build-tools/intro-to-biome/</guid>
            <pubDate>Fri, 10 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Rome 工具鏈的願景曾讓無數開發者心動，在其商業化失敗後，社群以 Biome 的名義繼承其衣缽。這個用 Rust 編寫的一體化工具鏈，能否成為我們工具箱的下一位成員？]]></description>
            <content:encoded><![CDATA[<h2>1. 前端開發的「工具疲勞」</h2>
<p>在現代前端開發中，一個專案通常需要一套複雜的工具組合才能順利執行：</p>
<ul>
<li><strong>Linter</strong>：ESLint 用於檢查 JavaScript/TypeScript 程式碼品質。</li>
<li><strong>Formatter</strong>：Prettier 用於統一程式碼風格。</li>
<li><strong>Compiler</strong>：Babel 或 TypeScript（tsc）用於程式碼轉換。</li>
<li><strong>Bundler</strong>：Webpack、Rollup 或 Vite 用於打包。</li>
</ul>
<p>管理這些工具的設定、外掛、版本和彼此間的互動，本身就是一項不小的工作，這便是所謂的「工具疲勞」。多年來，社群一直在探索「一體化工具鏈」的可能性，希望能用一個工具解決所有問題。</p>
<h2>2. Rome 的願景與 Biome 的誕生</h2>
<p>Rome 是由 Facebook 前員工、Babel 作者 Sebastian McKenzie 發起的雄心勃勃專案，旨在用 TypeScript 重寫整個前端工具鏈。它的願景是提供一個零設定、高效能的統一工具。然而，在經歷多年開發和商業化嘗試後，Rome Labs 公司於 2023 年宣布停止營運。</p>
<p>幸運的是，Rome 的核心程式碼是開源的。社群迅速行動起來，建立了一個名為 <strong>Biome</strong> 的分支，並由專門的社群組織維護，旨在繼承並實現 Rome 最初的願景。</p>
<h2>3. Biome 是什麼？</h2>
<p>Biome 是一個用 Rust 重寫的高效能前端工具鏈。它旨在提供統一、極速的開發體驗，以取代 ESLint、Prettier 等一系列獨立工具。</p>
<p>截至 2023 年底，Biome 的核心功能主要集中在 <strong>Linter</strong> 和 <strong>Formatter</strong> 上，並在這兩方面展現出驚人的實力。</p>
<h4>主要優勢：</h4>
<ul>
<li><strong>極致效能</strong>：得益於 Rust 語言，Biome 的執行速度遠超用 JavaScript 編寫的 ESLint 和 Prettier。對於大型程式碼庫，其效能提升可達一個數量級。</li>
<li><strong>統一設定</strong>：只需一個 <code>biome.json</code> 檔案，即可管理所有工具的行為，告別散落各處的 <code>.eslintrc</code>、<code>.prettierrc</code>、<code>.editorconfig</code>。</li>
<li><strong>詳盡的診斷資訊</strong>：Biome 的 Linter 不只能指出錯誤，還能提供非常詳盡的上下文和修正建議，協助開發者理解問題根源。</li>
<li><strong>與 Prettier 相容</strong>：Biome 的格式化工具旨在與 Prettier 95% 以上的規則相容，使得從 Prettier 遷移的成本非常低。</li>
</ul>
<h2>4. 快速上手</h2>
<p>你可以透過 <code>npx</code> 快速在專案裡嘗試 Biome：</p>
<pre><code># 检查当前项目中的代码问题
npx @biomejs/biome check ./src

# 自动修复可修复的 lint 问题
npx @biomejs/biome check --apply ./src

# 格式化代码
npx @biomejs/biome format --write ./src
</code></pre>
<p>建立一個 <code>biome.json</code> 檔案可以讓你自訂規則和設定：</p>
<pre><code>{
  "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}
</code></pre>
<h2>5. 未來展望</h2>
<p>Biome 的發展藍圖非常清晰：在穩定 Linter 和 Formatter 的基礎上，逐步實現 Compiler、Bundler、Test Runner 等功能，最終成為一個名副其實的「一體化」工具鏈。</p>
<p>雖然它還很年輕，但 Biome 憑藉出色的效能、社群驅動的開放模式，以及對開發者體驗的專注，已經成為前端工具領域一股不容忽視的新生力量。對於尋求簡化工具堆疊、提升開發效率的團隊而言，Biome 提供了一個非常有吸引力的未來選項。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Bun 1.0 正式發布：挑戰 Node.js 的全能執行環境]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/build-tools/bun-1-0-release/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/build-tools/bun-1-0-release/</guid>
            <pubDate>Sun, 10 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[2023 年 9 月，Bun 1.0 的發布在 JavaScript 社群掀起波瀾。這個從頭開始建構的 JavaScript 工具包，憑藉驚人的速度和「一體化」設計，正式向 Node.js 的主導地位發起挑戰。]]></description>
            <content:encoded><![CDATA[<h2>1. JavaScript 執行環境的新競爭者</h2>
<p>十多年來，Node.js 一直是伺服器端 JavaScript 的代名詞。儘管 Deno 在安全性和現代 API 方面提出了新的思路，但 Node.js 的地位似乎從未被真正動搖。然而，在 2023 年 9 月，隨著 Bun 1.0 正式發布，情況可能將迎來改變。</p>
<p>Bun 不只是另一個 JavaScript 執行環境，它是一個從零開始設計、注重效能的「一體化」工具包，旨在涵蓋從開發到部署的整個生命週期。</p>
<h2>2. Bun 的核心：速度、速度，還是速度</h2>
<p>Bun 的首要設計目標就是效能。為了實現這一點，它做出了幾個與眾不同的技術選擇：</p>
<ul>
<li><strong>Zig 語言</strong>：Bun 的底層由 Zig 編寫，這是一種現代、注重效能和記憶體控制的系統程式語言，使其能對底層細節進行極致最佳化。</li>
<li><strong>JavaScriptCore 引擎</strong>：與使用 Google V8 引擎的 Node.js 和 Deno 不同，Bun 選擇了 Apple 的 JavaScriptCore（JSC）引擎。JSC 以更快的啟動速度和通常更低的記憶體消耗而聞名。</li>
</ul>
<p>這些選擇使 Bun 在指令碼啟動、<code>bun install</code> 速度以及內建 API 的執行效率上，都展現出驚人的效能數據。</p>
<h2>3. 不只是一個執行環境</h2>
<p>Bun 的「一體化」設計是另一大特色。它不只是一個 <code>node</code> 指令的替代品，還內建了：</p>
<ul>
<li><strong>套件管理器</strong>：<code>bun install</code> 的速度是 <code>npm install</code> 的數倍甚至數十倍。它使用全域模組快取和高效的相依性解析演算法，大幅縮短安裝時間。</li>
<li><strong>建置工具／打包器</strong>：Bun 內建高效能打包器，可以直接將專案打包成可執行檔或瀏覽器使用的程式碼，其效能可與 esbuild 相比。</li>
<li><strong>原生轉譯器</strong>：你可以直接執行 TypeScript（<code>.ts</code>）和 JSX（<code>.jsx</code>、<code>.tsx</code>）檔案，無須預先設定 <code>tsc</code> 或 <code>babel</code>。Bun 會在執行時高速轉換它們。</li>
<li><strong>測試執行器</strong>：<code>bun test</code> 提供與 Jest 高度相容的測試環境，但執行速度快得多。</li>
</ul>
<p>這表示，對於一個新專案，你可能只需要安裝 <code>bun</code> 這一個工具，就能完成開發、測試和打包的全過程。</p>
<h2>4. Node.js 相容性</h2>
<p>為了方便遷移，Bun 團隊投入大量精力實現與 Node.js API 的相容性。它內建對 <code>node_modules</code> 解析、CommonJS（<code>require</code>）和 ESM（<code>import</code>）模組，以及大量 Node.js 核心模組（如 <code>fs</code>、<code>path</code>、<code>http</code>）的支援。</p>
<p>對於許多現有的 Node.js 專案，理論上可以直接用 <code>bun</code> 執行，並立即享受效能提升。</p>
<h2>5. 挑戰與未來</h2>
<p>Bun 的出現挑戰了前端界長期以來「組合小型專用工具」的哲學。它的「大而全」模式帶來無與倫比的便利性和開箱即用的高效能，但也可能犧牲一部分彈性。</p>
<p>Node.js 擁有極其龐大且成熟的生態系統，這是 Bun 在短期內難以企及的。然而，Bun 1.0 的發布標誌著它已為正式環境做好準備。其驚人的效能和一體化開發體驗，對新專案和追求極致效率的團隊而言，具有難以抗拒的吸引力。</p>
<p>無論 Bun 未來能否顛覆 Node.js 的地位，它的出現都已經為 JavaScript 工具鏈的發展注入新的活力，並設立了新的效能標竿。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[原生 CSS 巢狀（Nesting）終於來了！]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/css/native-css-nesting/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/css/native-css-nesting/</guid>
            <pubDate>Fri, 01 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[我們等了十年！Sass/Less 的核心功能——選擇器巢狀——終於獲得主流瀏覽器原生支援。告別前處理器，迎接更簡潔、更直觀的 CSS 編寫方式。]]></description>
            <content:encoded><![CDATA[<h2>1. 那個我們用了十年的「非官方」功能</h2>
<p>如果你寫過幾年前的前端程式碼，一定對 Sass 或 Less 不陌生。在這些 CSS 前處理器中，有一個功能幾乎是所有開發者的最愛：<strong>選擇器巢狀（Nesting）</strong>。它允許我們像寫 HTML 一樣，將子選擇器的規則寫在父選擇器內部，大幅提升程式碼的可讀性和組織性。</p>
<pre><code>// Sass 语法
nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { display: inline-block; }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
</code></pre>
<p>多年來，我們一直依賴建置工具將這種語法編譯成瀏覽器認得的普通 CSS。但現在，情況變了。</p>
<h2>2. 原生 CSS 巢狀登場</h2>
<p>從 2023 年初開始，主流瀏覽器（Chrome、Safari、Firefox）陸續宣布支援原生 CSS Nesting Module。這表示，我們可以直接在 <code>.css</code> 檔案裡編寫巢狀規則，無須任何前處理。</p>
<p>上面的範例用原生 CSS 可以這樣寫：</p>
<pre><code>/* 原生 CSS 语法 */
nav {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  li { display: inline-block; }

  a {
    display: block;
    padding: 6px 12px;
    text-decoration: none;
  }
}
</code></pre>
<p>是的，它看起來和 Sass 完全一樣！</p>
<h2>3. <code>&amp;</code> 的重要角色</h2>
<p>和前處理器一樣，<code>&amp;</code> 符號在原生巢狀中也扮演關鍵角色，它代表父選擇器。這在處理虛擬類別、虛擬元素或組合選擇器時非常有用。</p>
<pre><code>.card {
  background: white;
  border-radius: 8px;

  /* &amp; 代表 .card */
  &amp;:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  }

  /* &amp; 代表 .card，组合成 .card.dark-mode */
  &amp;.dark-mode {
    background: #333;
  }

  /* &amp; 代表 .card，组合成 .card &gt; .header */
  &gt; .header {
    font-weight: bold;
  }
}
</code></pre>
<h2>4. 與 Sass 的一個微小但重要的差異</h2>
<p>在 Sass 中，你可以直接巢狀類別選擇器，例如 <code>.card { .title { ... } }</code>。在原生 CSS Nesting 的早期規範中，這是不允許的，任何巢狀規則都必須以一個符號（如 <code>&amp;</code>、<code>&gt;</code>、<code>+</code>、<code>~</code>）開頭。</p>
<p>雖然最新規範放寬了這一限制，允許直接巢狀標籤選擇器（如 <code>article { h1 { ... } }</code>），但在巢狀類別選擇器時，為了明確並避免歧義，最佳實務仍然是使用 <code>&amp;</code>。</p>
<pre><code>/* 推荐写法 */
.article {
  &amp; .author-name {
    font-style: italic;
  }
}

/* 不推荐的写法 (在某些早期实现或严格解析器中可能无效) */
.article {
  .author-name {
    font-style: italic;
  }
}
</code></pre>
<p>使用 <code>&amp;</code> 可以清楚表達 <code>.author-name</code> 是 <code>.article</code> 的後代。</p>
<h2>5. 瀏覽器支援與未來</h2>
<p>截至 2023 年 9 月，所有主流現代瀏覽器（Chrome 112+、Safari 16.5+、Firefox 117+）均已支援 CSS Nesting。這表示我們可以開始在正式環境中逐步使用它。</p>
<p>原生 CSS 巢狀的到來，是 Web 平台自身演進的另一個重要里程碑。它減少我們對建置工具的依賴，讓 CSS 本身變得更強大且富有表現力，也讓剛入門的前端開發者能更直觀地編寫和理解樣式程式碼。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Utility-First CSS 的崛起：以 Tailwind CSS 為例]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/css/the-rise-of-utility-first/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/css/the-rise-of-utility-first/</guid>
            <pubDate>Sun, 20 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[告別 BEM 和 CSS-in-JS 的煩惱？Utility-First CSS 思想和其代表框架 Tailwind CSS 近年來席捲了前端社群。它究竟有何魅力？]]></description>
            <content:encoded><![CDATA[<h2>1. 傳統 CSS 方法論的困境</h2>
<p>在 Utility-First 出現之前，我們有許多優秀的 CSS 方法論來組織樣式，例如：</p>
<ul>
<li><strong>BEM (Block, Element, Modifier)</strong>: 透過 <code>block__element--modifier</code> 的命名約定來保證樣式的獨立性和可維護性。</li>
<li><strong>OOCSS (Object-Oriented CSS)</strong>: 提倡將結構與外觀分離，以及容器與內容分離。</li>
<li><strong>CSS-in-JS</strong>: 將 CSS 直接寫入 JavaScript 元件中，實現了元件層級的樣式封裝。</li>
</ul>
<p>這些方法在一定程度上解決了 CSS 全域污染和程式碼組織的問題，但通常也帶來了新的問題：為無數的 class 命名所耗費的心力、檔案切換的繁瑣，以及 CSS-in-JS 可能帶來的執行階段開銷。</p>
<h2>2. 什麼是 Utility-First？</h2>
<p>Utility-First（功能優先）是一種 CSS 思想，它主張提供一系列 <strong>單一用途、命名不變的功能類別</strong>，然後透過組合這些類別來建構複雜的 UI。</p>
<p>例如，你不會去寫一個 <code>.card</code> 類別，然後在 CSS 檔案裡定義它的 <code>display</code>、<code>padding</code>、<code>box-shadow</code>。
取而代之的是，你直接在 HTML 中這樣寫：</p>
<pre><code>&lt;div class="block p-6 rounded-lg shadow-lg bg-white"&gt;
  
&lt;/div&gt;
</code></pre>
<p>這裡的 <code>block</code>、<code>p-6</code>、<code>rounded-lg</code> 等都是功能類別，它們各自只負責一件小事。</p>
<h2>3. Tailwind CSS：Utility-First 的典範</h2>
<p>Tailwind CSS 是目前最流行的 Utility-First CSS 框架。它提供了一套極其詳盡的功能類別，幾乎涵蓋了所有常用的 CSS 屬性。</p>
<p><strong>核心優勢:</strong></p>
<ul>
<li><strong>極速開發</strong>: 你幾乎不需要離開 HTML 檔案。透過組合原子類別，可以快速建構出任何設計，極大地提升了原型設計和開發的效率。</li>
<li><strong>強制設計系統</strong>: 由於所有的樣式（顏色、間距、字號）都來自預先定義的設定 (theme)，團隊成員可以輕鬆地保持 UI 的一致性，避免了「魔法數字」的出現。</li>
<li><strong>無需為命名煩惱</strong>: 「電腦科學兩大難題：快取失效和命名。」Tailwind 讓你基本告別了為 CSS 類別命名的煩惱。</li>
<li><strong>極致效能</strong>: 在正式環境建置時，Tailwind 會掃描你的檔案，透過 PurgeCSS（或內建的 JIT 引擎）移除所有未被使用的 CSS 類別，最終的 CSS 檔案體積通常會非常小。</li>
</ul>
<h2>4. 「類別名稱地獄」？—— 看待缺點的不同視角</h2>
<p>對 Tailwind 最常見的批評就是它讓 HTML 變得非常臃腫，可讀性差，像是回到了「行內樣式」的時代。</p>
<pre><code>&lt;button class="py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-blue-500 hover:bg-blue-700"&gt;
  Click me
&lt;/button&gt;
</code></pre>
<p>這確實是一個需要適應的過程。但支持者認為：</p>
<ol>
<li>樣式和結構本就緊密耦合，將它們放在一起反而更便於維護。</li>
<li>透過提取可重複使用的元件（如 React/Vue 元件），可以將這部分「混亂」封裝起來。</li>
</ol>
<p>對於需要重複使用的樣式組合，Tailwind 提供了 <code>@apply</code> 指令：</p>
<pre><code>/* in your css file */
.btn-primary {
  @apply py-2 px-4 font-semibold rounded-lg shadow-md text-white bg-blue-500 hover:bg-blue-700;
}
</code></pre>
<p>這樣你就可以在 HTML 中使用 <code>.btn-primary</code> 了。不過，Tailwind 官方更推薦透過元件化來解決重複使用問題。</p>
<h2>結論</h2>
<p>Utility-First 和 Tailwind CSS 的成功，標誌著前端開發在「關注點分離」上的理念轉變——從「語言的分離」 (HTML/CSS/JS) 轉向了「元件的分離」。它透過一種看似「原始」的方式，解決了現代 Web 開發中的許多實際痛點，為開發者提供了一條通往高效、一致和高效能 UI 的捷徑。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[下一代端對端測試工具：Playwright 上手指南]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/testing/intro-to-playwright/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/testing/intro-to-playwright/</guid>
            <pubDate>Thu, 20 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[厭倦了不穩定的端對端（E2E）測試？由微軟推出的 Playwright 憑藉跨瀏覽器能力、自動等待機制和強大的偵錯工具，正成為自動化測試領域的新標竿。]]></description>
            <content:encoded><![CDATA[<h2>1. 端對端測試的挑戰</h2>
<p>端對端（End-to-End，E2E）測試是保障應用程式品質的最後一道防線。它透過模擬真實使用者操作（點擊、輸入、導覽），驗證整個應用程式流程是否順暢。然而，E2E 測試也以「脆弱」和「不穩定」聞名：</p>
<ul>
<li><strong>非同步問題</strong>：指令碼執行時，DOM 元素可能尚未載入完成，導致測試失敗。開發者需要手動加入大量 <code>wait</code> 或 <code>sleep</code>，讓測試變得不可靠。</li>
<li><strong>跨瀏覽器相容性</strong>：確保測試能在所有主流瀏覽器（Chrome、Firefox、Safari）上穩定執行，是一項巨大挑戰。</li>
<li><strong>偵錯困難</strong>：當測試在 CI/CD 環境中失敗時，重現和定位問題非常困難，也缺少現場快照與網路日誌。</li>
</ul>
<p>為了解決這些問題，新一代測試工具應運而生，而 Playwright 正是其中的佼佼者。</p>
<h2>2. Playwright 是什麼？</h2>
<p>Playwright 是由微軟開發的 Node.js 函式庫，用於自動化 Chromium（Chrome、Edge）、Firefox 和 WebKit（Safari）瀏覽器。它由最初開發 Google Puppeteer 的團隊核心成員建立，可視為 Puppeteer 的精神繼承者，但設計更加現代且強大。</p>
<h2>3. Playwright 的核心優勢</h2>
<h4>a. 真正的跨瀏覽器測試</h4>
<p>這是 Playwright 最顯著的優勢。使用一套統一的 API，就能撰寫可在三個主流瀏覽器引擎上執行的測試。這代表你能輕鬆發現並修正只在特定瀏覽器（例如 Safari）出現的 Bug。</p>
<h4>b. 自動等待（Auto-Waits）</h4>
<p>Playwright 從根本上解決了非同步問題。在執行一項操作（例如 <code>page.click()</code>）之前，Playwright 會自動進行一系列可操作性檢查，例如：</p>
<ul>
<li>等待元素出現在 DOM 中。</li>
<li>等待元素變得可見。</li>
<li>等待元素不再被動畫遮擋。</li>
<li>等待元素接收事件。</li>
</ul>
<p>這代表你幾乎不再需要撰寫任何手動等待程式碼，測試穩定性也獲得大幅提升。</p>
<h4>c. 強大的配套工具</h4>
<ul>
<li>
<p><strong>Codegen（程式碼產生器）</strong>：這是一項革命性的功能。你可以執行 <code>pnpm exec playwright codegen example.com</code>，Playwright 會開啟瀏覽器視窗。你在瀏覽器中的所有操作都會被自動錄製並轉換成測試程式碼，大幅降低撰寫 E2E 測試的門檻。</p>
</li>
<li>
<p><strong>Trace Viewer（軌跡檢視器）</strong>：這是 Playwright 的「時光機」。測試失敗時，你可以產生完整的測試軌跡檔案。用 Trace Viewer 開啟後，可以：</p>
<ul>
<li>檢視測試每一步的 DOM 快照。</li>
<li>檢視完整的網路請求日誌。</li>
<li>檢視 console 日誌和錯誤資訊。</li>
<li>在時間軸上來回移動，直觀地看到頁面在每項操作前後的變化。</li>
</ul>
</li>
</ul>
<p>這使得偵錯 CI 中的失敗案例變得前所未有地簡單。</p>
<h2>4. 一個簡單的測試範例</h2>
<p>Playwright 的 API 設計非常直觀。</p>
<pre><code>import { test, expect } from '@playwright/test';

test('页面应有正确的标题', async ({ page }) =&gt; {
  await page.goto('https://playwright.dev/');

  // 断言页面的 title 包含 "Playwright"
  await expect(page).toHaveTitle(/Playwright/);
});

test('点击 "Get started" 链接应跳转到介绍页', async ({ page }) =&gt; {
  await page.goto('https://playwright.dev/');

  // 通过角色和名称定位并点击链接
  await page.getByRole('link', { name: 'Get started' }).click();

  // 断言 URL 包含 "intro"
  await expect(page).toHaveURL(/.*intro/);
});
</code></pre>
<h2>結論</h2>
<p>Playwright 憑藉出色的跨瀏覽器支援、可靠的自動等待機制和無與倫比的偵錯工具（尤其是 Trace Viewer），為現代 Web 應用程式的 E2E 測試建立了新標準。它不僅提升了測試的穩定性與可靠性，也透過 Codegen 等工具大幅改善開發者撰寫測試的體驗。如果你正在為專案尋找現代且強大的自動化測試方案，Playwright 絕對是首選之一。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[Signals：前端響應式的新潮流]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/frameworks/the-rise-of-signals/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/frameworks/the-rise-of-signals/</guid>
            <pubDate>Sat, 20 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[從 SolidJS 到 Qwik，再到 Preact 和 Svelte 5，Signals 正在成為現代前端框架實現高效能響應式更新的核心範式。本文深入探討其運作原理及生態影響。]]></description>
            <content:encoded><![CDATA[<h2>1. 響應式模型的演進</h2>
<p>前端開發的本質是「將狀態映射為 UI」。多年來，我們見證了響應式模型的不斷演進：從手動 DOM 操作，到 MVC/MVVM 模式，再到由 React 推廣的虛擬 DOM（Virtual DOM）。VDOM 透過在元件層級進行狀態變更的批次處理和 Diffing，極大地簡化了 UI 開發。</p>
<p>然而，VDOM 並非銀彈。其元件層級的重新渲染和 Diffing 開銷，在某些高度動態的情境下會成為效能瓶頸。近年來，一種更古老但經過現代編譯器最佳化的範式——細粒度響應式（Fine-Grained Reactivity）——重新回到舞臺中央，而其核心載體正是 <strong>Signals</strong>。</p>
<h2>2. 什麼是 Signals？</h2>
<p>Signal 是一種響應式原語（Reactive Primitive），它封裝了一個值，並能在該值變化時自動通知其所有相依項目。與 VDOM 框架在元件層級追蹤變化不同，Signal 在資料讀取的層面自動建立相依關係圖。</p>
<p>當一個 Signal 的值被更新時，只有直接或間接相依於該 Signal 的計算（<code>Memo</code>）或副作用（<code>Effect</code>）會被重新執行，從而實現「外科手術式」的精確 DOM 更新，完全繞過了 VDOM Diffing 的需要。</p>
<p>其核心通常由三個原語構成：</p>
<ul>
<li><strong><code>createSignal(value)</code></strong>: 建立一個響應式的 state 單元。</li>
<li><strong><code>createEffect(() =&gt; {})</code></strong>: 建立一個自動追蹤相依關係並響應變更的副作用。</li>
<li><strong><code>createMemo(() =&gt; {})</code></strong>: 建立一個衍生的、帶快取的響應式計算。</li>
</ul>
<pre><code>// SolidJS 语法示例
const [count, setCount] = createSignal(0);

const doubleCount = createMemo(() =&gt; count() * 2);

createEffect(() =&gt; {
  console.log(`The double count is: ${doubleCount()}`);
});

setCount(1); // 这将触发 memo 重新计算，并触发 effect 重新执行
</code></pre>
<p>一個關鍵的心智模型是：在基於 Signal 的框架（如 SolidJS）中，元件函式本身只在初始化時執行一次。後續的更新完全由響應式系統驅動，而不是透過元件的重新渲染。</p>
<h2>3. 生態系統的廣泛採用</h2>
<p>Signal 並非全新概念，其思想可以追溯到 Knockout.js 等早期框架。但藉助現代 JavaScript 編譯器，它煥發了新的生機，並被各大框架廣泛採用：</p>
<ul>
<li><strong>SolidJS &amp; Qwik</strong>: 完全基於 Signal 建構，是該範式的標竿實作。</li>
<li><strong>Preact</strong>: 透過 <code>@preact/signals</code> 套件引入了官方支援，並可以與現有的 Preact/React 專案整合。</li>
<li><strong>Vue</strong>: 其 Composition API 中的 <code>ref</code> 和 <code>computed</code> 本質上就是 Signal 的一種實作。</li>
<li><strong>Svelte 5</strong>: 即將到來的「Runes」更新，是 Svelte 團隊對響應式模型的一次重構，其核心正是借鑑並實作了 Signal 範式。</li>
<li><strong>Angular</strong>: 近期版本中也引入了 Signals 作為新的響應式原語。</li>
</ul>
<p>這種跨框架的趨同，證明了 Signal 作為一種高效、可預測的響應式模型的價值。</p>
<h2>4. 優勢與權衡</h2>
<p><strong>優勢：</strong></p>
<ol>
<li><strong>卓越的效能</strong>: 透過避免 VDOM 和元件層級的重新渲染，在效能基準測試中通常表現優異。</li>
<li><strong>可預測性</strong>: 狀態更新的流向清晰，易於理解和除錯。</li>
<li><strong>記憶體占用小</strong>: 無需維護整個元件樹的虛擬表示。</li>
</ol>
<p><strong>權衡：</strong></p>
<ol>
<li><strong>心智模型轉變</strong>: 對於習慣了 React 渲染週期的開發者來說，需要適應「元件只執行一次」的新模型。</li>
<li><strong>生態整合</strong>: 雖然自身效能強大，但與龐大的 React VDOM 生態（例如，某些 UI 函式庫）的深度整合可能需要額外的適配工作。</li>
</ol>
<h2>結論</h2>
<p>Signals 的復興標誌著前端狀態管理範式的一次重要演進。它將開發者的注意力從「元件如何渲染」拉回到了「狀態如何流動」這一更本質的問題上。透過與編譯器的深度結合，Signal 在提供優秀開發體驗的同時，也為 Web 應用程式的效能邊界帶來了新的可能性。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[告別 API 文件：使用 tRPC 建構端對端型別安全的應用程式]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/typescript/typesafe-apis-with-trpc/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/typescript/typesafe-apis-with-trpc/</guid>
            <pubDate>Wed, 15 Mar 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[在全端 TypeScript 專案中，如何確保前端與後端的資料契約保持同步？tRPC 提供一套不需程式碼產生、沒有中介 schema 的方案，實現真正的端對端型別安全。]]></description>
            <content:encoded><![CDATA[<h2>1. 前後端協作的「契約」難題</h2>
<p>在傳統的前後端分離開發中，API 就是兩者之間的「契約」。我們通常透過以下方式維護這份契約：</p>
<ul>
<li><strong>RESTful API</strong>：依賴 OpenAPI（Swagger）等工具產生並維護詳盡的 API 文件。</li>
<li><strong>GraphQL</strong>：依賴嚴格的 Schema Definition Language（SDL）定義資料結構與操作。</li>
</ul>
<p>這些方案確實有效，但都存在一項共同問題：<strong>契約與實作彼此分離</strong>。當前端開發者呼叫 API 時，他相信該 API 會回傳文件或 schema 中描述的資料結構。但如果後端實作發生變更（例如修改欄位名稱），文件或 schema 卻未能及時更新，這種不一致只會在執行階段才暴露，進而造成 Bug。</p>
<p>有沒有一種方法，能讓這份「契約」自動與實作保持同步，甚至在編譯時便發現不一致？</p>
<h2>2. tRPC 的核心思想：共享型別，而非 schema</h2>
<p>tRPC（TypeScript Remote Procedure Call）提出一項激進但極其簡單的方案：<strong>如果前端與後端都使用 TypeScript，為什麼不直接共享型別？</strong></p>
<p>tRPC 讓你能將純 TypeScript 函式撰寫成後端 API，接著直接在前端呼叫，並享有完整的型別推斷與自動完成，就像呼叫本機模組中的函式一樣。</p>
<p>它不依賴任何 schema 或程式碼產生。唯一的「契約」就是 TypeScript 型別本身。</p>
<h2>3. 它如何運作？</h2>
<p>tRPC 的魔法來自型別推斷，以及一點巧妙的封裝。</p>
<h4>a. 後端：定義 API Router</h4>
<p>在後端（通常是 Node.js 服務），你使用 tRPC 函式建立一個或多個「Router」。每個 Router 都是一組可供呼叫的「Procedure」，也就是 API 端點。</p>
<pre><code>// server/router.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod'; // 使用 Zod 进行运行时校验

const t = initTRPC.create();

export const appRouter = t.router({
  // 定义一个名为 `getUser` 的查询 procedure
  getUser: t.procedure
    .input(z.object({ userId: z.string() }))
    .query(({ input }) =&gt; {
      // 在这里查询数据库或执行其他逻辑
      const user = { id: input.userId, name: 'Alex' };
      return user;
    }),

  // 定义一个名为 `createUser` 的变更 procedure
  createUser: t.procedure
    .input(z.object({ name: z.string() }))
    .mutation(({ input }) =&gt; {
      const user = { id: `${Math.random()}`, name: input.name };
      return user;
    }),
});

// 导出 router 的类型定义
export type AppRouter = typeof appRouter;
</code></pre>
<h4>b. 前端：建立用戶端並呼叫 API</h4>
<p>在前端，你只需要從後端匯入 <code>AppRouter</code> 這個<strong>型別</strong>。請注意，匯入的只有型別，而不是任何伺服器端程式碼。</p>
<pre><code>// client/trpc.ts
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '../server/router'; // 只导入类型

export const trpc = createTRPCReact&lt;AppRouter&gt;();
</code></pre>
<p>現在，你可以在 React 元件中像呼叫本機函式一樣呼叫 API，並獲得完整的型別安全與自動完成！</p>
<pre><code>// client/components/UserInfo.tsx
import { trpc } from '../trpc';

function UserInfo({ userId }: { userId: string }) {
  // `useQuery` 的第一个参数是 procedure 的路径
  // 你输入 `trpc.` 时，IDE 会自动提示 `getUser` 和 `createUser`
  const userQuery = trpc.getUser.useQuery({ userId });

  if (userQuery.isLoading) {
    return &lt;div&gt;Loading...&lt;/div&gt;;
  }

  // `userQuery.data` 的类型被自动推断为 { id: string; name: string }
  return &lt;div&gt;User: {userQuery.data?.name}&lt;/div&gt;;
}
</code></pre>
<p>如果此時後端開發者將 <code>getUser</code> 回傳的 <code>name</code> 欄位重新命名為 <code>fullName</code>，前端的 <code>userQuery.data?.name</code> 會立即在 TypeScript 編譯時報錯，而不是等到執行階段才被發現。</p>
<h2>4. 為什麼選擇 tRPC？</h2>
<ul>
<li><strong>絕對的端對端型別安全</strong>：這是它的核心價值，能消除因 API 契約不一致而產生的一整類 Bug。</li>
<li><strong>卓越的開發體驗</strong>：IDE 自動完成功能讓你不必查閱文件或猜測 API 結構。重構也變得異常輕鬆且安全。</li>
<li><strong>不需產生程式碼</strong>：沒有額外的建置步驟，回饋循環非常快速。</li>
<li><strong>輕量且靈活</strong>：tRPC 本身非常小，並可與任何前端框架及後端服務整合。</li>
</ul>
<h2>結論</h2>
<p>tRPC 為全端 TypeScript 開發帶來前所未有的流暢體驗。透過消除對 API 文件與 schema 的依賴，它讓前端和後端之間的協作變得無縫且極其安全。在 Monorepo 架構下，這些優勢會進一步放大。如果你正在建構全端 TypeScript 應用程式，tRPC 絕對是一項值得投入時間嘗試的革命性工具。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[探索 Astro：為內容驅動的網站而生]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/astro/exploring-astro-for-content-sites/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/astro/exploring-astro-for-content-sites/</guid>
            <pubDate>Tue, 10 Jan 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[在單頁應用程式（SPA）大行其道的今天，Astro 另闢蹊徑，專注於內容網站的極致效能。它的「島嶼架構」究竟有何魔力？]]></description>
            <content:encoded><![CDATA[<h2>1. 我們真的需要那麼多 JavaScript 嗎？</h2>
<p>React、Vue、Svelte 等現代前端框架大幅提升了建構複雜 Web 應用程式的開發體驗。但當我們將它們用於建構部落格、作品集、文件或行銷網站時，一個問題也隨之而來：這些以內容呈現為主的網站，真的需要將整個頁面作為單頁應用程式（SPA）來載入嗎？</p>
<p>答案通常是「否」。對於這類網站，使用者的核心需求是快速取得內容，過多的 JavaScript 反而會拖慢頁面的可互動時間（TTI），影響使用者體驗和 SEO。</p>
<p>Astro 正是為了解決這個問題而誕生的。</p>
<h2>2. Astro 的核心理念：內容優先，預設零 JS</h2>
<p>Astro 是一個現代化的靜態網站產生器（SSG），它的核心理念是<strong>內容優先</strong>。它旨在盡可能將更多工作放在建置時完成，向瀏覽器傳送盡可能少的 JavaScript。</p>
<p>為了實現這一點，Astro 提出了兩大創新：</p>
<ul>
<li><strong>預設零 JavaScript（Zero-JS by Default）</strong>：Astro 會將你的所有 UI 元件（無論使用 React、Vue 還是 Svelte 編寫）在建置時算繪成純粹的 HTML。如此一來，瀏覽器收到的就是靜態頁面，無須載入任何框架的 JS 執行環境，載入速度極快。</li>
<li><strong>島嶼架構（Islands Architecture）</strong>：這是 Astro 的精髓所在。在一個由靜態 HTML 構成的「海洋」中，任何需要用戶端互動的元件都可以被標記為一座「島嶼」。Astro 會獨立為這些島嶼打包並載入所需的 JavaScript，而頁面的其餘部分則保持純靜態。</li>
</ul>
<h2>3. 「島嶼」是如何運作的？</h2>
<p>在 Astro 中，你可以透過加入 <code>client:*</code> 指令，將一個元件標記為互動式島嶼。</p>
<p>例如，我們有一個用 React 編寫的計數器元件 <code>Counter.jsx</code>。在 Astro 頁面中可以這樣使用它：</p>
<pre><code>---
// src/pages/index.astro
import Counter from '../components/Counter.jsx';
---
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Astro 示例&lt;/h1&gt;
    &lt;p&gt;这是一个静态的段落，它不会加载任何 JS。&lt;/p&gt;

    {/* 这个组件是一个交互式岛屿 */}
    &lt;Counter client:load /&gt;

    &lt;p&gt;这是另一个静态段落。&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><code>client:load</code> 指令告訴 Astro：</p>
<ol>
<li>在伺服器端算繪 <code>Counter</code> 元件的初始 HTML。</li>
<li>為 <code>Counter</code> 元件建立一個獨立的 JS 套件。</li>
<li>在頁面載入時，自動載入這個 JS 套件並「啟用」（hydrate）該元件，使其具備互動性。</li>
</ol>
<p>Astro 還提供了更精細的載入指令，例如 <code>client:idle</code>（瀏覽器閒置時載入）和 <code>client:visible</code>（元件進入視窗時載入），讓效能最佳化更進一步。</p>
<h2>4. 框架無關性</h2>
<p>Astro 的另一大魅力在於它對 UI 框架的包容性。你可以在同一個 Astro 專案中，同時使用來自 React、Vue、Svelte、SolidJS、Lit 等不同框架的元件。這為團隊協作和技術選型提供了極大的彈性。</p>
<h2>結論</h2>
<p>Astro 並非要取代 React 或 Vue，而是為特定情境提供了更好的解法。如果你的專案是一個以內容為核心、對效能和 SEO 有極高要求的網站（例如這個部落格本身就是使用 Astro 建構的！），那麼 Astro 無疑是一個值得深入探索的絕佳選擇。它將靜態網站的效能優勢與現代框架的開發體驗完美地結合在一起。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[SvelteKit 1.0 正式發布：新一代 Web 應用程式建構方式]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/svelte/sveltekit-1-0-release/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/svelte/sveltekit-1-0-release/</guid>
            <pubDate>Thu, 01 Dec 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[歷經兩年多的開發，SvelteKit 1.0 終於在 2022 年 12 月正式發布！它被譽為建構各種規模 Web 應用程式的未來，本文將帶你了解其核心特性和設計哲學。]]></description>
            <content:encoded><![CDATA[<h2>SvelteKit 1.0 來了！</h2>
<p>2022 年 12 月，Svelte 團隊正式宣布 SvelteKit 1.0 版本發布。對於 Svelte 社群乃至整個前端領域來說，這都是一個里程碑式的事件。SvelteKit 不再僅僅是一個「Svelte 版的 Next.js 或 Nuxt.js」，它帶著自己獨特的設計哲學，旨在提供一種更簡單、更靈活、效能更高的 Web 應用程式建構方式。</p>
<h2>什麼是 SvelteKit？</h2>
<p>如果你熟悉 Svelte，你可能知道 Svelte 本身是一個<strong>元件框架</strong>。它透過一個激進的編譯器，在建置時將你的 <code>.svelte</code> 檔案轉換為高效的命令式 JavaScript 程式碼，從而實現了驚人的效能和極小的執行期開銷。</p>
<p>而 SvelteKit 則是建立在 Svelte 之上的<strong>應用程式框架</strong>。它為你處理了建構一個完整應用程式所需的一切：路由、伺服器端渲染（SSR）、資料載入、部署適配等等，讓你能專注於業務邏輯的開發。</p>
<h2>核心特性一覽</h2>
<h4>1. 檔案系統路由（Filesystem-based Routing）</h4>
<p>和 Next.js 類似，SvelteKit 的路由是基於你的檔案系統結構自動產生的。<code>src/routes</code> 目錄下的檔案結構直接映射為應用程式的 URL。</p>
<ul>
<li><code>src/routes/+page.svelte</code> -&gt; <code>/</code></li>
<li><code>src/routes/about/+page.svelte</code> -&gt; <code>/about</code></li>
<li><code>src/routes/blog/[slug]/+page.svelte</code> -&gt; <code>/blog/some-post</code></li>
</ul>
<p>這種慣例優於設定的方式極大地簡化了路由管理。</p>
<h4>2. 靈活的渲染模式</h4>
<p>SvelteKit 讓你可以在頁面層級精確控制渲染方式。無論是伺服器端渲染（SSR）、靜態網站產生（SSG），還是兩者的混合，都可以在同一個應用程式中實現。你甚至可以為單個頁面關閉 SSR，將其變為一個純粹的客戶端單頁應用程式（SPA）。</p>
<h4>3. 通用的 <code>load</code> 函式</h4>
<p>SvelteKit 統一了資料載入的模式。在每個頁面（或版面配置）的同層目錄下，你可以建立一個 <code>+page.js</code> 或 <code>+page.server.js</code> 檔案，並匯出一個 <code>load</code> 函式。</p>
<ul>
<li><code>+page.js</code> 中的 <code>load</code> 函式同時在伺服器端和客戶端執行。</li>
<li><code>+page.server.js</code> 中的 <code>load</code> 函式<strong>僅在伺服器端</strong>執行，你可以在這裡安全地存取資料庫或私人 API。</li>
</ul>
<p><code>load</code> 函式回傳的資料會自動傳遞給對應的 <code>+page.svelte</code> 元件。</p>
<pre><code>// src/routes/blog/[slug]/+page.server.js
import * as db from '$lib/server/database';

export async function load({ params }) {
  const post = await db.getPost(params.slug);
  return { post };
}
</code></pre>
<h4>4. 適配器（Adapters）</h4>
<p>「一次撰寫，隨處部署」是 SvelteKit 的核心承諾之一。透過<strong>適配器</strong>，SvelteKit 可以將你的應用程式打包成適用於任何目標平台的格式。</p>
<ul>
<li><code>@sveltejs/adapter-node</code>: 用於部署到傳統的 Node.js 伺服器。</li>
<li><code>@sveltejs/adapter-vercel</code>: 適配 Vercel 平台。</li>
<li><code>@sveltejs/adapter-static</code>: 將整個應用程式預先渲染為靜態檔案，適用於任何靜態主機。</li>
<li>還有更多適用於 Netlify、Cloudflare Workers 等平台的官方和社群適配器。</li>
</ul>
<h2>結論</h2>
<p>SvelteKit 1.0 的發布，標誌著 Svelte 生態的成熟。它將 Svelte 編譯器的極致效能與一個經過深思熟慮的應用程式框架相結合，為開發者提供了一個強大而愉悅的開發體驗。如果你正在尋找一個能夠建構從簡單靜態頁面到複雜動態應用程式的全能型框架，SvelteKit 絕對值得一試。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[React 狀態管理演進史：從 Props Drilling 到 Zustand]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/react/state-management-evolution/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/react/state-management-evolution/</guid>
            <pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[React 的狀態管理方案從最初的「屬性鑽探」到 Redux 的「大一統」，再到如今 Zustand 等輕量級函式庫的「百花齊放」，本文將帶你回顧這段精彩的演進歷史。]]></description>
            <content:encoded><![CDATA[<h2>引言</h2>
<p>「如何優雅地管理狀態？」這是每一個 React 開發者在專案成長過程中都必須面對的核心問題。從簡單的元件內部狀態，到複雜的跨元件全域狀態，React 社群探索出了多種解決方案。這些方案的演進，也反映了我們對元件化開發理解的不斷深化。</p>
<h2>階段一：樸素的時代 —— <code>useState</code> 與屬性鑽探（Props Drilling）</h2>
<p>在最開始，我們只有 <code>useState</code>（或類別元件的 <code>this.state</code>）。當一個狀態需要被多個元件共享時，React 的官方建議是 <strong>「狀態提升」（Lifting State Up）</strong>。我們將狀態提升到這些元件的最近公共父元件中，再透過 props 將狀態和更新函式傳遞下去。</p>
<p>當元件層級很深時，這種模式就導致了 <strong>「屬性鑽探」（Props Drilling）</strong>：一些中間層的元件僅僅是為了將 props 傳遞給更深層的子元件，自身完全用不到這些 props。這使得元件的耦合度增高，重構和維護變得困難。</p>
<h2>階段二：官方的答案 —— Context API</h2>
<p>為了解決「屬性鑽探」的問題，React 官方提供了 Context API。它允許我們建立一個「上下文」，並在元件樹的頂層提供（Provide）一個值，然後在任何深度的子元件中都可以直接取用（Consume）這個值，無需手動逐層傳遞。</p>
<pre><code>// 1. 创建 Context
const ThemeContext = React.createContext('light');

// 2. 在顶层提供值
&lt;ThemeContext.Provider value="dark"&gt;
  &lt;App /&gt;
&lt;/ThemeContext.Provider&gt;

// 3. 在子组件中消费
const theme = useContext(ThemeContext); // 'dark'
</code></pre>
<p>然而，Context API 也有其「陷阱」：<strong>效能問題</strong>。只要 <code>Provider</code> 的 <code>value</code> 發生變化，所有取用該 Context 的元件<strong>都會</strong>重新渲染，即使它關心的只是 <code>value</code> 物件中的一小部分。這使得 Context API 不適合管理頻繁變化的、複雜的全域狀態。</p>
<h2>階段三：大一統時代 —— Redux</h2>
<p>在 Context API 尚不成熟的年代，Redux 橫空出世，迅速成為大型複雜應用程式狀態管理的實際標準。它借鑑了 Flux 架構和函數式程式設計思想，帶來了：</p>
<ul>
<li><strong>單一資料來源（Single Source of Truth）</strong>: 整個應用程式的 state 被儲存在一個單一的 store 中。</li>
<li><strong>State 是唯讀的</strong>: 唯一改變 state 的方法就是 dispatch 一個 action。</li>
<li><strong>使用純函式來執行修改</strong>: Reducer 接收先前的 state 和 action，並回傳新的 state。</li>
</ul>
<p>Redux 憑藉其可預測性、強大的除錯工具（時間旅行）和豐富的中介軟體生態，解決了大型應用程式中的狀態管理難題。但它的缺點也同樣明顯：<strong>繁瑣的樣板程式碼（Boilerplate）</strong>。為了實作一個簡單的功能，開發者需要撰寫 Actions、Reducers、Dispatchers，心智負擔較重。</p>
<h2>階段四：文藝復興 —— 輕量級、Hooks-First 的方案</h2>
<p>隨著 React Hooks 的普及，以及對 Redux 複雜性的反思，社群開始湧現出一批新的、更輕量的狀態管理函式庫。它們的共同特點是 API 簡潔、心智模型簡單，並且充分利用了 Hooks 的能力。</p>
<p><strong>Zustand</strong> 是其中的傑出代表。它提供了一個極其簡單的 <code>create</code> 函式來建立 store，並且：</p>
<ul>
<li><strong>無需 Provider</strong>: Store 存在於 React 元件樹之外，可以在任何地方匯入使用。</li>
<li><strong>API 極簡</strong>: 透過一個 Hook 即可存取和更新狀態。</li>
<li><strong>選擇性訂閱，效能更優</strong>: 元件可以只訂閱它關心的那一部分 state，避免了 Context API 的效能問題。</li>
</ul>
<pre><code>const useStore = create(set =&gt; ({
  count: 0,
  inc: () =&gt; set(state =&gt; ({ count: state.count + 1 })),
}));

function Counter() {
  // 只订阅 count 的变化
  const count = useStore(state =&gt; state.count);
  return &lt;h1&gt;{count}&lt;/h1&gt;;
}
</code></pre>
<h2>結論：沒有銀彈，按需選擇</h2>
<p>React 狀態管理的演進史告訴我們，沒有一勞永逸的「最佳方案」，只有「最適合當前情境」的方案。</p>
<ul>
<li><strong><code>useState</code></strong>: 永遠是元件區域狀態的首選。</li>
<li><strong>Context API</strong>: 適合管理那些不經常變化的全域資料，如主題、使用者驗證資訊等。</li>
<li><strong>Zustand / Jotai</strong>: 對於大多數需要客戶端全域狀態的應用程式來說，它們在效能和開發體驗之間取得了絕佳的平衡。</li>
<li><strong>Redux（Redux Toolkit）</strong>: 對於需要嚴格資料流規範、複雜中介軟體和強大除錯能力的超大型應用程式，它依然是可靠的選擇。</li>
</ul>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[從 Webpack 到 Vite：一次平順的遷移之旅]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/build-tools/migrating-from-webpack-to-vite/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/build-tools/migrating-from-webpack-to-vite/</guid>
            <pubDate>Mon, 05 Sep 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[你的 Webpack 專案啟動需要一分鐘，熱更新要等十幾秒？是時候擁抱下一代前端建置工具了。本文記錄了一次從 Webpack 到 Vite 的遷移過程和心得。]]></description>
            <content:encoded><![CDATA[<h2>1. 為什麼要遷移？Webpack 的痛點</h2>
<p>Webpack 是一個極其強大且可設定的模組打包工具，多年來一直是前端專案的基石。但隨著專案規模增長，其以「打包」為核心的思路也帶來了效能瓶頸：</p>
<ul>
<li><strong>緩慢的冷啟動</strong>：每次啟動開發伺服器（<code>dev-server</code>），Webpack 都需要遍歷整個相依性圖，將所有模組打包到記憶體中。這個過程在大型專案中可能花費數分鐘。</li>
<li><strong>緩慢的熱更新（HMR）</strong>：當你修改一個檔案時，Webpack 需要重新計算相關模組並進行替換。雖然比整個頁面重新整理更快，但在大型專案中，這個過程的延遲也可能達到數秒甚至十幾秒，打斷開發心流。</li>
</ul>
<h2>2. Vite 如何解決這些問題？</h2>
<p>Vite（法語意為「快」）另闢蹊徑。它利用現代瀏覽器對原生 ES 模組（ESM）的支援，將建置過程分成兩部分：</p>
<ul>
<li><strong>開發時</strong>：Vite 啟動一個伺服器，但不會預先打包所有模組。相反地，它會攔截瀏覽器的模組請求，按需轉換並提供原始碼。例如，瀏覽器請求 <code>main.js</code>，Vite 提供它；<code>main.js</code> 匯入了 <code>Button.vue</code>，瀏覽器再次發起請求，Vite 再提供轉換後的 <code>Button.vue</code>。這使開發伺服器幾乎能瞬間啟動。</li>
<li><strong>正式環境</strong>：Vite 使用 Rollup（同樣高效的打包工具）進行打包，以獲得高度最佳化的靜態資源。</li>
</ul>
<p>這種模式徹底改變了開發體驗，帶來閃電般的啟動速度和毫秒級熱更新。</p>
<h2>3. 遷移實作步驟</h2>
<p>將一個現有 Webpack 專案遷移到 Vite，通常涉及以下步驟：</p>
<h4>a. 安裝相依套件並建立設定檔</h4>
<p>首先，安裝 Vite：</p>
<pre><code>pnpm add -D vite @vitejs/plugin-react # 或 @vitejs/plugin-vue
</code></pre>
<p>然後在專案根目錄建立 <code>vite.config.js</code>：</p>
<pre><code>import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [react()],
})
</code></pre>
<h4>b. 移動 <code>index.html</code></h4>
<p>Webpack 通常將 <code>index.html</code> 放在 <code>public</code> 目錄下，並自動注入打包後的 JS。Vite 則將 <code>index.html</code> 視為應用程式的進入點。你需要將它移到專案根目錄，並手動加入指令碼參照：</p>
<pre><code>
&lt;body&gt;
  &lt;div id="root"&gt;&lt;/div&gt;
  &lt;script type="module" src="/src/main.jsx"&gt;&lt;/script&gt;
&lt;/body&gt;
</code></pre>
<h4>c. 替換 Webpack 外掛</h4>
<p>你需要找到 Webpack Loaders 和 Plugins 所對應的 Vite 外掛。社群生態非常豐富，大部分需求都能找到解決方案。</p>
<table>
<thead>
<tr>
<th>Webpack</th>
<th>Vite</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>babel-loader</code></td>
<td><code>@vitejs/plugin-react</code>（內建 Babel）</td>
</tr>
<tr>
<td><code>vue-loader</code></td>
<td><code>@vitejs/plugin-vue</code></td>
</tr>
<tr>
<td><code>file-loader</code></td>
<td>Vite 內建支援</td>
</tr>
<tr>
<td><code>webpack-dev-server</code></td>
<td>Vite 內建開發伺服器</td>
</tr>
</tbody>
</table>
<h4>d. 處理環境變數</h4>
<p>在 Webpack 中，我們習慣使用 <code>process.env.NODE_ENV</code>。在 Vite 中，需要透過 <code>import.meta.env</code> 來存取環境變數，例如 <code>import.meta.env.VITE_API_URL</code>。</p>
<h4>e. 設定路徑別名</h4>
<p>在 <code>vite.config.js</code> 中設定路徑別名非常簡單：</p>
<pre><code>import path from 'node:path'
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  // ...
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})
</code></pre>
<h2>4. 遷移後的成果</h2>
<p>遷移到 Vite 後，最直觀的感受就是<strong>快</strong>。</p>
<ul>
<li>開發伺服器啟動時間從 <strong>50 秒</strong>縮短到 <strong>2 秒</strong>。</li>
<li>熱更新時間從<strong>平均 3–5 秒</strong>降低到<strong>幾乎無感的數十毫秒</strong>。</li>
<li>設定檔（<code>vite.config.js</code>）相比 <code>webpack.config.js</code> 大幅簡化。</li>
</ul>
<h2>結論</h2>
<p>雖然遷移過程可能需要處理一些專案特有的設定問題，但從 Webpack 遷移到 Vite 所帶來的開發體驗提升非常巨大。如果你還在忍受緩慢的建置過程，那麼現在就是擁抱 Vite 的最佳時機。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[零執行階段 CSS-in-JS：兼顧開發體驗與極致效能]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/css/zero-runtime-css-in-js/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/css/zero-runtime-css-in-js/</guid>
            <pubDate>Fri, 05 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[CSS-in-JS 提供了卓越的開發體驗，但其執行階段效能開銷一直備受爭議。零執行階段 CSS-in-JS 方案在建置時提取樣式到靜態 CSS 檔案，實現了兩全其美。]]></description>
            <content:encoded><![CDATA[<h2>1. CSS-in-JS 的「雙刃劍」</h2>
<p>CSS-in-JS 方案，如 <code>styled-components</code> 和 <code>Emotion</code>，透過允許開發者在 JavaScript/TypeScript 檔案中編寫 CSS，徹底改變了元件化開發的樣式管理方式。它帶來了許多好處：</p>
<ul>
<li><strong>元件作用域</strong>: 樣式預設與元件綁定，解決了 CSS 全域命名衝突的問題。</li>
<li><strong>動態樣式</strong>: 可以基於元件的 props 或 state 輕鬆建立動態樣式。</li>
<li><strong>程式碼共存</strong>: 樣式和元件邏輯放在同一個檔案中，便於維護和組織。</li>
</ul>
<p>然而，這些便利性並非沒有代價。傳統 CSS-in-JS 函式庫的核心是 <strong>執行階段 (Runtime)</strong>。當元件在瀏覽器中掛載時，CSS-in-JS 函式庫的 JavaScript 執行階段會：</p>
<ol>
<li>解析範本字串或物件中的 CSS。</li>
<li>產生唯一的類別名稱。</li>
<li>將樣式動態地注入到文件的 <code>&lt;head&gt;</code> 中的 <code>&lt;style&gt;</code> 標籤裡。</li>
</ol>
<p>這個過程會增加 JavaScript 的套件體積，並在應用程式啟動和渲染時帶來一定的計算開銷，影響效能。</p>
<h2>2. 「零執行階段」的破局之道</h2>
<p>為了解決執行階段效能問題，社群提出了一種新的思路：<strong>零執行階段 (Zero-Runtime) CSS-in-JS</strong>。</p>
<p>其核心思想是：保留 CSS-in-JS 的開發體驗，但在 <strong>建置時 (build time)</strong> 就完成所有工作。建置工具（通常是一個 Babel 外掛程式或 Vite 外掛程式）會掃描你的程式碼，找到所有 CSS-in-JS 的用法，然後：</p>
<ol>
<li>將 CSS 文字提取出來。</li>
<li>產生靜態的 <code>.css</code> 檔案。</li>
<li>將原始的 CSS-in-JS 程式碼替換為產生的、唯一的類別名稱。</li>
</ol>
<p>最終，交付給瀏覽器的 JavaScript 套件中 <strong>不包含任何 CSS-in-JS 的執行階段函式庫</strong>，其效果等同於手寫的高度最佳化的靜態 CSS 檔案。</p>
<h2>3. 主流零執行階段方案</h2>
<h4>a. Linaria</h4>
<p>Linaria 是零執行階段領域的先驅之一。它允許你使用熟悉的 <code>styled</code> 標籤範本字串語法。</p>
<pre><code>// YourComponent.js
import { styled } from '@linaria/react';

const Title = styled.h1`
  font-size: 2rem;
  color: tomato;
`;

// 构建时，这会被转换为：
// &lt;h1 class="Title_a1b2c3d"&gt;...&lt;/h1&gt;
//
// 并在一个静态 .css 文件中生成：
// .Title_a1b2c3d {
//   font-size: 2rem;
//   color: tomato;
// }
</code></pre>
<h4>b. vanilla-extract</h4>
<p>由 SEEK 公司團隊開發的 vanilla-extract 更進一步，它利用 TypeScript 來創造完全型別安全的樣式。所有樣式定義都是 <code>.css.ts</code> 檔案中的匯出變數，提供了強大的型別推斷和自動補全。</p>
<pre><code>// styles.css.ts
import { style } from '@vanilla-extract/css';

export const title = style({
  fontSize: '2rem',
  color: 'tomato',
});
</code></pre>
<h4>c. Panda CSS</h4>
<p>Panda CSS 是一個較新的競爭者，它借鑑了 Tailwind CSS 的思想，提供了一種「樣式屬性」 (Style Props) 的開發體驗，同時保證了零執行階段的輸出。</p>
<pre><code>import { css } from '../styled-system/css';

function MyComponent() {
  return &lt;div className={css({ fontSize: '2rem', color: 'tomato' })} /&gt;;
}
</code></pre>
<h2>4. 優勢與權衡</h2>
<p><strong>優勢</strong>:</p>
<ul>
<li><strong>極致效能</strong>: 最終產物是靜態 CSS，沒有 JavaScript 執行階段開銷。</li>
<li><strong>更小的套件體積</strong>: 無需在客戶端載入 CSS-in-JS 函式庫。</li>
<li><strong>強大的開發體驗</strong>: 依然享受元件作用域、TypeScript 型別安全和程式碼共存的好處。</li>
</ul>
<p><strong>權衡</strong>:</p>
<ul>
<li><strong>建置時相依性</strong>: 必須整合到建置流程中，設定相對複雜一些。</li>
<li><strong>動態性受限</strong>: 無法根據純執行階段的值（例如，來自 API 的資料）來產生全新的樣式。不過，可以透過 CSS 變數等技術來彌補大部分動態場景。</li>
</ul>
<h2>結論</h2>
<p>零執行階段 CSS-in-JS 是對傳統 CSS-in-JS 典範的一次「撥亂反正」。它巧妙地分離了「開發時體驗」和「執行階段效能」，讓我們不再需要在兩者之間做出艱難的妥協。對於追求極致效能和更小打包體積的現代 Web 應用程式來說，它提供了一個近乎完美的解決方案。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[ES2022 新特性概覽：Top-Level Await、.at() 及更多內容]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/es/es2022-features-overview/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/es/es2022-features-overview/</guid>
            <pubDate>Fri, 15 Jul 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[ECMAScript 2022 (ES2022) 正式發布，帶來了一系列實用的新特性。本文將帶你快速了解其中最值得關注的幾個更新，包括 Top-Level await、.at() 方法等。]]></description>
            <content:encoded><![CDATA[<h2>引言</h2>
<p>隨著 TC39 委員會的年度發布流程，JavaScript 語言每年都在不斷演進。ECMAScript 2022 (ES2022) 帶來了一系列旨在提升開發者體驗和程式碼可讀性的新功能。讓我們一起看看其中最亮眼的幾個特性。</p>
<h2>1. Top-Level <code>await</code></h2>
<p>這是 ES2022 中最受期待的特性之一。在此之前，<code>await</code> 關鍵字只能在 <code>async</code> 函式內部使用。這在模組的頂層作用域中處理非同步操作時非常不便，我們通常需要藉助 IIFE（立即呼叫函式運算式）來包裹非同步程式碼。</p>
<p><strong>之前 👎:</strong></p>
<pre><code>// data.js
import { fetchData } from './api.js';

let data;
(async () =&gt; {
  data = await fetchData();
  // ... 只能在这里使用 data
})();

export { data }; // 导出时 data 还是 undefined
</code></pre>
<p><strong>現在 (ES2022) 👍:</strong></p>
<pre><code>// data.js
import { fetchData } from './api.js';

const data = await fetchData();

export { data }; // 模块会等待 await 完成后再被其他模块评估
</code></pre>
<p>Top-level <code>await</code> 極大地簡化了模組的動態載入、相依性初始化等場景，讓非同步程式碼寫起來更符合直覺。</p>
<h2>2. <code>.at()</code> 陣列／字串索引方法</h2>
<p>在 JavaScript 中，取得陣列的最後一個元素通常需要寫 <code>arr[arr.length - 1]</code>，這既冗長又容易出錯。ES2022 引入了 <code>.at()</code> 方法，統一了正向和反向索引的體驗。</p>
<p><code>.at()</code> 方法接收一個整數，如果是正數，則回傳對應索引的元素；如果是負數，則從末尾開始倒數。</p>
<p><strong>之前 👎:</strong></p>
<pre><code>const arr = [1, 2, 3, 4, 5];
const lastElement = arr[arr.length - 1]; // 5
const secondToLast = arr[arr.length - 2]; // 4
</code></pre>
<p><strong>現在 (ES2022) 👍:</strong></p>
<pre><code>const arr = [1, 2, 3, 4, 5];
const lastElement = arr.at(-1); // 5
const secondToLast = arr.at(-2); // 4

// 同样适用于字符串
const str = 'hello';
console.log(str.at(-1)); // 'o'
</code></pre>
<h2>3. <code>Object.hasOwn(obj, prop)</code></h2>
<p>檢查一個物件是否擁有某個自有屬性（而不是繼承來的屬性），我們通常使用 <code>Object.prototype.hasOwnProperty.call(obj, prop)</code>。這種寫法非常繁瑣，且在某些情況下（如 <code>Object.create(null)</code> 建立的物件）會出錯。</p>
<p><code>Object.hasOwn()</code> 提供了一個更短、更可靠的靜態方法。</p>
<p><strong>之前 👎:</strong></p>
<pre><code>const obj = { a: 1 };
console.log(Object.prototype.hasOwnProperty.call(obj, 'a')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'toString')); // false
</code></pre>
<p><strong>現在 (ES2022) 👍:</strong></p>
<pre><code>const obj = { a: 1 };
console.log(Object.hasOwn(obj, 'a')); // true
console.log(Object.hasOwn(obj, 'toString')); // false
</code></pre>
<h2>4. 其他值得關注的特性</h2>
<ul>
<li><strong>Error Cause</strong>: <code>Error</code> 建構函式現在可以接收第二個參數，用於指定錯誤的「原因」，方便建構更清楚的錯誤鏈。<pre><code>try {
  // ...
} catch (err) {
  throw new Error('New error message', { cause: err });
}
</code></pre>
</li>
<li><strong>RegExp Match Indices (<code>/d</code> flag)</strong>: 當使用 <code>/d</code> 旗標時，規則運算式的比對結果會額外提供每個擷取群組的起始和結束索引。</li>
</ul>
<h2>結論</h2>
<p>ES2022 的新特性雖然沒有帶來顛覆性的變革，但它們在細節上打磨了 JavaScript，解決了許多開發者長期以來的痛點，讓程式碼變得更簡潔、更健壯。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[告別 Redux Boilerplate？輕量級狀態管理器 Zustand 詳解]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/react/zustand-vs-redux/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/react/zustand-vs-redux/</guid>
            <pubDate>Sun, 10 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Redux 的繁瑣讓你望而卻步？Zustand 提供了一個基於 Hooks 的、極簡的 React 狀態管理方案。本文將帶你了解它的核心用法和設計思想。]]></description>
            <content:encoded><![CDATA[<h2>1. Redux 的「煩惱」</h2>
<p>Redux 毫無疑問是 React 生態中最著名、最強大的狀態管理函式庫。它的單向資料流、時間旅行除錯等特性使其成為大型、複雜應用程式的可靠選擇。但對於許多中小型專案，Redux 的「樣板程式碼」（Boilerplate）卻常常令人頭疼：</p>
<ul>
<li><strong>Actions &amp; Action Creators</strong>: 定義大量的 action 類型和建立函式。</li>
<li><strong>Reducers</strong>: 撰寫龐大的 <code>switch</code> 陳述式來處理不同的 action。</li>
<li><strong>Dispatch &amp; Selectors</strong>: 在元件中透過 <code>dispatch</code> 派發 action，透過 <code>useSelector</code> 訂閱狀態變化。</li>
<li><strong>Context Provider</strong>: 需要在應用程式的根部包覆一個 <code>&lt;Provider&gt;</code>。</li>
</ul>
<p>這一切使得新增一個簡單的狀態也需要修改多個檔案，流程繁瑣。</p>
<h2>2. Zustand：一股清流</h2>
<p>Zustand（德語意為「狀態」）是由 Poimandres（<code>react-three-fiber</code> 的作者）團隊開發的一個輕量級狀態管理函式庫。它的設計哲學就是<strong>極簡</strong>和<strong>非侵入式</strong>。</p>
<p><strong>核心特點：</strong></p>
<ul>
<li><strong>程式碼量極少</strong>: 實作一個功能，Zustand 的程式碼量通常只有 Redux 的一小部分。</li>
<li><strong>基於 Hooks</strong>: 它的一切都圍繞一個自訂 Hook 展開，符合現代 React 的開發習慣。</li>
<li><strong>無需 Context Provider</strong>: 你不需要在應用程式頂層包覆任何 Provider。Store 獨立於元件樹，可以在任何地方匯入和使用。</li>
<li><strong>上手快</strong>: API 極其簡單，幾分鐘就能學會核心用法。</li>
</ul>
<h2>3. 核心用法</h2>
<p>Zustand 的使用分為兩步：<strong>建立 Store</strong> 和<strong>在元件中使用</strong>。</p>
<h4>a. 建立 Store</h4>
<p>你可以將 store 定義在任何一個 <code>.js</code> 或 <code>.ts</code> 檔案中。</p>
<pre><code>// src/store.js
import { create } from 'zustand';

const useBearStore = create((set) =&gt; ({
  bears: 0,
  increasePopulation: () =&gt; set((state) =&gt; ({ bears: state.bears + 1 })),
  removeAllBears: () =&gt; set({ bears: 0 }),
}));

export default useBearStore;
</code></pre>
<p><code>create</code> 函式接收一個回呼，該回呼的參數是 <code>set</code> 函式（類似於 React 的 <code>setState</code>）。你在這裡定義你的狀態和更新狀態的方法。</p>
<h4>b. 在元件中使用</h4>
<p>在任何元件中，像使用普通 Hook 一樣使用它。</p>
<pre><code>// src/components/BearCounter.jsx
import useBearStore from '../store';

function BearCounter() {
  const bears = useBearStore((state) =&gt; state.bears);
  return &lt;h1&gt;{bears} around here ...&lt;/h1&gt;;
}
</code></pre>
<p>注意，我們透過一個選擇器函式 <code>(state) =&gt; state.bears</code> 來訂閱 <code>bears</code> 狀態。這很重要，因為它能確保只有在 <code>bears</code> 狀態變化時，該元件才會重新渲染，避免了不必要的效能開銷。</p>
<pre><code>// src/components/Controls.jsx
import useBearStore from '../store';

function Controls() {
  const increasePopulation = useBearStore((state) =&gt; state.increasePopulation);
  return &lt;button onClick={increasePopulation}&gt;one up&lt;/button&gt;;
}
</code></pre>
<p>取得 action 也同樣簡單。</p>
<h2>4. 非同步 Action</h2>
<p>Zustand 處理非同步操作也非常自然，你不需要任何像 <code>redux-thunk</code> 或 <code>redux-saga</code> 這樣的中介軟體。</p>
<pre><code>const useAsyncStore = create((set) =&gt; ({
  data: null,
  fetchData: async (url) =&gt; {
    const response = await fetch(url);
    const data = await response.json();
    set({ data });
  },
}));
</code></pre>
<h2>結論</h2>
<p>Zustand 並非要完全取代 Redux。Redux 在嚴格的規範、可追溯性和龐大的生態系統方面仍然具有優勢。</p>
<p>然而，對於絕大多數 React 應用程式來說，Zustand 提供了一個更簡單、更快速、心智負擔更低的選擇。如果你厭倦了 Redux 的繁文縟節，或者你的下一個專案需要一個輕快、靈活的狀態管理器，那麼 Zustand 絕對是一個值得你嘗試的優秀方案。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[使用 Turborepo 管理你的 Monorepo 專案]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/build-tools/intro-to-turborepo/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/build-tools/intro-to-turborepo/</guid>
            <pubDate>Sun, 20 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[隨著專案越來越複雜，Monorepo 成為一種流行的程式碼組織方式。但隨之而來的建置效能問題該如何解決？Turborepo 正是為此而生的高效能建置系統。]]></description>
            <content:encoded><![CDATA[<h2>1. 為什麼選擇 Monorepo？</h2>
<p>Monorepo（單一程式碼儲存庫）是一種將多個獨立專案或套件（package）存放在同一個程式碼儲存庫中的策略。與每個專案擁有獨立儲存庫的 Polyrepo 模式相比，Monorepo 具有一些顯著優勢：</p>
<ul>
<li><strong>程式碼共享</strong>：在不同專案間共享元件、工具函式或型別定義變得非常容易。</li>
<li><strong>原子化提交</strong>：一個功能的修改如果涉及多個套件，可以透過一次提交完成，確保版本一致。</li>
<li><strong>簡化相依性管理</strong>：所有專案共享同一個 <code>node_modules</code>（或透過 pnpm/yarn workspace 最佳化），減少相依性衝突和版本不一致的問題。</li>
</ul>
<p>然而，當儲存庫規模變大時，Monorepo 的挑戰也隨之而來：<strong>建置效能</strong>。</p>
<h2>2. Monorepo 的痛點</h2>
<p>想像一下，你的儲存庫裡有 <code>docs</code>、<code>webapp</code>，以及一個共享的 <code>ui</code> 元件庫。當你只修改了 <code>docs</code> 中的一個錯字時，你最不希望發生的事情就是整個儲存庫的所有專案（包括 <code>webapp</code> 和 <code>ui</code>）都被重新建置和測試。</p>
<p>傳統的 Monorepo 管理工具（如 Lerna）在任務編排和建置快取方面有所不足，導致：</p>
<ul>
<li><strong>重複工作</strong>：每次 CI/CD 執行時，都會從頭建置和測試所有內容，即使大部分程式碼沒有變化。</li>
<li><strong>建置時間長</strong>：無法有效利用多核心 CPU 平行執行任務。</li>
<li><strong>複雜的指令碼</strong>：需要編寫複雜的 <code>package.json</code> 指令碼，手動控制任務的執行順序。</li>
</ul>
<h2>3. Turborepo：為速度而生</h2>
<p>Turborepo 是一個專為 JavaScript/TypeScript Monorepo 設計的高效能建置系統（後被 Vercel 收購）。它透過兩大核心技術解決上述痛點：<strong>增量建置</strong>和<strong>遠端快取</strong>。</p>
<h4>a. 增量建置與任務管線（Pipelines）</h4>
<p>Turborepo 讓你在根目錄的 <code>turbo.json</code> 檔案中定義任務之間的相依關係。</p>
<pre><code>// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": []
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false
    }
  }
}
</code></pre>
<ul>
<li><strong><code>dependsOn: ["^build"]</code></strong>：<code>^</code> 符號表示，一個套件的 <code>build</code> 任務依賴於其所依賴的所有套件的 <code>build</code> 任務。Turborepo 會根據這個關係圖，以最高效的平行方式執行任務。</li>
<li><strong><code>outputs</code></strong>：告訴 Turborepo 這個任務會產生哪些輸出檔案。</li>
</ul>
<p>當你執行 <code>pnpm turbo build</code> 時，Turborepo 會計算哪些檔案被修改，並且只重新建置受影響的套件。如果一個套件的原始碼和相依性沒有變化，Turborepo 會直接使用上次的建置產物，這個過程是瞬間完成的。</p>
<h4>b. 遠端快取（Remote Caching）</h4>
<p>這是 Turborepo 的「殺手級」功能。它不只在你的本機上快取建置結果，還能將這些快取上傳到共享的遠端伺服器（如 Vercel 或你自己的 S3 儲存貯體）。</p>
<p>這表示：</p>
<ul>
<li><strong>你的同事</strong>在取得最新程式碼後，如果他要建置的部分你已經建置過，他可以直接下載快取，無須在本機重新建置。</li>
<li><strong>CI/CD 伺服器</strong>也能連線到這個遠端快取。一個 PR 在 CI 中被建置並快取後，其他開發者或後續 CI 任務都能立即享用這項成果。</li>
</ul>
<p>這能將整個團隊的建置時間從數十分鐘縮短到幾分鐘，甚至幾十秒。</p>
<h2>結論</h2>
<p>Turborepo 並沒有發明 Monorepo，但它透過引入先進的快取和任務排程系統，大幅最佳化了 Monorepo 的開發體驗，解決其最核心的效能瓶頸。如果你正在使用或準備採用 Monorepo 架構，Turborepo 絕對是一個能為你和團隊節省大量時間的利器。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[SolidJS：一個真正具有響應性的 JavaScript 框架]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/frameworks/introduction-to-solidjs/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/frameworks/introduction-to-solidjs/</guid>
            <pubDate>Fri, 05 Nov 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[它看起來像 React，但它沒有虛擬 DOM。SolidJS 透過其創新的細粒度響應式系統，在效能上達到了新的高度。讓我們一探究竟。]]></description>
            <content:encoded><![CDATA[<h2>1. 前端框架的「聖杯」：效能與體驗</h2>
<p>自 React 推廣虛擬 DOM (Virtual DOM) 以來，VDOM 似乎成了現代前端框架的標準配備。它透過在記憶體中維護一個虛擬的 UI 表示，並透過 diff 演算法來計算最小化的 DOM 更新，從而提升了開發體驗和大多數場景下的效能。</p>
<p>但 VDOM 並非沒有代價。它本身需要佔用記憶體，diff 過程也需要計算成本。一個問題隨之而來：我們能否繞過 VDOM，直接將狀態變更精確地更新到 DOM 上，同時還保持宣告式的、類似 React 的開發體驗？</p>
<p>SolidJS 給出了它的答案。</p>
<h2>2. SolidJS 的核心：細粒度響應式 (Fine-Grained Reactivity)</h2>
<p>SolidJS 是一個宣告式的、響應式的 JavaScript 框架。它雖然使用 JSX，看起來和 React 非常相似，但其底層原理完全不同。</p>
<p>SolidJS 的編譯器會將你的 JSX 程式碼轉換為最佳的原生 DOM 操作。它不使用 VDOM，而是建立了一個由響應式「訊號」 (Signals) 組成的相依圖。當一個訊號的值發生變化時，只有訂閱了這個訊號的「效果」 (Effects) 或計算（Memos）會重新執行。</p>
<p><strong>一個顛覆性的心智模型：元件只執行一次！</strong></p>
<p>在 React 中，當 state 或 props 變化時，整個元件函式會重新執行。但在 SolidJS 中，你的元件函式 <strong>從頭到尾只會執行一次</strong>。</p>
<pre><code>import { createSignal } from 'solid-js';

function Counter() {
  console.log('Component function runs!'); // 这句话只会在组件挂载时打印一次

  const [count, setCount] = createSignal(0);

  const increment = () =&gt; setCount(count() + 1);

  return (
    &lt;button type="button" onClick={increment}&gt;
      Count: {count()}
    &lt;/button&gt;
  );
}
</code></pre>
<p>當你點擊按鈕時，只有 <code>count()</code> 這個訊號的讀取器和依賴它的 DOM 文字節點會更新。<code>Counter</code> 函式本身不會再次執行。這種「外科手術式」的更新是 SolidJS 極致效能的根源。</p>
<h2>3. 核心 API</h2>
<p>SolidJS 的響應式系統主要由三個核心原語構成：</p>
<ul>
<li><strong><code>createSignal(initialValue)</code></strong>: 建立一個響應式訊號，回傳一個 getter 和一個 setter。<code>[count, setCount]</code></li>
<li><strong><code>createEffect(() =&gt; {})</code></strong>: 建立一個「效果」，它會自動追蹤其內部讀取的所有訊號，並在任何一個訊號變化時重新執行。非常適合用來執行副作用，如手動操作 DOM。</li>
<li><strong><code>createMemo(() =&gt; {})</code></strong>: 建立一個快取的、衍生的計算值。只有當其內部相依的訊號變化時，它才會重新計算。</li>
</ul>
<pre><code>import { createSignal, createEffect } from 'solid-js';

function App() {
  const [firstName, setFirstName] = createSignal('John');
  const [lastName, setLastName] = createSignal('Smith');

  // createEffect 会在 firstName 或 lastName 变化时自动运行
  createEffect(() =&gt; {
    console.log(`Full name: ${firstName()} ${lastName()}`);
  });

  // ...
}
</code></pre>
<h2>4. 為什麼選擇 SolidJS？</h2>
<ul>
<li><strong>極致的效能</strong>: 在各種獨立的效能測試中，SolidJS 常常名列前茅，其效能與原生 JavaScript 程式碼非常接近。</li>
<li><strong>極小的套件體積</strong>: 由於沒有 VDOM 執行階段，其核心套件非常小。</li>
<li><strong>熟悉的開發體驗</strong>: 如果你熟悉 React Hooks，你可以很快上手 SolidJS。JSX 和響應式原語的組合既強大又直覺。</li>
<li><strong>真正的響應式</strong>: 它的模型更接近於 MobX 或 Vue 的 Composition API，但透過編譯器實現了零執行階段代價。</li>
</ul>
<h2>結論</h2>
<p>SolidJS 代表了前端框架演進的一個重要方向：即透過編譯器在建置時做更多的工作，來實現更少的執行階段程式碼和更高的效能。它向我們證明了，在不犧牲宣告式開發體驗的前提下，完全可以擺脫虛擬 DOM 的束縛。對於追求極致效能的應用程式，SolidJS 提供了一個極具吸引力的選擇。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
        <item>
            <title><![CDATA[你真的會用 Fetch 嗎？深入 AbortController 實現請求取消]]></title>
            <link>https://blog.m1ng.space/zh-tw/posts/javascript/fetch-with-abortcontroller/</link>
            <guid isPermaLink="false">https://blog.m1ng.space/zh-tw/posts/javascript/fetch-with-abortcontroller/</guid>
            <pubDate>Sat, 25 Sep 2021 00:00:00 GMT</pubDate>
            <description><![CDATA[在現代 Web 開發中，Fetch API 已經成為發起 HTTP 請求的標準。但你是否知道如何優雅地取消一個不再需要的請求？AbortController 就是你的答案。]]></description>
            <content:encoded><![CDATA[<h2>1. Fetch API：現代 Web 請求的基石</h2>
<p><code>Fetch API</code> 已經取代了老舊的 <code>XMLHttpRequest</code>，成為瀏覽器中發起 HTTP 請求的標準方式。它基於 Promise，提供了更簡潔、更強大的請求能力。</p>
<p>然而，<code>Fetch API</code> 預設並沒有提供一個直接取消請求的機制。在許多情境下，請求取消是一個非常重要的功能：</p>
<ul>
<li><strong>效能最佳化</strong>: 使用者快速切換頁面或輸入搜尋詞時，取消舊的、不再需要的請求可以節省頻寬和伺服器資源。</li>
<li><strong>避免競態條件</strong>: 在「搜尋即時輸入」的情境中，如果舊的請求比新的請求回傳得慢，可能會導致 UI 顯示過時的資料。</li>
<li><strong>防止記憶體洩漏</strong>: 在元件生命週期中發起的請求，如果元件在請求完成前被卸載，嘗試更新一個不存在的元件可能會導致錯誤或記憶體洩漏。</li>
</ul>
<h2>2. <code>AbortController</code>：通用的取消訊號</h2>
<p><code>AbortController</code> 是一個通用的 Web API，它提供了一種機制來中止一個或多個 Web 請求。它獨立於 <code>Fetch API</code>，但可以與 <code>Fetch</code> 完美配合。</p>
<p><code>AbortController</code> 的核心思想是：</p>
<ol>
<li>建立一個 <code>AbortController</code> 實例。</li>
<li>從這個實例中取得一個 <code>AbortSignal</code> 物件。</li>
<li>將 <code>AbortSignal</code> 傳遞給可中止的 Web API（如 <code>fetch</code>）。</li>
<li>當你需要取消時，呼叫 <code>AbortController</code> 實例的 <code>abort()</code> 方法。</li>
</ol>
<h2>3. <code>AbortController</code> 與 <code>Fetch</code> 的結合</h2>
<p>讓我們透過一個範例來看看如何使用 <code>AbortController</code> 取消 <code>Fetch</code> 請求。</p>
<pre><code>const controller = new AbortController();
const signal = controller.signal; // 获取信号对象

async function fetchDataWithCancellation(url) {
  try {
    console.log('Fetching data...');
    const response = await fetch(url, { signal }); // 将信号传递给 fetch
    const data = await response.json();
    console.log('Data received:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch request was aborted.');
    } else {
      console.error('Fetch error:', error);
    }
  }
}

// 示例用法
const promise = fetchDataWithCancellation('https://jsonplaceholder.typicode.com/posts/1');

// 假设在 500 毫秒后，我们决定取消这个请求
setTimeout(() =&gt; {
  controller.abort(); // 调用 abort() 方法取消请求
  console.log('Request aborted by timeout.');
}, 500);
</code></pre>
<p>當 <code>controller.abort()</code> 被呼叫時，<code>fetch</code> 請求會立即中止，並拋出一個 <code>AbortError</code>。你需要在 <code>catch</code> 區塊中捕捉這個錯誤，並檢查 <code>error.name === 'AbortError'</code> 來區分是取消操作還是其他網路錯誤。</p>
<h2>4. 實際應用情境</h2>
<h4>a. 搜尋即時輸入（Search-as-you-type）</h4>
<p>當使用者在搜尋框中快速輸入時，每次輸入都可能觸發一個新的請求。我們可以取消前一個未完成的請求，只保留最新的請求。</p>
<pre><code>let currentController = null;

document.getElementById('searchInput').addEventListener('input', (event) =&gt; {
  if (currentController) {
    currentController.abort(); // 取消上一个请求
  }
  currentController = new AbortController();
  const signal = currentController.signal;

  const query = event.target.value;
  if (query.length &gt; 2) {
    fetchDataWithCancellation(`/api/search?q=${query}`, signal);
  }
});
</code></pre>
<h4>b. 元件卸載時取消請求（Component Unmount）</h4>
<p>在 React 或 Vue 等框架中，當元件卸載時，取消其內部發起的未完成請求可以有效避免記憶體洩漏和不必要的 UI 更新。</p>
<pre><code>// React 示例
useEffect(() =&gt; {
  const controller = new AbortController();
  fetchDataWithCancellation('/api/data', controller.signal);

  return () =&gt; {
    controller.abort(); // 组件卸载时取消请求
  };
}, []);
</code></pre>
<h2>結論</h2>
<p><code>AbortController</code> 是現代 Web 開發中一個不可或缺的工具。它為 <code>Fetch API</code> 和其他非同步操作提供了原生的取消機制，幫助開發者建構更健壯、更高效、使用者體驗更好的應用程式。掌握 <code>AbortController</code> 的使用，是成為一名優秀前端開發者的必備技能之一。</p>
]]></content:encoded>
            <author>m1ngsama</author>
        </item>
    </channel>
</rss>