<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://notes.boshkuo.com/en/blog</id>
    <title>Bosh’s Tech Exploration Notes Blog</title>
    <updated>2026-07-14T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://notes.boshkuo.com/en/blog"/>
    <subtitle>Bosh’s Tech Exploration Notes Blog</subtitle>
    <icon>https://notes.boshkuo.com/en/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[弱掃報告修復決策：Node.js 套件 CVE 漏洞修復指南]]></title>
        <id>https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation</id>
        <link href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation"/>
        <updated>2026-07-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[記錄我處理 Node.js 套件 CVE 弱掃時，如何從掃描路徑、依賴樹與版本範圍，一步步選擇直接依賴與間接依賴的修復策略。]]></summary>
        <content type="html"><![CDATA[<p>最近在公司常常在處理 Node.js 專案的套件 CVE 弱點修復，趁著記憶力還很新鮮來記錄一下最近整理的完整判斷決策流程。最一開始看到弱掃報告裡的 CVE、Fixed Version 與一長串 <code>node_modules</code> 路徑時，我只知道「有套件要升」，卻不知道該怎麼升。前幾次只能一邊查資料、一邊問 AI，直到處理過幾種不同的依賴關係後，我才慢慢發現：<strong>修復 Node.js 套件漏洞，其實是一個讀依賴樹、判斷版本範圍，再選擇最小安全變更的過程。</strong> 因為當弱點落在直接依賴、間接依賴、嚴格版本宣告或多年未維護的套件上，後續選擇會完全不同。這篇文章我會把自己碰過與整理過的情境拆成案例，讓每一個決策都能看見前因後果。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>本文使用的套件管理器</div><div class="admonitionContent_BuS1"><p>本文以 <strong>Yarn Classic（Yarn 1）</strong> 專案為主，修復時會改 <code>package.json</code>、<code>yarn.lock</code>，並使用 Yarn 執行安裝與升級。Yarn Berry、npm-only、pnpm 都有各自的 lockfile 與覆寫機制，不能把本文的 Yarn 指令直接搬過去混用。</p></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="弱掃報告的第一個分岔區分漏洞來源"><strong>弱掃報告的第一個分岔：區分漏洞來源</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E5%BC%B1%E6%8E%83%E5%A0%B1%E5%91%8A%E7%9A%84%E7%AC%AC%E4%B8%80%E5%80%8B%E5%88%86%E5%B2%94%E5%8D%80%E5%88%86%E6%BC%8F%E6%B4%9E%E4%BE%86%E6%BA%90" class="hash-link" aria-label="Direct link to 弱掃報告的第一個分岔區分漏洞來源" title="Direct link to 弱掃報告的第一個分岔區分漏洞來源" translate="no">​</a></h2>
<p>我在 <a class="" href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning">如何使用 Trivy 在開發階段提前發現安全漏洞</a> 這篇文章提過，通常，主流的弱點掃描工具會同時掃到<strong>作業系統套件(os-pkgs)</strong> 以及<strong>語言生態系套件(lang-pkgs)</strong>。</p>
<p>對於後者而言：</p>
<ul>
<li class="">若 <code>PkgPath</code> 類似 <code>usr/src/app/node_modules/axios/package.json</code>，套件是由專案的 <code>package.json</code> 與 lockfile 安裝進來的。這種漏洞通常能靠升級專案依賴、調整解析版本，或是更換父層套件修補，也是本文要介紹的重點。</li>
<li class="">相反地，<code>usr/local/lib/node_modules/npm/node_modules/...</code> 通常屬於 Node.js base image 帶進來的 npm 工具本身。就算專案裡沒有使用該套件，掃描器仍然會在 image 裡找到它。此時改 <code>package.json</code> 不會產生任何效果，正確方向是檢查 Node base image 是否已有修補版，或依團隊規範升級 image 內的 npm。</li>
</ul>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="理解專案的依賴關係"><strong>理解專案的依賴關係</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E7%90%86%E8%A7%A3%E5%B0%88%E6%A1%88%E7%9A%84%E4%BE%9D%E8%B3%B4%E9%97%9C%E4%BF%82" class="hash-link" aria-label="Direct link to 理解專案的依賴關係" title="Direct link to 理解專案的依賴關係" translate="no">​</a></h2>
<p>確認漏洞屬於 App Dependencies 後，第一件事是找出它在依賴樹中的位置：它可能是專案直接安裝的套件，也可能是某個直接依賴再帶進來的套件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="直接依賴與間接依賴"><strong>直接依賴與間接依賴</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E7%9B%B4%E6%8E%A5%E4%BE%9D%E8%B3%B4%E8%88%87%E9%96%93%E6%8E%A5%E4%BE%9D%E8%B3%B4" class="hash-link" aria-label="Direct link to 直接依賴與間接依賴" title="Direct link to 直接依賴與間接依賴" translate="no">​</a></h3>
<p>假設根目錄的 <code>package.json</code> 只有這個宣告：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">package.json</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"dependencies"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"react-router-dom"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"^6.30.2"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>react-router-dom</code> 是<strong>直接依賴</strong>，因為它直接寫在根目錄的 <code>package.json</code>。它再安裝的 <code>react-router</code> 與 <code>@remix-run/router</code> 則是<strong>間接依賴</strong>。實際安裝後的邏輯關係如下：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ react-router-dom@6.30.2                 # 直接依賴</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   ├─ @remix-run/router@1.23.1             # 間接依賴</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ react-router@6.30.2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      └─ @remix-run/router@1.23.1 (deduped)</span><br></div></code></pre></div></div>
<p><code>deduped</code> 表示兩條路徑共用同一份已安裝的套件，而不是再裝第二份 <code>@remix-run/router</code>。不過它仍然有兩條邏輯上的依賴路徑，之後碰到多個父層時，不能只看其中一條。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="解讀-yarnlock"><strong>解讀 <code>yarn.lock</code></strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E8%A7%A3%E8%AE%80-yarnlock" class="hash-link" aria-label="Direct link to 解讀-yarnlock" title="Direct link to 解讀-yarnlock" translate="no">​</a></h3>
<p>新手常常誤會的是 <code>package.json</code> 的 <code>6.30.2</code> 不見得是「目前已安裝的版本」，而是可接受的<strong>版本範圍（version range）</strong>。Yarn 第一次安裝時會在這個範圍內選一個版本，再把結果記進 <code>yarn.lock</code>。因此真正需要比對弱掃版本的，是 lockfile 的 <code>version</code> 欄位：</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1784017879/Docusaurus%20Blog/Blog/Node.js%20%E5%A5%97%E4%BB%B6%20CVE%20%E6%BC%8F%E6%B4%9E%E4%BF%AE%E5%BE%A9%E6%8C%87%E5%8D%97/dependency-manifest-lockfile-tree_xtiltx.png" alt="從 package.json 的版本範圍、yarn.lock 的解析結果，到實際安裝依賴樹的關係" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview: 從 package.json 的版本範圍、yarn.lock 的解析結果，到實際安裝依賴樹的關係"></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">yarn.lock</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">react-router-dom@^6.30.2:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  version "6.30.2"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.2.tgz"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  dependencies:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "@remix-run/router" "1.23.1"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    react-router "6.30.2"</span><br></div></code></pre></div></div>
<p>lockfile entry 的第一行是「哪個版本範圍需要被解析」，<code>version</code> 才是這次安裝實際選到的版本。父層的 <code>dependencies</code> 區塊則記錄它允許或要求子套件的範圍。以後面常用的虛構範例來說：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">yarn.lock</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">package-a@^1.1.0:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  version "1.1.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  dependencies:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    vulnerable-lib "^1.0.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">vulnerable-lib@^1.0.0:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  version "1.0.3"</span><br></div></code></pre></div></div>
<p>這代表 <code>package-a</code> 允許 <code>vulnerable-lib</code> 從 <code>1.0.0</code> 升到 <code>2.0.0</code> 之前的任何版本，所以 <code>1.0.9</code> 可以被重新解析進來；若它寫的是精確的 <code>"1.0.3"</code>，就不能把 <code>1.0.9</code> 當成自然升級。這也是間接依賴修復前必須先看的限制。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>版本範圍標記符號補充說明：</div><div class="admonitionContent_BuS1"><ul>
<li class=""><code>^1.0.0</code> 表示 <code>&gt;= 1.0.0</code> 且 <code>&lt; 2.0.0</code></li>
<li class=""><code>~1.0.0</code> 表示 <code>&gt;= 1.0.0</code> 且 <code>&lt; 1.1.0</code>，</li>
<li class="">而 <code>1.0.3</code> 則只接受 <code>1.0.3</code> 本身</li>
</ul></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="追查弱點與依賴關係時會用的指令"><strong>追查弱點與依賴關係時會用的指令</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E8%BF%BD%E6%9F%A5%E5%BC%B1%E9%BB%9E%E8%88%87%E4%BE%9D%E8%B3%B4%E9%97%9C%E4%BF%82%E6%99%82%E6%9C%83%E7%94%A8%E7%9A%84%E6%8C%87%E4%BB%A4" class="hash-link" aria-label="Direct link to 追查弱點與依賴關係時會用的指令" title="Direct link to 追查弱點與依賴關係時會用的指令" translate="no">​</a></h2>
<p>有了依賴樹與 lockfile 的概念後，接下來要補齊幾個判斷所需的事實：弱點的安全版本是什麼、現在是誰帶進來、父層允許哪個範圍，以及父層是否已經有帶入修補版的新版本。我在 Yarn Classic 專案中仍會使用 npm 做查詢，因為 <code>npm ls</code> 的樹狀輸出很直觀；這些指令都是唯讀查詢，不會改動 <code>package.json</code> 或 <code>yarn.lock</code>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="先用-yarn-audit-確認弱點與修補下限"><strong>先用 <code>yarn audit</code> 確認弱點與修補下限</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E5%85%88%E7%94%A8-yarn-audit-%E7%A2%BA%E8%AA%8D%E5%BC%B1%E9%BB%9E%E8%88%87%E4%BF%AE%E8%A3%9C%E4%B8%8B%E9%99%90" class="hash-link" aria-label="Direct link to 先用-yarn-audit-確認弱點與修補下限" title="Direct link to 先用-yarn-audit-確認弱點與修補下限" translate="no">​</a></h3>
<p>若漏洞是從專案端發現，我會先執行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn audit</span><br></div></code></pre></div></div>
<p>輸出內容會隨漏洞資料庫而改變，實務上我主要看 <code>Package</code>、<code>Patched in</code>、<code>Dependency of</code> 與 <code>Path</code> 這幾個欄位：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">┌───────────────┬────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ Package       │ vulnerable-lib                     │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ Patched in    │ &gt;= 1.0.9                           │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ Dependency of │ package-a                          │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│ Path          │ package-a &gt; vulnerable-lib         │</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└───────────────┴────────────────────────────────────┘</span><br></div></code></pre></div></div>
<p>若來源是 image scan，則用報告中的 <code>PkgName</code>、<code>InstalledVersion</code>、<code>FixedVersion</code> 與 <code>PkgPath</code> 對照同一件事。無論從哪裡發現，這一步只得到「哪個版本有洞」與「安全下限」，還不知道改哪個套件才正確。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="用-npm-ls-找到目前的父層"><strong>用 <code>npm ls</code> 找到目前的父層</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E7%94%A8-npm-ls-%E6%89%BE%E5%88%B0%E7%9B%AE%E5%89%8D%E7%9A%84%E7%88%B6%E5%B1%A4" class="hash-link" aria-label="Direct link to 用-npm-ls-找到目前的父層" title="Direct link to 用-npm-ls-找到目前的父層" translate="no">​</a></h3>
<p>接著我會查出漏洞套件在當前安裝結果中的所有路徑。以 <code>@remix-run/router@1.23.1</code> 為例：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm ls @remix-run/router</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app@1.0.0 /path/to/my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─┬ react-router-dom@6.30.2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ├── @remix-run/router@1.23.1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  └─┬ react-router@6.30.2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    └── @remix-run/router@1.23.1 deduped</span><br></div></code></pre></div></div>
<p>這個結果先排除了「直接升級 <code>@remix-run/router</code>」的直覺做法：根目錄直接安裝的是 <code>react-router-dom</code>，<code>@remix-run/router</code> 是它帶進來的間接依賴。<code>npm ls</code> 也會把 <code>deduped</code> 顯示出來，剛好能確認同一個弱點是否從多條路徑進入專案。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="用-npm-info-看父層的版本範圍與可升級版本"><strong>用 <code>npm info</code> 看父層的版本範圍與可升級版本</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E7%94%A8-npm-info-%E7%9C%8B%E7%88%B6%E5%B1%A4%E7%9A%84%E7%89%88%E6%9C%AC%E7%AF%84%E5%9C%8D%E8%88%87%E5%8F%AF%E5%8D%87%E7%B4%9A%E7%89%88%E6%9C%AC" class="hash-link" aria-label="Direct link to 用-npm-info-看父層的版本範圍與可升級版本" title="Direct link to 用-npm-info-看父層的版本範圍與可升級版本" translate="no">​</a></h3>
<p>知道父層名稱後，我會先看「目前這一版父層到底怎麼宣告子套件」：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm info react-router-dom@6.30.2 dependencies</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  '@remix-run/router': '1.23.1',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  'react-router': '6.30.2'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p><code>1.23.1</code> 是精確版本，不允許 Yarn 自己浮動到 <code>1.23.2</code>。因此即使弱掃要求 <code>@remix-run/router &gt;= 1.23.2</code>，刪掉它的 lockfile entry 也不會修好，必須再確認父層有沒有新版本：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm info react-router-dom versions</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">[</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ...,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  '6.30.1',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  '6.30.2',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  '6.30.3',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">]</span><br></div></code></pre></div></div>
<p>最後查候選版本的依賴內容：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm info react-router-dom@6.30.3 dependencies</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  '@remix-run/router': '1.23.2',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  'react-router': '6.30.3'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>這樣才能確定修復點是 <code>react-router-dom@6.30.3</code>，而不是直接強壓 <code>@remix-run/router</code>。後面每個間接依賴案例，都是依序用這四個問題收斂出來的。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="直接依賴安全漏洞的修復策略"><strong>直接依賴安全漏洞的修復策略</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E7%9B%B4%E6%8E%A5%E4%BE%9D%E8%B3%B4%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E%E7%9A%84%E4%BF%AE%E5%BE%A9%E7%AD%96%E7%95%A5" class="hash-link" aria-label="Direct link to 直接依賴安全漏洞的修復策略" title="Direct link to 直接依賴安全漏洞的修復策略" translate="no">​</a></h2>
<p>若漏洞套件本來就寫在專案根目錄的 <code>package.json</code>，這種情況通常比較容易追查與修復，判斷流程如下圖所示，總共可以分成 5 種情境：</p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例一patch-升級即可修復"><strong>案例一：patch 升級即可修復</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%B8%80patch-%E5%8D%87%E7%B4%9A%E5%8D%B3%E5%8F%AF%E4%BF%AE%E5%BE%A9" class="hash-link" aria-label="Direct link to 案例一patch-升級即可修復" title="Direct link to 案例一patch-升級即可修復" translate="no">​</a></h3>
<p>假設根 <code>package.json</code> 有 <code>"http-client": "^1.6.0"</code>，掃描器要求 <code>http-client &gt;= 1.6.8</code>。<code>http-client</code> 是直接依賴，<code>1.6.8</code> 又仍在 <code>^1.6.0</code> 可接受的範圍內，這是最單純的情況。</p>
<p>我會把最低可接受版本明確提高：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn upgrade http-client@^1.6.8</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例二minor-升級能修但可能有隱性-breaking-change"><strong>案例二：minor 升級能修，但可能有隱性 breaking change</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%BA%8Cminor-%E5%8D%87%E7%B4%9A%E8%83%BD%E4%BF%AE%E4%BD%86%E5%8F%AF%E8%83%BD%E6%9C%89%E9%9A%B1%E6%80%A7-breaking-change" class="hash-link" aria-label="Direct link to 案例二minor-升級能修但可能有隱性-breaking-change" title="Direct link to 案例二minor-升級能修但可能有隱性-breaking-change" translate="no">​</a></h3>
<p>假設根目錄的 <code>package.json</code> 有 <code>build-helper@3.1.0</code>，其修補版是 <code>3.4.0</code>。理論上若套件有根據 <strong>semver</strong> 原則發佈，minor 版本不應破壞既有 API，但實務上 npm 生態不一定完全可靠。尤其是 build tool、bundler、lint plugin 與 framework plugin 常常會改變預設設定、輸出格式或 peer dependency 條件。</p>
<p>即使當作 minor 升級，我還是會先升級套件：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn upgrade build-helper@^3.4.0</span><br></div></code></pre></div></div>
<p>不過必須做額外的確認，如：</p>
<ul>
<li class="">看 changelog / release note</li>
<li class="">跑 unit test、build、lint、e2e</li>
<li class="">特別測一次最依賴該工具的核心流程</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例三需要-major-升級但專案只用到少量-api"><strong>案例三：需要 major 升級，但專案只用到少量 API</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%B8%89%E9%9C%80%E8%A6%81-major-%E5%8D%87%E7%B4%9A%E4%BD%86%E5%B0%88%E6%A1%88%E5%8F%AA%E7%94%A8%E5%88%B0%E5%B0%91%E9%87%8F-api" class="hash-link" aria-label="Direct link to 案例三需要-major-升級但專案只用到少量-api" title="Direct link to 案例三需要-major-升級但專案只用到少量-api" translate="no">​</a></h3>
<p>假設根目錄的 <code>package.json</code> 有 <code>report-kit@1.x</code>，且只有 <code>2.0.0</code> 才修好；而剛好專案只用了幾個 API，升級後只需要改動少量的程式碼。</p>
<p>業界的標準處理方式是先升級 major</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn upgrade report-kit@^2.0.0</span><br></div></code></pre></div></div>
<p>接著：</p>
<ul>
<li class="">依 migration guide 修改程式碼</li>
<li class="">補上修改處的測試，確認輸出的報表仍符合需求。</li>
<li class="">特別測一次最依賴該工具的核心流程</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例四需要-major-升級但專案深度綁定舊版"><strong>案例四：需要 major 升級，但專案深度綁定舊版</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E5%9B%9B%E9%9C%80%E8%A6%81-major-%E5%8D%87%E7%B4%9A%E4%BD%86%E5%B0%88%E6%A1%88%E6%B7%B1%E5%BA%A6%E7%B6%81%E5%AE%9A%E8%88%8A%E7%89%88" class="hash-link" aria-label="Direct link to 案例四需要-major-升級但專案深度綁定舊版" title="Direct link to 案例四需要-major-升級但專案深度綁定舊版" translate="no">​</a></h3>
<p>再假設根目錄的 <code>package.json</code> 有 <code>legacy-bundler@4.x</code>，只能靠升到 <code>5.x</code> 修補，但專案的 loader、plugin、CI 設定與部署產物都深度綁定 <code>v4</code>。此時「升一個套件」其實已經是一個涉及建置流程的遷移工作。</p>
<p>我會把它從一般弱掃修補切成正式的 upgrade task：</p>
<ul>
<li class="">先盤點 breaking changes 與使用點</li>
<li class="">再建立獨立 branch</li>
<li class="">把套件使用處集中封裝</li>
<li class="">做大版本更新後逐步調整程式碼</li>
<li class="">補足測試與部署驗證</li>
<li class="">最後分階段測試與合併。</li>
</ul>
<p>若客戶進版時程不允許一次完成，通常會做：</p>
<ul>
<li class="">記錄漏洞可利用的條件</li>
<li class="">確認現有補償控制</li>
<li class="">設定風險接受期限</li>
<li class="">安排正式的 upgrade task</li>
</ul>
<blockquote>
<p>如果弱點只影響某個 CLI build tool，而且不會進 production runtime，風險可能可以暫時接受；但如果是 production server 會處理外部輸入的套件，就不適合長期拖延。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例五套件停止維護而且沒有修補版本"><strong>案例五：套件停止維護，而且沒有修補版本</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%BA%94%E5%A5%97%E4%BB%B6%E5%81%9C%E6%AD%A2%E7%B6%AD%E8%AD%B7%E8%80%8C%E4%B8%94%E6%B2%92%E6%9C%89%E4%BF%AE%E8%A3%9C%E7%89%88%E6%9C%AC" class="hash-link" aria-label="Direct link to 案例五套件停止維護而且沒有修補版本" title="Direct link to 案例五套件停止維護而且沒有修補版本" translate="no">​</a></h3>
<p><code>old-excel-reader@0.18.5</code> 假設已經多年沒有維護，掃描器也沒有提供 Fixed Version。這種情況最危險的不是升級難，而是根本沒有可以升的方向。</p>
<p>我的評估順序通常是：</p>
<ul>
<li class="">先確認功能是否還在使用，能移除就移除</li>
<li class="">不能移除時尋找持續維護的替代套件</li>
<li class="">替換成本太高時才評估 fork 套件並自行修補</li>
<li class="">若短期只能風險接受，文件至少要說清楚漏洞是否接觸外部輸入、是否會跑在 production runtime、有哪些補償控制，以及何時重新評估。</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="間接依賴安全漏洞的修復策略"><strong>間接依賴安全漏洞的修復策略</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E9%96%93%E6%8E%A5%E4%BE%9D%E8%B3%B4%E5%AE%89%E5%85%A8%E6%BC%8F%E6%B4%9E%E7%9A%84%E4%BF%AE%E5%BE%A9%E7%AD%96%E7%95%A5" class="hash-link" aria-label="Direct link to 間接依賴安全漏洞的修復策略" title="Direct link to 間接依賴安全漏洞的修復策略" translate="no">​</a></h2>
<p>直接依賴的案例都能從根 <code>package.json</code> 動手；間接依賴則多了一層限制，必須尊重父套件當初宣告的相容範圍。這也是我最初覺得複雜、後來卻最有規律可循的部分。為了讓「實際安裝版本」與「父層宣告範圍」不混在一起，下面樹狀圖的套件版號代表目前安裝的版本，行尾的註解才是父層宣告的範圍。</p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例一修補版仍在父層允許範圍內"><strong>案例一：修補版仍在父層允許範圍內</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%B8%80%E4%BF%AE%E8%A3%9C%E7%89%88%E4%BB%8D%E5%9C%A8%E7%88%B6%E5%B1%A4%E5%85%81%E8%A8%B1%E7%AF%84%E5%9C%8D%E5%85%A7" class="hash-link" aria-label="Direct link to 案例一修補版仍在父層允許範圍內" title="Direct link to 案例一修補版仍在父層允許範圍內" translate="no">​</a></h3>
<p>假設目前的依賴關係如下。弱掃報告指出 <code>vulnerable-lib@1.0.3</code> 有漏洞，要求至少升到 <code>1.0.9</code>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-a@1.1.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@1.0.3   # package-a 宣告：^1.0.0</span><br></div></code></pre></div></div>
<p><code>^1.0.0</code> 允許 <code>1.0.9</code>，所以父層本身不需要更新，問題只在 lockfile 還保存舊的解析結果。</p>
<p>Yarn Classic 的 lockfile entry 可能像這樣：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">yarn.lock</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">vulnerable-lib@^1.0.0:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  version "1.0.3"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resolved "https://registry.yarnpkg.com/vulnerable-lib/-/vulnerable-lib-1.0.3.tgz"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  integrity sha512-...</span><br></div></code></pre></div></div>
<p>在確認<strong>所有引用這個 entry 的版本範圍都能接受安全版</strong>後，我會在獨立 commit 中只移除這一整段 entry，再執行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn install</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1784017878/Docusaurus%20Blog/Blog/Node.js%20%E5%A5%97%E4%BB%B6%20CVE%20%E6%BC%8F%E6%B4%9E%E4%BF%AE%E5%BE%A9%E6%8C%87%E5%8D%97/yarn-lock-entry-reresolution_htfxu0.png" alt="父層範圍維持不變，只重新解析單一 yarn.lock entry 的前後狀態" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview: 父層範圍維持不變，只重新解析單一 yarn.lock entry 的前後狀態"></p>
<p><code>yarn install</code> 發現 lockfile 缺少該範圍的解析紀錄後，會從 registry 重新選一個符合 <code>^1.0.0</code> 的版本。若 registry 中最新的安全版本是 <code>1.0.9</code>，這段 <code>yarn.lock</code> 會被寫回成：</p>
<div class="language-diff codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">yarn.lock</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-diff codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"> vulnerable-lib@^1.0.0:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-  version "1.0.3"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-  resolved "https://registry.yarnpkg.com/vulnerable-lib/-/vulnerable-lib-1.0.3.tgz"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">-  integrity sha512-...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">+  version "1.0.9"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">+  resolved "https://registry.yarnpkg.com/vulnerable-lib/-/vulnerable-lib-1.0.9.tgz"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">+  integrity sha512-...</span><br></div></code></pre></div></div>
<p>entry 的 key 還是 <code>vulnerable-lib@^1.0.0</code>，因為父層的需求沒有變；變的是 Yarn 在這個範圍內選到的實際版本。重新安裝後，我會再執行 <code>npm ls vulnerable-lib</code>，確認輸出真的已經是 <code>1.0.9</code>。</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>不要刪除整份 <code>yarn.lock</code></div><div class="admonitionContent_BuS1"><p>整份 lockfile 刪掉再安裝，會讓所有可浮動依賴一起重新解析，diff 變得巨大，也難以知道哪個變更修了漏洞。這個案例的目的只是更新一個已確認可安全浮動的解析結果。</p><p>重新解析時不能使用 <code>--frozen-lockfile</code>，因為它的用途正是禁止 lockfile 被更新；等安全版寫回 lockfile、完成檢查後，CI 才應繼續使用 <code>--frozen-lockfile</code> 保持可重現安裝。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例二修補版不被舊父層允許但同-major-父層已有新版"><strong>案例二：修補版不被舊父層允許，但同 major 父層已有新版</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%BA%8C%E4%BF%AE%E8%A3%9C%E7%89%88%E4%B8%8D%E8%A2%AB%E8%88%8A%E7%88%B6%E5%B1%A4%E5%85%81%E8%A8%B1%E4%BD%86%E5%90%8C-major-%E7%88%B6%E5%B1%A4%E5%B7%B2%E6%9C%89%E6%96%B0%E7%89%88" class="hash-link" aria-label="Direct link to 案例二修補版不被舊父層允許但同-major-父層已有新版" title="Direct link to 案例二修補版不被舊父層允許但同-major-父層已有新版" translate="no">​</a></h3>
<p>假設目前安裝的 <code>package-a@1.1.4</code> 只允許 <code>vulnerable-lib</code> v1，而 <code>1.x</code> 全部有漏洞，安全版從 <code>2.0.0</code> 才開始：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-a@1.1.4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@1.0.3   # package-a 宣告：^1.0.0</span><br></div></code></pre></div></div>
<p>直接把子依賴升到 v2 會越過父層的相容性邊界。</p>
<p>不過查詢 package-a 新版本的依賴資訊，發現 <code>package-a@1.2.0</code> 已改成宣告 <code>vulnerable-lib@^2.0.0</code>。此時真正該升的是父層：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm info package-a@1.1.4 dependencies</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm info package-a@1.2.0 dependencies</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn upgrade package-a@^1.2.0</span><br></div></code></pre></div></div>
<p>升級後的關係才會變成：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-a@1.2.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@2.0.0   # package-a 宣告：^2.0.0</span><br></div></code></pre></div></div>
<p>因為新版 <code>package-a</code> 已經調整它自己的 dependency range，所以 <code>vulnerable-lib</code> 會自然被帶到 patched version。這個做法讓專案只需驗證 <code>package-a</code> 的 minor 升級是否影響自己的使用方式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例三父層必須-major-upgrade-才能帶入安全子依賴"><strong>案例三：父層必須 major upgrade 才能帶入安全子依賴</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%B8%89%E7%88%B6%E5%B1%A4%E5%BF%85%E9%A0%88-major-upgrade-%E6%89%8D%E8%83%BD%E5%B8%B6%E5%85%A5%E5%AE%89%E5%85%A8%E5%AD%90%E4%BE%9D%E8%B3%B4" class="hash-link" aria-label="Direct link to 案例三父層必須-major-upgrade-才能帶入安全子依賴" title="Direct link to 案例三父層必須-major-upgrade-才能帶入安全子依賴" translate="no">​</a></h3>
<p>如果 <code>package-a@1.x</code> 全部都依賴 <code>vulnerable-lib@1.x</code>，只有 <code>package-a@2.x</code> 才改用 <code>vulnerable-lib</code> v2，關係會像這樣：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-a@1.8.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@1.0.3   # package-a 宣告：^1.0.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">可修復的目標</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-a@2.0.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@2.0.0   # package-a 宣告：^2.0.0</span><br></div></code></pre></div></div>
<p>這個間接依賴問題就回到了前面「直接依賴 major 升級」的案例三或案例四的判斷流程。要升的仍是根目錄直接使用的 <code>package-a</code>，只是它的升級理由是為了換掉子依賴。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例四父層久未發布新版但-resolutions-是合理的暫時修補"><strong>案例四：父層久未發布新版，但 <code>resolutions</code> 是合理的暫時修補</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E5%9B%9B%E7%88%B6%E5%B1%A4%E4%B9%85%E6%9C%AA%E7%99%BC%E5%B8%83%E6%96%B0%E7%89%88%E4%BD%86-resolutions-%E6%98%AF%E5%90%88%E7%90%86%E7%9A%84%E6%9A%AB%E6%99%82%E4%BF%AE%E8%A3%9C" class="hash-link" aria-label="Direct link to 案例四父層久未發布新版但-resolutions-是合理的暫時修補" title="Direct link to 案例四父層久未發布新版但-resolutions-是合理的暫時修補" translate="no">​</a></h3>
<p>假設父層把 <code>vulnerable-lib</code> 精確鎖在 <code>1.0.3</code>，而不是一個可浮動的版本範圍：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-a@1.1.4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@1.0.3   # package-a 宣告：1.0.3</span><br></div></code></pre></div></div>
<p>公告指出 <code>1.0.9</code> 是修補同一個 API 的 patch，但上游遲遲沒有發布新版。團隊也已在獨立環境驗證 <code>package-a</code> 搭配 <code>1.0.9</code> 的核心功能、測試與 build 都正常。這才是我會考慮 Yarn Classic <code>resolutions</code> 的情境：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">package.json</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"resolutions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"package-a/vulnerable-lib"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1.0.9"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>接著執行 <code>yarn install</code>，確認 lockfile 與 <code>npm ls vulnerable-lib</code> 都反映指定版本。</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>resolutions 的使用時機</div><div class="admonitionContent_BuS1"><p><code>resolutions</code> 能讓根專案在依賴樹解析時覆寫特定子依賴版本，<a href="https://classic.yarnpkg.com/en/docs/selective-version-resolutions/" target="_blank" rel="noopener noreferrer" class="">Yarn 文件</a>也將「重要安全更新但上游尚未調整子依賴」列為典型用途。</p><p>但我會把它當作<strong>有到期日的相容性假設</strong>：必須保留相關測試、追蹤 <code>package-a</code> 是否發布正式修補版，並在父層更新後移除 <code>resolutions</code>。它不是直接依賴升級的替代品，更不是不必測試的萬用開關。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例五父層久未發布新版但安全版是-breaking-change不能用-resolutions-硬壓"><strong>案例五：父層久未發布新版，但安全版是 breaking change，不能用 <code>resolutions</code> 硬壓</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E4%BA%94%E7%88%B6%E5%B1%A4%E4%B9%85%E6%9C%AA%E7%99%BC%E5%B8%83%E6%96%B0%E7%89%88%E4%BD%86%E5%AE%89%E5%85%A8%E7%89%88%E6%98%AF-breaking-change%E4%B8%8D%E8%83%BD%E7%94%A8-resolutions-%E7%A1%AC%E5%A3%93" class="hash-link" aria-label="Direct link to 案例五父層久未發布新版但安全版是-breaking-change不能用-resolutions-硬壓" title="Direct link to 案例五父層久未發布新版但安全版是-breaking-change不能用-resolutions-硬壓" translate="no">​</a></h3>
<p>沿用案例四的關係，但假設修補版本只有 <code>vulnerable-lib@2.0.0</code>，而且 API 從 <code>oldParse(input)</code> 改成 <code>parse(input, options)</code>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-a@1.1.4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@1.0.3   # package-a 宣告：1.0.3</span><br></div></code></pre></div></div>
<p>此時即使在根目錄寫下強制覆寫：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">package.json</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"resolutions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"vulnerable-lib"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2.0.0"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>package-a@1.1.4</code> 內部仍會用 v1 的方式呼叫：</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// package-a@1.1.4 期待的是 v1 API</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> vulnerableLib</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">oldParse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>安裝可能順利完成，程式卻會在 runtime 因為 <code>oldParse is not a function</code> 失敗。這種情況應回到父層看是否可以 major upgrade、替換父層，或在父層很小且維護成本可接受時 fork 後修改它的 API 呼叫。若都無法立即完成，才提出書面解釋有期限的風險接受。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="案例六多個父層帶入同一個弱點必須看完所有路徑"><strong>案例六：多個父層帶入同一個弱點，必須看完所有路徑</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#%E6%A1%88%E4%BE%8B%E5%85%AD%E5%A4%9A%E5%80%8B%E7%88%B6%E5%B1%A4%E5%B8%B6%E5%85%A5%E5%90%8C%E4%B8%80%E5%80%8B%E5%BC%B1%E9%BB%9E%E5%BF%85%E9%A0%88%E7%9C%8B%E5%AE%8C%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%91" class="hash-link" aria-label="Direct link to 案例六多個父層帶入同一個弱點必須看完所有路徑" title="Direct link to 案例六多個父層帶入同一個弱點必須看完所有路徑" translate="no">​</a></h3>
<p>假設父層 <code>package-a</code> 與 <code>package-b</code> 都依賴於 <code>vulnerable-lib</code>，這有可能會遇到兩種情況：</p>
<p><strong>版本範圍互相重疊時，可以收斂到同一個修復版本。</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├─ package-a@1.1.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│  └─ vulnerable-lib@1.0.3   # package-a 宣告：^1.0.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-b@2.4.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@1.0.3   # package-b 宣告：~1.0.0（deduped）</span><br></div></code></pre></div></div>
<p>因為 <code>1.0.9</code> 同時符合兩個父層的依賴範圍，<code>yarn.lock</code> 可能會用一個 entry 合併記錄它們：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">yarn.lock</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">vulnerable-lib@^1.0.0, vulnerable-lib@~1.0.0:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  version "1.0.3"</span><br></div></code></pre></div></div>
<p>刪除這個 entry 後再跑一次 <code>yarn install</code>，兩條路徑就能一起重新解析到 <code>1.0.9</code>。</p>
<p><strong>版本範圍衝突時，Yarn 會保留兩份不同 major 的套件。</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-app</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├─ package-a@1.1.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│  └─ vulnerable-lib@1.0.3   # package-a 宣告：^1.0.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└─ package-b@2.4.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   └─ vulnerable-lib@2.0.0   # package-b 宣告：^2.0.0</span><br></div></code></pre></div></div>
<p>這種情況即便重新解析，Yarn 也只會把 <code>package-b</code> 帶到安全的 v2；<code>package-a</code> 仍被困在 vulnerable 的 v1 範圍。這時候就必須回到本章節的案例二、三、四、五，升級父層、評估 <code>resolutions</code> 是否相容，或採取其他處置。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/nodejs-package-cve-remediation#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><strong><a class="" href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning">如何使用 Trivy 在開發階段提前發現安全漏洞</a></strong></li>
<li class=""><strong><a href="https://nvd.nist.gov/vuln/detail/CVE-2026-22029" target="_blank" rel="noopener noreferrer" class="">CVE-2026-22029，NVD</a></strong></li>
<li class=""><strong><a href="https://classic.yarnpkg.com/en/docs/cli/audit/" target="_blank" rel="noopener noreferrer" class="">yarn audit，Yarn Classic</a></strong></li>
<li class=""><strong><a href="https://docs.npmjs.com/cli/v11/commands/npm-ls/" target="_blank" rel="noopener noreferrer" class="">npm ls，npm Docs</a></strong></li>
<li class=""><strong><a href="https://docs.npmjs.com/cli/v11/commands/npm-view/" target="_blank" rel="noopener noreferrer" class="">npm info / npm view，npm Docs</a></strong></li>
<li class=""><strong><a href="https://classic.yarnpkg.com/en/docs/selective-version-resolutions/" target="_blank" rel="noopener noreferrer" class="">Selective dependency resolutions，Yarn Classic</a></strong></li>
<li class=""><strong><a href="https://classic.yarnpkg.com/en/docs/cli/upgrade/" target="_blank" rel="noopener noreferrer" class="">yarn upgrade，Yarn Classic</a></strong></li>
<li class=""><strong><a href="https://classic.yarnpkg.com/en/docs/cli/install/" target="_blank" rel="noopener noreferrer" class="">yarn install，Yarn Classic</a></strong></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="Node.js" term="Node.js"/>
        <category label="Security" term="Security"/>
        <category label="npm" term="npm"/>
        <category label="yarn" term="yarn"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[如何使用 Trivy 在開發階段提前發現安全漏洞]]></title>
        <id>https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning</id>
        <link href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning"/>
        <updated>2026-02-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇文章分享我在工作中如何使用 Trivy 在本地端掃描 Docker image，不再依賴 DevOps 部門的弱掃報告，提前在開發階段發現並修復安全漏洞。文章包含 Trivy 的基本介紹、實際使用的 script、jq 過濾技巧，以及如何透過 Class 和 PkgPath 精準定位漏洞位置。]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言"><strong>前言</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="Direct link to 前言" title="Direct link to 前言" translate="no">​</a></h2>
<p>在工作上開發專案時，通常都會定期進行弱點掃描（弱掃）。我們公司是透過 <strong>AWS ECR</strong> 內建的掃描功能來檢查所有專案的 Docker image。現有的工具看似完善，但實際運作起來卻有個痛點：只有 <strong>DevOps 部門跟主管層級</strong>的人才有 AWS 控制台的存取權限。也就是說，身為 RD 的我，根本沒辦法自己去看弱掃報告。</p>
<p>整個流程通常是這樣：</p>
<blockquote>
<p>DevOps 掃完 → 整理報告 → 通知 RD → RD 才知道要修哪些套件</p>
</blockquote>
<p>這種被動等通知的流程，在趕交付的時候特別讓人焦慮。有幾次甚至是快要交付了，才突然收到一份通知說有幾個高風險漏洞要修，然後就得在最後關頭緊急升版本、重新測試、重新建置 image……</p>
<p>更麻煩的是，修完套件之後，因為弱掃工具不再自己手上，我也沒辦法立刻驗證是否真的修乾淨了，只能等 DevOps 重新跑一次掃描才知道結果。</p>
<p>後來同事推薦了 <strong>Trivy</strong> 這個開源掃描工具，它能夠在本地端直接掃描 Docker image，完美解決了我無法自主掌握掃描進度的問題。而且最棒的是，Trivy 甚至可以直接用 <code>docker run</code> 來執行，完全不用在本機安裝任何軟體，使用方式非常簡單。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="trivy-是什麼"><strong>Trivy 是什麼？</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#trivy-%E6%98%AF%E4%BB%80%E9%BA%BC" class="hash-link" aria-label="Direct link to trivy-是什麼" title="Direct link to trivy-是什麼" translate="no">​</a></h2>
<p><a href="https://trivy.dev/" target="_blank" rel="noopener noreferrer" class="">Trivy</a> 是一款開源且多功能的雲端原生安全掃描工具。它的設計哲學就如同它的名字（取自 Trivial），旨在讓安全掃描變得極度簡單。</p>
<p>相較於其他掃描工具，Trivy 提供了非常全面的掃描目標（Target）與種類（Scanners）：</p>
<p><strong>掃描目標（Target）</strong></p>
<table><thead><tr><th>目標類型</th><th>說明</th></tr></thead><tbody><tr><td><code>image</code></td><td>掃描 Docker / OCI Container image</td></tr><tr><td><code>fs</code> / <code>rootfs</code></td><td>掃描本地檔案系統或目錄</td></tr><tr><td><code>repo</code></td><td>直接掃描遠端 Git Repository</td></tr><tr><td><code>sbom</code></td><td>針對 SBOM（Software Bill of Materials）文件進行掃描</td></tr><tr><td><code>k8s</code></td><td>直接掃描 Kubernetes 叢集</td></tr></tbody></table>
<p><strong>掃描類型（Scanners）</strong></p>
<table><thead><tr><th>掃描類型</th><th>說明</th></tr></thead><tbody><tr><td><code>vuln</code></td><td>已知的 CVE 安全漏洞檢查</td></tr><tr><td><code>misconfig</code></td><td>設定檔錯誤檢查（如 Dockerfile、Kubernetes YAML、Terraform）</td></tr><tr><td><code>secret</code></td><td>掃描程式碼中硬編碼的 Secret 或 API Key</td></tr><tr><td><code>license</code></td><td>檢查套件的開源授權條款合規性</td></tr></tbody></table>
<p>除了在本地手動執行，Trivy 也非常適合整合進 <strong>CI/CD Pipeline</strong> 中。目前的 <a href="https://trivy.dev/docs/latest/tutorials/integrations/" target="_blank" rel="noopener noreferrer" class="">官方文檔</a> 也提供了與 GitHub Actions, GitLab CI, Jenkins 等主流 CI/CD 平台的整合方案。例如在 GitHub Actions 中，可以直接使用官方提供的 <code>aquasecurity/trivy-action</code>，當 PR 建立時自動掃描 image 並將結果輸出至 GitHub Security 分頁，藉此達到自動化的防護。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際使用方式"><strong>實際使用方式</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#%E5%AF%A6%E9%9A%9B%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to 實際使用方式" title="Direct link to 實際使用方式" translate="no">​</a></h2>
<p>我日常的開發習慣是：<strong>先把專案的 Docker image build 出來，然後用 Trivy 掃這個 image</strong>。相較於掃 filesystem，掃 image 的好處是它反映的是<strong>最終部署到生產環境的真實狀態</strong>，不會有「本機能跑、但 container 裡面環境有漏洞」的落差。</p>
<p>Trivy 最方便的地方在於不需安裝，直接透過 Docker 執行即可：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker run --rm \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v /var/run/docker.sock:/var/run/docker.sock \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v $(pwd)/.trivycache:/root/.cache/trivy \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v $(pwd):/output \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  aquasec/trivy:latest image \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --scanners vuln \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --format json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output /output/raw-report.json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  your-image:latest</span><br></div></code></pre></div></div>
<p>幾個掛載點說明：</p>
<ul>
<li class=""><code>-v /var/run/docker.sock:/var/run/docker.sock</code>：讓 Trivy container 可以存取本機的 Docker daemon，讀取剛 build 好的 image。</li>
<li class=""><code>-v $(pwd)/.trivycache:/root/.cache/trivy</code>：把漏洞資料庫快取到本地目錄，避免每次執行都重新下載。</li>
<li class=""><code>-v $(pwd):/output</code>：將當前目錄掛載進去，讓掃描報告能存回本機。</li>
</ul>
<p>這樣執行完後，Trivy 會輸出一份原汁原味的 JSON 報告（<code>raw-report.json</code>）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="觀察-raw-reportjson-的結構"><strong>觀察 raw-report.json 的結構</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#%E8%A7%80%E5%AF%9F-raw-reportjson-%E7%9A%84%E7%B5%90%E6%A7%8B" class="hash-link" aria-label="Direct link to 觀察-raw-reportjson-的結構" title="Direct link to 觀察-raw-reportjson-的結構" translate="no">​</a></h3>
<p>這份 <code>raw-report.json</code> 非常詳細，動輒幾萬行程式碼。裡面包含了各個套件的完整依賴樹、Image config 等等資訊。但身為想知道「哪裡有洞、該升哪個版本」的開發者，大部分資訊其實我們不需要看：</p>
<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary>展開查看 raw-report.json 結構範例</summary><div><div class="collapsibleContent_i85q"><div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"SchemaVersion"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"ArtifactName"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"your-image:latest"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Metadata"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"OS"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"Family"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"alpine"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"Name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"3.21.5"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"ImageConfig"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* ... */</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Results"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Target"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"your-image:latest (alpine 3.21.5)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Class"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"os-pkgs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"alpine"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Vulnerabilities"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"VulnerabilityID"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"CVE-2025-15467"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"PkgName"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"libcrypto3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"Severity"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"CRITICAL"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"FixedVersion"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"3.3.6-r0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"Title"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"..."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"Description"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"..."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Target"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Node.js"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Class"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"lang-pkgs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"npm"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"Vulnerabilities"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">         </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"VulnerabilityID"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"CVE-2026-25639"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"PkgName"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"axios"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"PkgPath"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"usr/src/app/node_modules/axios/package.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"Severity"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"HIGH"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token property" style="color:#36acaa">"FixedVersion"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1.13.5, 0.30.3"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div></div></div></details>
<p>從上面的結構可以觀察出，漏洞都統整在 <code>Results</code> 陣列裡的 <code>Vulnerabilities</code> 欄位中，並且可以透過 <code>Class</code> 欄位來區別這個漏洞是屬於系統套件還是語言套件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何區分漏洞的位置"><strong>如何區分漏洞的位置？</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#%E5%A6%82%E4%BD%95%E5%8D%80%E5%88%86%E6%BC%8F%E6%B4%9E%E7%9A%84%E4%BD%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 如何區分漏洞的位置" title="Direct link to 如何區分漏洞的位置" translate="no">​</a></h3>
<p><strong>第一層：用 <code>Class</code> 區分系統套件與語言套件</strong></p>
<p>Trivy 預設會根據 <code>Class</code> 欄位將安全漏洞分成幾個類型：</p>
<table><thead><tr><th>Class</th><th>說明</th></tr></thead><tbody><tr><td><code>os-pkgs</code></td><td><strong>作業系統套件</strong>：由 OS 的套件管理員安裝的套件（如 Alpine 的 <code>apk</code>、Debian 的 <code>apt</code>）</td></tr><tr><td><code>lang-pkgs</code></td><td><strong>語言生態系套件</strong>：npm、pip、gem、maven 等安裝的套件</td></tr></tbody></table>
<p>不同類型的漏洞，我們的處理對策也不同：</p>
<ul>
<li class=""><strong><code>os-pkgs</code></strong>：通常需要升級 Base Image（例如從 <code>node:22-alpine3.20</code> 換成 <code>node:22-alpine3.21</code>），或在 Dockerfile 中明確執行安裝更新版的系統套件指令。</li>
<li class=""><strong><code>lang-pkgs</code></strong>：需要在 <code>package.json</code> 等依賴清單中提升套件版本，並重新安裝以進行更新。</li>
</ul>
<p><strong>第二層：透過 <code>PkgPath</code> 細分 npm 套件的歸屬</strong></p>
<p><code>lang-pkgs</code> 裡面雖然都是 npm 套件，但並不代表全部都是 <strong>這個專案</strong> 自己安裝的。以 Node.js 的 image 為例，常見的套件路徑有兩種：</p>
<ol>
<li class="">
<p><strong><code>usr/local/lib/node_modules/npm/node_modules/...</code></strong>
這其實是 <strong>npm 工具本身</strong>的依賴套件，是從 Node.js 官方 image 帶進來的環境問題。這些套件漏洞通常需要等官方推出新版 Node image 才能一併解決，我們若只改專案內的 <code>package.json</code> 是沒辦法修補到它的。</p>
</li>
<li class="">
<p><strong><code>usr/src/app/node_modules/...</code></strong>
這才是我們專案透過 <code>npm install</code> 裝進來的套件，這也是我們主要需要動手升級版本的目標。</p>
</li>
</ol>
<p>因此，每當我看到標註 HIGH 或 CRITICAL 的漏洞時，我不會急著去翻 <code>package.json</code>，而是先看一眼 <code>PkgPath</code>，藉此確認漏洞的來源。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="用-jq-整理報告內容"><strong>用 jq 整理報告內容</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#%E7%94%A8-jq-%E6%95%B4%E7%90%86%E5%A0%B1%E5%91%8A%E5%85%A7%E5%AE%B9" class="hash-link" aria-label="Direct link to 用-jq-整理報告內容" title="Direct link to 用-jq-整理報告內容" translate="no">​</a></h3>
<p>既然我們知道要看什麼欄位了，我通常會用一段 <code>jq</code> 腳本，把那動輒幾萬行的 <code>raw-report.json</code> ，精簡成只包含 <code>Class</code> 分類以及我們需要的重點屬性：</p>
<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary>展開查看完整腳本與 jq 過濾器</summary><div><div class="collapsibleContent_i85q"><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">#!/bin/bash</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TARGET_IMAGE="your-image:latest"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "🚀 開始掃描 $TARGET_IMAGE..."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">docker run --rm \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v /var/run/docker.sock:/var/run/docker.sock \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v $(pwd)/.trivycache:/root/.cache/trivy \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v $(pwd):/output \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  aquasec/trivy:latest image \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --scanners vuln \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --format json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output /output/raw-report.json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  $TARGET_IMAGE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "✅ 掃描完成，正在整理報告..."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">jq 'reduce (.Results[]? | select(.Vulnerabilities)) as $res ({};</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  .[$res.Class] += ($res.Vulnerabilities | map({</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    VulnerabilityID: .VulnerabilityID,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    PkgID: .PkgID,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    PkgName: .PkgName,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    PkgPath: .PkgPath,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Title: .Title,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Description: .Description,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Severity: .Severity,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    FixedVersion: .FixedVersion</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }))</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">)' raw-report.json &gt; clean-report.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "🎉 整理完成！請查看檔案: clean-report.json"</span><br></div></code></pre></div></div></div></div></details>
<p>先找出 <code>Results</code> 內帶有 <code>Vulnerabilities</code> 的節點，接著根據我們剛剛討論過的 <code>Class</code> 來做歸類（map），最後只留下 <code>VulnerabilityID</code>、<code>PkgPath</code>、<code>FixedVersion</code> 等能夠幫助我們迅速定位和修復漏洞的核心屬性。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="進階指令與使用技巧"><strong>進階指令與使用技巧</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#%E9%80%B2%E9%9A%8E%E6%8C%87%E4%BB%A4%E8%88%87%E4%BD%BF%E7%94%A8%E6%8A%80%E5%B7%A7" class="hash-link" aria-label="Direct link to 進階指令與使用技巧" title="Direct link to 進階指令與使用技巧" translate="no">​</a></h2>
<p>為了適應不同的情境，我們也可以加上常見的 CLI flags 來調整 Trivy 掃描的行為。（可參閱 <a href="https://trivy.dev/docs/latest/references/configuration/cli/trivy_image/" target="_blank" rel="noopener noreferrer" class="">官方 CLI 說明</a> 了解更多）</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="--download-db-only預先下載漏洞資料庫"><strong><code>--download-db-only</code>：預先下載漏洞資料庫</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#--download-db-only%E9%A0%90%E5%85%88%E4%B8%8B%E8%BC%89%E6%BC%8F%E6%B4%9E%E8%B3%87%E6%96%99%E5%BA%AB" class="hash-link" aria-label="Direct link to --download-db-only預先下載漏洞資料庫" title="Direct link to --download-db-only預先下載漏洞資料庫" translate="no">​</a></h3>
<p>首次執行 Trivy 時，程式會先從遠端下載數百 MB 的漏洞資料庫。如果希望將下載資料與後續的掃描任務拆開，可以使用這個 flag 先把資料庫庫存抓下來：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker run --rm \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -v $(pwd)/.trivycache:/root/.cache/trivy \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  aquasec/trivy:latest image \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --download-db-only</span><br></div></code></pre></div></div>
<p>這在環境網路不穩定，或是設計 CI/CD pipeline 階段的優化時相當實用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="--skip-update跳過資料庫更新"><strong><code>--skip-update</code>：跳過資料庫更新</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#--skip-update%E8%B7%B3%E9%81%8E%E8%B3%87%E6%96%99%E5%BA%AB%E6%9B%B4%E6%96%B0" class="hash-link" aria-label="Direct link to --skip-update跳過資料庫更新" title="Direct link to --skip-update跳過資料庫更新" translate="no">​</a></h3>
<p>如果在離線環境，或是已經確認快取資料是最新的，不想在每次跑掃描時都做一次更新查詢的話，加上 <code>--skip-update</code> 便能直接使用目前的本地資料庫快取，大幅節省執行時間。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker run --rm \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  # ... 省略 volume 掛載等基本指令 ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  aquasec/trivy:latest image \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --scanners vuln \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --skip-update \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --format json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output /output/raw-report.json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  your-image:latest</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="--ignore-unfixed過濾掉未有修復版本的漏洞"><strong><code>--ignore-unfixed</code>：過濾掉未有修復版本的漏洞</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#--ignore-unfixed%E9%81%8E%E6%BF%BE%E6%8E%89%E6%9C%AA%E6%9C%89%E4%BF%AE%E5%BE%A9%E7%89%88%E6%9C%AC%E7%9A%84%E6%BC%8F%E6%B4%9E" class="hash-link" aria-label="Direct link to --ignore-unfixed過濾掉未有修復版本的漏洞" title="Direct link to --ignore-unfixed過濾掉未有修復版本的漏洞" translate="no">​</a></h3>
<p>有些掃描出來的漏洞，當前可能<strong>還沒有發布對應的修復版本（Fixed Version）</strong>。這些問題雖然客觀存在，但從實務維護的角度來看，因為開發團隊也沒有升級選項可以選擇，我們其實難以著手修補這些問題。</p>
<p>加上 <code>--ignore-unfixed</code> 就能讓 Trivy 將這些沒有對應修復版本的項目從報告中隱藏：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">docker run --rm \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  # ... 省略 volume 掛載等基本指令 ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  aquasec/trivy:latest image \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --scanners vuln \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --ignore-unfixed \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --format json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output /output/raw-report.json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  your-image:latest</span><br></div></code></pre></div></div>
<p>這個選項幫助我在看報表時，可以把精力聚焦在「現在立刻能動手修復」的重大風險上，避免被過多無能為力的警告給分散了注意力。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語"><strong>結語</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#%E7%B5%90%E8%AA%9E" class="hash-link" aria-label="Direct link to 結語" title="Direct link to 結語" translate="no">​</a></h2>
<p>自從將 Trivy 納入日常開發流程後，現在不用再被動地等 DevOps 通知，在開發階段便有能力提前發現並修補存在的漏洞，漏洞修補完成後也能夠自行確認，這點對於大幅提升整個團隊的工作與溝通效率非常有幫助。</p>
<p>不過，這篇文章主要著重在「<strong>如何透過 Trivy 提早找出專案套件的 CVE 漏洞</strong>」。當我們拿到掃描報告，知道哪裡有洞之後，下一步就是實際動手把洞補起來。而關於「<strong>該怎麼修補 Node.js 專案的依賴漏洞</strong>」，這部分其實也有不少眉角，之後有時間再與大家分享我的實戰做法。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/trivy-vulnerability-scanning#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://github.com/aquasecurity/trivy" target="_blank" rel="noopener noreferrer" class=""><strong>Trivy GitHub Repository</strong></a></li>
<li class=""><a href="https://trivy.dev/docs/latest/guide/" target="_blank" rel="noopener noreferrer" class=""><strong>Trivy 官方文件</strong></a></li>
<li class=""><a href="https://www.aquasec.com/" target="_blank" rel="noopener noreferrer" class=""><strong>Aqua Security</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="DevOps" term="DevOps"/>
        <category label="Security" term="Security"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 shadcn/ui 前該補的 TailwindCSS 基礎知識(六) - shadcn/ui 生態系工具鏈]]></title>
        <id>https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6</id>
        <link href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6"/>
        <updated>2025-11-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[深入探討 shadcn/ui 背後的核心工具鏈：clsx、tailwind-merge、tw-animate-css、CVA 和 components.json。學習如何透過這些工具解決條件式樣式、className 衝突、動畫實作、元件變體管理等實際開發問題，打造更靈活且易維護的 UI 元件系統。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本文是「使用 shadcn/ui 前該補的 TailwindCSS 基礎知識」系列文章的第六篇</p>
<p><strong>系列文章：</strong></p>
<ol>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-1" target="_blank" rel="noopener noreferrer" class="">從 MUI 到 TailwindCSS 設計哲學的轉變</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-2" target="_blank" rel="noopener noreferrer" class="">理解 TailwindCSS 的運作原理</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-3" target="_blank" rel="noopener noreferrer" class="">TailwindCSS v4 內建 Utility Classes 速查</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-4" target="_blank" rel="noopener noreferrer" class="">透過 @theme 自訂設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-5" target="_blank" rel="noopener noreferrer" class="">解析 shadcn/ui 的設計系統</a></li>
<li class=""><strong>shadcn/ui 生態系工具鏈（本篇）</strong></li>
</ol>
</blockquote>
<p>在使用過 shadcn/ui 的提供的程式碼後，會發現 shadcn/ui 並不是只有單純使用 Tailwind 而已，常會搭配幾個工具來解決在實際開發中會遇到的各種問題，例如：條件式樣式、className 衝突、元件變體管理等。以下將逐一介紹這些工具。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="clsx-與-tailwind-mergeclassname-管理的最佳拍檔"><strong>clsx 與 tailwind-merge：className 管理的最佳拍檔</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#clsx-%E8%88%87-tailwind-mergeclassname-%E7%AE%A1%E7%90%86%E7%9A%84%E6%9C%80%E4%BD%B3%E6%8B%8D%E6%AA%94" class="hash-link" aria-label="Direct link to clsx-與-tailwind-mergeclassname-管理的最佳拍檔" title="Direct link to clsx-與-tailwind-mergeclassname-管理的最佳拍檔" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="clsx條件式-classname-組合"><strong>clsx：條件式 className 組合</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#clsx%E6%A2%9D%E4%BB%B6%E5%BC%8F-classname-%E7%B5%84%E5%90%88" class="hash-link" aria-label="Direct link to clsx條件式-classname-組合" title="Direct link to clsx條件式-classname-組合" translate="no">​</a></h3>
<p>在寫 React 元件時，經常會遇到需要根據 props 或狀態來決定要套用哪些 className 的情況。例如：</p>
<ul>
<li class="">按鈕有不同的變體（primary、secondary、outline）</li>
<li class="">元件有不同的尺寸（sm、md、lg）</li>
<li class="">根據狀態顯示不同樣式（active、disabled、loading）</li>
</ul>
<p>如果用傳統的字串拼接，程式碼會變得很難維護：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// ❌ 難以維護的寫法</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> className </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string" style="color:#e3116c">"btn"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isPrimary </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">" btn-primary"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isLarge </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">" btn-large"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">isDisabled </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">" btn-disabled"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>clsx</code>&nbsp; 就是為了解決這個問題而生的工具。它讓你可以用更直觀的方式組合條件式的 className：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">clsx</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"clsx"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// ✅ 清晰易讀的寫法</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> className </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">clsx</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"btn"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"btn-primary"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> isPrimary</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"btn-large"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> isLarge</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"btn-disabled"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> isDisabled</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailwind-merge解決-classname-衝突"><strong>tailwind-merge：解決 className 衝突</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#tailwind-merge%E8%A7%A3%E6%B1%BA-classname-%E8%A1%9D%E7%AA%81" class="hash-link" aria-label="Direct link to tailwind-merge解決-classname-衝突" title="Direct link to tailwind-merge解決-classname-衝突" translate="no">​</a></h3>
<p>在建立可重複使用的元件時，經常會遇到一個棘手的問題：<strong>使用者傳入的 className 可能會與元件預設的 className 衝突</strong>。</p>
<p>例如，元件預設有 &nbsp;<code>p-4</code>&nbsp; 的內距，但使用者想要傳入 &nbsp;<code>p-8</code>&nbsp; 來覆蓋它。如果只用 clsx，兩個 className 都會存在，而實際套用哪個取決於 CSS 載入順序，結果不可預測：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// ❌ 問題：兩個 padding 都存在</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript function" style="color:#d73a49">clsx</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript string" style="color:#e3116c">"p-4"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> customPadding</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 如果 customPadding = 'p-8'，p-4 和 p-8 都會存在 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 實際套用哪個？不確定！ */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><code>tailwind-merge</code>&nbsp; 會智慧地判斷哪些 className 是衝突的，並保留後面的那一個：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> twMerge </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tailwind-merge"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// ✅ 解決方案：後面的覆蓋前面的</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript function" style="color:#d73a49">twMerge</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript string" style="color:#e3116c">"p-4"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"p-8"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 結果: "p-8"（p-4 被移除） */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cn-函數clsx--tailwind-merge-的完美組合"><strong>cn 函數：clsx + tailwind-merge 的完美組合</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#cn-%E5%87%BD%E6%95%B8clsx--tailwind-merge-%E7%9A%84%E5%AE%8C%E7%BE%8E%E7%B5%84%E5%90%88" class="hash-link" aria-label="Direct link to cn-函數clsx--tailwind-merge-的完美組合" title="Direct link to cn-函數clsx--tailwind-merge-的完美組合" translate="no">​</a></h3>
<p>在實際開發中，通常會同時需要 <code>clsx</code> 的條件式組合能力和 <code>tailwind-merge</code> 的衝突解決能力。因此，shadcn/ui 定義了一個 &nbsp;<code>cn</code>&nbsp; 函數，結合了兩者的優勢。</p>
<p>在 shadcn/ui 的 &nbsp;<code>utils.ts</code>&nbsp; 中可以看到這個函數的定義：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> clsx</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">ClassValue</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"clsx"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> twMerge </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tailwind-merge"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">cn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">inputs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">ClassValue</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">twMerge</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">clsx</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">inputs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這個簡單的函數先用 clsx 處理條件式組合，再用 tailwind-merge 解決衝突。使用者可以輕鬆地覆蓋預設樣式，而不用擔心 className 衝突的問題。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tw-animate-csstailwindcss-v4-的動畫解決方案"><strong>tw-animate-css：TailwindCSS v4 的動畫解決方案</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#tw-animate-csstailwindcss-v4-%E7%9A%84%E5%8B%95%E7%95%AB%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88" class="hash-link" aria-label="Direct link to tw-animate-csstailwindcss-v4-的動畫解決方案" title="Direct link to tw-animate-csstailwindcss-v4-的動畫解決方案" translate="no">​</a></h2>
<p>在 TailwindCSS v4 推出後，原本廣泛使用的 &nbsp;<code>tailwindcss-animate</code>&nbsp; 插件因為基於舊的 JavaScript 插件系統而無法直接使用。<code>tw-animate-css</code>&nbsp; 就是為了解決這個問題而生的替代方案，它採用 TailwindCSS v4 的 CSS-first 架構，提供純 CSS 的動畫解決方案。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝與使用"><strong>安裝與使用</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E5%AE%89%E8%A3%9D%E8%88%87%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="Direct link to 安裝與使用" title="Direct link to 安裝與使用" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install tw-animate-css</span><br></div></code></pre></div></div>
<p>在 CSS 入口檔案中引入：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@import</span><span class="token atrule" style="color:#00a4db"> </span><span class="token atrule string" style="color:#e3116c">"tailwindcss"</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@import</span><span class="token atrule" style="color:#00a4db"> </span><span class="token atrule string" style="color:#e3116c">"tw-animate-css"</span><span class="token atrule punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="基本用法"><strong>基本用法</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95" class="hash-link" aria-label="Direct link to 基本用法" title="Direct link to 基本用法" translate="no">​</a></h3>
<p><strong>Enter/Exit 動畫：</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 淡入動畫</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-in fade-in duration-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  淡入效果</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 從上方滑入</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-in slide-in-from-top duration-300</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  從上方滑入</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 淡出動畫</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-out fade-out duration-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  淡出效果</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 組合多種效果</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-in fade-in slide-in-from-bottom duration-700 delay-100</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  延遲 100ms 後，從下方淡入滑入</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><strong>動畫參數控制：</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 控制動畫時長</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-in fade-in duration-150</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">快速淡入</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-in fade-in duration-1000</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">慢速淡入</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 控制緩動函數</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-in slide-in-from-left ease-in-out</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  使用 ease-in-out</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 控制延遲</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-in fade-in delay-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">延遲 500ms</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 控制重複次數</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-bounce repeat-infinite</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">無限彈跳</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-pulse repeat-3</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">重複 3 次</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 控制方向</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-bounce direction-alternate</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">來回彈跳</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><strong>現成的動畫：</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Accordion 動畫（常用於展開/收合元件）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-accordion-down</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">展開</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-accordion-up</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">收合</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 閃爍游標（常用於輸入提示）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">span</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">animate-caret-blink</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">|</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">span</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>shadcn/ui 的許多元件都內建了動畫效果，這些動畫大多使用 &nbsp;<code>tw-animate-css</code>&nbsp; 或類似的動畫工具實作。例如：</p>
<ul>
<li class=""><strong>Accordion</strong>：使用 &nbsp;<code>accordion-down</code>&nbsp; 和 &nbsp;<code>accordion-up</code></li>
<li class=""><strong>Dialog</strong>：使用 &nbsp;<code>fade-in</code>&nbsp; 和 &nbsp;<code>slide-in-from-bottom</code></li>
<li class=""><strong>Dropdown Menu</strong>：使用 &nbsp;<code>slide-in-from-top</code>&nbsp; 和 &nbsp;<code>fade-in</code></li>
<li class=""><strong>Toast</strong>：使用 &nbsp;<code>slide-in-from-right</code>&nbsp; 和 &nbsp;<code>fade-in</code></li>
</ul>
<p>可以查看 &nbsp;<a href="https://github.com/Wombosvideo/tw-animate-css" target="_blank" rel="noopener noreferrer" class="">tw-animate-css GitHub</a>&nbsp; 可以了解更多進階用法和完整的 API 文件。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="class-variance-authority-cva元件變體管理"><strong>Class Variance Authority (CVA)：元件變體管理</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#class-variance-authority-cva%E5%85%83%E4%BB%B6%E8%AE%8A%E9%AB%94%E7%AE%A1%E7%90%86" class="hash-link" aria-label="Direct link to class-variance-authority-cva元件變體管理" title="Direct link to class-variance-authority-cva元件變體管理" translate="no">​</a></h2>
<p>在建立可重複使用的元件時，經常會遇到這樣的需求：</p>
<ul>
<li class="">按鈕有不同的外觀變體（primary、secondary、outline、ghost）</li>
<li class="">每個變體有不同的尺寸（sm、md、lg）</li>
<li class="">需要組合這些變體（例如：大尺寸的 outline 按鈕）</li>
</ul>
<p>如果用傳統的方式，需要寫很多 if-else 或 switch-case 來處理這些組合，程式碼會變得很難維護。</p>
<p><strong>CVA (Class Variance Authority)</strong>&nbsp; 就是為了解決這個問題而生的工具。它讓我們可以用宣告式的方式定義元件的「基礎樣式」和「變體樣式」，然後根據 props 自動組合出正確的 className。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cva-的核心概念"><strong>CVA 的核心概念</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#cva-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="Direct link to cva-的核心概念" title="Direct link to cva-的核心概念" translate="no">​</a></h3>
<p>CVA 將元件的樣式分為三個部分：</p>
<ol>
<li class=""><strong>基礎樣式 (Base)</strong>：所有變體都共用的樣式</li>
<li class=""><strong>變體樣式 (Variants)</strong>：不同變體的專屬樣式</li>
<li class=""><strong>複合變體 (Compound Variants)</strong>：當多個變體組合時的特殊樣式（選用）</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際應用button-元件"><strong>實際應用：Button 元件</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E5%AF%A6%E9%9A%9B%E6%87%89%E7%94%A8button-%E5%85%83%E4%BB%B6" class="hash-link" aria-label="Direct link to 實際應用button-元件" title="Direct link to 實際應用button-元件" translate="no">​</a></h3>
<p>以 shadcn/ui 的 &nbsp;Button 元件為例，來看看 CVA 如何使用：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> cva</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">VariantProps</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"class-variance-authority"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> buttonVariants </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">cva</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 基礎樣式：所有按鈕都會套用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string" style="color:#e3116c">"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    variants</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// variant 變體：定義不同的外觀</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      variant</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">default</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bg-primary text-primary-foreground hover:bg-primary/90"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        destructive</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bg-destructive text-white hover:bg-destructive/90"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        outline</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        secondary</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"bg-secondary text-secondary-foreground hover:bg-secondary/80"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        ghost</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"hover:bg-accent hover:text-accent-foreground"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        link</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"text-primary underline-offset-4 hover:underline"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// size 變體：定義不同的尺寸</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      size</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">default</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"h-9 px-4 py-2"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        sm</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"h-8 rounded-md px-3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        lg</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"h-10 rounded-md px-6"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        icon</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"size-9"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 預設值：當使用者沒有指定時使用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    defaultVariants</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      variant</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      size</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"default"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>定義好 &nbsp;<code>buttonVariants</code>&nbsp; 後，在元件中使用：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Button</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> className</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> variant</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> size</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">props </span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">ButtonProps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript function" style="color:#d73a49">cn</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript function" style="color:#d73a49">buttonVariants</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> variant</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> size</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> className </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag spread punctuation" style="color:#393A34">{</span><span class="token tag spread operator" style="color:#393A34">...</span><span class="token tag spread" style="color:#00009f">props</span><span class="token tag spread punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">    </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 使用範例</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">預設按鈕</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">destructive</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">size</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">大尺寸刪除按鈕</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">ghost</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">size</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">icon</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">&lt;Icon /&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><strong>CVA 的核心優勢：</strong></p>
<ol>
<li class=""><strong>型別安全</strong>：TypeScript 會自動推斷可用的變體選項，寫錯會直接報錯</li>
<li class=""><strong>易於維護</strong>：所有樣式集中在一個地方，修改時不用到處找</li>
<li class=""><strong>靈活組合</strong>：可以輕鬆組合不同的變體，不用手動處理邏輯</li>
<li class=""><strong>預設值支援</strong>：可以設定預設的變體，簡化使用</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="進階功能compoundvariants"><strong>進階功能：compoundVariants</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E9%80%B2%E9%9A%8E%E5%8A%9F%E8%83%BDcompoundvariants" class="hash-link" aria-label="Direct link to 進階功能compoundvariants" title="Direct link to 進階功能compoundvariants" translate="no">​</a></h3>
<p>CVA 還支援 &nbsp;<code>compoundVariants</code>，用於定義「當多個變體組合時的特殊樣式」。例如：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> buttonVariants </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">cva</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"base-styles"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  variants</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    variant</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      primary</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bg-blue-500"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      secondary</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"bg-gray-500"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    size</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      sm</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"text-sm"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      lg</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"text-lg"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    outlined</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token boolean" style="color:#36acaa">true</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"border-2"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token boolean" style="color:#36acaa">false</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 複合變體：當多個條件同時滿足時套用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  compoundVariants</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// 當 variant="primary" 且 outlined=true 時</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      variant</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"primary"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      outlined</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">class</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"border-blue-500 bg-transparent text-blue-500 hover:bg-blue-50"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// 當 size="lg" 且 outlined=true 時</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      size</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"lg"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      outlined</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">class</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"border-4"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 大尺寸的外框按鈕使用更粗的邊框</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  defaultVariants</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    variant</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"primary"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    size</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"sm"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    outlined</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>使用範例：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 會套用 primary + outlined 的複合變體樣式</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">primary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">outlined</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript boolean" style="color:#36acaa">true</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  Primary Outlined Button</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 會套用 lg + outlined 的複合變體樣式</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">size</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">outlined</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript boolean" style="color:#36acaa">true</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  Large Outlined Button</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 會同時套用兩個複合變體的樣式</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">primary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">size</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">outlined</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript boolean" style="color:#36acaa">true</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  Large Primary Outlined Button</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="componentsjsonshadcnui-的配置中心"><strong>components.json：shadcn/ui 的配置中心</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#componentsjsonshadcnui-%E7%9A%84%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83" class="hash-link" aria-label="Direct link to componentsjsonshadcnui-的配置中心" title="Direct link to componentsjsonshadcnui-的配置中心" translate="no">​</a></h2>
<p><code>components.json</code>&nbsp; 是 shadcn/ui CLI 的核心配置檔。當使用 &nbsp;<code>npx shadcn@latest add button</code>&nbsp; 安裝元件時，CLI 會讀取這個檔案來決定：</p>
<ul>
<li class="">元件應該安裝到哪個目錄</li>
<li class="">使用什麼樣的風格（default 或 new-york）</li>
<li class="">是否使用 TypeScript</li>
<li class="">路徑別名是什麼</li>
<li class="">是否使用 CSS 變數</li>
<li class="">從哪些 registry 安裝元件</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="componentsjson-的完整結構"><strong>components.json 的完整結構</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#componentsjson-%E7%9A%84%E5%AE%8C%E6%95%B4%E7%B5%90%E6%A7%8B" class="hash-link" aria-label="Direct link to componentsjson-的完整結構" title="Direct link to componentsjson-的完整結構" translate="no">​</a></h3>
<p>以下是一個完整的 &nbsp;<code>components.json</code>&nbsp; 範例：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"$schema"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://ui.shadcn.com/schema.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"style"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"new-york"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"rsc"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"tsx"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"tailwind"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"config"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"css"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"src/index.css"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"baseColor"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"neutral"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"cssVariables"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"prefix"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"iconLibrary"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"lucide"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"aliases"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"components"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/components"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"utils"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/lib/utils"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"ui"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/components/ui"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"lib"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/lib"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"hooks"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/hooks"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"registries"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="各欄位說明"><strong>各欄位說明</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E5%90%84%E6%AC%84%E4%BD%8D%E8%AA%AA%E6%98%8E" class="hash-link" aria-label="Direct link to 各欄位說明" title="Direct link to 各欄位說明" translate="no">​</a></h3>
<p><strong>$schema</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"$schema"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://ui.shadcn.com/schema.json"</span><br></div></code></pre></div></div>
<p>指向 shadcn/ui 的 JSON schema，提供 IDE 自動完成功能和欄位驗證。</p>
<p><strong>style</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"style"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"new-york"</span><span class="token comment" style="color:#999988;font-style:italic">// 或 "default"</span><br></div></code></pre></div></div>
<p>選擇元件風格。shadcn/ui 提供兩種預設風格：</p>
<ul>
<li class=""><strong>default</strong>：較為簡潔的設計</li>
<li class=""><strong>new-york</strong>：更現代、更精緻的設計</li>
</ul>
<p>不同風格的元件在視覺設計和細節上有所不同，但功能完全相同。</p>
<p><strong>rsc</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"rsc"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><br></div></code></pre></div></div>
<p>是否使用 React Server Components（Next.js 13+ 的功能）。如果專案使用 Next.js App Router，可以設為 &nbsp;<code>true</code>。</p>
<p><strong>tsx</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"tsx"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><br></div></code></pre></div></div>
<p>是否使用 TypeScript。設為 &nbsp;<code>true</code>&nbsp; 時，安裝的元件會是 &nbsp;<code>.tsx</code>&nbsp; 檔案；設為 &nbsp;<code>false</code>&nbsp; 則是 &nbsp;<code>.jsx</code>&nbsp; 檔案。</p>
<p><strong>tailwind</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"tailwind"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"config"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"css"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"src/index.css"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"baseColor"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"neutral"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"cssVariables"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"prefix"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>TailwindCSS 相關配置：</p>
<ul>
<li class=""><strong>config</strong>：tailwind.config 檔案的位置（TailwindCSS v4 通常不需要此檔案，可留空）</li>
<li class=""><strong>css</strong>：CSS 入口檔案的位置，CLI 會在這個檔案中注入必要的 CSS 變數</li>
<li class=""><strong>baseColor</strong>：基礎顏色主題，可選：<code>zinc</code>、<code>slate</code>、<code>stone</code>、<code>gray</code>、<code>neutral</code></li>
<li class=""><strong>cssVariables</strong>：是否使用 CSS 變數（強烈建議開啟，支援暗色模式和主題切換）</li>
<li class=""><strong>prefix</strong>：TailwindCSS class 的前綴（例如設為 &nbsp;<code>"tw-"</code>&nbsp; 後，所有 class 都會變成 &nbsp;<code>tw-flex</code>、<code>tw-p-4</code>&nbsp; 等）</li>
</ul>
<p><strong>iconLibrary</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"iconLibrary"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"lucide"</span><br></div></code></pre></div></div>
<p>指定使用的圖示庫。shadcn/ui 預設使用 &nbsp;<a href="https://lucide.dev/" target="_blank" rel="noopener noreferrer" class="">Lucide Icons</a>。</p>
<p><strong>aliases</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"aliases"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"components"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/components"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"utils"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/lib/utils"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"ui"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/components/ui"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"lib"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/lib"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"hooks"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/hooks"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>路徑別名配置，告訴 CLI 各種檔案應該安裝到哪裡：</p>
<ul>
<li class=""><strong>components</strong>：元件的根目錄</li>
<li class=""><strong>utils</strong>：工具函數的位置（<code>cn</code>&nbsp; 函數會安裝在這裡）</li>
<li class=""><strong>ui</strong>：UI 元件的目標目錄</li>
<li class=""><strong>lib</strong>：函式庫目錄</li>
<li class=""><strong>hooks</strong>：自訂 hooks 的目錄</li>
</ul>
<p>這些別名必須與專案的 &nbsp;<code>tsconfig.json</code>&nbsp; 或 &nbsp;<code>jsconfig.json</code>&nbsp; 中的 &nbsp;<code>paths</code>&nbsp; 設定一致。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="registries自訂元件來源"><strong>registries：自訂元件來源</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#registries%E8%87%AA%E8%A8%82%E5%85%83%E4%BB%B6%E4%BE%86%E6%BA%90" class="hash-link" aria-label="Direct link to registries自訂元件來源" title="Direct link to registries自訂元件來源" translate="no">​</a></h3>
<p><code>registries</code>&nbsp; 是 components.json 中最強大但也最容易被忽略的功能。它允許從多個來源安裝元件，包括：</p>
<ul>
<li class="">shadcn/ui 官方 registry</li>
<li class="">第三方 registry（如 v0.dev、Magic UI）</li>
<li class="">私有 registry（公司內部的元件庫）</li>
<li class="">自建 registry</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="基本配置"><strong>基本配置</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 基本配置" title="Direct link to 基本配置" translate="no">​</a></h3>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"registries"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@v0"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://v0.dev/chat/b/{name}"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@magicui"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://magicui.design/r/{name}.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@acme"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://registry.acme.com/{name}.json"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>{name}</code>&nbsp; 會被替換成元件名稱。例如執行：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx shadcn@latest add @v0/dashboard</span><br></div></code></pre></div></div>
<p>CLI 會從 &nbsp;<code>https://v0.dev/chat/b/dashboard</code>&nbsp; 下載元件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="進階配置帶認證的私有-registry"><strong>進階配置：帶認證的私有 registry</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E9%80%B2%E9%9A%8E%E9%85%8D%E7%BD%AE%E5%B8%B6%E8%AA%8D%E8%AD%89%E7%9A%84%E7%A7%81%E6%9C%89-registry" class="hash-link" aria-label="Direct link to 進階配置帶認證的私有-registry" title="Direct link to 進階配置帶認證的私有-registry" translate="no">​</a></h3>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"registries"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@company"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"url"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://registry.company.com/ui/{name}.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"headers"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"Authorization"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Bearer ${REGISTRY_TOKEN}"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"X-API-Key"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"${API_KEY}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"params"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"latest"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"team"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"frontend"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>環境變數（<code>${VAR_NAME}</code>&nbsp; 格式）會自動從系統環境變數中讀取。</p>
<p>使用方式：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 設定環境變數export REGISTRY_TOKEN="your-token"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export API_KEY="your-api-key"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 安裝私有元件</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx shadcn@latest add @company/custom-button</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="多-registry-混合使用"><strong>多 registry 混合使用</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#%E5%A4%9A-registry-%E6%B7%B7%E5%90%88%E4%BD%BF%E7%94%A8" class="hash-link" aria-label="Direct link to 多-registry-混合使用" title="Direct link to 多-registry-混合使用" translate="no">​</a></h3>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"registries"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@shadcn"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://ui.shadcn.com/r/{name}.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@v0"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://v0.dev/chat/b/{name}"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@company"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"url"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://registry.company.com/{name}.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"headers"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"Authorization"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Bearer ${COMPANY_TOKEN}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"@team"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"url"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://team.company.com/{name}.json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"params"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"team"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"frontend"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"${REGISTRY_VERSION}"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這樣就可以從不同來源安裝元件：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 從 shadcn/ui 官方安裝</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx shadcn@latest add @shadcn/button</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 從 v0.dev 安裝</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx shadcn@latest add @v0/dashboard</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 從公司內部 registry 安裝</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx shadcn@latest add @company/auth-form</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 從團隊 registry 安裝</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx shadcn@latest add @team/data-table</span><br></div></code></pre></div></div>
<blockquote>
<p>components.json 與 shadcn CLI 的關係 &nbsp;components.json&nbsp; 是 shadcn CLI 的「設定檔」，CLI 會根據這個檔案：</p>
<ol>
<li class=""><strong>決定安裝位置</strong>：根據 &nbsp;<code>aliases</code>&nbsp; 決定檔案要放在哪裡</li>
<li class=""><strong>選擇元件風格</strong>：根據 &nbsp;<code>style</code>&nbsp; 下載對應風格的元件</li>
<li class=""><strong>處理依賴</strong>：自動安裝元件所需的 npm 套件</li>
<li class=""><strong>注入 CSS 變數</strong>：在 &nbsp;<code>tailwind.css</code>&nbsp; 中注入必要的 CSS 變數</li>
<li class=""><strong>解析 registry</strong>：從指定的 registry 下載元件</li>
</ol>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-6#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://tailwindcss.com/" target="_blank" rel="noopener noreferrer" class=""><strong>TailwindCSS</strong></a></li>
<li class=""><a href="https://ui.shadcn.com/" target="_blank" rel="noopener noreferrer" class=""><strong>shadcn/ui</strong></a></li>
<li class=""><a href="https://github.com/lukeed/clsx" target="_blank" rel="noopener noreferrer" class=""><strong>clsx</strong></a></li>
<li class=""><a href="https://github.com/dcastil/tailwind-merge" target="_blank" rel="noopener noreferrer" class=""><strong>tailwind-merge</strong></a></li>
<li class=""><a href="https://github.com/Wombosvideo/tw-animate-css" target="_blank" rel="noopener noreferrer" class=""><strong>tw-animate-css</strong></a></li>
<li class=""><a href="https://cva.style/" target="_blank" rel="noopener noreferrer" class=""><strong>Class Variance Authority (CVA)</strong></a></li>
<li class=""><a href="https://ui.shadcn.com/docs/registry" target="_blank" rel="noopener noreferrer" class=""><strong>shadcn/ui Registry</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="shadcn/ui" term="shadcn/ui"/>
        <category label="TailwindCSS" term="TailwindCSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 shadcn/ui 前該補的 TailwindCSS 基礎知識(五) - 解析 shadcn/ui 的設計系統]]></title>
        <id>https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5</id>
        <link href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5"/>
        <updated>2025-11-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[深入解析 shadcn/ui 的 index.css（或 globals.css）設計系統，完整理解 @theme inline、@custom-variant、@layer 等進階語法，以及兩種深色模式實作方式的運作原理。透過視覺化圖解，徹底搞懂 CSS 變數如何在亮暗主題間動態切換。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本文是「使用 shadcn/ui 前該補的 TailwindCSS 基礎知識」系列文章的第五篇</p>
<p><strong>系列文章：</strong></p>
<ol>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-1" target="_blank" rel="noopener noreferrer" class="">從 MUI 到 TailwindCSS 設計哲學的轉變</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-2" target="_blank" rel="noopener noreferrer" class="">理解 TailwindCSS 的運作原理</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-3" target="_blank" rel="noopener noreferrer" class="">TailwindCSS v4 內建 Utility Classes 速查</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-4" target="_blank" rel="noopener noreferrer" class="">透過 @theme 自訂設計系統</a></li>
<li class=""><strong>解析 shadcn/ui 的設計系統（本篇）</strong></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-6" target="_blank" rel="noopener noreferrer" class="">shadcn/ui 生態系工具鏈</a></li>
</ol>
</blockquote>
<p>當你使用 <code>npx shadcn@latest init</code> 初始化 shadcn/ui 專案時，它會自動生成一個 <code>index.css</code>（或 <code>globals.css</code>）檔案。這個檔案乍看之下可能讓人有些困惑：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@import</span><span class="token atrule" style="color:#00a4db"> </span><span class="token atrule string" style="color:#e3116c">"tailwindcss"</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@custom-variant</span><span class="token atrule" style="color:#00a4db"> dark </span><span class="token atrule punctuation" style="color:#393A34">(</span><span class="token atrule" style="color:#00a4db">&amp;</span><span class="token atrule punctuation" style="color:#393A34">:</span><span class="token atrule function" style="color:#d73a49">is</span><span class="token atrule punctuation" style="color:#393A34">(</span><span class="token atrule" style="color:#00a4db">.dark *</span><span class="token atrule punctuation" style="color:#393A34">)</span><span class="token atrule punctuation" style="color:#393A34">)</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* ... 更多 token */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">white</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#1a1a1a</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.8</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">250</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> base</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector" style="color:#00009f">body</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> bg-background text-foreground</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這些 <code>@theme inline</code>、<code>@custom-variant</code>、<code>@layer</code> 到底是什麼？<code>:root</code> 和 <code>.dark</code> 又是如何實現主題切換的？</p>
<p>本篇文章將<strong>逐一拆解 shadcn/ui 的 CSS 設計系統</strong>，幫助你完全理解每一行程式碼背後的原理。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="theme定義設計-token"><strong>@theme：定義設計 Token</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#theme%E5%AE%9A%E7%BE%A9%E8%A8%AD%E8%A8%88-token" class="hash-link" aria-label="Direct link to theme定義設計-token" title="Direct link to theme定義設計-token" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="什麼是-design-token"><strong>什麼是 Design Token？</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E4%BB%80%E9%BA%BC%E6%98%AF-design-token" class="hash-link" aria-label="Direct link to 什麼是-design-token" title="Direct link to 什麼是-design-token" translate="no">​</a></h3>
<p>在 shadcn/ui 範例程式碼的 &nbsp;<code>index.css</code>&nbsp; 或 &nbsp;<code>globals.css</code>&nbsp; 中，經常會看到類似這樣的程式碼：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-lg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.8</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">250</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>剛開始用 shadcn/ui 時，我大概能猜得到 &nbsp;<code>:root</code>&nbsp; 和 &nbsp;<code>.dark</code>&nbsp; 是在定義主題變數，但還是有幾個疑問：</p>
<ul>
<li class=""><code>@theme inline</code>&nbsp; 是什麼？看起來也挺像主題變數的，跟 &nbsp;<code>:root</code>&nbsp; 有什麼差別？</li>
<li class="">這些變數如何變成 TailwindCSS 的 utility class？</li>
</ul>
<p>這些變數其實就是 &nbsp;<strong>Design Token</strong>，它就像是設計系統的「變數庫」。在設計一個網站時，會需要定義：</p>
<ul>
<li class="">主色調、次要色調</li>
<li class="">不同大小的間距</li>
<li class="">統一的圓角大小</li>
<li class="">陰影效果</li>
</ul>
<p>這些都可以定義成 Token，然後在整個專案中重複使用。<code>@theme</code>&nbsp; 就是 TailwindCSS v4 提供的工具，用來將這些 Design Token 轉換成可用的 utility class。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="theme-的基本語法與使用方式"><strong>@theme 的基本語法與使用方式</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#theme-%E7%9A%84%E5%9F%BA%E6%9C%AC%E8%AA%9E%E6%B3%95%E8%88%87%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to theme-的基本語法與使用方式" title="Direct link to theme-的基本語法與使用方式" translate="no">​</a></h3>
<p>以下是一段範例程式碼：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-sm</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token unit">px</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-md</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token unit">px</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-lg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-xl</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token unit">px</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-secondary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--secondary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-destructive</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--destructive</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--secondary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.9245</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.0138</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">92.9892</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這段程式碼做了什麼？</p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="token-轉換成-utility-class-的規則"><strong>Token 轉換成 Utility Class 的規則</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#token-%E8%BD%89%E6%8F%9B%E6%88%90-utility-class-%E7%9A%84%E8%A6%8F%E5%89%87" class="hash-link" aria-label="Direct link to token-轉換成-utility-class-的規則" title="Direct link to token-轉換成-utility-class-的規則" translate="no">​</a></h3>
<p>從上面的例子我們可以看出，定義了 &nbsp;<code>--color-primary</code>&nbsp; 後，在 React 或 HTML 中就可以使用 &nbsp;<code>bg-primary</code>。這背後有一套明確的命名轉換規則。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>關於各種 Token 類型的詳細用法，可以參考 <a href="https://tailwindcss.com/docs/v4-beta#theme" target="_blank" rel="noopener noreferrer" class="">TailwindCSS v4 官方文件 - Theme</a> 以及本系列的第四篇文章：<a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-4" target="_blank" rel="noopener noreferrer" class="">透過 @theme 自訂設計系統</a>。</p></div></div>
<p><strong>規則 1：移除類型前綴</strong></p>
<p>Token 名稱的類型前綴（如 &nbsp;<code>--color-</code>、<code>--spacing-</code>、<code>--radius-</code>&nbsp; 等）在轉換成 utility class 時會被移除，只保留語意名稱。</p>
<p><strong>規則 2：配合屬性前綴使用</strong></p>
<p>不同的 CSS 屬性有不同的 TailwindCSS 前綴，需要搭配使用：</p>
<table><thead><tr><th>Token 類型</th><th>定義範例</th><th>使用方式</th></tr></thead><tbody><tr><td>顏色</td><td><code>--color-brand</code></td><td><code>bg-brand</code>、<code>text-brand</code>、<code>border-brand</code></td></tr><tr><td>間距</td><td><code>--spacing-lg</code></td><td><code>p-lg</code>、<code>m-lg</code>、<code>gap-lg</code></td></tr><tr><td>圓角</td><td><code>--radius-card</code></td><td><code>rounded-card</code></td></tr><tr><td>寬度</td><td><code>--width-sidebar</code></td><td><code>w-sidebar</code>、<code>min-w-sidebar</code></td></tr><tr><td>高度</td><td><code>--height-header</code></td><td><code>h-header</code>、<code>max-h-header</code></td></tr><tr><td>字型</td><td><code>--font-family-heading</code></td><td><code>font-heading</code></td></tr><tr><td>字體大小</td><td><code>--font-size-xl</code></td><td><code>text-xl</code></td></tr><tr><td>字重</td><td><code>--font-weight-bold</code></td><td><code>font-bold</code></td></tr><tr><td>陰影</td><td><code>--shadow-card</code></td><td><code>shadow-card</code></td></tr><tr><td>透明度</td><td><code>--opacity-soft</code></td><td><code>opacity-soft</code></td></tr></tbody></table>
<p><strong>實際範例：</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* 在 index.css 定義 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-brand</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#ff6b6b</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--spacing-card</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.5</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-button</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.375</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--font-family-heading</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Inter"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sans-serif</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--font-size-xl</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.25</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--shadow-elevated</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token unit">px</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">6</span><span class="token unit">px</span><span class="token plain"> </span><span class="token color function" style="color:#d73a49">rgba</span><span class="token color punctuation" style="color:#393A34">(</span><span class="token color number" style="color:#36acaa">0</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">0</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">0</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">0.1</span><span class="token color punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--width-sidebar</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">16</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 在 React/HTML 中使用</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Card</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag attr-value" style="color:#e3116c">      bg-brand              {/* --color-brand */}</span><br></div><div class="token-line" style="color:#393A34"><span class="token tag attr-value" style="color:#e3116c">      p-card                {/* --spacing-card */}</span><br></div><div class="token-line" style="color:#393A34"><span class="token tag attr-value" style="color:#e3116c">      rounded-button        {/* --radius-button */}</span><br></div><div class="token-line" style="color:#393A34"><span class="token tag attr-value" style="color:#e3116c">      shadow-elevated       {/* --shadow-elevated */}</span><br></div><div class="token-line" style="color:#393A34"><span class="token tag attr-value" style="color:#e3116c">      w-sidebar             {/* --width-sidebar */}</span><br></div><div class="token-line" style="color:#393A34"><span class="token tag attr-value" style="color:#e3116c">    </span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">    </span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-heading text-xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">" "</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* --font-family-heading, --font-size-xl */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        標題</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>圓角的命名差異</div><div class="admonitionContent_BuS1"><p>圓角的轉換規則有一個特別之處：Token 定義時使用 &nbsp;<code>--radius-</code>，但對應的 utility class 前綴是 &nbsp;<code>rounded-</code>：</p><ul>
<li class=""><code>--radius-lg</code>&nbsp;→&nbsp;<code>rounded-lg</code>（Token 用 &nbsp;<code>radius</code>，utility 用 &nbsp;<code>rounded</code>）</li>
<li class=""><code>--radius-button</code>&nbsp;→&nbsp;<code>rounded-button</code></li>
</ul><p>這與其他 Token 不同，例如顏色：</p><ul>
<li class=""><code>--color-primary</code>&nbsp;→&nbsp;<code>bg-primary</code>（都是 &nbsp;<code>color</code>&nbsp; 相關）</li>
</ul></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="theme-inlineinline-關鍵字的作用"><strong>@theme inline：inline 關鍵字的作用</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#theme-inlineinline-%E9%97%9C%E9%8D%B5%E5%AD%97%E7%9A%84%E4%BD%9C%E7%94%A8" class="hash-link" aria-label="Direct link to theme-inlineinline-關鍵字的作用" title="Direct link to theme-inlineinline-關鍵字的作用" translate="no">​</a></h2>
<p>在前面的範例中，我們看到的都是 &nbsp;<code>@theme inline</code>，而不是單純的 &nbsp;<code>@theme</code>。這兩者之間的差異在於是否保留 CSS 變數的動態特性，而這個差異將會直接影響了暗色模式等主題切換功能的實現（會在後面的「方法二：使用語意化顏色」章節中詳細介紹）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="inline-關鍵字的作用保留動態特性"><strong>inline 關鍵字的作用：保留動態特性</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#inline-%E9%97%9C%E9%8D%B5%E5%AD%97%E7%9A%84%E4%BD%9C%E7%94%A8%E4%BF%9D%E7%95%99%E5%8B%95%E6%85%8B%E7%89%B9%E6%80%A7" class="hash-link" aria-label="Direct link to inline-關鍵字的作用保留動態特性" title="Direct link to inline-關鍵字的作用保留動態特性" translate="no">​</a></h3>
<p><code>inline</code>&nbsp; 這個關鍵字非常重要，它告訴 Tailwind：「這些主題變數的值是『動態的』，它們的值會在瀏覽器執行時 (runtime) 透過引用其他 CSS 變數來決定，而不是在編譯時 (build time) 就固定下來。」</p>
<p>讓我們用實際範例來理解：</p>
<p><strong>不加 &nbsp;<code>inline</code>&nbsp; 的情況：</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* 定義 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.8</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">250</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>TailwindCSS 在編譯時會生成：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 編譯出的全域變數 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.bg-primary</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* utility 使用上面的 --color-primary */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這樣做的問題是，<code>--color-primary</code>&nbsp; 的值在 &nbsp;<code>:root</code>&nbsp; 層級就被「鎖定」了。當在深層的 &nbsp;<code>.dark</code>&nbsp; 選擇器中改變 &nbsp;<code>--primary</code>&nbsp; 的值時（關於 &nbsp;<code>.dark</code>&nbsp; 的原理後面會詳細說明），<code>--color-primary</code>&nbsp; 可能無法即時反應這個變化，導致 &nbsp;<code>bg-primary</code>&nbsp; 拿到的仍然是舊的值。</p>
<p><strong>加上 &nbsp;<code>inline</code>&nbsp; 的情況：</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* 定義 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.8</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">250</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>TailwindCSS 會跳過中間的 &nbsp;<code>--color-primary</code>&nbsp; 變數，直接讓 utility class 引用你指定的語意變數：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector class" style="color:#00009f">.bg-primary</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 沒有中繼的 --color-primary，直接引用 --primary */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這樣一來，<code>--primary</code>&nbsp; 的值在任何層級被改動（無論是 &nbsp;<code>.dark</code>、<code>[data-theme="..."]</code>，甚至是某個特定的元件容器），<code>bg-primary</code>&nbsp; 都會即時地、正確地反映出最新的顏色。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實作亮暗主題---方法一使用-dark-前綴"><strong>實作亮暗主題 - 方法一：使用 dark: 前綴</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E5%AF%A6%E4%BD%9C%E4%BA%AE%E6%9A%97%E4%B8%BB%E9%A1%8C---%E6%96%B9%E6%B3%95%E4%B8%80%E4%BD%BF%E7%94%A8-dark-%E5%89%8D%E7%B6%B4" class="hash-link" aria-label="Direct link to 實作亮暗主題---方法一使用-dark-前綴" title="Direct link to 實作亮暗主題---方法一使用-dark-前綴" translate="no">​</a></h2>
<p>在理解了 &nbsp;<code>@theme inline</code>&nbsp; 的運作原理後，接下來探討如何實作完整的亮暗主題切換功能。</p>
<p>在 TailwindCSS 中，有<strong>兩種主要方式</strong>實現亮暗主題：</p>
<ol>
<li class=""><strong>方法一：使用 &nbsp;<code>dark:</code>&nbsp; 前綴</strong>（需要為每個樣式寫兩次）</li>
<li class=""><strong>方法二：使用語意化顏色</strong>（自動適應，推薦）</li>
</ol>
<p>本章節先介紹<strong>方法一</strong>，這種方式需要為每個樣式明確指定亮色和暗色兩種狀態。</p>
<p><strong>使用方式：</strong></p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-white dark:bg-gray-800 text-gray-900 dark:text-white</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  需要為每個樣式寫兩次</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>關鍵就在 &nbsp;<code>dark:</code>&nbsp; 這個前綴，我們稱之為<strong>變體 (Variant)</strong>。它就像一個條件觸發器，意思是：「當『dark』這個條件成立時，才套用後面的樣式」。</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>常見的變體有:</div><div class="admonitionContent_BuS1"><ul>
<li class=""><code>hover:</code>&nbsp;→ 滑鼠 hover 狀態才套用</li>
<li class=""><code>focus:</code>&nbsp;→ 元件被 focus 才套用</li>
<li class=""><code>dark:</code>&nbsp;→ 在 &nbsp;<code>.dark</code>&nbsp; 主題下才套用</li>
</ul></div></div>
<blockquote>
<p>那麼，這個叫做 &nbsp;dark&nbsp; 的條件到底是什麼？這就需要我們親自來定義它。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="用-custom-variant-定義-dark-變體"><strong>用 @custom-variant 定義 dark: 變體</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E7%94%A8-custom-variant-%E5%AE%9A%E7%BE%A9-dark-%E8%AE%8A%E9%AB%94" class="hash-link" aria-label="Direct link to 用-custom-variant-定義-dark-變體" title="Direct link to 用-custom-variant-定義-dark-變體" translate="no">​</a></h3>
<p>Tailwind v4 提供了 &nbsp;<code>@custom-variant</code>&nbsp; 工具，可以自訂變體。要定義 &nbsp;<code>dark:</code>&nbsp; 變體，需要在 CSS 中加上這一行：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@custom-variant</span><span class="token atrule" style="color:#00a4db"> dark </span><span class="token atrule punctuation" style="color:#393A34">(</span><span class="token atrule" style="color:#00a4db">&amp;</span><span class="token atrule punctuation" style="color:#393A34">:</span><span class="token atrule function" style="color:#d73a49">is</span><span class="token atrule punctuation" style="color:#393A34">(</span><span class="token atrule" style="color:#00a4db">.dark *</span><span class="token atrule punctuation" style="color:#393A34">)</span><span class="token atrule punctuation" style="color:#393A34">)</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* shadcn 預設定義 */</span><br></div></code></pre></div></div>
<p>我們把它拆解成三個部分來看：</p>
<ol>
<li class=""><strong><code>@custom-variant</code></strong>：這是告訴 Tailwind：「我要創造一個新的變體了！」</li>
<li class=""><strong><code>dark</code></strong>：這是你為這個變體取的名字。因為我們取名為 &nbsp;<code>dark</code>，所以之後就能用 &nbsp;<code>dark:</code>。如果你把它取名為 &nbsp;<code>night-mode</code>，那你之後就要寫 &nbsp;<code>night-mode:bg-primary</code>。</li>
<li class=""><strong><code>(&amp;:is(.dark *))</code></strong>：這是最關鍵的觸發規則，它在定義 CSS 選擇器。把它翻譯成白話文就是：「當我這個元素（<code>&amp;</code>）被放在任何一個帶有 &nbsp;<code>.dark</code>&nbsp;class 的祖先元素裡面時」。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="背後的編譯原理"><strong>背後的編譯原理</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E8%83%8C%E5%BE%8C%E7%9A%84%E7%B7%A8%E8%AD%AF%E5%8E%9F%E7%90%86" class="hash-link" aria-label="Direct link to 背後的編譯原理" title="Direct link to 背後的編譯原理" translate="no">​</a></h3>
<p>當寫下 &nbsp;<code>dark:bg-foreground dark:text-background</code>&nbsp; 時，背後的編譯流程如下：</p>
<p><strong>1. Tailwind 編譯基本的 utility class</strong></p>
<p>當 Tailwind 看到 &nbsp;<code>bg-background text-foreground</code>，它會根據 &nbsp;<code>@theme</code>&nbsp; 的設定產生基本的 utility class：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector class" style="color:#00009f">.bg-background</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--color-background</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.text-foreground</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--color-foreground</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>2. Tailwind 編譯 dark: 變體</strong></p>
<p>當 Tailwind 看到 &nbsp;<code>dark:bg-foreground dark:text-background</code>，它會去查找叫做 &nbsp;<code>dark</code>&nbsp; 的變體。它找到了你用 &nbsp;<code>@custom-variant</code>&nbsp; 定義的規則 &nbsp;<code>(&amp;:is(.dark *))</code>。於是，它會把這個規則和 utility class 結合起來，產生一個新的、帶有條件的 class：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* dark 變體展開後 → 等價於加上 .dark 包起來 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token selector" style="color:#00009f"> </span><span class="token selector class" style="color:#00009f">.dark</span><span class="token selector" style="color:#00009f">\</span><span class="token selector pseudo-class" style="color:#00009f">:bg-foreground</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--color-foreground</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token selector" style="color:#00009f"> </span><span class="token selector class" style="color:#00009f">.dark</span><span class="token selector" style="color:#00009f">\</span><span class="token selector pseudo-class" style="color:#00009f">:text-background</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--color-background</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這個新選擇器 &nbsp;<code>.dark .dark\:bg-foreground</code>&nbsp; 的意思是：「只有當一個元素同時擁有 &nbsp;<code>dark:bg-foreground</code>&nbsp; 這個 class，『並且』它是 &nbsp;<code>.dark</code>&nbsp; 元素的後代時，這個樣式才會生效。」</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>為什麼要用 &amp;<!-- -->:is<!-- -->(.dark *) 而不是 .dark &amp;？</div><div class="admonitionContent_BuS1"><p><code>&amp;:is(.dark *)</code> 允許 <code>.dark</code> class 在元素的<strong>任何祖先層級</strong>生效，而不是只能在直接父層。這樣不論 <code>.dark</code> 加在 <code>&lt;html&gt;</code> 還是 <code>&lt;body&gt;</code> 上，深層的子元素都能正確套用暗色樣式。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="在-react-中控制亮暗主題"><strong>在 React 中控制亮暗主題</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E5%9C%A8-react-%E4%B8%AD%E6%8E%A7%E5%88%B6%E4%BA%AE%E6%9A%97%E4%B8%BB%E9%A1%8C" class="hash-link" aria-label="Direct link to 在-react-中控制亮暗主題" title="Direct link to 在-react-中控制亮暗主題" translate="no">​</a></h3>
<p>根據上面解釋的原理，要讓 <code>dark:</code> 樣式生效，元素必須是 <code>.dark</code> 的後代。因此，最常見的做法是<strong>在 <code>&lt;html&gt;</code> 元素上切換 <code>.dark</code> class</strong>，這樣整個頁面的所有元素都會成為 <code>.dark</code> 的後代，所有 <code>dark:</code> 樣式就會同時生效：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useState</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useEffect </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"react"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">App</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">dark</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setDark</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">useEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 在 &lt;html&gt; 上切換 .dark class</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 讓所有 dark: 樣式同時生效或失效</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">documentElement</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">classList</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toggle</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"dark"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> dark</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">dark</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">min-h-screen grid place-items-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">px-4 py-2 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript function" style="color:#d73a49">setDark</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript" style="color:#00009f">d</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript operator" style="color:#393A34">!</span><span class="token tag script language-javascript" style="color:#00009f">d</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        切換 </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">dark </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Light"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Dark"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>完整流程：</strong></p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實作亮暗主題---方法二使用語意化顏色推薦"><strong>實作亮暗主題 - 方法二：使用語意化顏色（推薦）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E5%AF%A6%E4%BD%9C%E4%BA%AE%E6%9A%97%E4%B8%BB%E9%A1%8C---%E6%96%B9%E6%B3%95%E4%BA%8C%E4%BD%BF%E7%94%A8%E8%AA%9E%E6%84%8F%E5%8C%96%E9%A1%8F%E8%89%B2%E6%8E%A8%E8%96%A6" class="hash-link" aria-label="Direct link to 實作亮暗主題---方法二使用語意化顏色推薦" title="Direct link to 實作亮暗主題---方法二使用語意化顏色推薦" translate="no">​</a></h2>
<p>這是 shadcn/ui 採用的方法，也是前面「@theme inline：inline 關鍵字的作用」章節介紹的 &nbsp;<code>@theme inline</code>&nbsp; 與 &nbsp;<code>:root</code>/<code>.dark</code>&nbsp; 分工的實際應用。這種方法只需要定義一次樣式，顏色會自動適應主題。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="切換機制css-變數的覆蓋原理"><strong>切換機制：CSS 變數的覆蓋原理</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E5%88%87%E6%8F%9B%E6%A9%9F%E5%88%B6css-%E8%AE%8A%E6%95%B8%E7%9A%84%E8%A6%86%E8%93%8B%E5%8E%9F%E7%90%86" class="hash-link" aria-label="Direct link to 切換機制css-變數的覆蓋原理" title="Direct link to 切換機制css-變數的覆蓋原理" translate="no">​</a></h3>
<p>這個方法的核心在於 <strong>CSS 變數的覆蓋機制</strong>：</p>
<ul>
<li class="">當元素<strong>沒有</strong> <code>.dark</code> 祖先時，瀏覽器使用 <code>:root</code> 定義的變數值</li>
<li class="">當元素<strong>有</strong> <code>.dark</code> 祖先時，<code>.dark</code> 定義的變數值會覆蓋 <code>:root</code> 的值</li>
</ul>
<p>下圖展示了這個機制的運作流程：</p>
<p><img decoding="async" loading="lazy" alt="語意化顏色的運作流程" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4MDAgNDAwIj4KICA8ZGVmcz4KICAgIDxtYXJrZXIgaWQ9ImFycm93IiBtYXJrZXJXaWR0aD0iMTAiIG1hcmtlckhlaWdodD0iNyIgcmVmWD0iOSIgcmVmWT0iMy41IiBvcmllbnQ9ImF1dG8iPgogICAgICA8cG9seWdvbiBwb2ludHM9IjAgMCwgMTAgMy41LCAwIDciIGZpbGw9IiM2MGE1ZmEiLz4KICAgIDwvbWFya2VyPgogIDwvZGVmcz4KCiAgPCEtLSDog4zmma8gLS0+CiAgPHJlY3Qgd2lkdGg9IjgwMCIgaGVpZ2h0PSI0MDAiIGZpbGw9IiMwZjE3MmEiIHJ4PSIxMiIvPgogIAogIDwhLS0g5qiZ6aGMIC0tPgogIDx0ZXh0IHg9IjQwMCIgeT0iMzUiIGZpbGw9IiNmMWY1ZjkiIGZvbnQtc2l6ZT0iMTgiIGZvbnQtd2VpZ2h0PSJib2xkIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCBzYW5zLXNlcmlmIj7oqp7mhI/ljJbpoY/oibLnmoTpgYvkvZzmtYHnqIs8L3RleHQ+CgogIDwhLS0gU3RlcCAxOiBIVE1MIOe1kOaniyAtLT4KICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMCwgNjApIj4KICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIyMjAiIGhlaWdodD0iMTMwIiByeD0iMTAiIGZpbGw9IiMxZTI5M2IiIHN0cm9rZT0iIzQ3NTU2OSIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICA8dGV4dCB4PSIxMTAiIHk9IjI1IiBmaWxsPSIjOTRhM2I4IiBmb250LXNpemU9IjEyIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCBzYW5zLXNlcmlmIj5TdGVwIDE6IEhUTUwg57WQ5qeLPC90ZXh0PgogICAgCiAgICA8IS0tIOS6ruiJsuaooeW8jyAtLT4KICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwLCA0MCkiPgogICAgICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iOTAiIGhlaWdodD0iNzUiIHJ4PSI2IiBmaWxsPSIjZjhmYWZjIiBzdHJva2U9IiNlMmU4ZjAiLz4KICAgICAgPHRleHQgeD0iNDUiIHk9IjE4IiBmaWxsPSIjMWUyOTNiIiBmb250LXNpemU9IjEwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIj4mbHQ7aHRtbCZndDs8L3RleHQ+CiAgICAgIDx0ZXh0IHg9IjQ1IiB5PSIzNSIgZmlsbD0iIzY0NzQ4YiIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpIj7nhKEgLmRhcmsgY2xhc3M8L3RleHQ+CiAgICAgIDxjaXJjbGUgY3g9IjQ1IiBjeT0iNTUiIHI9IjEyIiBmaWxsPSIjZmJiZjI0Ii8+CiAgICAgIDx0ZXh0IHg9IjQ1IiB5PSI1OSIgZmlsbD0iIzFlMjkzYiIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+4piA77iPPC90ZXh0PgogICAgPC9nPgogICAgCiAgICA8IS0tIOaal+iJsuaooeW8jyAtLT4KICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyMCwgNDApIj4KICAgICAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjkwIiBoZWlnaHQ9Ijc1IiByeD0iNiIgZmlsbD0iIzFlMjkzYiIgc3Ryb2tlPSIjMzM0MTU1Ii8+CiAgICAgIDx0ZXh0IHg9IjQ1IiB5PSIxOCIgZmlsbD0iI2YxZjVmOSIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSI+Jmx0O2h0bWwgY2xhc3M9ImRhcmsiJmd0OzwvdGV4dD4KICAgICAgPHRleHQgeD0iNDUiIHk9IjM1IiBmaWxsPSIjOTRhM2I4IiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJzeXN0ZW0tdWkiPuaciSAuZGFyayBjbGFzczwvdGV4dD4KICAgICAgPGNpcmNsZSBjeD0iNDUiIGN5PSI1NSIgcj0iMTIiIGZpbGw9IiM2MzY2ZjEiLz4KICAgICAgPHRleHQgeD0iNDUiIHk9IjU5IiBmaWxsPSJ3aGl0ZSIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+8J+MmTwvdGV4dD4KICAgIDwvZz4KICA8L2c+CgogIDwhLS0g566t6aCtIDEgLS0+CiAgPHBhdGggZD0iTSAyNjAgMTI1IEwgMjkwIDEyNSIgc3Ryb2tlPSIjNjBhNWZhIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3cpIi8+CgogIDwhLS0gU3RlcCAyOiBDU1Mg6K6K5pW45p+l5om+IC0tPgogIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMCwgNjApIj4KICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMTMwIiByeD0iMTAiIGZpbGw9IiMxZTI5M2IiIHN0cm9rZT0iIzQ3NTU2OSIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgICA8dGV4dCB4PSIxMDAiIHk9IjI1IiBmaWxsPSIjOTRhM2I4IiBmb250LXNpemU9IjEyIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCBzYW5zLXNlcmlmIj5TdGVwIDI6IENTUyDorormlbjmn6Xmib48L3RleHQ+CiAgICAKICAgIDxyZWN0IHg9IjEwIiB5PSI0MCIgd2lkdGg9IjE4MCIgaGVpZ2h0PSI3NSIgcng9IjYiIGZpbGw9IiMzMTJlODEiLz4KICAgIDx0ZXh0IHg9IjEwMCIgeT0iNjAiIGZpbGw9IiNhNWI0ZmMiIGZvbnQtc2l6ZT0iMTAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiPmJnLWJhY2tncm91bmQ8L3RleHQ+CiAgICA8dGV4dCB4PSIxMDAiIHk9Ijc4IiBmaWxsPSIjYzRiNWZkIiBmb250LXNpemU9IjEwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIj7ihpM8L3RleHQ+CiAgICA8dGV4dCB4PSIxMDAiIHk9Ijk1IiBmaWxsPSIjZmJiZjI0IiBmb250LXNpemU9IjEwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIj52YXIoLS1iYWNrZ3JvdW5kKTwvdGV4dD4KICA8L2c+CgogIDwhLS0g566t6aCtIDIgLS0+CiAgPHBhdGggZD0iTSA1MTAgMTI1IEwgNTQwIDEyNSIgc3Ryb2tlPSIjNjBhNWZhIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiIG1hcmtlci1lbmQ9InVybCgjYXJyb3cpIi8+CgogIDwhLS0gU3RlcCAzOiDntZDmnpwgLS0+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTUwLCA2MCkiPgogICAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjIyMCIgaGVpZ2h0PSIxMzAiIHJ4PSIxMCIgZmlsbD0iIzFlMjkzYiIgc3Ryb2tlPSIjNDc1NTY5IiBzdHJva2Utd2lkdGg9IjIiLz4KICAgIDx0ZXh0IHg9IjExMCIgeT0iMjUiIGZpbGw9IiM5NGEzYjgiIGZvbnQtc2l6ZT0iMTIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJzeXN0ZW0tdWksIHNhbnMtc2VyaWYiPlN0ZXAgMzog5Y+W5b6X6aGP6Imy5YC8PC90ZXh0PgogICAgCiAgICA8IS0tIOS6ruiJsue1kOaenCAtLT4KICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwLCA0MCkiPgogICAgICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iOTAiIGhlaWdodD0iNzUiIHJ4PSI2IiBmaWxsPSIjZjhmYWZjIiBzdHJva2U9IiNlMmU4ZjAiLz4KICAgICAgPHRleHQgeD0iNDUiIHk9IjIwIiBmaWxsPSIjMWUyOTNiIiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJzeXN0ZW0tdWkiPuW+niA6cm9vdCDlj5blvpc8L3RleHQ+CiAgICAgIDxyZWN0IHg9IjE1IiB5PSIzMCIgd2lkdGg9IjYwIiBoZWlnaHQ9IjMwIiByeD0iNCIgZmlsbD0id2hpdGUiIHN0cm9rZT0iI2UyZThmMCIvPgogICAgICA8dGV4dCB4PSI0NSIgeT0iNTAiIGZpbGw9IiMxZTI5M2IiIGZvbnQtc2l6ZT0iOSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSI+d2hpdGU8L3RleHQ+CiAgICA8L2c+CiAgICAKICAgIDwhLS0g5pqX6Imy57WQ5p6cIC0tPgogICAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTIwLCA0MCkiPgogICAgICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iOTAiIGhlaWdodD0iNzUiIHJ4PSI2IiBmaWxsPSIjMWUyOTNiIiBzdHJva2U9IiMzMzQxNTUiLz4KICAgICAgPHRleHQgeD0iNDUiIHk9IjIwIiBmaWxsPSIjOTRhM2I4IiBmb250LXNpemU9IjkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtZmFtaWx5PSJzeXN0ZW0tdWkiPuW+niAuZGFyayDlj5blvpc8L3RleHQ+CiAgICAgIDxyZWN0IHg9IjE1IiB5PSIzMCIgd2lkdGg9IjYwIiBoZWlnaHQ9IjMwIiByeD0iNCIgZmlsbD0iIzFhMWExYSIgc3Ryb2tlPSIjMzM0MTU1Ii8+CiAgICAgIDx0ZXh0IHg9IjQ1IiB5PSI1MCIgZmlsbD0iIzk0YTNiOCIgZm9udC1zaXplPSI5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIj4jMWExYTFhPC90ZXh0PgogICAgPC9nPgogIDwvZz4KCiAgPCEtLSDpl5zpjbXoqqrmmI4gLS0+CiAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzAsIDIyMCkiPgogICAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9Ijc0MCIgaGVpZ2h0PSIxNTAiIHJ4PSIxMCIgZmlsbD0iIzE0NTMyZCIgc3Ryb2tlPSIjMjJjNTVlIiBzdHJva2Utd2lkdGg9IjIiLz4KICAgIDx0ZXh0IHg9IjM3MCIgeT0iMzAiIGZpbGw9IiM4NmVmYWMiIGZvbnQtc2l6ZT0iMTQiIGZvbnQtd2VpZ2h0PSJib2xkIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCBzYW5zLXNlcmlmIj7wn5SRIOmXnOmNteWOn+eQhu+8mkNTUyDorormlbjnmoTopobok4vmqZ/liLY8L3RleHQ+CiAgICAKICAgIDwhLS0g5bem6YKK77ya5Lqu6Imy5qih5byPIC0tPgogICAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjAsIDUwKSI+CiAgICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIzNDAiIGhlaWdodD0iODAiIHJ4PSI2IiBmaWxsPSIjMTY2NTM0Ii8+CiAgICAgIDx0ZXh0IHg9IjE3MCIgeT0iMjAiIGZpbGw9IiNiYmY3ZDAiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSJib2xkIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCBzYW5zLXNlcmlmIj7imIDvuI8g5Lqu6Imy5qih5byP77yI54ShIC5kYXJrIGNsYXNz77yJPC90ZXh0PgogICAgICA8dGV4dCB4PSIyMCIgeT0iNDUiIGZpbGw9IiM4NmVmYWMiIGZvbnQtc2l6ZT0iMTEiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiPjpyb290IHsgLS1iYWNrZ3JvdW5kOiB3aGl0ZTsgfTwvdGV4dD4KICAgICAgPHRleHQgeD0iMjAiIHk9IjY1IiBmaWxsPSIjNGFkZTgwIiBmb250LXNpemU9IjEwIiBmb250LWZhbWlseT0ic3lzdGVtLXVpIj7ihpIg54CP6Ka95Zmo5L2/55SoIDpyb290IOWumue+qeeahOWAvDwvdGV4dD4KICAgIDwvZz4KICAgIAogICAgPCEtLSDlj7PpgorvvJrmmpfoibLmqKHlvI8gLS0+CiAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzODAsIDUwKSI+CiAgICAgIDxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIzNDAiIGhlaWdodD0iODAiIHJ4PSI2IiBmaWxsPSIjMTY2NTM0Ii8+CiAgICAgIDx0ZXh0IHg9IjE3MCIgeT0iMjAiIGZpbGw9IiNiYmY3ZDAiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtd2VpZ2h0PSJib2xkIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLCBzYW5zLXNlcmlmIj7wn4yZIOaal+iJsuaooeW8j++8iOaciSAuZGFyayBjbGFzc++8iTwvdGV4dD4KICAgICAgPHRleHQgeD0iMjAiIHk9IjQ1IiBmaWxsPSIjODZlZmFjIiBmb250LXNpemU9IjExIiBmb250LWZhbWlseT0ibW9ub3NwYWNlIj4uZGFyayB7IC0tYmFja2dyb3VuZDogIzFhMWExYTsgfTwvdGV4dD4KICAgICAgPHRleHQgeD0iMjAiIHk9IjY1IiBmaWxsPSIjNGFkZTgwIiBmb250LXNpemU9IjEwIiBmb250LWZhbWlseT0ic3lzdGVtLXVpIj7ihpIgLmRhcmsg55qE5a6a576p6KaG6JOLIDpyb290IOeahOWAvDwvdGV4dD4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=" width="800" height="400" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview: 語意化顏色的運作流程"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="設定方式"><strong>設定方式</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E8%A8%AD%E5%AE%9A%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to 設定方式" title="Direct link to 設定方式" translate="no">​</a></h3>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* index.css */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--foreground</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">white</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 亮色模式的背景色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">black</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 亮色模式的文字色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#1a1a1a</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 暗色模式的背景色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">white</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 暗色模式的文字色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>使用方式：</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-background text-foreground</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  自動適應亮暗主題，不需要 dark: 前綴</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>之所以可以這樣自動切換，關鍵在於：</p>
<ol>
<li class=""><code>bg-background</code> 最終會被編譯成 <code>background-color: var(--background)</code></li>
<li class="">當 <code>&lt;html&gt;</code> 沒有 <code>.dark</code> class 時，<code>--background</code> 的值是 <code>:root</code> 定義的 <code>white</code></li>
<li class="">當 <code>&lt;html&gt;</code> 有 <code>.dark</code> class 時，<code>.dark</code> 定義的 <code>#1a1a1a</code> 會覆蓋 <code>:root</code> 的值</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="theme-inline-與-dark-的分工"><strong>@theme inline 與 <!-- -->:root<!-- -->/.dark 的分工</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#theme-inline-%E8%88%87-dark-%E7%9A%84%E5%88%86%E5%B7%A5" class="hash-link" aria-label="Direct link to theme-inline-與-dark-的分工" title="Direct link to theme-inline-與-dark-的分工" translate="no">​</a></h3>
<p>在這個設計中，Design Token 的定義分成兩層：</p>
<p><strong>第一層：<code>:root</code>&nbsp; 和 &nbsp;<code>.dark</code>&nbsp; 定義原始值</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">white</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 亮色模式的背景色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">black</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 亮色模式的文字色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.dark</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#1a1a1a</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 暗色模式的背景色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">white</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 暗色模式的文字色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這些是 &nbsp;<strong>CSS 變數</strong>，只能用 &nbsp;<code>var(--background)</code>&nbsp; 的方式在 CSS 中使用，<strong>不能直接用於 TailwindCSS class</strong>。</p>
<p><strong>第二層：<code>@theme inline</code>&nbsp; 將原始值轉換成 TailwindCSS Token</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-background</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--background</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">/* 將 --background 轉換成 TailwindCSS 可用的 Token */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--foreground</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這些是 &nbsp;<strong>TailwindCSS Token</strong>，可以直接用於 utility class：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// ✅ 可以使用：bg-background 會產生 background-color: var(--background)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-background text-foreground</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">// ❌ 雖然技術上可行，但不建議這樣寫</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">// 因為 TailwindCSS 無法追蹤這個變數，也無法在 IDE 中提供自動完成</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-[var(--background)]</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>這種分層設計的好處是：</p>
<ol>
<li class=""><strong>在  <code>:root</code>/<code>.dark</code>  中切換主題值</strong>（例如暗色模式）</li>
<li class=""><strong>在  <code>@theme inline</code>  中定義 TailwindCSS Token</strong></li>
<li class=""><strong>Token 會自動反映主題值的變化</strong></li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>關鍵在於  <code>inline</code></div><div class="admonitionContent_BuS1"><p>因為使用了  <code>@theme inline</code>，TailwindCSS 會保留 CSS 變數的動態特性，讓 utility class 直接引用  <code>var(--background)</code>  而不是固定的值。這樣當  <code>.dark</code> class 覆蓋 CSS 變數時，utility class 就能自動反映新的值。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="在-react-中控制主題"><strong>在 React 中控制主題</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E5%9C%A8-react-%E4%B8%AD%E6%8E%A7%E5%88%B6%E4%B8%BB%E9%A1%8C" class="hash-link" aria-label="Direct link to 在-react-中控制主題" title="Direct link to 在-react-中控制主題" translate="no">​</a></h3>
<p>與方法一相同，在 React 中透過切換 <code>&lt;html&gt;</code> 的 <code>.dark</code> class 來控制主題。差別在於這裡<strong>不需要為每個樣式寫 <code>dark:</code> 前綴</strong>，因為 CSS 變數會根據 <code>.dark</code> 的存在與否自動切換：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useState</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useEffect </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"react"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">App</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">dark</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setDark</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">useEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">documentElement</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">classList</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toggle</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"dark"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> dark</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">dark</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">min-h-screen grid place-items-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">px-4 py-2 rounded-md bg-primary text-primary-foreground</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript function" style="color:#d73a49">setDark</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript" style="color:#00009f">d</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript operator" style="color:#393A34">!</span><span class="token tag script language-javascript" style="color:#00009f">d</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        切換 </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">dark </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Light"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Dark"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>完整流程：</strong></p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="兩種方法的對比"><strong>兩種方法的對比</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E5%85%A9%E7%A8%AE%E6%96%B9%E6%B3%95%E7%9A%84%E5%B0%8D%E6%AF%94" class="hash-link" aria-label="Direct link to 兩種方法的對比" title="Direct link to 兩種方法的對比" translate="no">​</a></h3>
<table><thead><tr><th>比較項目</th><th>方法一：<code>dark:</code> 前綴</th><th>方法二：語意化顏色（推薦）</th></tr></thead><tbody><tr><td><strong>寫法</strong></td><td>每個樣式寫兩次</td><td>只寫一次</td></tr><tr><td><strong>維護性</strong></td><td>較差，修改需改多處</td><td>較佳，改 CSS 變數即可</td></tr><tr><td><strong>程式碼量</strong></td><td>較多</td><td>較少</td></tr><tr><td><strong>用途</strong></td><td>臨時覆蓋、特殊情況</td><td>系統性的主題設計</td></tr><tr><td><strong>shadcn/ui</strong></td><td>❌ 非主要方式</td><td>✅ 預設使用</td></tr></tbody></table>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// ❌ 方法一：每個顏色都要寫兩次</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-white dark:bg-gray-900 text-black dark:text-white border-gray-200 dark:border-gray-700</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  內容</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// ✅ 方法二：語意化顏色，簡潔易維護</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-background text-foreground border-border</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  內容</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="layer組織樣式"><strong>@layer：組織樣式</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#layer%E7%B5%84%E7%B9%94%E6%A8%A3%E5%BC%8F" class="hash-link" aria-label="Direct link to layer組織樣式" title="Direct link to layer組織樣式" translate="no">​</a></h2>
<p><code>@layer</code>&nbsp; 讓你可以控制樣式的優先順序，並且組織自訂的 CSS。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailwindcss-的三個層級"><strong>TailwindCSS 的三個層級</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#tailwindcss-%E7%9A%84%E4%B8%89%E5%80%8B%E5%B1%A4%E7%B4%9A" class="hash-link" aria-label="Direct link to tailwindcss-的三個層級" title="Direct link to tailwindcss-的三個層級" translate="no">​</a></h3>
<p>TailwindCSS 將 CSS 分成三個層級：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> base</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 基礎樣式：重置、預設樣式 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> components</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 元件樣式：可重複使用的元件 class */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> utilities</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 工具樣式：單一用途的 utility class */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>層級優先順序：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">utilities &gt; components &gt; base</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="實務範例layer-base-的應用"><strong>實務範例：@layer base 的應用</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E5%AF%A6%E5%8B%99%E7%AF%84%E4%BE%8Blayer-base-%E7%9A%84%E6%87%89%E7%94%A8" class="hash-link" aria-label="Direct link to 實務範例layer-base-的應用" title="Direct link to 實務範例layer-base-的應用" translate="no">​</a></h3>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> base</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector" style="color:#00009f">*</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> border-border outline-ring/</span><span class="token atrule number" style="color:#36acaa">50</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector" style="color:#00009f">body</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> bg-background text-foreground</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這段程式碼做了什麼？</p>
<ol>
<li class=""><strong>所有元素（*）</strong>：<!-- -->
<ul>
<li class="">預設邊框顏色使用 &nbsp;<code>border-border</code></li>
<li class="">預設 outline 顏色使用 &nbsp;<code>outline-ring/50</code>（50% 透明度）</li>
</ul>
</li>
<li class=""><strong>body 元素</strong>：<!-- -->
<ul>
<li class="">背景色使用 &nbsp;<code>bg-background</code></li>
<li class="">文字顏色使用 &nbsp;<code>text-foreground</code></li>
</ul>
</li>
</ol>
<p><strong>@apply 指令：</strong></p>
<p><code>@apply</code>&nbsp; 讓你可以在 CSS 中使用 TailwindCSS 的 utility classes：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token selector class" style="color:#00009f">.my-button</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> bg-blue-500 text-white px-4 py-2 rounded </span><span class="token atrule property" style="color:#36acaa">hover</span><span class="token atrule punctuation" style="color:#393A34">:</span><span class="token atrule" style="color:#00a4db">bg-blue-600</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="什麼時候使用-layer"><strong>什麼時候使用 @layer？</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#%E4%BB%80%E9%BA%BC%E6%99%82%E5%80%99%E4%BD%BF%E7%94%A8-layer" class="hash-link" aria-label="Direct link to 什麼時候使用-layer" title="Direct link to 什麼時候使用-layer" translate="no">​</a></h3>
<p>三個層級的使用時機可以用「<strong>你想要改變什麼？</strong>」來判斷：</p>
<p><strong>@layer base：改變 HTML 元素的預設樣式</strong></p>
<p><strong>使用時機：</strong>&nbsp; 當你想要改變「所有 &nbsp;<code>&lt;h1&gt;</code>」、「所有 &nbsp;<code>&lt;a&gt;</code>」、「所有 &nbsp;<code>&lt;body&gt;</code>」等 &nbsp;<strong>HTML 元素本身</strong>&nbsp; 的預設外觀時。</p>
<p><strong>特徵：</strong></p>
<ul>
<li class="">針對 HTML 標籤（<code>h1</code>,&nbsp;<code>body</code>,&nbsp;<code>a</code>,&nbsp;&nbsp; 等）</li>
<li class="">全域生效，影響整個網站</li>
<li class="">通常在專案初期設定一次就不再修改</li>
</ul>
<p><strong>範例：</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> base</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 設定所有 h1 的預設樣式 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector" style="color:#00009f">h1</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> text-4</span><span class="token atrule unit" style="color:#00a4db">xl</span><span class="token atrule" style="color:#00a4db"> font-bold</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 設定所有連結的預設樣式 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector" style="color:#00009f">a</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> text-blue-500 </span><span class="token atrule property" style="color:#36acaa">hover</span><span class="token atrule punctuation" style="color:#393A34">:</span><span class="token atrule" style="color:#00a4db">underline</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 設定所有元素的預設邊框和 outline */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector" style="color:#00009f">*</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> border-border outline-ring/</span><span class="token atrule number" style="color:#36acaa">50</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>實際效果：</strong>&nbsp; 之後在 HTML 中寫 &nbsp;<code>&lt;h1&gt;標題&lt;/h1&gt;</code>，就會自動套用 &nbsp;<code>text-4xl font-bold</code>，不需要額外加 class。</p>
<hr>
<p><strong>@layer components：建立可重複使用的元件 class</strong></p>
<p><strong>使用時機：</strong>&nbsp; 當你有一組樣式需要在多個地方重複使用，並且這組樣式代表一個「元件」（如按鈕、卡片）時。</p>
<p><strong>特徵：</strong></p>
<ul>
<li class="">針對自訂的 class 名稱（<code>.btn-primary</code>,&nbsp;<code>.card</code>&nbsp; 等）</li>
<li class="">需要在 HTML 中手動加上 class 才會生效</li>
<li class="">通常用於建立設計系統中的元件</li>
</ul>
<p><strong>範例：</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> components</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.btn-primary</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> bg-blue-500 text-white px-4 py-2 rounded </span><span class="token atrule property" style="color:#36acaa">hover</span><span class="token atrule punctuation" style="color:#393A34">:</span><span class="token atrule" style="color:#00a4db">bg-blue-600</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.card</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token atrule rule" style="color:#00a4db">@apply</span><span class="token atrule" style="color:#00a4db"> bg-white rounded-lg shadow-md p-6</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>使用方式：</strong></p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">btn-primary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">按鈕</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">card</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">卡片內容</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意</div><div class="admonitionContent_BuS1"><p>在現代的 TailwindCSS 開發中，通常<strong>不建議過度使用 @layer components</strong>。因為這會失去 Utility-First 的優勢。更好的做法是直接在 React/Vue 元件中組合 utility classes，或使用 CVA（下一篇文章會介紹）。</p></div></div>
<hr>
<p><strong>@layer utilities：建立自訂的 utility class</strong></p>
<p><strong>使用時機：</strong>&nbsp; 當 TailwindCSS 內建的 utility class 不夠用，你需要新增一個「單一用途」的工具類別時。</p>
<p><strong>特徵：</strong></p>
<ul>
<li class="">針對自訂的 utility class（<code>.text-balance</code>,&nbsp;<code>.scrollbar-hide</code>&nbsp; 等）</li>
<li class="">每個 class 只做一件事（單一職責）</li>
<li class="">用來擴充 TailwindCSS 的工具類別</li>
</ul>
<p><strong>範例：</strong></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@layer</span><span class="token atrule" style="color:#00a4db"> utilities</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 新增文字平衡排版 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.text-balance</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">text-wrap</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> balance</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 新增隱藏捲軸的工具 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.scrollbar-hide</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">-ms-overflow-style</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> none</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">scrollbar-width</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> none</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token selector class" style="color:#00009f">.scrollbar-hide</span><span class="token selector pseudo-element" style="color:#00009f">::-webkit-scrollbar</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">display</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> none</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>使用方式：</strong></p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-balance</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">這段標題會使用平衡排版</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">overflow-auto scrollbar-hide</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">捲動區域，但不顯示捲軸</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<hr>
<p><strong>快速判斷表</strong></p>
<table><thead><tr><th>我想要...</th><th>使用哪個 layer</th><th>範例</th></tr></thead><tbody><tr><td>改變所有 &nbsp;<code>&lt;h1&gt;</code>&nbsp; 的預設樣式</td><td><code>@layer base</code></td><td><code>h1 { @apply text-4xl; }</code></td></tr><tr><td>建立一個按鈕元件 class</td><td><code>@layer components</code></td><td><code>.btn { @apply px-4 py-2; }</code></td></tr><tr><td>新增一個工具 class（如隱藏捲軸）</td><td><code>@layer utilities</code></td><td><code>.scrollbar-hide { ... }</code></td></tr><tr><td>在 React 元件中組合樣式</td><td><strong>不使用 @layer</strong>，直接用 utility classes</td><td><code>&lt;div className="px-4 py-2 bg-blue-500"&gt;</code></td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-5#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://tailwindcss.com/docs/v4-beta" target="_blank" rel="noopener noreferrer" class=""><strong>TailwindCSS v4 Beta Documentation</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/v4-beta#theme" target="_blank" rel="noopener noreferrer" class=""><strong>Theme Configuration - @theme</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/v4-beta#custom-variants" target="_blank" rel="noopener noreferrer" class=""><strong>Custom Variants - @custom-variant</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/v4-beta#layers" target="_blank" rel="noopener noreferrer" class=""><strong>Layers - @layer</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/dark-mode" target="_blank" rel="noopener noreferrer" class=""><strong>Dark Mode</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="shadcn/ui" term="shadcn/ui"/>
        <category label="TailwindCSS" term="TailwindCSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 shadcn/ui 前該補的 TailwindCSS 基礎知識(四) - 透過 @theme 自訂設計系統]]></title>
        <id>https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4</id>
        <link href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4"/>
        <updated>2025-11-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇技術筆記深入解析 TailwindCSS v4 的 @theme 功能，說明如何透過 CSS 變數定義設計 token，並自動產生對應的 utility classes。涵蓋顏色系統、圓角、字型、間距的客製化方式，以及 shadcn/ui 使用的語意化顏色與深色模式實作。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本文是「使用 shadcn/ui 前該補的 TailwindCSS 基礎知識」系列文章的第四篇</p>
<p><strong>系列文章：</strong></p>
<ol>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-1" target="_blank" rel="noopener noreferrer" class="">從 MUI 到 TailwindCSS 設計哲學的轉變</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-2" target="_blank" rel="noopener noreferrer" class="">理解 TailwindCSS 的運作原理</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-3" target="_blank" rel="noopener noreferrer" class="">TailwindCSS v4 內建 Utility Classes 速查</a></li>
<li class=""><strong>透過 @theme 自訂設計系統（本篇）</strong></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-5" target="_blank" rel="noopener noreferrer" class="">解析 shadcn/ui 的設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-6" target="_blank" rel="noopener noreferrer" class="">shadcn/ui 生態系工具鏈</a></li>
</ol>
</blockquote>
<p>在上一篇中，我們學習了 TailwindCSS 內建的 utility classes。但在實際專案中，你可能會需要：</p>
<ul>
<li class="">定義品牌專屬的顏色（如 <code>primary</code>、<code>secondary</code>）</li>
<li class="">統一管理設計 token（如圓角大小、字型）</li>
<li class="">建立可主題化的設計系統</li>
</ul>
<p>這就是 <code>@theme</code> 的用途。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="theme-的核心概念"><strong>@theme 的核心概念</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#theme-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="Direct link to theme-的核心概念" title="Direct link to theme-的核心概念" translate="no">​</a></h2>
<p><code>@theme</code> 是 TailwindCSS v4 的新功能，讓你可以在 CSS 中定義<strong>設計 token</strong>，TailwindCSS 會自動將這些 token 轉換成對應的 utility classes。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="命名空間規則"><strong>命名空間規則</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E5%91%BD%E5%90%8D%E7%A9%BA%E9%96%93%E8%A6%8F%E5%89%87" class="hash-link" aria-label="Direct link to 命名空間規則" title="Direct link to 命名空間規則" translate="no">​</a></h3>
<p>這是最重要的概念：TailwindCSS 會根據 <strong>CSS 變數的前綴</strong> 來決定要產生哪些 utility classes。</p>
<table><thead><tr><th>CSS 變數前綴</th><th>產生的 utility classes</th><th>說明</th></tr></thead><tbody><tr><td><code>--color-*</code></td><td><code>bg-*</code>、<code>text-*</code>、<code>border-*</code>、<code>ring-*</code> 等</td><td>所有跟顏色相關的屬性</td></tr><tr><td><code>--spacing-*</code></td><td><code>p-*</code>、<code>m-*</code>、<code>gap-*</code>、<code>w-*</code>、<code>h-*</code> 等</td><td>所有跟間距相關的屬性</td></tr><tr><td><code>--radius-*</code></td><td><code>rounded-*</code></td><td>圓角</td></tr><tr><td><code>--font-family-*</code></td><td><code>font-*</code></td><td>字型種類</td></tr><tr><td><code>--font-size-*</code></td><td><code>text-*</code></td><td>字型大小</td></tr><tr><td><code>--font-weight-*</code></td><td><code>font-*</code></td><td>字型粗細</td></tr><tr><td><code>--line-height-*</code></td><td><code>leading-*</code></td><td>行高</td></tr><tr><td><code>--letter-spacing-*</code></td><td><code>tracking-*</code></td><td>字距</td></tr><tr><td><code>--shadow-*</code></td><td><code>shadow-*</code></td><td>陰影</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="轉換規則"><strong>轉換規則</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E8%BD%89%E6%8F%9B%E8%A6%8F%E5%89%87" class="hash-link" aria-label="Direct link to 轉換規則" title="Direct link to 轉換規則" translate="no">​</a></h3>
<p>轉換時會移除「類型前綴」：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">--color-primary       → bg-primary, text-primary, border-primary ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">--radius-lg           → rounded-lg</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">--font-family-heading → font-heading</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">--spacing-sidebar     → p-sidebar, m-sidebar, w-sidebar ...</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="顏色系統的客製化"><strong>顏色系統的客製化</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E9%A1%8F%E8%89%B2%E7%B3%BB%E7%B5%B1%E7%9A%84%E5%AE%A2%E8%A3%BD%E5%8C%96" class="hash-link" aria-label="Direct link to 顏色系統的客製化" title="Direct link to 顏色系統的客製化" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="基礎用法"><strong>基礎用法</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E5%9F%BA%E7%A4%8E%E7%94%A8%E6%B3%95" class="hash-link" aria-label="Direct link to 基礎用法" title="Direct link to 基礎用法" translate="no">​</a></h3>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* index.css */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* 定義顏色 token */</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-secondary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.9245</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.0138</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">92.9892</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>定義後，你就可以在 JSX 中使用：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-primary text-white</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">主要按鈕</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-secondary text-black</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">次要按鈕</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2 border-primary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">主色邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span><strong>產生了哪些 utility classes？</strong></div><div class="admonitionContent_BuS1"><p>定義 <code>--color-primary</code> 後，TailwindCSS 會自動產生：</p><ul>
<li class=""><code>bg-primary</code>（背景色）</li>
<li class=""><code>text-primary</code>（文字色）</li>
<li class=""><code>border-primary</code>（邊框色）</li>
<li class=""><code>ring-primary</code>（ring 顏色）</li>
<li class=""><code>shadow-primary</code>（陰影顏色）</li>
<li class=""><code>from-primary</code> / <code>to-primary</code> / <code>via-primary</code>（漸層）</li>
<li class="">... 以及所有其他需要顏色的 utility classes</li>
</ul></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="shadcnui-的顏色系統"><strong>Shadcn/ui 的顏色系統</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#shadcnui-%E7%9A%84%E9%A1%8F%E8%89%B2%E7%B3%BB%E7%B5%B1" class="hash-link" aria-label="Direct link to shadcnui-的顏色系統" title="Direct link to shadcnui-的顏色系統" translate="no">​</a></h3>
<p>shadcn/ui 使用了一個更進階的模式：將 CSS 變數分成兩層。</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* index.css */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* Layer 1: TailwindCSS 的設計 token（會產生 utility classes） */</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary-foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--primary-foreground</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-destructive</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--destructive</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-destructive-foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--destructive-foreground</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* Layer 2: 實際的顏色值 */</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--primary-foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--destructive</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.5</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.2</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">30</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--destructive-foreground</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>為什麼要分兩層？</strong></p>
<ol>
<li class=""><code>@theme inline</code> 中的變數會讓 TailwindCSS 產生 utility classes</li>
<li class=""><code>:root</code> 中的變數只是存放實際顏色值</li>
<li class="">這樣可以在 <code>.dark</code> 中覆蓋顏色值，而不需要重新定義 @theme</li>
</ol>
<p>使用方式：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// primary 是背景色，primary-foreground 是該背景上的文字色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-primary text-primary-foreground</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  主要按鈕</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// destructive 是警告/刪除用的紅色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-destructive text-destructive-foreground</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  刪除按鈕</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span><strong>什麼是 foreground？</strong></div><div class="admonitionContent_BuS1"><p><code>foreground</code> 是「前景色」，通常指該背景色上的文字顏色。</p><p>shadcn/ui 會為每個語意化顏色提供一對顏色：</p><ul>
<li class="">主色（如 <code>primary</code>）→ 作為背景色</li>
<li class="">前景色（如 <code>primary-foreground</code>）→ 作為該背景上的文字顏色</li>
</ul><p>這確保了文字與背景之間有足夠的對比度。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="語意化顏色的好處"><strong>語意化顏色的好處</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E8%AA%9E%E6%84%8F%E5%8C%96%E9%A1%8F%E8%89%B2%E7%9A%84%E5%A5%BD%E8%99%95" class="hash-link" aria-label="Direct link to 語意化顏色的好處" title="Direct link to 語意化顏色的好處" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// ❌ 使用固定顏色：如果要改主題色，要改很多地方</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500 text-white</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">按鈕</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-blue-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-blue-600</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">文字</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// ✅ 使用語意化顏色：只需要改 CSS 變數，所有地方自動更新</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-primary text-primary-foreground</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">按鈕</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-primary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-primary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">文字</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="圓角的客製化"><strong>圓角的客製化</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E5%9C%93%E8%A7%92%E7%9A%84%E5%AE%A2%E8%A3%BD%E5%8C%96" class="hash-link" aria-label="Direct link to 圓角的客製化" title="Direct link to 圓角的客製化" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="定義語意化圓角"><strong>定義語意化圓角</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E5%AE%9A%E7%BE%A9%E8%AA%9E%E6%84%8F%E5%8C%96%E5%9C%93%E8%A7%92" class="hash-link" aria-label="Direct link to 定義語意化圓角" title="Direct link to 定義語意化圓角" translate="no">​</a></h3>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* index.css */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-sm</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token unit">px</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-md</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token unit">px</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-lg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius-xl</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calc</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token unit">px</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--radius</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>使用方式：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-sm</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">較小圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-md</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">中等圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">標準圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">較大圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span><strong>為什麼用 calc？</strong></div><div class="admonitionContent_BuS1"><p>使用 <code>calc(var(--radius) - 2px)</code> 的好處是：只要修改 <code>:root</code> 中的 <code>--radius</code> 值，所有圓角大小會等比例調整。</p></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="字型的客製化"><strong>字型的客製化</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E5%AD%97%E5%9E%8B%E7%9A%84%E5%AE%A2%E8%A3%BD%E5%8C%96" class="hash-link" aria-label="Direct link to 字型的客製化" title="Direct link to 字型的客製化" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="定義自訂字型"><strong>定義自訂字型</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E5%AE%9A%E7%BE%A9%E8%87%AA%E8%A8%82%E5%AD%97%E5%9E%8B" class="hash-link" aria-label="Direct link to 定義自訂字型" title="Direct link to 定義自訂字型" translate="no">​</a></h3>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* index.css */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--font-family-sans</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Inter"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ui-sans-serif</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> system-ui</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sans-serif</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--font-family-heading</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Cal Sans"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Inter"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> sans-serif</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--font-family-mono</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Fira Code"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ui-monospace</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> monospace</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>使用方式：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-sans</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">內文使用 Inter</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-heading</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">標題使用 Cal Sans</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">code</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-mono</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">程式碼使用 Fira Code</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">code</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="間距的客製化"><strong>間距的客製化</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E9%96%93%E8%B7%9D%E7%9A%84%E5%AE%A2%E8%A3%BD%E5%8C%96" class="hash-link" aria-label="Direct link to 間距的客製化" title="Direct link to 間距的客製化" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="定義語意化間距"><strong>定義語意化間距</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#%E5%AE%9A%E7%BE%A9%E8%AA%9E%E6%84%8F%E5%8C%96%E9%96%93%E8%B7%9D" class="hash-link" aria-label="Direct link to 定義語意化間距" title="Direct link to 定義語意化間距" translate="no">​</a></h3>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* index.css */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token atrule" style="color:#00a4db"> inline</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--spacing-sidebar</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">16</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--spacing-header</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--spacing-content</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>使用方式：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">aside</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-sidebar</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">側邊欄寬度 16rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">aside</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">header</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">h-header</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">頂部高度 4rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">header</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">main</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">p-content</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">內容區域內距 2rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">main</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://tailwindcss.com/docs/customizing-colors" target="_blank" rel="noopener noreferrer" class=""><strong>Customizing Colors - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/v4-beta#theme" target="_blank" rel="noopener noreferrer" class=""><strong>Theme Configuration - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/font-family" target="_blank" rel="noopener noreferrer" class=""><strong>Font Family - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/border-radius" target="_blank" rel="noopener noreferrer" class=""><strong>Border Radius - TailwindCSS</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="shadcn/ui" term="shadcn/ui"/>
        <category label="TailwindCSS" term="TailwindCSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 shadcn/ui 前該補的 TailwindCSS 基礎知識(三) - TailwindCSS v4 內建 Utility Classes 速查]]></title>
        <id>https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3</id>
        <link href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3"/>
        <updated>2025-11-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇技術筆記整理了 TailwindCSS v4 的核心語法速查，涵蓋顏色系統、字型設定、間距配置、版面布局、響應式設計等基礎概念，並提供實用的程式碼範例，幫助開發者快速掌握 TailwindCSS 的使用方式，為學習 shadcn/ui 打下紮實基礎。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本文是「使用 shadcn/ui 前該補的 TailwindCSS 基礎知識」系列文章的第三篇</p>
<p><strong>系列文章：</strong></p>
<ol>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-1" target="_blank" rel="noopener noreferrer" class="">從 MUI 到 TailwindCSS 設計哲學的轉變</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-2" target="_blank" rel="noopener noreferrer" class="">理解 TailwindCSS 的運作原理</a></li>
<li class=""><strong>TailwindCSS v4 內建 Utility Classes 速查（本篇）</strong></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-4" target="_blank" rel="noopener noreferrer" class="">透過 @theme 自訂設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-5" target="_blank" rel="noopener noreferrer" class="">解析 shadcn/ui 的設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-6" target="_blank" rel="noopener noreferrer" class="">shadcn/ui 生態系工具鏈</a></li>
</ol>
</blockquote>
<p>這篇文章介紹的所有 utility classes 都是 TailwindCSS <strong>預設內建</strong>的，只要安裝 TailwindCSS 就能直接使用，不需要任何額外配置。</p>
<p>如果你想了解如何透過 <code>@theme</code> 自訂設計系統（例如 shadcn/ui 的語意化顏色），請參考下一篇：<a class="" href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4">透過 @theme 自訂設計系統</a>。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="顏色系統"><strong>顏色系統</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%A1%8F%E8%89%B2%E7%B3%BB%E7%B5%B1" class="hash-link" aria-label="Direct link to 顏色系統" title="Direct link to 顏色系統" translate="no">​</a></h2>
<p>TailwindCSS 內建了豐富的調色盤，格式為：<strong><code>{屬性}-{顏色}-{深淺度}</code></strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="顏色相關的-utility-classes"><strong>顏色相關的 Utility Classes</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%A1%8F%E8%89%B2%E7%9B%B8%E9%97%9C%E7%9A%84-utility-classes" class="hash-link" aria-label="Direct link to 顏色相關的-utility-classes" title="Direct link to 顏色相關的-utility-classes" translate="no">​</a></h3>
<p>同一個顏色可以套用到不同的 CSS 屬性上，以下是最常用的幾種：</p>
<table><thead><tr><th>前綴</th><th>對應的 CSS 屬性</th><th>範例</th></tr></thead><tbody><tr><td><code>bg-</code></td><td><code>background-color</code></td><td><code>bg-blue-500</code></td></tr><tr><td><code>text-</code></td><td><code>color</code></td><td><code>text-blue-500</code></td></tr><tr><td><code>border-</code></td><td><code>border-color</code></td><td><code>border-blue-500</code></td></tr><tr><td><code>ring-</code></td><td><code>box-shadow</code>（外框環）</td><td><code>ring-blue-500</code></td></tr><tr><td><code>outline-</code></td><td><code>outline-color</code></td><td><code>outline-blue-500</code></td></tr><tr><td><code>shadow-</code></td><td><code>box-shadow</code>（陰影色）</td><td><code>shadow-blue-500</code></td></tr><tr><td><code>accent-</code></td><td><code>accent-color</code>（表單元素）</td><td><code>accent-blue-500</code></td></tr><tr><td><code>caret-</code></td><td><code>caret-color</code>（游標色）</td><td><code>caret-blue-500</code></td></tr><tr><td><code>fill-</code></td><td><code>fill</code>（SVG 填充）</td><td><code>fill-blue-500</code></td></tr><tr><td><code>stroke-</code></td><td><code>stroke</code>（SVG 線條）</td><td><code>stroke-blue-500</code></td></tr><tr><td><code>from-</code> / <code>to-</code> / <code>via-</code></td><td>漸層顏色</td><td><code>from-blue-500</code></td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="內建顏色名稱"><strong>內建顏色名稱</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%85%A7%E5%BB%BA%E9%A1%8F%E8%89%B2%E5%90%8D%E7%A8%B1" class="hash-link" aria-label="Direct link to 內建顏色名稱" title="Direct link to 內建顏色名稱" translate="no">​</a></h3>
<p>TailwindCSS 預設提供以下顏色：</p>
<ul>
<li class=""><strong>灰階系列</strong>：<code>slate</code>、<code>gray</code>、<code>zinc</code>、<code>neutral</code>、<code>stone</code></li>
<li class=""><strong>彩色系列</strong>：<code>red</code>、<code>orange</code>、<code>amber</code>、<code>yellow</code>、<code>lime</code>、<code>green</code>、<code>emerald</code>、<code>teal</code>、<code>cyan</code>、<code>sky</code>、<code>blue</code>、<code>indigo</code>、<code>violet</code>、<code>purple</code>、<code>fuchsia</code>、<code>pink</code>、<code>rose</code></li>
</ul>
<blockquote>
<p>完整顏色參考：<a href="https://tailwindcss.com/docs/customizing-colors" target="_blank" rel="noopener noreferrer" class="">TailwindCSS Colors 官方文件</a></p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="深淺度規則"><strong>深淺度規則</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E6%B7%B1%E6%B7%BA%E5%BA%A6%E8%A6%8F%E5%89%87" class="hash-link" aria-label="Direct link to 深淺度規則" title="Direct link to 深淺度規則" translate="no">​</a></h3>
<p>每個顏色都有 11 個深淺等級：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">50  → 最淺（幾乎是白色）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">100 → 很淺</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">200 → 淺</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">300 → 稍淺</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">400 → 中淺</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">500 → 標準（主色調）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">600 → 中深</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">700 → 深</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">800 → 很深</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">900 → 最深（幾乎是黑色）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">950 → 極深（v3.2+ 新增）</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用範例"><strong>使用範例</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E4%BD%BF%E7%94%A8%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="Direct link to 使用範例" title="Direct link to 使用範例" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 背景色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">藍色背景</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-red-600</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">紅色背景（較深）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-green-400</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">綠色背景（較淺）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 文字顏色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-gray-700</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">深灰色文字</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-blue-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">藍色文字</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 邊框顏色（需要先設定邊框寬度）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2 border-red-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">紅色邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="透明度控制"><strong>透明度控制</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%80%8F%E6%98%8E%E5%BA%A6%E6%8E%A7%E5%88%B6" class="hash-link" aria-label="Direct link to 透明度控制" title="Direct link to 透明度控制" translate="no">​</a></h3>
<p>TailwindCSS 支援兩種透明度語法：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 方法 1：使用 / 語法（推薦，只影響該屬性）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500/50</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">50% 透明度的藍色背景</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-gray-900/80</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">80% 透明度的文字</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 方法 2：使用 opacity 屬性（影響整個元素，包括子元素）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500 opacity-50</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">整個元素 50% 透明</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span><strong>兩種透明度的差異</strong></div><div class="admonitionContent_BuS1"><ul>
<li class=""><code>bg-blue-500/50</code>：只有背景色是半透明的，文字和其他內容不受影響</li>
<li class=""><code>opacity-50</code>：整個元素（包括文字、圖片、子元素）都會變成半透明</li>
</ul></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="文字與字型"><strong>文字與字型</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E6%96%87%E5%AD%97%E8%88%87%E5%AD%97%E5%9E%8B" class="hash-link" aria-label="Direct link to 文字與字型" title="Direct link to 文字與字型" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="字型種類font-family"><strong>字型種類（Font Family）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%AD%97%E5%9E%8B%E7%A8%AE%E9%A1%9Efont-family" class="hash-link" aria-label="Direct link to 字型種類font-family" title="Direct link to 字型種類font-family" translate="no">​</a></h3>
<p>TailwindCSS 內建三種字型堆疊：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-sans</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">無襯線字體（預設）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-serif</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">襯線字體</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-mono</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">等寬字體（程式碼用）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>這三個 class 對應的 CSS：</p>
<table><thead><tr><th>Class</th><th>對應的 CSS font-family</th></tr></thead><tbody><tr><td><code>font-sans</code></td><td><code>ui-sans-serif, system-ui, sans-serif, ...</code></td></tr><tr><td><code>font-serif</code></td><td><code>ui-serif, Georgia, Cambria, ...</code></td></tr><tr><td><code>font-mono</code></td><td><code>ui-monospace, SFMono-Regular, Menlo, ...</code></td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="字體大小font-size"><strong>字體大小（Font Size）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%AD%97%E9%AB%94%E5%A4%A7%E5%B0%8Ffont-size" class="hash-link" aria-label="Direct link to 字體大小font-size" title="Direct link to 字體大小font-size" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-xs</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">極小文字 (0.75rem / 12px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-sm</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">小文字 (0.875rem / 14px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-base</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">基礎文字 (1rem / 16px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">大文字 (1.125rem / 18px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">特大文字 (1.25rem / 20px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-2xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">2倍大 (1.5rem / 24px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-3xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">3倍大 (1.875rem / 30px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-4xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">4倍大 (2.25rem / 36px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><strong>規則</strong>：<code>text-{size}</code>，size 可以是 <code>xs</code>、<code>sm</code>、<code>base</code>、<code>lg</code>、<code>xl</code>、<code>2xl</code> ... 到 <code>9xl</code></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="字體粗細font-weight"><strong>字體粗細（Font Weight）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%AD%97%E9%AB%94%E7%B2%97%E7%B4%B0font-weight" class="hash-link" aria-label="Direct link to 字體粗細font-weight" title="Direct link to 字體粗細font-weight" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-thin</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">極細 (100)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-light</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">細 (300)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-normal</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">正常 (400)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-medium</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">中等 (500)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-semibold</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">半粗 (600)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-bold</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">粗體 (700)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-extrabold</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">特粗 (800)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">font-black</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">極粗 (900)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="行高line-height"><strong>行高（Line Height）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E8%A1%8C%E9%AB%98line-height" class="hash-link" aria-label="Direct link to 行高line-height" title="Direct link to 行高line-height" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">leading-none</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">無行高 (1)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">leading-tight</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">緊湊行高 (1.25)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">leading-snug</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">稍緊行高 (1.375)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">leading-normal</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">正常行高 (1.5)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">leading-relaxed</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">寬鬆行高 (1.625)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">leading-loose</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">很寬鬆行高 (2)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="字距letter-spacing"><strong>字距（Letter Spacing）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%AD%97%E8%B7%9Dletter-spacing" class="hash-link" aria-label="Direct link to 字距letter-spacing" title="Direct link to 字距letter-spacing" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">tracking-tighter</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">極緊字距 (-0.05em)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">tracking-tight</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">緊字距 (-0.025em)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">tracking-normal</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">正常字距 (0)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">tracking-wide</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">寬字距 (0.025em)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">tracking-wider</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">較寬字距 (0.05em)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">tracking-widest</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">極寬字距 (0.1em)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="文字對齊與裝飾"><strong>文字對齊與裝飾</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E6%96%87%E5%AD%97%E5%B0%8D%E9%BD%8A%E8%88%87%E8%A3%9D%E9%A3%BE" class="hash-link" aria-label="Direct link to 文字對齊與裝飾" title="Direct link to 文字對齊與裝飾" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 對齊</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-left</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">靠左對齊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">置中對齊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-right</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">靠右對齊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-justify</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">兩端對齊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 裝飾</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">underline</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">底線</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">line-through</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">刪除線</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">no-underline</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">移除底線</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 大小寫轉換</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">uppercase</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">全部大寫 (HELLO)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">lowercase</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">全部小寫 (hello)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">capitalize</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">首字母大寫 (Hello World)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="間距系統spacing"><strong>間距系統（Spacing）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%96%93%E8%B7%9D%E7%B3%BB%E7%B5%B1spacing" class="hash-link" aria-label="Direct link to 間距系統spacing" title="Direct link to 間距系統spacing" translate="no">​</a></h2>
<p>這是 TailwindCSS 最重要的概念之一。預設的間距單位是 <code>0.25rem</code>（4px）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="間距數值對照表"><strong>間距數值對照表</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%96%93%E8%B7%9D%E6%95%B8%E5%80%BC%E5%B0%8D%E7%85%A7%E8%A1%A8" class="hash-link" aria-label="Direct link to 間距數值對照表" title="Direct link to 間距數值對照表" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">數字 × 0.25rem = 實際大小</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">0   → 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1   → 0.25rem (4px)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2   → 0.5rem  (8px)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3   → 0.75rem (12px)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">4   → 1rem    (16px)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">6   → 1.5rem  (24px)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">8   → 2rem    (32px)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">12  → 3rem    (48px)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">16  → 4rem    (64px)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="margin外距"><strong>Margin（外距）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#margin%E5%A4%96%E8%B7%9D" class="hash-link" aria-label="Direct link to margin外距" title="Direct link to margin外距" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 四個方向</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">m-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">四周 margin 1rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 單一方向</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">mt-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">上方 margin (top)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">mr-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">右方 margin (right)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">mb-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">下方 margin (bottom)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">ml-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左方 margin (left)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 水平/垂直</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">mx-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左右 margin (x 軸)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">my-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">上下 margin (y 軸)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 自動置中</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">mx-auto</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">水平置中</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 負值（向外延伸）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">-mt-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">負 margin（向上移動 1rem）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="padding內距"><strong>Padding（內距）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#padding%E5%85%A7%E8%B7%9D" class="hash-link" aria-label="Direct link to padding內距" title="Direct link to padding內距" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 四個方向</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">p-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">四周 padding 1rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 單一方向</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">pt-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">上方 padding</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">pr-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">右方 padding</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">pb-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">下方 padding</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">pl-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左方 padding</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 水平/垂直</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">px-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左右 padding</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">py-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">上下 padding</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gapflexbox--grid-子元素間距"><strong>Gap（Flexbox / Grid 子元素間距）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#gapflexbox--grid-%E5%AD%90%E5%85%83%E7%B4%A0%E9%96%93%E8%B7%9D" class="hash-link" aria-label="Direct link to gapflexbox--grid-子元素間距" title="Direct link to gapflexbox--grid-子元素間距" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Flexbox 範例</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex gap-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">項目 1</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">項目 2</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">項目 3</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Grid 範例</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">grid grid-cols-3 gap-6</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">格子 1</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">格子 2</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">格子 3</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 分別控制水平/垂直間距</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">grid gap-x-8 gap-y-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 水平間距 8 (2rem)，垂直間距 4 (1rem) */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="版面配置layout"><strong>版面配置（Layout）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E7%89%88%E9%9D%A2%E9%85%8D%E7%BD%AElayout" class="hash-link" aria-label="Direct link to 版面配置layout" title="Direct link to 版面配置layout" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="display-屬性"><strong>Display 屬性</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#display-%E5%B1%AC%E6%80%A7" class="hash-link" aria-label="Direct link to display-屬性" title="Direct link to display-屬性" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">block</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">區塊元素</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">inline</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">行內元素</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">inline-block</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">行內區塊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Flexbox 容器</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">inline-flex</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">行內 Flexbox</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">grid</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Grid 容器</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">hidden</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">隱藏元素 (display: none)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flexbox-布局"><strong>Flexbox 布局</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#flexbox-%E5%B8%83%E5%B1%80" class="hash-link" aria-label="Direct link to flexbox-布局" title="Direct link to flexbox-布局" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 基礎 Flex 容器</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">項目 1</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">項目 2</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 方向</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex flex-row</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">水平排列（預設）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex flex-col</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">垂直排列</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex flex-row-reverse</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">水平反向</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex flex-col-reverse</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">垂直反向</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 主軸對齊 (justify-content)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex justify-start</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">靠起點</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex justify-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">置中</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex justify-end</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">靠終點</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex justify-between</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">兩端對齊，中間平分</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex justify-around</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">環繞對齊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex justify-evenly</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">完全平分</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 交叉軸對齊 (align-items)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex items-start</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">頂部對齊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex items-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">垂直置中</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex items-end</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">底部對齊</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex items-stretch</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">拉伸填滿（預設）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 換行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex flex-wrap</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">允許換行</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex flex-nowrap</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">不換行（預設）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span><strong>常用組合：完美置中</strong></div><div class="admonitionContent_BuS1"><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex items-center justify-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 水平 + 垂直都置中 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="grid-布局"><strong>Grid 布局</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#grid-%E5%B8%83%E5%B1%80" class="hash-link" aria-label="Direct link to grid-布局" title="Direct link to grid-布局" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 基礎 Grid</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">grid grid-cols-3 gap-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/*          ^^^^^^^^^^^^ 3 欄   ^^^^^ 間距 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">1</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">2</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">3</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 響應式 Grid（常用模式）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/*          ^^^^^^^^ 手機 1 欄  ^^^^^^ 平板 2 欄  ^^^^^^ 桌面 3 欄 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="尺寸控制"><strong>尺寸控制</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%B0%BA%E5%AF%B8%E6%8E%A7%E5%88%B6" class="hash-link" aria-label="Direct link to 尺寸控制" title="Direct link to 尺寸控制" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="寬度width"><strong>寬度（Width）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%AF%AC%E5%BA%A6width" class="hash-link" aria-label="Direct link to 寬度width" title="Direct link to 寬度width" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 固定寬度（使用間距數值）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-64</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">寬度 16rem (256px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-96</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">寬度 24rem (384px)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 百分比</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-1/2</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">50% 寬度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-1/3</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">33.333% 寬度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-2/3</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">66.666% 寬度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-full</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">100% 寬度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 特殊值</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-screen</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">100vw（視窗寬度）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-auto</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">自動寬度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">w-fit</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">適應內容寬度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 最小/最大寬度</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">min-w-0</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">最小寬度 0</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">max-w-md</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">最大寬度 28rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">max-w-screen-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">最大寬度 1024px</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="高度height"><strong>高度（Height）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%AB%98%E5%BA%A6height" class="hash-link" aria-label="Direct link to 高度height" title="Direct link to 高度height" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 固定高度</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">h-64</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">高度 16rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">h-screen</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">100vh（視窗高度）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 百分比</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">h-full</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">100% 高度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">h-1/2</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">50% 高度</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 最小/最大高度</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">min-h-screen</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">最小高度 100vh</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">max-h-96</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">最大高度 24rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="size同時設定寬高"><strong>Size（同時設定寬高）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#size%E5%90%8C%E6%99%82%E8%A8%AD%E5%AE%9A%E5%AF%AC%E9%AB%98" class="hash-link" aria-label="Direct link to size同時設定寬高" title="Direct link to size同時設定寬高" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v4 新增：size 屬性</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">size-16</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">寬高都是 4rem</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">size-full</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">寬高都是 100%</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 實務範例：圖標按鈕</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">size-9 rounded-md flex items-center justify-center</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Icon</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="邊框與圓角"><strong>邊框與圓角</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%82%8A%E6%A1%86%E8%88%87%E5%9C%93%E8%A7%92" class="hash-link" aria-label="Direct link to 邊框與圓角" title="Direct link to 邊框與圓角" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="邊框border"><strong>邊框（Border）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%82%8A%E6%A1%86border" class="hash-link" aria-label="Direct link to 邊框border" title="Direct link to 邊框border" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 邊框寬度（預設無邊框，需明確指定）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">1px 邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">2px 邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">4px 邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 單邊邊框</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-t</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">上邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-r</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">右邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-b</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">下邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-l</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 邊框顏色</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2 border-gray-300</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">灰色邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2 border-blue-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">藍色邊框</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 邊框樣式</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2 border-solid</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">實線（預設）</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2 border-dashed</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">虛線</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border-2 border-dotted</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">點線</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="圓角border-radius"><strong>圓角（Border Radius）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E5%9C%93%E8%A7%92border-radius" class="hash-link" aria-label="Direct link to 圓角border-radius" title="Direct link to 圓角border-radius" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 圓角大小</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-none</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">無圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-sm</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">小圓角 (0.125rem)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">預設圓角 (0.25rem)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-md</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">中圓角 (0.375rem)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">大圓角 (0.5rem)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">特大圓角 (0.75rem)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-2xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">2倍大圓角 (1rem)</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-full</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">完全圓形</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 單邊圓角</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-t-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">上方圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-r-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">右方圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-b-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">下方圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-l-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左方圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 單一角落</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-tl-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左上圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-tr-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">右上圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-br-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">右下圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">rounded-bl-lg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">左下圓角</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="狀態變化pseudo-classes"><strong>狀態變化（Pseudo-classes）</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E7%8B%80%E6%85%8B%E8%AE%8A%E5%8C%96pseudo-classes" class="hash-link" aria-label="Direct link to 狀態變化pseudo-classes" title="Direct link to 狀態變化pseudo-classes" translate="no">​</a></h2>
<p>TailwindCSS 使用「前綴:」語法來處理各種狀態。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hover-狀態"><strong>Hover 狀態</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#hover-%E7%8B%80%E6%85%8B" class="hash-link" aria-label="Direct link to hover-狀態" title="Direct link to hover-狀態" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500 hover:bg-blue-600</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  滑鼠移上去會變深藍色</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">a</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-blue-500 hover:underline</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  滑鼠移上去會有底線</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">a</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 多個 hover 效果</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-white hover:bg-gray-100 hover:shadow-lg transition</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  滑鼠移上去：背景變灰 + 陰影變大</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="focus-狀態"><strong>Focus 狀態</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#focus-%E7%8B%80%E6%85%8B" class="hash-link" aria-label="Direct link to focus-狀態" title="Direct link to focus-狀態" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">border border-gray-300 focus:border-blue-500 focus:ring-2 focus:ring-blue-200</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">placeholder</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">點擊輸入框看效果</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"></span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 移除預設 outline，改用 ring</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">focus:outline-none focus:ring-2 focus:ring-blue-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  自訂 focus 樣式</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="active-與-disabled-狀態"><strong>Active 與 Disabled 狀態</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#active-%E8%88%87-disabled-%E7%8B%80%E6%85%8B" class="hash-link" aria-label="Direct link to active-與-disabled-狀態" title="Direct link to active-與-disabled-狀態" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// Active（點擊當下）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500 active:bg-blue-700</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  點擊時會變更深</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// Disabled</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">disabled</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f"></span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  停用按鈕</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="響應式設計"><strong>響應式設計</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%9F%BF%E6%87%89%E5%BC%8F%E8%A8%AD%E8%A8%88" class="hash-link" aria-label="Direct link to 響應式設計" title="Direct link to 響應式設計" translate="no">​</a></h2>
<p>TailwindCSS 使用 <strong>mobile-first</strong> 的響應式設計。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="斷點規則"><strong>斷點規則</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E6%96%B7%E9%BB%9E%E8%A6%8F%E5%89%87" class="hash-link" aria-label="Direct link to 斷點規則" title="Direct link to 斷點規則" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">（無前綴）  所有尺寸（從 0px 開始）</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sm:        640px  以上</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">md:        768px  以上</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">lg:        1024px 以上</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">xl:        1280px 以上</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2xl:       1536px 以上</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span><strong>重要觀念</strong></div><div class="admonitionContent_BuS1"><p>沒有前綴的 class 會套用到<strong>所有尺寸</strong>，帶前綴的 class 會從該斷點<strong>往上</strong>覆蓋。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="響應式範例"><strong>響應式範例</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#%E9%9F%BF%E6%87%89%E5%BC%8F%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="Direct link to 響應式範例" title="Direct link to 響應式範例" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 這行的意思是：</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">flex flex-col md:flex-row</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 0-767px: flex-col（垂直排列）*/</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 768px 以上: flex-row（水平排列）*/</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 不同螢幕尺寸顯示不同文字大小</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-2xl md:text-4xl lg:text-6xl</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 0-767px: text-2xl */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 768-1023px: text-4xl */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 1024px 以上: text-6xl */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  響應式標題</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 不同螢幕尺寸顯示/隱藏</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">hidden md:block</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 0-767px: 隱藏 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 768px 以上: 顯示 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  桌面版選單</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">block md:hidden</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 0-767px: 顯示 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* 768px 以上: 隱藏 */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  手機版選單</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><strong>實務範例：響應式 Grid</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* </span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    手機 (0-639px): 1 欄</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    小平板 (640-1023px): 2 欄</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    大平板 (1024-1279px): 3 欄</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    桌面 (1280px+): 4 欄</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">  */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">items</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">item</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Card</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">key</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">item</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">id</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag spread punctuation" style="color:#393A34">{</span><span class="token tag spread operator" style="color:#393A34">...</span><span class="token tag spread" style="color:#00009f">item</span><span class="token tag spread punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-3#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://tailwindcss.com/docs/customizing-colors" target="_blank" rel="noopener noreferrer" class=""><strong>Customizing Colors - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/background-color" target="_blank" rel="noopener noreferrer" class=""><strong>Background Color - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/color" target="_blank" rel="noopener noreferrer" class=""><strong>Text Color - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/border-color" target="_blank" rel="noopener noreferrer" class=""><strong>Border Color - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/font-family" target="_blank" rel="noopener noreferrer" class=""><strong>Font Family - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/font-size" target="_blank" rel="noopener noreferrer" class=""><strong>Font Size - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/font-weight" target="_blank" rel="noopener noreferrer" class=""><strong>Font Weight - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/line-height" target="_blank" rel="noopener noreferrer" class=""><strong>Line Height - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/letter-spacing" target="_blank" rel="noopener noreferrer" class=""><strong>Letter Spacing - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/text-align" target="_blank" rel="noopener noreferrer" class=""><strong>Text Align - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/padding" target="_blank" rel="noopener noreferrer" class=""><strong>Padding - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/margin" target="_blank" rel="noopener noreferrer" class=""><strong>Margin - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/gap" target="_blank" rel="noopener noreferrer" class=""><strong>Gap - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/display" target="_blank" rel="noopener noreferrer" class=""><strong>Display - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/flex" target="_blank" rel="noopener noreferrer" class=""><strong>Flexbox - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/grid-template-columns" target="_blank" rel="noopener noreferrer" class=""><strong>Grid Template Columns - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/justify-content" target="_blank" rel="noopener noreferrer" class=""><strong>Justify Content - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/align-items" target="_blank" rel="noopener noreferrer" class=""><strong>Align Items - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/width" target="_blank" rel="noopener noreferrer" class=""><strong>Width - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/height" target="_blank" rel="noopener noreferrer" class=""><strong>Height - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/border-width" target="_blank" rel="noopener noreferrer" class=""><strong>Border Width - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/border-radius" target="_blank" rel="noopener noreferrer" class=""><strong>Border Radius - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/hover-focus-and-other-states" target="_blank" rel="noopener noreferrer" class=""><strong>Hover, Focus, and Other States - TailwindCSS</strong></a></li>
<li class=""><a href="https://tailwindcss.com/docs/responsive-design" target="_blank" rel="noopener noreferrer" class=""><strong>Responsive Design - TailwindCSS</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="shadcn/ui" term="shadcn/ui"/>
        <category label="TailwindCSS" term="TailwindCSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 shadcn/ui 前該補的 TailwindCSS 基礎知識(二) - 理解 TailwindCSS 的運作原理]]></title>
        <id>https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2</id>
        <link href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2"/>
        <updated>2025-11-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇技術筆記深入探討 TailwindCSS 的核心運作原理，包括 JIT 引擎的工作機制、PostCSS 處理流程，以及在不同開發環境（Vite、Next.js）中的配置方式，幫助開發者理解 TailwindCSS 如何實現高效的樣式編譯與優化。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本文是「使用 shadcn/ui 前該補的 TailwindCSS 基礎知識」系列文章的第二篇</p>
<p><strong>系列文章：</strong></p>
<ol>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-1" target="_blank" rel="noopener noreferrer" class="">從 MUI 到 TailwindCSS 設計哲學的轉變</a></li>
<li class=""><strong>理解 TailwindCSS 的運作原理（本篇）</strong></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-3" target="_blank" rel="noopener noreferrer" class="">TailwindCSS v4 內建 Utility Classes 速查</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-4" target="_blank" rel="noopener noreferrer" class="">透過 @theme 自訂設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-5" target="_blank" rel="noopener noreferrer" class="">解析 shadcn/ui 的設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-6" target="_blank" rel="noopener noreferrer" class="">shadcn/ui 生態系工具鏈</a></li>
</ol>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心套件tailwindcss"><strong>核心套件：tailwindcss</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#%E6%A0%B8%E5%BF%83%E5%A5%97%E4%BB%B6tailwindcss" class="hash-link" aria-label="Direct link to 核心套件tailwindcss" title="Direct link to 核心套件tailwindcss" translate="no">​</a></h2>
<p>到目前為止，你可能會有個疑問：「照理來說瀏覽器根本看不懂 <code>bg-blue-500</code> 這種 class 啊？它到底是怎麼變成真正的 CSS 的？」</p>
<p>這個問題的答案就藏在 TailwindCSS 的核心套件 <code>tailwindcss</code> 中。這個套件是 TailwindCSS 的核心引擎，負責：</p>
<ul>
<li class="">解析你的 HTML/JSX/TSX 檔案</li>
<li class="">掃描所有使用到的 class 名稱</li>
<li class="">生成對應的 CSS 規則</li>
</ul>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// TailwindCSS 會掃描這個檔案</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">Card</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-white rounded-lg p-4 shadow-md</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* TailwindCSS 看到：bg-white, rounded-lg, p-4, shadow-md */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h2</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text-xl font-bold</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">標題</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h2</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* TailwindCSS 看到：text-xl, font-bold */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>TailwindCSS 會解析這些 utility class 名稱，並根據內建的<strong>命名規則</strong>生成對應的 CSS：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* bg-white → 「bg-」代表 background，「white」是顏色值 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.bg-white</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#fff</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* rounded-lg → 「rounded-」代表 border-radius，「lg」是 large（0.5rem） */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.rounded-lg</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">border-radius</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* p-4 → 「p-」代表 padding (四邊)，「4」代表 1rem（TailwindCSS 的間距單位：4 × 0.25rem = 1rem） */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.p-4</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">padding</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* shadow-md → 「shadow-」代表 box-shadow，「md」是 medium 預設陰影值 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.shadow-md</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">box-shadow</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token unit">px</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">6</span><span class="token unit">px</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">-1</span><span class="token unit">px</span><span class="token plain"> </span><span class="token color function" style="color:#d73a49">rgba</span><span class="token color punctuation" style="color:#393A34">(</span><span class="token color number" style="color:#36acaa">0</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">0</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">0</span><span class="token color punctuation" style="color:#393A34">,</span><span class="token color"> </span><span class="token color number" style="color:#36acaa">0.1</span><span class="token color punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* text-xl → 「text-」代表字體相關，「xl」是 extra-large（1.25rem） */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.text-xl</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">font-size</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.25</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">line-height</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.75</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* font-bold → 「font-」代表 font-weight，「bold」對應 700 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.font-bold</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">font-weight</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">700</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>簡單來說，TailwindCSS 的轉換邏輯遵循一個 <strong>「前綴-值」</strong> 的命名模式：</p>
<ul>
<li class=""><strong>前綴</strong>（如 <code>bg-</code>、<code>p-</code>、<code>text-</code>）決定要設定哪個 CSS 屬性</li>
<li class=""><strong>值</strong>（如 <code>white</code>、<code>4</code>、<code>xl</code>）決定該屬性的具體數值</li>
</ul>
<p>這就是 TailwindCSS 的核心工作：<strong>將 utility class 名稱轉換成真正的 CSS 規則</strong>。但這個轉換過程是如何發生的呢？在探究這個問題之前我們需要先理解 TailwindCSS 與 PostCSS 的關係。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="postcss-與-tailwindcss-的關係"><strong>PostCSS 與 TailwindCSS 的關係</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#postcss-%E8%88%87-tailwindcss-%E7%9A%84%E9%97%9C%E4%BF%82" class="hash-link" aria-label="Direct link to postcss-與-tailwindcss-的關係" title="Direct link to postcss-與-tailwindcss-的關係" translate="no">​</a></h2>
<p>PostCSS 是什麼？簡單來說，它是「CSS 的 Babel」。</p>
<blockquote>
<p><strong>Babel</strong> 的角色：將現代 JavaScript 轉換成瀏覽器能理解的 JavaScript
<strong>PostCSS</strong> 的角色：將現代 CSS（或特殊語法）轉換成瀏覽器能理解的 CSS</p>
</blockquote>
<p>而 TailwindCSS 其實本質上就是一個 PostCSS 插件。讓我們看看處理流程：</p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<p>實際範例：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* src/index.css - 輸入 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@import</span><span class="token atrule" style="color:#00a4db"> </span><span class="token atrule string" style="color:#e3116c">"tailwindcss"</span><span class="token atrule punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token atrule rule" style="color:#00a4db">@theme</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>PostCSS + TailwindCSS 處理後：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* 輸出 - 瀏覽器能理解的 CSS */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector pseudo-class" style="color:#00009f">:root</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">oklch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.6171</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1375</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">39.0427</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.bg-primary</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.text-primary</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--color-primary</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* ... 其他使用到的 utility classes */</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span><strong>@theme 命名空間規則</strong></div><div class="admonitionContent_BuS1"><p>在 <code>@theme</code> 區塊中定義的 CSS 變數，TailwindCSS 會根據<strong>變數名稱的前綴</strong>自動判斷要產生哪些 utility classes：</p><table><thead><tr><th>變數前綴</th><th>產生的 utility classes</th><th>範例</th></tr></thead><tbody><tr><td><code>--color-*</code></td><td><code>.bg-*</code>、<code>.text-*</code>、<code>.border-*</code> 等顏色相關</td><td><code>--color-primary</code> → <code>.bg-primary</code>、<code>.text-primary</code></td></tr><tr><td><code>--spacing-*</code></td><td><code>.p-*</code>、<code>.m-*</code>、<code>.gap-*</code> 等間距相關</td><td><code>--spacing-lg</code> → <code>.p-lg</code>、<code>.m-lg</code></td></tr><tr><td><code>--font-size-*</code></td><td><code>.text-*</code> 字體大小</td><td><code>--font-size-huge</code> → <code>.text-huge</code></td></tr><tr><td><code>--radius-*</code></td><td><code>.rounded-*</code> 圓角</td><td><code>--radius-xl</code> → <code>.rounded-xl</code></td></tr></tbody></table><p>換句話說，當你定義 <code>--color-primary</code> 時，TailwindCSS 會自動理解這是一個「顏色」類型的設計 token，因此會為所有需要顏色的 utility classes（背景、文字、邊框等）都產生對應的 <code>.xxx-primary</code> 類別。</p><p>關於這些規則的詳細設定方式與更多範例，我們會在 <a class="" href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-4">透過 @theme 自訂設計系統</a> 中深入探討。</p></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="jit-引擎just-in-time-編譯"><strong>JIT 引擎：Just-In-Time 編譯</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#jit-%E5%BC%95%E6%93%8Ejust-in-time-%E7%B7%A8%E8%AD%AF" class="hash-link" aria-label="Direct link to jit-引擎just-in-time-編譯" title="Direct link to jit-引擎just-in-time-編譯" translate="no">​</a></h2>
<p>理解了 PostCSS 與 TailwindCSS 的關係後，現在我們可以來繼續探索更關鍵的問題：<strong>當專案中有成千上萬個可能的 utility class 時，TailwindCSS 如何知道該生成哪些 CSS？</strong></p>
<p>TailwindCSS 使用 <strong>JIT (Just-In-Time) 即時編譯引擎</strong> 來解決這個問題。簡單來說，它會在你開發和打包時掃描程式碼，找出實際用到的 class，再即時生成對應的 CSS。讓我們看看它在不同階段是怎麼運作的。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="開發階段-development---即時掃描與生成"><strong>開發階段 (Development) - 即時掃描與生成</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#%E9%96%8B%E7%99%BC%E9%9A%8E%E6%AE%B5-development---%E5%8D%B3%E6%99%82%E6%8E%83%E6%8F%8F%E8%88%87%E7%94%9F%E6%88%90" class="hash-link" aria-label="Direct link to 開發階段-development---即時掃描與生成" title="Direct link to 開發階段-development---即時掃描與生成" translate="no">​</a></h3>
<p>在開發模式下，JIT 引擎會監聽檔案變化（如 &nbsp;<code>.html</code>、<code>.jsx</code>、<code>.tsx</code>、<code>.vue</code>&nbsp; 等），即時掃描新的 class 並立即生成對應的 CSS。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 你在開發時新增了一個元件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">NewComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-gradient-to-r from-purple-500 to-pink-500</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token comment" style="color:#999988;font-style:italic">/* JIT 引擎立即偵測到這些 class */</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      漸層背景</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>JIT 引擎的處理流程：</strong></p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* 生成的 CSS */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* bg-gradient-to-r：設定漸層方向為「向右」 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* 使用 CSS 的 linear-gradient，「to right」表示從左到右 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* 顏色來源會參考 --tw-gradient-from 和 --tw-gradient-to 這兩個 CSS 變數 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.bg-gradient-to-r</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">linear-gradient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    to right</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--tw-gradient-from</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">/* 起始顏色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token variable" style="color:#36acaa">--tw-gradient-to</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain">         </span><span class="token comment" style="color:#999988;font-style:italic">/* 結束顏色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* from-purple-500：設定漸層的「起始顏色」 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* 這個 class 只負責定義 CSS 變數的值，不直接設定 background */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.from-purple-500</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--tw-gradient-from</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#a855f7</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">/* 紫色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* to-pink-500：設定漸層的「結束顏色」 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* 同樣只定義 CSS 變數，與 from-* 搭配使用 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.to-pink-500</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token variable" style="color:#36acaa">--tw-gradient-to</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#ec4899</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">     </span><span class="token comment" style="color:#999988;font-style:italic">/* 粉紅色 */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span><strong>漸層 class 的協作機制</strong></div><div class="admonitionContent_BuS1"><p>這三個 class 展示了 TailwindCSS 的設計哲學 — <strong>組合式設計</strong>：</p><ul>
<li class=""><code>bg-gradient-to-r</code>：定義「要用漸層」且「方向向右」</li>
<li class=""><code>from-purple-500</code>：定義「起始顏色是紫色」</li>
<li class=""><code>to-pink-500</code>：定義「結束顏色是粉紅色」</li>
</ul><p>它們透過 CSS 變數（<code>--tw-gradient-from</code>、<code>--tw-gradient-to</code>）互相協作，最終組合成完整的漸層效果。這讓你可以靈活地混搭不同的方向和顏色，而不需要記住一堆組合好的 class 名稱。</p></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span><strong>JIT 的優勢：</strong></div><div class="admonitionContent_BuS1"><ul>
<li class=""><strong>快速</strong>：只生成你用到的 CSS，不會產生幾 MB 的完整 CSS 檔</li>
<li class=""><strong>精準</strong>：支援任意值，如 &nbsp;<code>w-[237px]</code>、<code>bg-[#1da1f2]</code></li>
<li class=""><strong>即時</strong>：修改 class 後立即看到效果</li>
</ul></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="打包階段-build---最終掃描與最佳化"><strong>打包階段 (Build) - 最終掃描與最佳化</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#%E6%89%93%E5%8C%85%E9%9A%8E%E6%AE%B5-build---%E6%9C%80%E7%B5%82%E6%8E%83%E6%8F%8F%E8%88%87%E6%9C%80%E4%BD%B3%E5%8C%96" class="hash-link" aria-label="Direct link to 打包階段-build---最終掃描與最佳化" title="Direct link to 打包階段-build---最終掃描與最佳化" translate="no">​</a></h3>
<p>在打包時，JIT 引擎會掃描所有原始碼檔案，收集所有使用到的 class，然後生成最小化的 CSS 檔案並移除未使用的樣式。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm run build</span><br></div></code></pre></div></div>
<p><strong>JIT 引擎的處理流程：</strong></p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span><strong>重點提醒：</strong></div><div class="admonitionContent_BuS1"><p>&nbsp;JIT 引擎只會掃描「靜態的 class 名稱」。以下寫法會導致樣式遺失：</p><p>❌&nbsp;<strong>錯誤做法</strong>：動態組合 class 名稱</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// JIT 引擎看不到完整的 class 名稱const color = 'blue';</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript template-string string" style="color:#e3116c">bg-</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token tag script language-javascript template-string interpolation" style="color:#00009f">color</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript template-string string" style="color:#e3116c">-500</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:#e3116c">`</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">  // ❌ 不會生成 bg-blue-500</span><br></div></code></pre></div></div><p>✅&nbsp;<strong>正確做法</strong>：使用完整的 class 名稱</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 方法 1：條件判斷</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">color </span><span class="token tag script language-javascript operator" style="color:#393A34">===</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">'blue'</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript operator" style="color:#393A34">?</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">'bg-blue-500'</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">'bg-red-500'</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">// 方法 2：使用物件映射const colorMap = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  blue: 'bg-blue-500',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  red: 'bg-red-500',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">};</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">colorMap</span><span class="token tag script language-javascript punctuation" style="color:#393A34">[</span><span class="token tag script language-javascript" style="color:#00009f">color</span><span class="token tag script language-javascript punctuation" style="color:#393A34">]</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="在不同環境中配置-tailwindcss"><strong>在不同環境中配置 TailwindCSS</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#%E5%9C%A8%E4%B8%8D%E5%90%8C%E7%92%B0%E5%A2%83%E4%B8%AD%E9%85%8D%E7%BD%AE-tailwindcss" class="hash-link" aria-label="Direct link to 在不同環境中配置-tailwindcss" title="Direct link to 在不同環境中配置-tailwindcss" translate="no">​</a></h2>
<p>理解了 TailwindCSS 的運作原理後，讓我們看看如何在實際專案中配置它。不同的建置工具（Vite、Next.js）有不同的整合方式，這是因為它們背後的 &nbsp;<strong>CSS 處理機制</strong>略有不同。</p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-專案配置"><strong>Vite 專案配置</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#vite-%E5%B0%88%E6%A1%88%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to vite-專案配置" title="Direct link to vite-專案配置" translate="no">​</a></h3>
<p>Vite 有自己的插件系統，可以直接處理 CSS 轉換。因此 TailwindCSS 提供了專用的 Vite 插件。</p>
<p><strong>安裝套件：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install -D tailwindcss @tailwindcss/vite</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>tailwindcss</code>：核心引擎</li>
<li class=""><code>@tailwindcss/vite</code>：Vite 插件，將 TailwindCSS 整合進 Vite 的建置流程</li>
</ul>
<p><code>vite.config.ts</code>：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">tailwindcss</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@tailwindcss/vite"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> defineConfig </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"vite"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defineConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">react</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">tailwindcss</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 直接作為 Vite 插件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>src/index.css</code>：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@import</span><span class="token atrule" style="color:#00a4db"> </span><span class="token atrule string" style="color:#e3116c">"tailwindcss"</span><span class="token atrule punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="nextjs-專案配置"><strong>Next.js 專案配置</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#nextjs-%E5%B0%88%E6%A1%88%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to nextjs-專案配置" title="Direct link to nextjs-專案配置" translate="no">​</a></h3>
<p>Next.js 使用 PostCSS 來處理 CSS（包括內建的 CSS Modules、Autoprefixer、CSS-in-JS 等）。因此 TailwindCSS 需要作為 PostCSS 插件來整合。</p>
<p><strong>安裝套件：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install -D tailwindcss @tailwindcss/postcss</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>tailwindcss</code>：核心引擎</li>
<li class=""><code>@tailwindcss/postcss</code>：PostCSS 插件，將 TailwindCSS 整合進 PostCSS 處理流程</li>
</ul>
<p><code>postcss.config.mjs：</code></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">"@tailwindcss/postcss"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 作為 PostCSS 插件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>app/globals.css：</code></p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token atrule rule" style="color:#00a4db">@import</span><span class="token atrule" style="color:#00a4db"> </span><span class="token atrule string" style="color:#e3116c">"tailwindcss"</span><span class="token atrule punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-2#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><strong><a href="https://tailwindcss.com/docs" target="_blank" rel="noopener noreferrer" class="">TailwindCSS Official Documentation</a></strong></li>
<li class=""><strong><a href="https://postcss.org/" target="_blank" rel="noopener noreferrer" class="">PostCSS Documentation</a></strong></li>
<li class=""><strong><a href="https://vitejs.dev/" target="_blank" rel="noopener noreferrer" class="">Vite Documentation</a></strong></li>
<li class=""><strong><a href="https://nextjs.org/docs/app/building-your-application/styling/css" target="_blank" rel="noopener noreferrer" class="">Next.js Documentation - CSS Support</a></strong></li>
<li class=""><strong><a href="https://tailwindcss.com/docs/just-in-time-mode" target="_blank" rel="noopener noreferrer" class="">JIT Mode in TailwindCSS</a></strong></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="shadcn/ui" term="shadcn/ui"/>
        <category label="TailwindCSS" term="TailwindCSS"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 shadcn/ui 前該補的 TailwindCSS 基礎知識(一) - 從 MUI 到 TailwindCSS 設計哲學的轉變]]></title>
        <id>https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1</id>
        <link href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1"/>
        <updated>2025-11-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇技術筆記詳細介紹了 TailwindCSS 與 shadcn/ui 的崛起與爆紅現象，並以 MUI 使用者的角度來討論如何開始學習 TailwindCSS。]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言"><strong>前言</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="Direct link to 前言" title="Direct link to 前言" translate="no">​</a></h2>
<p>自從 shadcn/ui 在 2023 年 問世以來，它迅速席捲整個 React 生態圈。短短一年之內，這個「不是 UI Library 的 UI Library」幾乎成了前端開發者間的共同話題。shadcn 在 2023 年甚至被評為 GitHub 當年度最熱門的開源專案，其火熱程度相信每位 React 開發者都感受得到。</p>
<p>這兩年來，幾乎所有 React 圈知名的 YouTuber 與 KOL 都在談 shadcn。越來越多初學者的第一個 UI 套件，不再是老牌的 <strong>Bootstrap</strong>、<strong>MUI</strong> 或 <strong>Ant Design</strong>，而是直接選擇了 <strong>shadcn/ui</strong>。更有趣的是，許多人甚至略過了傳統 CSS，直接投入 <strong>TailwindCSS</strong> 的懷抱，不得不感嘆前端的潮流真的變化得太快了(別更了，快學不動了 🥲)。我從第一次接觸 React，到第一份正式工作，一路都使用 <strong>MUI</strong>。在三年前，它仍是 React 生態中最具代表性的 component library。而如今，看到全世界都在玩 shadcn，連讓 AI 生成 React 專案的範例都會自動引入 shadcn，讓我也不得不跟上潮流來玩玩看 shadcn(俗話說打不贏就加入 😎)。</p>
<p>作為一個 MUI 的老用戶，我能明顯感受到 shadcn 與 MUI 之間的開發體驗差異。shadcn 的美感與客製化能力確實勝出，但就我的學習體驗來說，對初學者而言 shadcn 的學習曲線並不算平緩。畢竟它本質上不是傳統的 UI Library，而是一組以 <strong>TailwindCSS</strong> 為核心堆砌出的「可複製元件範本」。若對 TailwindCSS 不夠熟悉，剛開始入門 shadcn 就會陷入知道怎麼抄但不會改，或者只能讓 AI 幫你改的處境。</p>
<p>這也正是我想寫這個系列文的原因，希望幫助自己和想開始玩 shadcn 的人補上一些關鍵的 TailwindCSS 知識。在深入 shadcn 之前，先把基礎打穩，理解它的設計哲學與底層邏輯，開發體驗會順暢得多。</p>
<p>這是一個完整的 TailwindCSS 與 shadcn/ui 學習系列，總共分為五篇文章：</p>
<ol>
<li class=""><strong>從 MUI 到 TailwindCSS 設計哲學的轉變（本篇）</strong></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-2" target="_blank" rel="noopener noreferrer" class="">理解 TailwindCSS 的運作原理</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-3" target="_blank" rel="noopener noreferrer" class="">TailwindCSS v4 內建 Utility Classes 速查</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-4" target="_blank" rel="noopener noreferrer" class="">透過 @theme 自訂設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-5" target="_blank" rel="noopener noreferrer" class="">解析 shadcn/ui 的設計系統</a></li>
<li class=""><a href="https://notes.boshkuo.com/blog/tailwindcss-knowledge-before-shadcn-ui-6" target="_blank" rel="noopener noreferrer" class="">shadcn/ui 生態系工具鏈</a></li>
</ol>
<p>在這幾篇文章記錄了我在學習過程中遇到了許多困惑，像是：</p>
<ul>
<li class=""><strong>MUI 到 TailwindCSS 的概念轉換</strong></li>
<li class=""><strong>改如何正確配置環境</strong></li>
<li class=""><strong>TailwindCSS 工具鏈</strong></li>
<li class=""><strong>切換主題的實作</strong></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="utility-first的設計哲學"><strong>「Utility-First」的設計哲學</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1#utility-first%E7%9A%84%E8%A8%AD%E8%A8%88%E5%93%B2%E5%AD%B8" class="hash-link" aria-label="Direct link to utility-first的設計哲學" title="Direct link to utility-first的設計哲學" translate="no">​</a></h2>
<p>作為 MUI 的長期使用者，我一直都很習慣這樣寫元件：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Button</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@mui/material"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">MyComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">contained</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">color</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">primary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      點擊我</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這是典型的 &nbsp;<strong>Component-Based（元件導向）</strong>&nbsp; 設計。使用預先封裝好的元件，透過 props 來控制樣式變化。</p>
<p>而 TailwindCSS 採用的是 &nbsp;<strong>Utility-First（工具優先）</strong>&nbsp; 設計哲學：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">MyComponent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      點擊我</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>有各種「工具類別」（utility classes）可以選擇：<code>bg-blue-500</code>（背景色）、<code>text-white</code>（文字顏色）、<code>px-4</code>（水平內距）、<code>py-2</code>（垂直內距）、<code>rounded</code>（圓角）。我們可以自由組合這些工具，打造出想要的樣式。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>Utility-First 不代表「沒有元件」，而是「先用工具類別組合出樣式，再視需要封裝成元件」。這個順序的差異，決定了你的開發流程。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailwindcss-的崛起與-shadcnui-的爆紅現象"><strong>TailwindCSS 的崛起與 Shadcn/ui 的爆紅現象</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1#tailwindcss-%E7%9A%84%E5%B4%9B%E8%B5%B7%E8%88%87-shadcnui-%E7%9A%84%E7%88%86%E7%B4%85%E7%8F%BE%E8%B1%A1" class="hash-link" aria-label="Direct link to tailwindcss-的崛起與-shadcnui-的爆紅現象" title="Direct link to tailwindcss-的崛起與-shadcnui-的爆紅現象" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailwindcss-為什麼會崛起"><strong>TailwindCSS 為什麼會崛起？</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1#tailwindcss-%E7%82%BA%E4%BB%80%E9%BA%BC%E6%9C%83%E5%B4%9B%E8%B5%B7" class="hash-link" aria-label="Direct link to tailwindcss-為什麼會崛起" title="Direct link to tailwindcss-為什麼會崛起" translate="no">​</a></h3>
<p>近年來，越來越多公司希望打造獨特的設計系統與品牌識別，而不是讓產品看起來只是另一個 Material Design 或 Bootstrap。傳統元件庫的「一體適用」風格反而成了限制。要深度客製化一個現成元件庫，往往比從零開始更複雜。TailwindCSS 成為了其中一條解決方向，它提供一套完整的設計語彙（例如間距、顏色、字型大小等 token），卻不強加任何美學風格，因此成為建立自家設計系統的理想工具。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailwindcss-與傳統-css-的維護性差異"><strong>TailwindCSS 與傳統 CSS 的維護性差異</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1#tailwindcss-%E8%88%87%E5%82%B3%E7%B5%B1-css-%E7%9A%84%E7%B6%AD%E8%AD%B7%E6%80%A7%E5%B7%AE%E7%95%B0" class="hash-link" aria-label="Direct link to tailwindcss-與傳統-css-的維護性差異" title="Direct link to tailwindcss-與傳統-css-的維護性差異" translate="no">​</a></h3>
<p>傳統 CSS 的問題：</p>
<div class="language-css codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-css codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">/* styles.css */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.button-primary</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#3b82f6</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token color">white</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">padding</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token unit">rem</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">border-radius</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.375</span><span class="token unit">rem</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.button-primary</span><span class="token selector pseudo-class" style="color:#00009f">:hover</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">background-color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token hexcode color">#2563eb</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* 三個月後，專案裡可能有 50 個類似的 class... */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.btn-blue</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* ... */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.primary-btn</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* ... */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token selector class" style="color:#00009f">.action-button</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* ... */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>TailwindCSS 的解決方案：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 所有樣式都在 HTML 中，一眼就能看出這個按鈕長什麼樣子</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  點擊我</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>這種方式的優勢：</p>
<ul>
<li class=""><strong>沒有命名煩惱</strong>：不用想「這個按鈕該叫什麼 class 名稱」</li>
<li class=""><strong>樣式就在眼前</strong>：不用在 HTML 和 CSS 檔案之間跳來跳去</li>
<li class=""><strong>刪除即清理</strong>：刪除元件時，樣式也一起消失，不會留下死 CSS</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="shadcnui-的爆紅可複製的元件範例集合"><strong>Shadcn/ui 的爆紅：可複製的元件範例集合</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1#shadcnui-%E7%9A%84%E7%88%86%E7%B4%85%E5%8F%AF%E8%A4%87%E8%A3%BD%E7%9A%84%E5%85%83%E4%BB%B6%E7%AF%84%E4%BE%8B%E9%9B%86%E5%90%88" class="hash-link" aria-label="Direct link to shadcnui-的爆紅可複製的元件範例集合" title="Direct link to shadcnui-的爆紅可複製的元件範例集合" translate="no">​</a></h3>
<p>而 &nbsp;<code>shadcn/ui</code>&nbsp; 的爆紅，正是這股潮流下的必然結果。它不是傳統的「樣式黑箱式元件庫」，而是一組 &nbsp;<strong>可複製的元件範例集合</strong>。開發者透過 CLI 工具，直接把元件程式碼「複製」到專案裡，並擁有 &nbsp;<strong>100% 修改權</strong>。這種模式結合了 TailwindCSS 的 Utility-First 哲學，讓開發者能在「快速起步」與「完全掌控」之間找到最佳平衡。</p>
<p>傳統元件庫（如 MUI）：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install @mui/material  # 安裝整個套件</span><br></div></code></pre></div></div>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Button</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@mui/material"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 從 node_modules 引入</span><br></div></code></pre></div></div>
<p>Shadcn/ui 的做法：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx shadcn@latest add button  # 把元件「複製」到你的專案裡</span><br></div></code></pre></div></div>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Button</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@/components/ui/button"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 從你自己的專案引入</span><br></div></code></pre></div></div>
<p>這帶來幾個關鍵優勢：</p>
<ol>
<li class=""><strong>完全掌控</strong>：元件的程式碼在你的專案裡，你可以隨意修改</li>
<li class=""><strong>零依賴負擔</strong>：只安裝你需要的元件，不會有用不到的程式碼</li>
<li class=""><strong>學習最佳實踐</strong>：每個元件都是 TailwindCSS + TypeScript 的範例</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="mui-使用者的-tailwindcss-學習路徑"><strong>MUI 使用者的 TailwindCSS 學習路徑</strong><a href="https://notes.boshkuo.com/en/blog/tailwindcss-knowledge-before-shadcn-ui-1#mui-%E4%BD%BF%E7%94%A8%E8%80%85%E7%9A%84-tailwindcss-%E5%AD%B8%E7%BF%92%E8%B7%AF%E5%BE%91" class="hash-link" aria-label="Direct link to mui-使用者的-tailwindcss-學習路徑" title="Direct link to mui-使用者的-tailwindcss-學習路徑" translate="no">​</a></h2>
<p>以下我整理了我自己的 TailwindCSS 學習路徑：</p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<p><strong>學習重點：</strong></p>
<ul>
<li class=""><strong>第一階段</strong>：專注在最常用的類別，建立肌肉記憶</li>
<li class=""><strong>第二階段</strong>：理解如何處理不同裝置尺寸與使用者互動</li>
<li class=""><strong>第三階段</strong>：學習進階配置，打造自己的設計系統</li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="shadcn/ui" term="shadcn/ui"/>
        <category label="TailwindCSS" term="TailwindCSS"/>
        <category label="MUI" term="MUI"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[軟體工程基本功：用 Semver 駕馭版本控制的藝術]]></title>
        <id>https://notes.boshkuo.com/en/blog/semver</id>
        <link href="https://notes.boshkuo.com/en/blog/semver"/>
        <updated>2025-03-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇文章從我親身踩坑的經驗出發，帶你一步步理解 Semantic Versioning（語意化版本）是什麼、該怎麼判斷要升哪個欄位、以及常見的誤解與實務應用。無論你是剛接觸版本管理的新手，還是想幫團隊建立更穩定發佈流程的工程師，都能在這篇筆記中找到實用觀念與範例。]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="論版本號的重要性從一個掉進坑裡的故事說起"><strong>論版本號的重要性，從一個掉進坑裡的故事說起</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E8%AB%96%E7%89%88%E6%9C%AC%E8%99%9F%E7%9A%84%E9%87%8D%E8%A6%81%E6%80%A7%E5%BE%9E%E4%B8%80%E5%80%8B%E6%8E%89%E9%80%B2%E5%9D%91%E8%A3%A1%E7%9A%84%E6%95%85%E4%BA%8B%E8%AA%AA%E8%B5%B7" class="hash-link" aria-label="Direct link to 論版本號的重要性從一個掉進坑裡的故事說起" title="Direct link to 論版本號的重要性從一個掉進坑裡的故事說起" translate="no">​</a></h2>
<p>我剛社會第一年的時候，對套件版本隱藏的相容性問題沒什麼概念，還記得當時我看到一個平常在用的套件發布了最新版，心想：</p>
<blockquote>
<p>「哦～有新版本釋出了！第五版耶，官網上描述的新功能看起來挺酷的，更新來玩玩看好了。」</p>
</blockquote>
<p>我就不假思索，直接把我專案裡原本用的第四版套件升級到第五版。誰知道這麼一升級，我整個專案就跑不起來了。翻了一下文件才發現，原本我用的某些 API 在第五版已經被移除了，甚至連套件的引入方式都整個改掉了。</p>
<p>舉例來說，原本這樣寫是沒問題的：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">functionA</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'some-library'</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>但新版本強制使用 named import，變成要這樣寫才行：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> functionA </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'some-library'</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>後來我才知道，這是因為這個套件升級的是「<strong>大版號（Major）</strong>」，代表它可能包含<strong>破壞性變更（breaking changes）</strong>，使用方式可能會跟之前不一樣。也正是從這次經驗開始，我才慢慢了解版本號其實<strong>不是只是個數字而已</strong>，而是有一套設計邏輯與意圖，背後還有「<strong>語意化版本（Semantic Versioning）</strong>」這個規則存在。</p>
<p>後來，我在工作上也接觸到了開發與維護套件的任務，成為了套件與原始碼的維護方，<strong>Semantic Versioning</strong> 這套軟體版本管理的原則變得更加重要，因為套件版本管理的好與壞會直接影響到團隊協作、使用者體驗，甚至是產品的穩定性。這篇文章我想帶大家一起來探索 <strong>Semantic Versioning</strong> 的核心概念與我在實際維護套件時的一些經驗，礙於篇幅可能沒辦法涵蓋到 <strong>Semantic Versioning</strong> 的所有細節，建議讀者有時間的話可以搭配 <a href="https://semver.org/" target="_blank" rel="noopener noreferrer" class="">Semantic Versioning 2.0.0 官方文件</a> 一起閱讀。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="語意化版本semantic-versioning是什麼基礎規則與範例一次搞懂"><strong>語意化版本（Semantic Versioning）是什麼？基礎規則與範例一次搞懂</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E8%AA%9E%E6%84%8F%E5%8C%96%E7%89%88%E6%9C%ACsemantic-versioning%E6%98%AF%E4%BB%80%E9%BA%BC%E5%9F%BA%E7%A4%8E%E8%A6%8F%E5%89%87%E8%88%87%E7%AF%84%E4%BE%8B%E4%B8%80%E6%AC%A1%E6%90%9E%E6%87%82" class="hash-link" aria-label="Direct link to 語意化版本semantic-versioning是什麼基礎規則與範例一次搞懂" title="Direct link to 語意化版本semantic-versioning是什麼基礎規則與範例一次搞懂" translate="no">​</a></h2>
<p>版本號其實是種「溝通的語言」，那麼這個語言是怎麼設計的呢？這就要介紹 <strong>Semantic Versioning</strong>（簡稱 <strong>SemVer</strong>）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="語意化版本的格式majorminorpatch"><strong>語意化版本的格式：<code>MAJOR.MINOR.PATCH</code></strong><a href="https://notes.boshkuo.com/en/blog/semver#%E8%AA%9E%E6%84%8F%E5%8C%96%E7%89%88%E6%9C%AC%E7%9A%84%E6%A0%BC%E5%BC%8Fmajorminorpatch" class="hash-link" aria-label="Direct link to 語意化版本的格式majorminorpatch" title="Direct link to 語意化版本的格式majorminorpatch" translate="no">​</a></h3>
<p>SemVer 採用一種簡單的三位數格式：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">MAJOR.MINOR.PATCH</span><br></div></code></pre></div></div>
<p>各位數的意義如下：</p>
<table><thead><tr><th>欄位</th><th>說明</th></tr></thead><tbody><tr><td>MAJOR</td><td><strong>破壞性變更</strong>：不相容的 API 變更</td></tr><tr><td>MINOR</td><td><strong>新增功能</strong>：向下相容的功能擴充</td></tr><tr><td>PATCH</td><td><strong>修復錯誤</strong>：向下相容的 bug 修正</td></tr></tbody></table>
<p>來看幾個簡單的例子：</p>
<ul>
<li class=""><code>1.2.3</code> → <code>1.2.4</code>：修了一個小 bug（patch）</li>
<li class=""><code>1.2.3</code> → <code>1.3.0</code>：加入一個新功能（minor）</li>
<li class=""><code>1.2.3</code> → <code>2.0.0</code>：有破壞性變更，升級需特別注意（major）</li>
</ul>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>SemVer 的核心精神是「傳遞改動的意圖」。給出版本號，不只是「程式寫完了」，更是告訴使用者：「這次我改了什麼，影響到你嗎？」</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="預發布版本與建構元資料"><strong>預發布版本與建構元資料</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E9%A0%90%E7%99%BC%E5%B8%83%E7%89%88%E6%9C%AC%E8%88%87%E5%BB%BA%E6%A7%8B%E5%85%83%E8%B3%87%E6%96%99" class="hash-link" aria-label="Direct link to 預發布版本與建構元資料" title="Direct link to 預發布版本與建構元資料" translate="no">​</a></h3>
<p>除了主版本號，SemVer 還支援額外的標記，用來描述還沒正式發布或內部用途的預發布版本（Pre-release）:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">格式為：1.0.0-&lt;標記&gt;</span><br></div></code></pre></div></div>
<p>常見標記有：</p>
<ul>
<li class=""><code>1.0.0-alpha</code>：非常早期，可能還不穩</li>
<li class=""><code>1.0.0-beta</code>：比 alpha 穩定，仍可能有問題</li>
<li class=""><code>1.0.0-rc.1</code>（release candidate）：候選版本，準備好要正式釋出了</li>
</ul>
<blockquote>
<p>📌 預發布版本不會自動被安裝，除非你特別指定，例如 npm install some-lib@beta</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="該升級哪個版本號用元件庫範例學會判斷-patch--minor--major"><strong>該升級哪個版本號？用元件庫範例學會判斷 <code>Patch / Minor / Major</code></strong><a href="https://notes.boshkuo.com/en/blog/semver#%E8%A9%B2%E5%8D%87%E7%B4%9A%E5%93%AA%E5%80%8B%E7%89%88%E6%9C%AC%E8%99%9F%E7%94%A8%E5%85%83%E4%BB%B6%E5%BA%AB%E7%AF%84%E4%BE%8B%E5%AD%B8%E6%9C%83%E5%88%A4%E6%96%B7-patch--minor--major" class="hash-link" aria-label="Direct link to 該升級哪個版本號用元件庫範例學會判斷-patch--minor--major" title="Direct link to 該升級哪個版本號用元件庫範例學會判斷-patch--minor--major" translate="no">​</a></h2>
<p>上一章我們學會了版本號三個欄位的意義，這章就要帶大家進入實務場景，用 <strong>Component Library</strong> 的範例，幫你建立「版本直覺」。判斷邏輯其實可以簡化成一個簡單的直覺：</p>
<blockquote>
<p>會壞就 bump <code>major</code>，加功能就 bump <code>minor</code>，修 bug 就 bump <code>patch</code>。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-大版號major-x00破壞性變更breaking-changes"><strong>🔴 大版號（Major, <code>X.0.0</code>）：破壞性變更（Breaking Changes）</strong><a href="https://notes.boshkuo.com/en/blog/semver#-%E5%A4%A7%E7%89%88%E8%99%9Fmajor-x00%E7%A0%B4%E5%A3%9E%E6%80%A7%E8%AE%8A%E6%9B%B4breaking-changes" class="hash-link" aria-label="Direct link to -大版號major-x00破壞性變更breaking-changes" title="Direct link to -大版號major-x00破壞性變更breaking-changes" translate="no">​</a></h3>
<p>當你做出「<strong>升級後原本的寫法會壞掉</strong>」的改動，就屬於破壞性變更，應該 bump <code>MAJOR</code>。</p>
<p><strong>✅ 刪除或修改現有 API</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.x 寫法</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">outlined</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// v2.0.0 移除 `variant` prop</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 🚨 升級後錯誤：variant 不被支援</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p><strong>✅ 改變命名或使用方式</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.x</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Modal</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">isOpen</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript boolean" style="color:#36acaa">true</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// v2.0.0 改為 open（更貼近 HTML 標準）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Modal</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">open</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript boolean" style="color:#36acaa">true</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 🚨 需要改程式碼才能用</span><br></div></code></pre></div></div>
<p><strong>✅ 改變元件的預設行為</strong></p>
<ul>
<li class="">原本 <code>Table</code> 預設是 server-side pagination，改為 client-side → 會直接改變使用者的畫面與資料邏輯。</li>
</ul>
<p><strong>✅ 改變 TypeScript 類型（造成型別不相容）</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.x</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">InputProps</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// v2.0.0 僅接受 string</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">InputProps</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>這會導致原本傳 <code>number</code> 的使用者出現型別錯誤。</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>判斷提示</div><div class="admonitionContent_BuS1"><p>只要升級會讓現有的寫法不能用，就是 breaking change。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-中版號minor-xy0新增功能但相容"><strong>🟡 中版號（Minor, <code>X.Y.0</code>）：新增功能但相容</strong><a href="https://notes.boshkuo.com/en/blog/semver#-%E4%B8%AD%E7%89%88%E8%99%9Fminor-xy0%E6%96%B0%E5%A2%9E%E5%8A%9F%E8%83%BD%E4%BD%86%E7%9B%B8%E5%AE%B9" class="hash-link" aria-label="Direct link to -中版號minor-xy0新增功能但相容" title="Direct link to -中版號minor-xy0新增功能但相容" translate="no">​</a></h3>
<p>只要是<strong>加東西但不破壞原有功能</strong>，通常都可以放心 bump <code>MINOR</code>。</p>
<p><strong>✅ 新增新的 prop</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.2.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Tooltip</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">content</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">Hi</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// v1.3.0 新增 delay prop</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Tooltip</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">content</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">Hi</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">delay</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">300</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// ✅ 舊寫法照常可用</span><br></div></code></pre></div></div>
<p><strong>✅ 新增支援的事件</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.2.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleClick</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// v1.3.0 新增 onDoubleClick</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleClick</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onDoubleClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleDoubleClick</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></div></code></pre></div></div>
<p><strong>✅ 新增樣式選項、變體</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.3.0 新增 variant="secondary"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">secondary</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></div></code></pre></div></div>
<p><strong>✅ 新增元件</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.4.0 加入 AvatarGroup</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">AvatarGroup</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Avatar</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">user1.jpg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Avatar</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">user2.jpg</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">AvatarGroup</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>判斷提示</div><div class="admonitionContent_BuS1"><p>新增功能，但不會讓使用者既有程式碼壞掉，就是 minor。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-小版號patch-xyz錯誤修正與內部優化"><strong>🟢 小版號（Patch, <code>X.Y.Z</code>）：錯誤修正與內部優化</strong><a href="https://notes.boshkuo.com/en/blog/semver#-%E5%B0%8F%E7%89%88%E8%99%9Fpatch-xyz%E9%8C%AF%E8%AA%A4%E4%BF%AE%E6%AD%A3%E8%88%87%E5%85%A7%E9%83%A8%E5%84%AA%E5%8C%96" class="hash-link" aria-label="Direct link to -小版號patch-xyz錯誤修正與內部優化" title="Direct link to -小版號patch-xyz錯誤修正與內部優化" translate="no">​</a></h3>
<p>這類改動的特點是：<strong>使用者的寫法不變，但效果更好或問題被修正了。</strong></p>
<p><strong>✅ 修 bug</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.2.3: Modal 的 onClose 不會觸發（bug）</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// v1.2.4 修正後</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Modal</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">open</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript boolean" style="color:#36acaa">true</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClose</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript console class-name" style="color:#00009f">console</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript method function property-access" style="color:#d73a49">log</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript string" style="color:#e3116c">'關閉'</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></div></code></pre></div></div>
<p>使用方式完全沒改，但行為正確了。</p>
<p><strong>✅ 修樣式問題</strong></p>
<ul>
<li class="">修正 <code>Button</code> 在 Firefox 中 padding 過大的問題。</li>
</ul>
<p><strong>✅ 效能優化、不影響 API 的重構</strong></p>
<ul>
<li class=""><code>Table</code> 加上虛擬化，提升渲染效能</li>
<li class="">移除多餘的 re-render，但對外部 API 無影響</li>
</ul>
<p><strong>✅ 改進 TypeScript 定義（不影響現有用法）</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// v1.2.3</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">TextFieldProps</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">onChange</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">any</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// v1.2.4</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token class-name">TextFieldProps</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">onChange</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">event</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ChangeEvent</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">HTMLInputElement</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>對使用者來說可選擇更明確的型別，但舊寫法仍可用。</p>
<p><strong>✅ 升級相依套件但不影響對外行為</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">chore(deps): update react to 18.2.0</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>判斷提示</div><div class="admonitionContent_BuS1"><p>使用者感受不到改動，但行為更正確、更穩定，就是 patch。</p></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="semantic-versioning-常見問題整理"><strong>Semantic Versioning 常見問題整理</strong><a href="https://notes.boshkuo.com/en/blog/semver#semantic-versioning-%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C%E6%95%B4%E7%90%86" class="hash-link" aria-label="Direct link to semantic-versioning-常見問題整理" title="Direct link to semantic-versioning-常見問題整理" translate="no">​</a></h2>
<p>到了這裡，相信你對 <strong>Semantic Versioning</strong> 已經有一定掌握了。不過在實務中，還是常會遇到一些讓人搞混的地方。本章我們就用 <strong>常見問題 FAQ 的方式</strong>，快速釐清一些細節與誤解。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我只改了內部邏輯沒改-api還需要-bump-版本號嗎"><strong>❓我只改了內部邏輯，沒改 API，還需要 bump 版本號嗎？</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E6%88%91%E5%8F%AA%E6%94%B9%E4%BA%86%E5%85%A7%E9%83%A8%E9%82%8F%E8%BC%AF%E6%B2%92%E6%94%B9-api%E9%82%84%E9%9C%80%E8%A6%81-bump-%E7%89%88%E6%9C%AC%E8%99%9F%E5%97%8E" class="hash-link" aria-label="Direct link to 我只改了內部邏輯沒改-api還需要-bump-版本號嗎" title="Direct link to 我只改了內部邏輯沒改-api還需要-bump-版本號嗎" translate="no">​</a></h3>
<p>✅ <strong>需要</strong>。</p>
<p>即使 API 沒變，但如果改動可能導致元件行為變化（例如回傳值不同、預設行為變了），就應該 bump 版本，視影響程度選擇適當欄位。</p>
<blockquote>
<p>📌 常見錯誤：「內部邏輯不算 API」</p>
<p>👉 實際上，<strong>行為</strong>也是一種 API。只要使用者感受得到變化，就要負責。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="已經-deprecated-的功能如果移除了算是-breaking-change-嗎"><strong>❓已經 deprecated 的功能，如果移除了，算是 breaking change 嗎？</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E5%B7%B2%E7%B6%93-deprecated-%E7%9A%84%E5%8A%9F%E8%83%BD%E5%A6%82%E6%9E%9C%E7%A7%BB%E9%99%A4%E4%BA%86%E7%AE%97%E6%98%AF-breaking-change-%E5%97%8E" class="hash-link" aria-label="Direct link to 已經-deprecated-的功能如果移除了算是-breaking-change-嗎" title="Direct link to 已經-deprecated-的功能如果移除了算是-breaking-change-嗎" translate="no">​</a></h3>
<p>✅ <strong>是</strong>，<strong>還是 breaking change</strong>，應該 bump <strong>Major</strong>。</p>
<p>就算功能早已標註 deprecated，只要使用者還能用，就不能隨意移除。移除的那一刻，仍然是破壞性改動。</p>
<blockquote>
<p>⚠️ 重要觀念：deprecated 是預告，不代表你可以跳過版本規則。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我新增了一個參數prop但改變了元件預設行為這樣算-breaking-change-嗎"><strong>❓我新增了一個參數（prop），但改變了元件預設行為，這樣算 breaking change 嗎？</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E6%88%91%E6%96%B0%E5%A2%9E%E4%BA%86%E4%B8%80%E5%80%8B%E5%8F%83%E6%95%B8prop%E4%BD%86%E6%94%B9%E8%AE%8A%E4%BA%86%E5%85%83%E4%BB%B6%E9%A0%90%E8%A8%AD%E8%A1%8C%E7%82%BA%E9%80%99%E6%A8%A3%E7%AE%97-breaking-change-%E5%97%8E" class="hash-link" aria-label="Direct link to 我新增了一個參數prop但改變了元件預設行為這樣算-breaking-change-嗎" title="Direct link to 我新增了一個參數prop但改變了元件預設行為這樣算-breaking-change-嗎" translate="no">​</a></h3>
<p>🟠 <strong>視情況而定</strong>，但通常應該算是 <strong>breaking change</strong>。</p>
<p>舉例來說：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 原本 Tooltip 預設永遠顯示在右側</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Tooltip</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">content</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">Hi</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 新版新增 `placement`，但預設改成 bottom</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Tooltip</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">content</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">Hi</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></div></code></pre></div></div>
<p>雖然寫法沒變，但畫面結果改了，可能影響使用者體驗或布局。這類行為建議 bump <strong>Major</strong> 或至少寫清楚 release notes。</p>
<blockquote>
<p>💡 判斷依據：使用者不修改程式碼卻出現不同結果 → 通常就算是 breaking。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我看到套件版本是-010這代表它穩定了嗎"><strong>❓我看到套件版本是 <code>0.1.0</code>，這代表它穩定了嗎？</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E6%88%91%E7%9C%8B%E5%88%B0%E5%A5%97%E4%BB%B6%E7%89%88%E6%9C%AC%E6%98%AF-010%E9%80%99%E4%BB%A3%E8%A1%A8%E5%AE%83%E7%A9%A9%E5%AE%9A%E4%BA%86%E5%97%8E" class="hash-link" aria-label="Direct link to 我看到套件版本是-010這代表它穩定了嗎" title="Direct link to 我看到套件版本是-010這代表它穩定了嗎" translate="no">​</a></h3>
<p>❌ <strong>不是</strong>。</p>
<p>根據 SemVer 定義，只要是 <code>0.x.y</code> 版本，代表這個專案還在「開發階段」，<strong>不保證 API 穩定</strong>。任何欄位變動都可能是 breaking change。</p>
<blockquote>
<p>📌 常見誤解：「0.1.0 應該就是第一版吧？」</p>
<p>👉 錯，<strong><code>1.0.0</code> 才代表第一個穩定版</strong>，這時才開始遵守 MAJOR/MINOR/PATCH 的變更邏輯。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="可以用日期版本像-20240301來取代-semver-嗎"><strong>❓可以用日期版本（像 <code>2024.03.01</code>）來取代 SemVer 嗎？</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E5%8F%AF%E4%BB%A5%E7%94%A8%E6%97%A5%E6%9C%9F%E7%89%88%E6%9C%AC%E5%83%8F-20240301%E4%BE%86%E5%8F%96%E4%BB%A3-semver-%E5%97%8E" class="hash-link" aria-label="Direct link to 可以用日期版本像-20240301來取代-semver-嗎" title="Direct link to 可以用日期版本像-20240301來取代-semver-嗎" translate="no">​</a></h3>
<p>✅ <strong>可以，但目的不同</strong>。</p>
<p>這種稱為 <strong>CalVer（Calendar Versioning）</strong>，適合快速迭代的產品（如 Ubuntu、Chrome）。但如果你的專案強調 API 穩定性與相容性，<strong>SemVer 更適合用來溝通版本意圖</strong>。</p>
<blockquote>
<p>📌 CalVer 看得出發行時間，但看不出改動類型；SemVer 則反過來。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-額外補充npm-中的--跟--是什麼意思"><strong>🧩 額外補充：npm 中的 <code>^</code> 跟 <code>~</code> 是什麼意思？</strong><a href="https://notes.boshkuo.com/en/blog/semver#-%E9%A1%8D%E5%A4%96%E8%A3%9C%E5%85%85npm-%E4%B8%AD%E7%9A%84--%E8%B7%9F--%E6%98%AF%E4%BB%80%E9%BA%BC%E6%84%8F%E6%80%9D" class="hash-link" aria-label="Direct link to -額外補充npm-中的--跟--是什麼意思" title="Direct link to -額外補充npm-中的--跟--是什麼意思" translate="no">​</a></h3>
<p>這些符號是用來表示「可以接受的版本範圍」。</p>
<table><thead><tr><th>符號</th><th>意思</th><th>範例</th></tr></thead><tbody><tr><td><code>^</code></td><td>同一個 MAJOR 內最新</td><td><code>^1.2.3</code> → 可以裝到 <code>1.999.999</code></td></tr><tr><td><code>~</code></td><td>同一個 MINOR 內最新</td><td><code>~1.2.3</code> → 可以裝到 <code>1.2.x</code></td></tr></tbody></table>
<blockquote>
<p>✅ 小結：^ 比 ~ 更寬鬆。選擇哪個要看你想控制更新的程度。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-package-lockjson-的角色是什麼"><strong>🔐 <code>package-lock.json</code> 的角色是什麼？</strong><a href="https://notes.boshkuo.com/en/blog/semver#-package-lockjson-%E7%9A%84%E8%A7%92%E8%89%B2%E6%98%AF%E4%BB%80%E9%BA%BC" class="hash-link" aria-label="Direct link to -package-lockjson-的角色是什麼" title="Direct link to -package-lockjson-的角色是什麼" translate="no">​</a></h3>
<p>即使 <code>package.json</code> 使用了版本範圍（如 <code>^1.2.3</code>），實際安裝時會根據 <code>package-lock.json</code> 中<strong>記錄的精確版本</strong>來進行安裝。</p>
<p>這樣做的好處是：</p>
<ul>
<li class="">確保你團隊每次安裝的套件版本一致</li>
<li class="">避免「今天能跑、明天壞掉」的神祕 bug</li>
</ul>
<blockquote>
<p>📌 小提醒：如果你刪掉 lock file 再裝一次，可能會裝到不一樣的版本。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語"><strong>結語</strong><a href="https://notes.boshkuo.com/en/blog/semver#%E7%B5%90%E8%AA%9E" class="hash-link" aria-label="Direct link to 結語" title="Direct link to 結語" translate="no">​</a></h2>
<p>當我們的程式碼不再只是自己用，而是給團隊、開源社群，甚至未來的自己使用時，版本管理就不再是選項，而是基本功的一部分。希望這篇文章能幫助你更清楚 Semantic Versioning 的精神與實務應用，無論你是正在維護套件、架構元件庫，還是只是好奇版本號背後的設計哲學，都能從中找到一些啟發。</p>
<p>如果你正在維護一個套件、元件庫，或只是想讓自己的 side project 更有品質，不妨從下一個 release 開始，認真地思考這次改動該升哪個號碼吧！</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/semver#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://semver.org/" target="_blank" rel="noopener noreferrer" class=""><strong>Semantic Versioning 2.0.0</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="Software Engineering" term="Software Engineering"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Gitmoji x Conventional Commit 工作流 (三) - 使用 commit-and-tag-version 自動化生成 CHANGELOG.md]]></title>
        <id>https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version</id>
        <link href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version"/>
        <updated>2024-12-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇文章深入探討如何根據 Keep a Changelog 標準撰寫結構化、清晰的日誌，並介紹如 commit-and-tag-version 等工具，自動化生成變更日誌，讓維護工作更加高效。同時提供實踐範例與調整建議，幫助專案參與者打造更專業的工作流。]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="變更日誌的意義與重要性"><strong>變更日誌的意義與重要性</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E8%AE%8A%E6%9B%B4%E6%97%A5%E8%AA%8C%E7%9A%84%E6%84%8F%E7%BE%A9%E8%88%87%E9%87%8D%E8%A6%81%E6%80%A7" class="hash-link" aria-label="Direct link to 變更日誌的意義與重要性" title="Direct link to 變更日誌的意義與重要性" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="變更日誌是什麼"><strong>變更日誌是什麼？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E8%AE%8A%E6%9B%B4%E6%97%A5%E8%AA%8C%E6%98%AF%E4%BB%80%E9%BA%BC" class="hash-link" aria-label="Direct link to 變更日誌是什麼" title="Direct link to 變更日誌是什麼" translate="no">​</a></h3>
<p>在軟體專案中，<strong>變更日誌（Change Log）</strong> 是一份用來記錄專案功能變更、問題修復、優化內容的重要「歷史日記」。這份日誌的對象通常是開發者、測試人員。它可以幫助專案參與者快速掌握專案歷史，甚至在需要版本回滾時節省大量時間。</p>
<p>變更日誌最主要的目的是為了讓大家能「看懂版本變化」。它有幾個很實用的好處：</p>
<ol>
<li class="">讓專案更透明，無論是團隊成員還是用戶，都能快速知道每次更新改了些什麼。</li>
<li class="">提高團隊效率，讓團隊成員快速理解改動，避免重複查詢。</li>
<li class="">輔助問題追蹤與版本回滾</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何寫出好的變更日誌"><strong>如何寫出好的變更日誌？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E5%A6%82%E4%BD%95%E5%AF%AB%E5%87%BA%E5%A5%BD%E7%9A%84%E8%AE%8A%E6%9B%B4%E6%97%A5%E8%AA%8C" class="hash-link" aria-label="Direct link to 如何寫出好的變更日誌" title="Direct link to 如何寫出好的變更日誌" translate="no">​</a></h3>
<p>可能你會覺得，寫個變更日誌能有多難？隨便列幾個點就好啦。但真要讓日誌清晰又有條理，還真有不少學問。這裡就不得不提到 <a href="https://keepachangelog.com/zh-TW/0.3.0/" target="_blank" rel="noopener noreferrer" class=""><strong>Keep a Changelog</strong></a>。它是一套專門為變更日誌設計的標準，目的是幫助大家寫出「清晰且結構化」的日誌。這套標準幫我們解決了寫日誌的常見問題，比如內容亂、不知道該記哪些東西、不統一的格式等等。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="好的變更日誌的核心原則"><strong>好的</strong>變更<strong>日誌的核心原則</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E5%A5%BD%E7%9A%84%E8%AE%8A%E6%9B%B4%E6%97%A5%E8%AA%8C%E7%9A%84%E6%A0%B8%E5%BF%83%E5%8E%9F%E5%89%87" class="hash-link" aria-label="Direct link to 好的變更日誌的核心原則" title="Direct link to 好的變更日誌的核心原則" translate="no">​</a></h3>
<p>這套標準有幾個核心原則，非常值得參考：</p>
<ul>
<li class=""><strong>為人而非機器書寫：</strong> 不用寫得像機器碼一樣複雜，越自然越好，重點是大家能看懂。</li>
<li class=""><strong>保持結構化與易讀性：</strong> 使用 Markdown 格式，方便快速跳轉或引用特定內容。</li>
<li class=""><strong>頂部留一個 <code>Unreleased</code> 區域：</strong> 未正式發布的改動都放在這裡，等發布時再歸檔到對應版本。</li>
<li class=""><strong>最新版本置頂：</strong> 日誌按照時間倒序排列，最新的放最上面，方便查找。</li>
<li class=""><strong>統一的日期格式：</strong> 建議使用 <code>YYYY-MM-DD</code>，像 2024-11-21，這種格式清晰易懂，國際化也沒問題。</li>
<li class=""><strong>遵守語義化版本控制：</strong> 和版本號一樣，要讓讀者知道變更的範圍，比如新增功能是小版本更新，Bug 修復則是補丁版本。</li>
<li class=""><strong>分類記錄變更：</strong> 把每次改動歸類到以下幾種標籤裡，方便閱讀<!-- -->
<ul>
<li class=""><strong>Added</strong>：新增的功能。</li>
<li class=""><strong>Changed</strong>：改變的功能。</li>
<li class=""><strong>Deprecated</strong>：不建議用的功能（但還沒刪）。</li>
<li class=""><strong>Removed</strong>：刪掉的功能。</li>
<li class=""><strong>Fixed</strong>：修復的 Bug。</li>
<li class=""><strong>Security</strong>：修正的安全漏洞。</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="好的-change-log-長什麼樣子"><strong>好的 Change Log 長什麼樣子？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E5%A5%BD%E7%9A%84-change-log-%E9%95%B7%E4%BB%80%E9%BA%BC%E6%A8%A3%E5%AD%90" class="hash-link" aria-label="Direct link to 好的-change-log-長什麼樣子" title="Direct link to 好的-change-log-長什麼樣子" translate="no">​</a></h3>
<p>來看看下面這個範例，這是一個符合 Keep a Changelog 標準的變更日誌：</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">#</span><span class="token title important"> Changelog</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">All notable changes to this project will be documented in this file.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">The format is based on </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">Keep a Changelog</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://keepachangelog.com/en/1.1.0/</span><span class="token url" style="color:#36acaa">)</span><span class="token plain">,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">and this project adheres to </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">Semantic Versioning</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://semver.org/spec/v2.0.0.html</span><span class="token url" style="color:#36acaa">)</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> [Unreleased]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> Added</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> v1.1 Brazilian Portuguese translation.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> v1.1 German Translation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> Changed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Use frontmatter title &amp; description in each language version template</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Replace broken OpenGraph image with an appropriately-sized Keep a Changelog </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  image that will render properly (although in English for all languages)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Fix OpenGraph title &amp; description for all languages so the title and </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">description when links are shared are language-appropriate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> Removed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Trademark sign previously shown after the project description in version </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">0.3.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> [1.1.1] - 2023-03-05</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> Added</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Arabic translation (#444).</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> v1.1 French translation.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> Fixed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Improve French translation (#377).</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Fix missing logo in 1.1 pages.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> Changed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Upgrade dependencies: Ruby 3.2.1, Middleman, etc.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> Removed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Unused normalize.css file.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> Identical links assigned in each translation file.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p><strong>範本的結構說明</strong></p>
<ol>
<li class=""><strong><code>Unreleased</code> 區域：</strong> 還沒發布的改動先放這裡，等確定版本號時再移到下一節。</li>
<li class=""><strong>版本章節：</strong> 每個版本對應一個章節，標明版本號和發布日期（例如 <code>[1.0.0] - 2024-11-21</code>）。</li>
<li class=""><strong>分類標籤：</strong> 把變更內容分成 <code>Added</code>（新增功能）、<code>Fixed</code>（修復問題）等類別，方便快速檢索。</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="工具概覽-commit-and-tag-version">工具概覽: <strong>commit-and-tag-version</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E5%B7%A5%E5%85%B7%E6%A6%82%E8%A6%BD-commit-and-tag-version" class="hash-link" aria-label="Direct link to 工具概覽-commit-and-tag-version" title="Direct link to 工具概覽-commit-and-tag-version" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="自動化-change-log-工具選擇"><strong>自動化 Change Log 工具選擇</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E8%87%AA%E5%8B%95%E5%8C%96-change-log-%E5%B7%A5%E5%85%B7%E9%81%B8%E6%93%87" class="hash-link" aria-label="Direct link to 自動化-change-log-工具選擇" title="Direct link to 自動化-change-log-工具選擇" translate="no">​</a></h3>
<p>雖說寫變更日誌很重要，但想想，如果你是一個專案的維護者，每更新一次版號就要回去翻上個版號到這個版號之間所有的 commit 紀錄，還要想辦法去蕪存菁，過濾掉一些不是很重要的變更紀錄，是一件多折騰人的事。</p>
<p>因此你會發現，很多開源專案都會使用自動化生成變更日誌的工具，來輔助專案維護者快速撈出重要的 commit 歷史紀錄，有些甚至會結合 CICD ，在分支合併進主幹時自動進版、發布 Release notes。我在尋找這類自動生成 Change Log 的工具時，發現市面上有不少選擇：</p>
<p><strong>1. <a href="https://github.com/conventional-changelog/conventional-changelog?tab=readme-ov-file" target="_blank" rel="noopener noreferrer" class="">conventional-changelog</a></strong></p>
<ul>
<li class="">
<p><strong>特點：</strong><br>
<code>conventional-changelog</code> 是一個專門用於生成 Change Log 的工具，它專注於解析 Git 提交訊息，根據 <a href="https://www.conventionalcommits.org/" target="_blank" rel="noopener noreferrer" class="">Conventional Commits</a> 規範生成日誌。然而，它僅僅專注於 Change Log 的生成，沒有其他額外功能。</p>
</li>
<li class="">
<p><strong>限制：</strong><br>
<!-- -->原生不支援 Gitmoji 格式，經過我的實測，即使跟著網路上的教學搭配 <code>conventional-changelog-gitmoji-config</code>，仍無法正確生成 Change Log。</p>
</li>
</ul>
<p><strong>2. <a href="https://github.com/conventional-changelog/standard-version" target="_blank" rel="noopener noreferrer" class="">standard-version</a></strong></p>
<ul>
<li class="">
<p><strong>特點：</strong><br>
<code>standard-version</code> 是一個多功能工具，除了生成 Change Log，還包含自動版本升級（bumping）、生成 Git Tag 和提交（commit）的功能。</p>
</li>
<li class="">
<p><strong>現狀：</strong><br>
<!-- -->它是 <code>commit-and-tag-version</code> 的前身，但目前已經被社群標記為 <strong>deprecated</strong>（停止維護）。主要原因是維護停滯、功能擴展困難，無法滿足更多使用場景需求。</p>
</li>
</ul>
<p><strong>3. <a href="https://github.com/absolute-version/commit-and-tag-version" target="_blank" rel="noopener noreferrer" class="">commit-and-tag-version</a></strong></p>
<ul>
<li class="">
<p><strong>特點：</strong><br>
<code>commit-and-tag-version</code> 是 <code>standard-version</code> 的 Fork，並進一步改進和擴展功能：</p>
<ul>
<li class=""><strong>持續維護：</strong> 提供社群支持，保持與最新需求的同步。</li>
<li class=""><strong>靈活配置：</strong> 更細緻的參數控制，滿足多場景應用。</li>
<li class=""><strong>高整合性：</strong> 繼承了 <code>standard-version</code> 的全部功能，並支持 <code>.versionrc</code> 文件進行自定義。</li>
</ul>
</li>
</ul>
<p><strong>4. <a href="https://github.com/semantic-release/semantic-release" target="_blank" rel="noopener noreferrer" class="">semantic-release</a></strong></p>
<ul>
<li class="">
<p><strong>特點：</strong><br>
<code>semantic-release</code> 是功能最強大的選項，除了 Change Log 的生成，還可以結合複雜的自動化工作流（如自動推送、發佈到 npm 或 GitHub）。</p>
</li>
<li class="">
<p><strong>限制：</strong><br>
<!-- -->設定相對複雜，對於單純關注 Change Log 生成的專案來說，可能顯得過於繁重。本篇文章的範疇並不涉及與遠端倉庫的整合，因此不選擇這個工具。</p>
</li>
</ul>
<blockquote>
<p>我將上述工具都下載下來實際測試後，最終選擇了 <code>commit-and-tag-version</code>。相比其他工具，<code>commit-and-tag-version</code> 可能是目前功能性相對完整且配置靈活的選擇，且最重要的是，它目前仍然持續在更新，短期內不用擔心沒有人維護。</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="commit-and-tag-version-的基本工作流"><strong>commit-and-tag-version 的基本工作流</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#commit-and-tag-version-%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%B7%A5%E4%BD%9C%E6%B5%81" class="hash-link" aria-label="Direct link to commit-and-tag-version-的基本工作流" title="Direct link to commit-and-tag-version-的基本工作流" translate="no">​</a></h3>
<p>根據官方文件，<code>commit-and-tag-version</code> 的執行流程如下：</p>
<ol>
<li class="">
<p><strong>遵循 Conventional Commits 規範：</strong></p>
<p>確保專案的提交訊息格式符合 <a href="https://www.conventionalcommits.org/" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Commits</strong></a> 標準。</p>
</li>
<li class="">
<p><strong>執行 <code>commit-and-tag-version</code> 指令：</strong></p>
<ul>
<li class=""><strong>檢索當前版本號：</strong> 從 <code>packageFiles</code> 或最新的 Git Tag 中獲取版本號。</li>
<li class=""><strong>版本升級：</strong> 根據提交訊息自動升級版本號（bump）。</li>
<li class=""><strong>生成 Change Log：</strong> 使用 <code>conventional-changelog</code> 底層工具生成結構化的日誌。</li>
<li class=""><strong>生成 Commit：</strong> 自動提交包含新版本號和 Change Log 的變更。</li>
<li class=""><strong>生成 Git Tag：</strong> 添加新的版本 Tag。</li>
</ul>
</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p><code>commit-and-tag-version</code> 提供了一個非常實用的功能焦作 <code>Dry Run Mode</code>。可以預覽即將執行的命令與生成的 Change Log，避免直接修改檔案或 Git 紀錄。</p><ul>
<li class="">
<p>使用方式：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx commit-and-tag-version --dry-run</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>輸出示例：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">$ commit-and-tag-version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✔ bump version from 1.0.0 to 1.1.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✔ generate CHANGELOG.md</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[Dry Run Mode] Skipping tag creation and commit.</span><br></div></code></pre></div></div>
</li>
</ul></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="聚焦-change-log-生成"><strong>聚焦 Change Log 生成</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E8%81%9A%E7%84%A6-change-log-%E7%94%9F%E6%88%90" class="hash-link" aria-label="Direct link to 聚焦-change-log-生成" title="Direct link to 聚焦-change-log-生成" translate="no">​</a></h3>
<p>不過在本篇文章中，我們僅關注 Change Log 的自動生成，因此需要在CLI 參數或是設定檔中禁用其他功能，如版本升級、提交與 Tagging。<code>commit-and-tag-version</code> 支援透過 <code>.versionrc</code>、<code>.versionrc.json</code> 或 <code>.versionrc.js</code> 文件進行配置。所有 CLI 中的參數均可直接寫入設定檔。</p>
<p>以下是 <strong><code>.versionrc</code></strong> 配置範例，跳過一些預設的流程：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"skip"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"bump"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 禁用版本升級</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"changelog"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commit"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 禁用自動提交</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"tag"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 禁用自動 Tag</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<blockquote>
<p>補充：也可以在 CLI 中加上 <code>—skip.bump</code>/<code>—skip.commit</code>/<code>—skip.tag</code>} 等參數的方式來跳過預設流程</p>
</blockquote>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span><strong>如何客製化 CHANGELOG 的格式?</strong></div><div class="admonitionContent_BuS1"><p><code>commit-and-tag-version</code> 預設使用 <a href="https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits" target="_blank" rel="noopener noreferrer" class=""><strong>conventionalcommits preset</strong></a> 這套規則，找出特定類型的 commit 紀錄，以特定的格式填寫進變更日誌。當然，我們也可以在 <code>.versionrc</code> 中調整變更日誌的格式。所有與 Change Log 相關的設定規範定義於 <a href="https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.2.0/README.md" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Changelog Configuration Spec (v2.1.0)</strong></a>。</p></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="無使用-gitmoji-的自動化-changelogmd-生成"><strong>無使用 Gitmoji 的自動化 CHANGELOG.md 生成</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E7%84%A1%E4%BD%BF%E7%94%A8-gitmoji-%E7%9A%84%E8%87%AA%E5%8B%95%E5%8C%96-changelogmd-%E7%94%9F%E6%88%90" class="hash-link" aria-label="Direct link to 無使用-gitmoji-的自動化-changelogmd-生成" title="Direct link to 無使用-gitmoji-的自動化-changelogmd-生成" translate="no">​</a></h2>
<p>接下來，我們來看看搭配前面兩篇文章的配置，在不使用 Gitmoji 的工作流下要如何用 <strong>commit-and-tag-version</strong> 自動生成 Change Log 吧！</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝"><strong>安裝</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E5%AE%89%E8%A3%9D" class="hash-link" aria-label="Direct link to 安裝" title="Direct link to 安裝" translate="no">​</a></h3>
<p>首先，我們需要安裝 <code>commit-and-tag-version</code> 工具：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev commit-and-tag-version</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置-versionrcjs-文件"><strong>配置 <code>.versionrc.js</code> 文件</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E9%85%8D%E7%BD%AE-versionrcjs-%E6%96%87%E4%BB%B6" class="hash-link" aria-label="Direct link to 配置-versionrcjs-文件" title="Direct link to 配置-versionrcjs-文件" translate="no">​</a></h3>
<p>在專案根目錄創建 <code>.versionrc.js</code> 文件，並加入以下配置：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">types</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"feat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"✨ Features | 新功能"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fix"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🐛 Bug Fixes | 修復 Bug"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"📝 Documentation | 文件變更"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"style"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🎨 Styles | 程式碼格式"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"refactor"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"♻️ Code Refactoring | 程式碼重構"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"perf"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"⚡ Performance Improvements | 改善效能的程式碼變更"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"test"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"✅ Tests | 添加或修正測試"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"revert"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"⏪ Revert | 回退先前的提交"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"build"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"📦 Build System | 影響構建系統或外部依賴的變更"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"chore"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🔧 chore | 其他不修改 src 或測試文件的變更"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ci"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"👷 Continuous Integration | CI 配置"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">skip</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">bump</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">changelog</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">commit</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">tag</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="生成-changelogmd">生成 CHANGELOG.md<a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E7%94%9F%E6%88%90-changelogmd" class="hash-link" aria-label="Direct link to 生成 CHANGELOG.md" title="Direct link to 生成 CHANGELOG.md" translate="no">​</a></h3>
<p>在 package.json 內，將生成指令加入 script:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"release"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"commit-and-tag-version"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<p>通過以上配置所產生的 CHANGELOG.md 範本如下</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">#</span><span class="token title important"> Changelog</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">All notable changes to this project will be documented in this file. See </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">commit-and-tag-version</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://github.com/absolute-version/commit-and-tag-version</span><span class="token url" style="color:#36acaa">)</span><span class="token plain"> for commit guidelines.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> 1.1.0 (2024-11-25)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> ✨ Features | 新功能</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">*</span><span class="token plain"> rEADME 5f9cf7e</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> 🐛 Bug Fixes | 修復 Bug</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">*</span><span class="token plain"> a bug fix fec43ca</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> 📦 Build System | 影響構建系統或外部依賴的變更</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">*</span><span class="token plain"> package.json 70d5f77</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> 🔧 chore | 其他不修改 src 或測試文件的變更</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">*</span><span class="token plain"> .gitignore ca32df4</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span><strong>注意事項：</strong></div><div class="admonitionContent_BuS1"><ul>
<li class="">
<p><strong>提交訊息需遵循 Conventional Commits 格式</strong>：</p>
<p>為了讓工具正確生成日誌，所有的 Git 提交訊息應符合 Conventional Commits 的規範，例如：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">feat: 新增第三方登入功能</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fix: 修正登入頁按鈕的樣式問題</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>自行納入其他類型的 Commit</strong>：</p>
<p>預設配置僅生成 <code>Features</code> 和 <code>Bug Fixes</code> 兩種標題，若專案有其他提交類型需求（如文件變更、性能改進等），可以在 <code>.versionrc.js</code> 中根據 <a href="https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.2.0/README.md" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Changelog Configuration Spec (v2.1.0)</strong></a> 定義的規範手動添加。</p>
</li>
</ul></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實踐維護變更日誌的最佳方式"><strong>實踐維護變更日誌的最佳方式</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E5%AF%A6%E8%B8%90%E7%B6%AD%E8%AD%B7%E8%AE%8A%E6%9B%B4%E6%97%A5%E8%AA%8C%E7%9A%84%E6%9C%80%E4%BD%B3%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to 實踐維護變更日誌的最佳方式" title="Direct link to 實踐維護變更日誌的最佳方式" translate="no">​</a></h2>
<p>自動化工具讓我們輕鬆生成 Change Log，但要讓日誌真正服務於團隊與用戶，還需要適度的手動調整。這章節我們會討論如何將 Change Log 納入工作流，以及在哪些階段需要補充或調整內容，讓日誌成為專案透明度的助力。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何將-change-log-納入工作流"><strong>如何將 Change Log 納入工作流？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E5%A6%82%E4%BD%95%E5%B0%87-change-log-%E7%B4%8D%E5%85%A5%E5%B7%A5%E4%BD%9C%E6%B5%81" class="hash-link" aria-label="Direct link to 如何將-change-log-納入工作流" title="Direct link to 如何將-change-log-納入工作流" translate="no">​</a></h3>
<p><strong>1. 開發階段：提交訊息規範是基礎</strong></p>
<p>一份好的 Change Log，從提交訊息就開始打基礎。如果提交訊息不規範或缺乏細節，即使使用再強大的工具，也無法生成高品質的日誌。因此，建議團隊在開發階段做到以下幾點：</p>
<ul>
<li class="">
<p><strong>統一提交訊息格式</strong>：</p>
<p>使用 Conventional Commits 或 Gitmoji，讓提交訊息的類型和內容清晰有條。例如：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">feat: 新增用戶登入功能</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fix: 修正登入頁按鈕的樣式問題</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>加強訊息的上下文</strong>：</p>
<p>提交訊息應簡明扼要，但又能準確描述改動的重點。例如：</p>
<ul>
<li class="">
<p><strong>不好的例子：</strong></p>
<div class="language-makefile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-makefile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">fix: 修正按鈕問題</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>好的例子：</strong></p>
<div class="language-makefile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-makefile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">fix: 修正登入頁按鈕的樣式問題，避免重疊顯示</span><br></div></code></pre></div></div>
</li>
</ul>
</li>
</ul>
<p><strong>2. 合併分支後：生成初版 Change Log</strong></p>
<p>當功能完成並合併到主分支後，可以使用像 <code>commit-and-tag-version</code> 這類的工具生成初版 Change Log。這一步的目的是確保所有提交都能被正確記錄，但生成的內容可能需要進一步調整。</p>
<p>例如，以下是工具生成的初版日誌：</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> [1.2.0] - 2024-11-22</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> ✨ Features | 新功能</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 新增角色管理模組 </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">#c2e3b7</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://example.com/commit/c2e3b7</span><span class="token url" style="color:#36acaa">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> 🐛 Bug Fixes | 修復 Bug</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 修正角色刪除時的權限問題 </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">#a1b2c3</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://example.com/commit/a1b2c3</span><span class="token url" style="color:#36acaa">)</span><br></div></code></pre></div></div>
<p>這個結果已經很接近可以發布的狀態，但還需要進一步手動調整來提升可讀性。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="手動調整的關鍵點"><strong>手動調整的關鍵點</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E6%89%8B%E5%8B%95%E8%AA%BF%E6%95%B4%E7%9A%84%E9%97%9C%E9%8D%B5%E9%BB%9E" class="hash-link" aria-label="Direct link to 手動調整的關鍵點" title="Direct link to 手動調整的關鍵點" translate="no">​</a></h3>
<p>工具生成的 Change Log 是基於提交訊息的內容，因此難免會有不夠清晰或不夠全面的情況。以下是手動調整的常見場景和處理方式：</p>
<p><strong>1. 補充上下文說明</strong></p>
<p>對於工具生成的內容，可以補充一些上下文，幫助讀者更全面地理解變更的影響。例如：</p>
<ul>
<li class="">
<p><strong>自動生成的日誌：</strong></p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> ✨ Features | 新功能</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 新增角色管理模組 </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">#c2e3b7</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://example.com/commit/c2e3b7</span><span class="token url" style="color:#36acaa">)</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>手動補充後的日誌：</strong></p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> ✨ Features | 新功能</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 新增角色管理模組，支援查看、編輯和刪除角色操作 </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">#c2e3b7</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://example.com/commit/c2e3b7</span><span class="token url" style="color:#36acaa">)</span><br></div></code></pre></div></div>
</li>
</ul>
<p><strong>2. 修改模板字樣</strong></p>
<p>有些日誌生成的模板內容可能不符合專案需求，這時可以直接調整工具的模板設置。例如，在 <code>.versionrc.js</code> 中自定義標題樣式，讓輸出更加貼近專案的風格。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">types</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"feat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🚀 Features | 新功能"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fix"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">section</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🛠️ Fixes | 修復問題"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>生成的結果會更新為：</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> 🚀 Features | 新功能</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 新增角色管理模組 </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">#c2e3b7</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://example.com/commit/c2e3b7</span><span class="token url" style="color:#36acaa">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> 🛠️ Fixes | 修復問題</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 修正角色刪除時的權限問題 </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">#a1b2c3</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://example.com/commit/a1b2c3</span><span class="token url" style="color:#36acaa">)</span><br></div></code></pre></div></div>
<p><strong>3. 手動添加 <code>## [Unreleased]</code> 區塊</strong></p>
<p>大多數工具生成的日誌是針對已發佈的版本，未涵蓋尚未發布的變更內容。因此，需要手動添加 <code>## [Unreleased]</code> 區塊，幫助團隊跟蹤當前的變更記錄。</p>
<ul>
<li class="">
<p><strong>手動添加的區塊範例：</strong></p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> [Unreleased]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">###</span><span class="token title important"> 🚀 Features | 新功能</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> 支援用戶密碼重設功能 </span><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">#d4e5f6</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">https://example.com/commit/d4e5f6</span><span class="token url" style="color:#36acaa">)</span><br></div></code></pre></div></div>
</li>
</ul>
<p>這樣的設置可以作為待發布版本的臨時記錄，在正式發布時再移到對應版本區域。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語打造高效且透明的提交工作流"><strong>結語：打造高效且透明的提交工作流</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E7%B5%90%E8%AA%9E%E6%89%93%E9%80%A0%E9%AB%98%E6%95%88%E4%B8%94%E9%80%8F%E6%98%8E%E7%9A%84%E6%8F%90%E4%BA%A4%E5%B7%A5%E4%BD%9C%E6%B5%81" class="hash-link" aria-label="Direct link to 結語打造高效且透明的提交工作流" title="Direct link to 結語打造高效且透明的提交工作流" translate="no">​</a></h2>
<p>在這三篇系列文章中，我們一步步搭建起了一套高效的 Git 提交工作流，從撰寫規範的提交訊息到自動化生成結構化的 Change Log，每一環節都圍繞提升團隊協作效率和專案透明度展開。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="commit-and-tag-version-的自動化優勢"><strong>commit-and-tag-version 的自動化優勢</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#commit-and-tag-version-%E7%9A%84%E8%87%AA%E5%8B%95%E5%8C%96%E5%84%AA%E5%8B%A2" class="hash-link" aria-label="Direct link to commit-and-tag-version-的自動化優勢" title="Direct link to commit-and-tag-version-的自動化優勢" translate="no">​</a></h3>
<p>在這篇文章中，我們重點介紹了 <code>commit-and-tag-version</code>，一個專注於版本管理與 Change Log 自動化的工具。它的自動化能力不僅幫助我們快速生成符合規範的日誌，還提供了靈活的配置方式，可以很好地滿足使用 Gitmoji 的開發者的需求。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="自動化工具在工作流中的重要性"><strong>自動化工具在工作流中的重要性</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#%E8%87%AA%E5%8B%95%E5%8C%96%E5%B7%A5%E5%85%B7%E5%9C%A8%E5%B7%A5%E4%BD%9C%E6%B5%81%E4%B8%AD%E7%9A%84%E9%87%8D%E8%A6%81%E6%80%A7" class="hash-link" aria-label="Direct link to 自動化工具在工作流中的重要性" title="Direct link to 自動化工具在工作流中的重要性" translate="no">​</a></h3>
<p>從 <strong>Commitizen</strong> 的互動式提交，到 <strong>Commitlint</strong> 與 <strong>Husky</strong> 的格式校驗，再到 Change Log 的自動生成，自動化工具的引入顯著降低了開發者在日常操作中的負擔。它們不僅提升了操作的一致性和規範性，還讓團隊能更專注於核心業務，減少因溝通或流程問題造成的摩擦。</p>
<p>回顧這三篇系列文章，我們已構建出一條完整的 Conventional Commit 工作流：</p>
<ol>
<li class=""><strong>使用 Commitizen：</strong> 互動式工具幫助快速生成規範的提交訊息，無需手動記憶格式。</li>
<li class=""><strong>結合 Commitlint 與 Husky：</strong> 在提交階段即時校驗，確保所有提交符合規範，避免不規範訊息進入版本控制歷史。</li>
<li class=""><strong>借助 commit-and-tag-version：</strong> 自動生成結構化的 Change Log，讓版本變更更加清晰可追溯。</li>
</ol>
<p>這套工作流不僅讓提交訊息更具可讀性，還能通過自動化流程大幅提升團隊的開發效率與專案透明度。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="future"><strong>Future</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#future" class="hash-link" aria-label="Direct link to future" title="Direct link to future" translate="no">​</a></h3>
<p>實踐 Conventional Commit 並非終點，而是一個持續優化的過程。隨著團隊規模的擴大與專案需求的變化，我們還可以結合更進階的工具（如 <code>semantic-release</code>），打造一條更加自動化、可擴展的 DevOps 工作流。</p>
<p>希望這三篇文章能為你的專案帶來啟發與幫助。如果你有任何想法或實踐經驗，歡迎留言分享，讓我們一起探索更高效的工作流解決方案！</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commit-and-tag-version#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://sean22492249.medium.com/%E6%88%91%E5%BF%83%E4%B8%AD-release-note-%E8%88%87-changelog-%E5%9C%A8%E7%94%A2%E5%93%81%E4%B8%AD%E6%87%89%E7%94%A8%E7%9A%84%E5%B7%AE-a466a69ae595" target="_blank" rel="noopener noreferrer" class=""><strong>我心中 Release note 與 changelog 在產品中應用的差異</strong></a></li>
<li class=""><a href="https://keepachangelog.com/zh-TW/0.3.0/" target="_blank" rel="noopener noreferrer" class=""><strong>如何維護更新日誌</strong></a></li>
<li class=""><a href="https://github.com/conventional-changelog/conventional-changelog?tab=readme-ov-file" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Changelog</strong></a></li>
<li class=""><a href="https://github.com/conventional-changelog/standard-version" target="_blank" rel="noopener noreferrer" class=""><strong>standard-version</strong></a></li>
<li class=""><a href="https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-cli" target="_blank" rel="noopener noreferrer" class=""><strong>conventional-changelog-cli</strong></a></li>
<li class=""><a href="https://github.com/arvinxx/gitmoji-commit-workflow/tree/master/packages/changelog#readme" target="_blank" rel="noopener noreferrer" class=""><strong>conventional-changelog-gitmoji-config</strong></a></li>
<li class=""><a href="https://github.com/absolute-version/commit-and-tag-version" target="_blank" rel="noopener noreferrer" class=""><strong>commit-and-tag-version</strong></a></li>
<li class=""><a href="https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.2.0/README.md" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Changelog Configuration Spec (v2.1.0)</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="實作紀錄" term="實作紀錄"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Gitmoji x Conventional Commit 工作流 (二) - 使用 Commitlint + Husky 校驗提交格式]]></title>
        <id>https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky</id>
        <link href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky"/>
        <updated>2024-12-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[在這篇文章中，我們將深入探討如何透過 Commitlint 校驗提交訊息格式，並利用 Husky 自動化校驗流程，確保提交訊息始終符合規範。不論是 不使用 Gitmoji 還是 使用 Gitmoji 的情境，我們都將展示具體的配置與效果，幫助你打造嚴謹又高效的提交工作流。]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言"><strong>前言</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="Direct link to 前言" title="Direct link to 前言" translate="no">​</a></h2>
<p>在上一篇文章中，我們介紹了如何透過 <strong>Commitizen</strong> 與 <strong>Gitmoji</strong> 規範化提交訊息。但規範的建立並不保證會被嚴格執行，許多團隊仍可能面臨這樣的情況：</p>
<blockquote>
<p>「提交訊息格式看起來很簡單，但實際操作時，不是漏寫了類型，就是格式寫錯了。」</p>
</blockquote>
<p>這種情況下，提交訊息的規範就形同虛設。因此，若要確保提交訊息始終符合規範，勢必得設立一道自動化檢核機制來攔截不合格的提交。</p>
<p>這正是 <strong>Commitlint</strong> 發揮作用的地方！它專門校驗提交訊息是否符合規範，無論是 Conventional Commits 還是 Gitmoji，都可透過自定義配置嚴格檢查，避免不合格的提交進入版本控制歷史。當然，工具的強大功能需要在正確的時間觸發才能發揮作用，搭配 <strong>Husky</strong>，我們能利用 Git hooks 在提交前自動執行 Commitlint 的校驗邏輯，將自動化檢核落實於日常開發流程。</p>
<p>本篇文章將帶你實作以下內容：</p>
<ol>
<li class=""><strong>不使用 Gitmoji 的提交校驗</strong>：延續前一篇的配置，透過 <strong>@commitlint/config-conventional</strong> 校驗提交訊息。</li>
<li class=""><strong>使用 Gitmoji 的提交校驗</strong>：加入 <strong>commitlint-config-gitmoji</strong> 與 <strong>commitlint-config-cz</strong>，支援帶有 Emoji 的訊息格式。</li>
<li class=""><strong>整合 Husky</strong>：將 Commitlint 校驗嵌入 Git Hooks，阻止不符合規範的提交。</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="提交訊息校驗工具-commitlint"><strong>提交訊息校驗工具: Commitlint</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E6%8F%90%E4%BA%A4%E8%A8%8A%E6%81%AF%E6%A0%A1%E9%A9%97%E5%B7%A5%E5%85%B7-commitlint" class="hash-link" aria-label="Direct link to 提交訊息校驗工具-commitlint" title="Direct link to 提交訊息校驗工具-commitlint" translate="no">​</a></h2>
<p><strong>Commitlint</strong> 是一個專門用來檢查提交訊息格式的工具，能根據預設規範或自訂規則檢查每一筆提交訊息是否合格。它的核心功能包括：</p>
<ol>
<li class=""><strong>校驗提交訊息格式</strong>：確保每次提交的訊息都符合既定規範，例如 Conventional Commits 或自訂的 Gitmoji 規範。</li>
<li class=""><strong>即時阻止不合規提交</strong>：透過與 Git Hooks 的整合（如搭配 Husky），在提交階段自動執行校驗邏輯，避免錯誤訊息進入版本控制歷史。</li>
<li class=""><strong>高度可擴展性</strong>：支援多種現成的規範配置，例如 <strong>@commitlint/config-conventional</strong>、<strong>commitlint-config-gitmoji</strong>，也可自定義檢核規則。</li>
</ol>
<p>舉例來說，以下這樣的提交訊息格式符合 Conventional Commits 的規範：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">feat(api): add new endpoint for user authentication</span><br></div></code></pre></div></div>
<p>但若提交訊息不符合規範，例如這樣：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">updated something</span><br></div></code></pre></div></div>
<p>Commitlint 會立即提示錯誤，並阻止提交，確保不規範的訊息不會進入版本控制歷史。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實作一不使用-gitmoji-的-commitlint-配置"><strong>實作一：不使用 Gitmoji 的 Commitlint 配置</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E5%AF%A6%E4%BD%9C%E4%B8%80%E4%B8%8D%E4%BD%BF%E7%94%A8-gitmoji-%E7%9A%84-commitlint-%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 實作一不使用-gitmoji-的-commitlint-配置" title="Direct link to 實作一不使用-gitmoji-的-commitlint-配置" translate="no">​</a></h2>
<p>在上一篇中，我們針對不使用 Gitmoji 的情境提出了兩種工具選擇：</p>
<ol>
<li class="">
<p><strong>cz-conventional-changelog</strong><br>
<!-- -->配置簡單，生成的提交訊息符合 Conventional Changelog 標準，可直接通過 <strong><code>@commitlint/config-conventional</code></strong> 的檢核。</p>
</li>
<li class="">
<p><strong>@commitlint/cz-commitlint + @commitlint/config-conventional</strong><br>
<!-- -->從 commitlint 的角度出發，根據指定規則（設定於<code>commitlint.config.js</code>）生成提交訊息，適合需要自定義驗證規則的專案。</p>
</li>
</ol>
<p>本節將延續第一篇文章中的設定，透過 <strong>Commitlint</strong> 與 <strong>@commitlint/config-conventional</strong>，實現基於 Conventional Commits 規範的提交訊息校驗，並確保提交符合標準格式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝與配置-commitlint"><strong>安裝與配置 Commitlint</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E5%AE%89%E8%A3%9D%E8%88%87%E9%85%8D%E7%BD%AE-commitlint" class="hash-link" aria-label="Direct link to 安裝與配置-commitlint" title="Direct link to 安裝與配置-commitlint" translate="no">​</a></h3>
<p><strong>步驟 1：安裝 Commitlint</strong></p>
<p>在專案中安裝 Commitlint CLI 和 Conventional Commits 的預設配置：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev @commitlint/{cli,config-conventional}</span><br></div></code></pre></div></div>
<p><strong>步驟 2：新增配置文件</strong></p>
<p>在專案根目錄新增 <code>commitlint.config.js</code> 文件，內容如下：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">extends</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'@commitlint/config-conventional'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>這裡的配置表示我們將使用 Conventional Commits 的規範。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="整合-commitizen-與-commitlint"><strong>整合 Commitizen 與 Commitlint</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E6%95%B4%E5%90%88-commitizen-%E8%88%87-commitlint" class="hash-link" aria-label="Direct link to 整合-commitizen-與-commitlint" title="Direct link to 整合-commitizen-與-commitlint" translate="no">​</a></h3>
<p>為了讓提交訊息規範與 Commitizen 完美配合，我們將測試使用 <strong>cz-conventional-changelog</strong> 或是 <strong>@commitlint/cz-commitlint + @commitlint/config-conventional</strong> 這兩種方法生成的提交訊息是否符合 <strong>@commitlint/config-conventional</strong> 的校驗標準。</p>
<p><strong>步驟 1：在 <code>package.json</code> 中配置 Commitizen</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commit"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"config"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commitizen"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"path"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz-conventional-changelog"</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 或 @commitlint/cz-commitlint</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>步驟 2：使用 Commitizen 產生互動式提交</strong></p>
<p>使用以下命令啟動互動式提交：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm run commit</span><br></div></code></pre></div></div>
<p>假設我們在互動式提交中輸入以下訊息：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">type: feat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">scope: api</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">subject: add authentication endpoint</span><br></div></code></pre></div></div>
<p>Commitizen 生成的提交訊息將是：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">feat(api): add authentication endpoint</span><br></div></code></pre></div></div>
<p><strong>步驟 3：測試 Commitizen 產生的提交訊息</strong></p>
<p>Commitlint 安裝完成後，可以使用以下命令測試前一個提交訊息的校驗：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx commitlint --from=HEAD~1 --to=HEAD</span><br></div></code></pre></div></div>
<p>執行上述命令後，Commitlint 不會報錯，表示提交訊息符合規範。</p>
<p><strong>步驟 4：測試不符合規範的提交訊息</strong></p>
<p>嘗試手動輸入一條不符合規範的提交訊息，例如：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">update something</span><br></div></code></pre></div></div>
<p>Commitlint 會提示錯誤，阻止提交：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">⧗   input: update something</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ✖   type must not be empty [type-empty]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ✖   found 2 problems, 0 warnings</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實作二使用-gitmoji-的-commitlint-配置"><strong>實作二：使用 Gitmoji 的 Commitlint 配置</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E5%AF%A6%E4%BD%9C%E4%BA%8C%E4%BD%BF%E7%94%A8-gitmoji-%E7%9A%84-commitlint-%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 實作二使用-gitmoji-的-commitlint-配置" title="Direct link to 實作二使用-gitmoji-的-commitlint-配置" translate="no">​</a></h2>
<p>在上一節中，我們實作了不使用 Gitmoji 的提交訊息校驗。本節將專注於帶有 Gitmoji 的提交訊息，並介紹兩個 Commitlint 配置模組 <strong>commitlint-config-gitmoji</strong> 與 <strong>commitlint-config-cz</strong> 的作用與差異，幫助你選擇適合的工具來進一步規範提交工作流。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="commitlint-config-gitmoji-與-commitlint-config-cz-的作用與差別"><strong>commitlint-config-gitmoji 與 commitlint-config-cz 的作用與差別</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#commitlint-config-gitmoji-%E8%88%87-commitlint-config-cz-%E7%9A%84%E4%BD%9C%E7%94%A8%E8%88%87%E5%B7%AE%E5%88%A5" class="hash-link" aria-label="Direct link to commitlint-config-gitmoji-與-commitlint-config-cz-的作用與差別" title="Direct link to commitlint-config-gitmoji-與-commitlint-config-cz-的作用與差別" translate="no">​</a></h3>
<p><a href="https://www.npmjs.com/package/commitlint-config-gitmoji" target="_blank" rel="noopener noreferrer" class=""><strong>commitlint-config-gitmoji</strong></a></p>
<ul>
<li class=""><strong>功能</strong>：校驗提交訊息是否符合 Gitmoji 規範，要求每次提交訊息必須包含對應的 Emoji。</li>
<li class=""><strong>限制</strong>：該配置<strong>無法讀取 .cz-config.js 中自定義的 type</strong>，若你在 <strong>cz-customizable</strong> 中新增了自定義 type，則提交時會被認為是不符合規範的訊息。例如：<!-- -->
<ul>
<li class="">
<p><code>.cz-config.js</code> 自定義 type 為 <code>:construction_worker: bosh</code>。</p>
</li>
<li class="">
<p>提交訊息：</p>
<div class="language-scss codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-scss codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">👷 bosh: Hi, I'm bosh</span><br></div></code></pre></div></div>
<ul>
<li class="">結果：commitlint 報錯，因為該 type 不在 <strong>commitlint-config-gitmoji</strong> 內建的 types 列表中。</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><a href="https://www.npmjs.com/package/commitlint-config-cz" target="_blank" rel="noopener noreferrer" class=""><strong>commitlint-config-cz</strong></a></p>
<ul>
<li class=""><strong>功能</strong>：校驗提交訊息是否符合 <strong>cz-customizable</strong> 所定義的格式，支援自定義 type。</li>
<li class=""><strong>限制</strong>：相較於 <strong>commitlint-config-gitmoji</strong>，其校驗較為寬鬆。如果提交訊息完全沒有 type，例如 <code>First commit</code>，也不會報錯。</li>
<li class=""><strong>報錯場景</strong>：<!-- -->
<ul>
<li class="">
<p>偵測到提交訊息有 type，但該 type 不在 <code>.cz-config.js</code> 的定義中。例如：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">typo: fix spelling error</span><br></div></code></pre></div></div>
<ul>
<li class="">結果：commitlint 報錯，因為 <code>typo</code> 不在自定義 type 的範圍內。</li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>若你在第一篇文章中已配置 <strong>cz-customizable</strong>，建議根據團隊需求選擇其一即可，無需同時安裝兩者。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝與配置-commitlint-1"><strong>安裝與配置 Commitlint</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E5%AE%89%E8%A3%9D%E8%88%87%E9%85%8D%E7%BD%AE-commitlint-1" class="hash-link" aria-label="Direct link to 安裝與配置-commitlint-1" title="Direct link to 安裝與配置-commitlint-1" translate="no">​</a></h3>
<p>根據需求選擇安裝以下其中一個模組：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 安裝 commitlint-config-gitmoji</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev commitlint-config-gitmoji</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 或安裝 commitlint-config-cz</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev commitlint-config-cz</span><br></div></code></pre></div></div>
<p>在專案根目錄的 <code>commitlint.config.js</code> 中，擴展對應的配置：</p>
<ul>
<li class="">
<p>若使用 <strong>commitlint-config-gitmoji</strong>：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">extends</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'gitmoji'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>若使用 <strong>commitlint-config-cz</strong>：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">extends</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'cz'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ul>
<p>這裡的配置表示我們將使用 <strong>commitlint-config-gitmoji</strong> 的規範，或是 <strong>commitlint-config-cz</strong> 的規範。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="整合-commitizen-與-commitlint-1"><strong>整合 Commitizen 與 Commitlint</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E6%95%B4%E5%90%88-commitizen-%E8%88%87-commitlint-1" class="hash-link" aria-label="Direct link to 整合-commitizen-與-commitlint-1" title="Direct link to 整合-commitizen-與-commitlint-1" translate="no">​</a></h3>
<p>與前一章節的配置邏輯相同，如果我們想要產生帶有 Gitmoji 的互動式提示，就要設定 commitizen 使用 cz-customizable</p>
<p><strong>步驟 1：在 <code>package.json</code> 中配置 Commitizen</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commit"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"config"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commitizen"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"path"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz-customizable"</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 或 @commitlint/cz-commitlint</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>接下來可以 follow 前一章節相同的步驟，使用 Commitizen 產生互動式提交，接著使用 commintlint 來測試提交訊息。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="如何選擇合適的配置"><strong>如何選擇合適的配置？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E5%A6%82%E4%BD%95%E9%81%B8%E6%93%87%E5%90%88%E9%81%A9%E7%9A%84%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 如何選擇合適的配置" title="Direct link to 如何選擇合適的配置" translate="no">​</a></h3>
<table><thead><tr><th><strong>需求</strong></th><th><strong>建議配置</strong></th><th><strong>原因</strong></th></tr></thead><tbody><tr><td>團隊需要嚴謹的提交規範，強制包含 Gitmoji</td><td><strong>commitlint-config-gitmoji</strong></td><td>適合不需要自定義 type 的情境，校驗邏輯嚴格。</td></tr><tr><td>使用 cz-customizable 且有自定義需求</td><td><strong>commitlint-config-cz</strong></td><td>可讀取 <code>.cz-config.js</code> 的自定義 type。</td></tr></tbody></table>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="整合-husky實現-git-hooks-自動化執行-commitlint"><strong>整合 Husky：實現 Git Hooks 自動化執行 Commitlint</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E6%95%B4%E5%90%88-husky%E5%AF%A6%E7%8F%BE-git-hooks-%E8%87%AA%E5%8B%95%E5%8C%96%E5%9F%B7%E8%A1%8C-commitlint" class="hash-link" aria-label="Direct link to 整合-husky實現-git-hooks-自動化執行-commitlint" title="Direct link to 整合-husky實現-git-hooks-自動化執行-commitlint" translate="no">​</a></h2>
<p>在前面章節中，我們介紹了如何透過 Commitlint 校驗提交訊息，但僅靠手動執行 <code>npx commitlint</code> 並不足以確保每次提交都符合規範。因此，這個章節將帶你實作 <strong>Husky</strong>，利用 Git Hooks 在提交時自動執行 Commitlint。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝與配置-husky"><strong>安裝與配置 Husky</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E5%AE%89%E8%A3%9D%E8%88%87%E9%85%8D%E7%BD%AE-husky" class="hash-link" aria-label="Direct link to 安裝與配置-husky" title="Direct link to 安裝與配置-husky" translate="no">​</a></h3>
<p><strong>步驟 1：安裝 Husky</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev husky</span><br></div></code></pre></div></div>
<p><strong>步驟 2：啟用 Husky</strong></p>
<p>執行以下命令初始化 Husky 並啟用 Git Hooks：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx husky init</span><br></div></code></pre></div></div>
<p>此命令會在專案根目錄新增一個 <code>.husky/</code> 資料夾，用於存放 Git Hooks 腳本，並且會自動在<code>package.json</code> 中新增 <code>prepare</code> 腳本：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"prepare"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"husky"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><strong>步驟 4：新增 commit-msg 鉤子</strong></p>
<p>為了在每次提交訊息時執行 Commitlint 校驗，我們需要新增 <code>commit-msg</code> 鉤子：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">echo "npx --no-install commitlint --edit $1" &gt; .husky/commit-msg</span><br></div></code></pre></div></div>
<p>執行後，<code>.husky/</code> 資料夾內將新增一個 <code>commit-msg</code> 腳本，其內容如下：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx --no-install commitlint --edit $1</span><br></div></code></pre></div></div>
<p>這段腳本的作用是：</p>
<ol>
<li class="">捕捉提交訊息文件。</li>
<li class="">執行 Commitlint 校驗提交訊息，若不符合規範則阻止提交。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="效果展示"><strong>效果展示</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA" class="hash-link" aria-label="Direct link to 效果展示" title="Direct link to 效果展示" translate="no">​</a></h3>
<p>這裡我用前一章節「<strong>實作二：使用 Gitmoji 的 Commitlint 配置</strong>」的設定來測試。</p>
<p><strong>測試不符合規範的提交訊息：</strong></p>
<p>使用 npm run commit 產生以下提交訊息：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">&gt; conventional-commit-workflow@1.0.0 commit</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">&gt; cz</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cz-cli@4.3.1, cz-customizable@7.2.1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">All lines except first will be wrapped after 100 characters.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">? 此提交是否包含重大變更？如果是，請詳細描述（可選，輸入 enter 跳過）： </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">? 選擇您要提交的變更類型： 🔧 chore: 其他不修改 src 或測試文件的變更</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">? 選擇此變更的範圍（可選，輸入 enter 跳過）： empty</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">? 簡短描述變更（必填）： 新增 .husky</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">? 詳細描述（可選，輸入 enter 跳過）： </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">? 列出此變更相關的 ISSUE，例如：#31, #34（可選，輸入 enter 跳過）： </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">###--------------------------------------------------------###</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">:wrench: chore: 新增 .husky</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">###--------------------------------------------------------###</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">? 您確定要提交上述內容嗎？ Yes</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[main d9c48dd] :wrench: chore: 新增 .husky</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> 2 files changed, 1 insertion(+)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> create mode 100644 .husky/commit-msg</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> create mode 100644 .husky/pre-commit</span><br></div></code></pre></div></div>
<p>執行後 Commitlint 通過校驗，提交成功。</p>
<p><strong>測試不符合規範的提交訊息：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "add new features"</span><br></div></code></pre></div></div>
<p>執行後 Commitlint 馬上報錯並阻止提交：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">⧗   input: add new features</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✖   Your commit should start with gitmoji code. Please check the emoji code on https://gitmoji.dev/. [start-with-gitmoji]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✖   subject may not be empty [subject-empty]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✖   type may not be empty [type-empty]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">✖   found 3 problems, 0 warnings</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ⓘ   Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">husky - commit-msg script failed (code 1)</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語規範與效率的雙贏"><strong>結語：規範與效率的雙贏</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#%E7%B5%90%E8%AA%9E%E8%A6%8F%E7%AF%84%E8%88%87%E6%95%88%E7%8E%87%E7%9A%84%E9%9B%99%E8%B4%8F" class="hash-link" aria-label="Direct link to 結語規範與效率的雙贏" title="Direct link to 結語規範與效率的雙贏" translate="no">​</a></h2>
<p>總結在這篇文章中我們所引入的工具：</p>
<ol>
<li class=""><strong>Commitlint</strong>：確保提交訊息的結構化與一致性，避免因提交格式問題影響團隊協作。</li>
<li class=""><strong>Husky</strong>：將規範執行嵌入 Git Hooks，使校驗流程無縫融入日常開發，實現提交訊息規範的自動化。</li>
</ol>
<p>再加上本系列的前一篇文章，我們逐步構建了一個規範化且高效的提交工作流。下一步，我將在下一篇文章中介紹，基於這套規範化的提交工作流，如何自動生成變更日誌並實現語義化版本管理，進一步提升版本控制與專案管理的效率。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitlint-husky#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://www.conventionalcommits.org/en/v1.0.0/" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Commits</strong></a></li>
<li class=""><a href="https://gitmoji.dev/" target="_blank" rel="noopener noreferrer" class=""><strong>Gitmoji</strong></a></li>
<li class=""><a href="https://commitizen.github.io/cz-cli/" target="_blank" rel="noopener noreferrer" class=""><strong>Commitizen</strong></a></li>
<li class=""><a href="https://commitlint.js.org/" target="_blank" rel="noopener noreferrer" class=""><strong>commitlint</strong></a></li>
<li class=""><a href="https://www.npmjs.com/package/commitlint-config-gitmoji" target="_blank" rel="noopener noreferrer" class=""><strong>commitlint-config-gitmoji</strong></a></li>
<li class=""><a href="https://www.npmjs.com/package/commitlint-config-cz" target="_blank" rel="noopener noreferrer" class=""><strong>commitlint-config-cz</strong></a></li>
<li class=""><a href="https://typicode.github.io/husky/" target="_blank" rel="noopener noreferrer" class=""><strong>Husky</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="實作紀錄" term="實作紀錄"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Gitmoji x Conventional Commit 工作流 (一) - 使用 Commitizen 互動式產生 Conventional Commit]]></title>
        <id>https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen</id>
        <link href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen"/>
        <updated>2024-11-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[在這篇文章中，我分享了如何利用 Commitizen 和 Gitmoji 打造一套規範化的提交訊息工作流，幫助開發者更高效地管理專案歷史紀錄。我們將深入探討 Conventional Commits 的重要性，並比較不同的適配器（如 cz-conventional-changelog、@commitlint/cz-commitlint 和 cz-customizable）的特點與應用場景。]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言"><strong>前言</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="Direct link to 前言" title="Direct link to 前言" translate="no">​</a></h2>
<p>最近我在研究 Git 專案的專業開發流程，比如怎麼按照 Conventional Commits 規範撰寫提交訊息，還有如何根據 Semantic Versioning 原則管理版本號等等。研究過程中，我花了不少時間瀏覽 Github 上一些知名的開源專案，透過它們的提交紀錄、PR 歷史、Change log 和 Release notes，一步步學習怎麼建立一套高效又規範化的工作流程。</p>
<p>經過一段時間的摸索，我慢慢整理出一些具體的流程，未來可以應用在新專案上，比如：</p>
<ol>
<li class="">使用互動式問答工具，輔助撰寫符合規範的提交訊息。</li>
<li class="">配合 Git Hook，自動檢查提交訊息是否遵循規範。</li>
<li class="">利用規範化的提交紀錄自動生成 Changelog</li>
</ol>
<p>在實踐這些流程的過程中，我研究了不少相關工具。有趣的是，我發現許多開源專案的提交訊息裡經常出現 Emoji，看起來不僅直觀，還挺有趣的。稍微查了一下後，我才知道這其實是一套叫 <strong>Gitmoji</strong> 的規範。於是，我決定順便研究一下怎麼把 Gitmoji 整合到剛剛提到的流程中。</p>
<p>由於內容較多，我將以上三個工作階段分成三篇文章來介紹，並在每篇文章中詳細講解有使用 Gitmoji 和未使用 Gitmoji 的情況下，該如何安裝、配置所需工具，以及實際使用的效果。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conventional-commits"><strong>Conventional Commits</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#conventional-commits" class="hash-link" aria-label="Direct link to conventional-commits" title="Direct link to conventional-commits" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="為什麼需要規範提交訊息"><strong>為什麼需要規範提交訊息？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E7%82%BA%E4%BB%80%E9%BA%BC%E9%9C%80%E8%A6%81%E8%A6%8F%E7%AF%84%E6%8F%90%E4%BA%A4%E8%A8%8A%E6%81%AF" class="hash-link" aria-label="Direct link to 為什麼需要規範提交訊息" title="Direct link to 為什麼需要規範提交訊息" translate="no">​</a></h3>
<p>你是否曾經遇過這種情況：打開 Git 的提交記錄，看到滿屏的「Fix bug」「Update」「Change」這些模糊又單調的訊息，完全搞不清楚這些提交究竟改了什麼內容？甚至連哪裡可能受影響都無從得知。</p>
<p>對我來說，一個好的提交訊息不僅是記錄改動的工具，更是團隊協作和專案管理的關鍵之一。清晰又規範的提交訊息不僅能幫助團隊快速了解改動內容，甚至能透過規範的格式直接結合工具實現自動化生成變更日誌或升級版本號。</p>
<p>這就是為什麼會有&nbsp;<a href="https://www.conventionalcommits.org/en/v1.0.0/" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Commits</strong></a>&nbsp;規範的原因。它為提交訊息提供了一套簡單實用的格式規範，比如用固定的「標籤」來描述提交類型（像 <code>feat</code> 表示新增功能，<code>fix</code> 表示修復問題）。</p>
<blockquote>
<p>如果你對 <strong>Conventional Commits</strong> 規範有興趣，建議可以看看我之前寫的這篇文章：<a href="https://notes.boshkuo.com/docs/DevTools/Git/conventional-commits" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Commits 的實踐指南：寫出乾淨的提交訊息</strong></a></p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="commitizen規範化提交的好幫手"><strong>Commitizen：規範化提交的好幫手</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#commitizen%E8%A6%8F%E7%AF%84%E5%8C%96%E6%8F%90%E4%BA%A4%E7%9A%84%E5%A5%BD%E5%B9%AB%E6%89%8B" class="hash-link" aria-label="Direct link to commitizen規範化提交的好幫手" title="Direct link to commitizen規範化提交的好幫手" translate="no">​</a></h3>
<p>雖然提交訊息規範化聽起來挺有道理，但實際上要執行起來，很多人還是會感到麻煩：每次提交都要手動寫那麼多規範的內容？也太費時了吧！</p>
<p>本篇文章要介紹的主角 <strong>Commitizen</strong> 就是一個用來解決這個問題的工具。簡單來說，Commitizen 是一個幫助你撰寫規範化提交訊息的工具，它最大的亮點就是「互動式提示」。當你準備提交程式碼時，Commitizen 會以問答的形式引導你一步步完成提交訊息，讓你不用擔心忘記格式或細節。在接下來的章節裡，我會記錄我如何實作 <strong>Commitizen</strong> 安裝與配置。此外，我還會進一步教大家如何搭配 <strong>Gitmoji</strong> 在提交訊息前面加上自定義的 emoji 表情符號，讓提交訊息更生動 😁</p>
<p>不過，在介紹 <strong>Commitizen</strong> 搭配 <strong>Gitmoji</strong> 的實作之前，讓我們先來了解一下什麼是 <strong>Gitmoji</strong> 🧐。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gitmoji-簡介"><strong>Gitmoji 簡介</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#gitmoji-%E7%B0%A1%E4%BB%8B" class="hash-link" aria-label="Direct link to gitmoji-簡介" title="Direct link to gitmoji-簡介" translate="no">​</a></h3>
<p><a href="https://gitmoji.dev/" target="_blank" rel="noopener noreferrer" class=""><strong>Gitmoji</strong></a> 是一套專門為提交訊息設計的表情符號（Emoji）規範，它為一些 emoji 圖示定義了專屬的<strong>語義化的標記</strong>（也就是名稱），再將它們透過工具轉為 Emoji 符號，透過可視化的方式來強調提交類型。比如：</p>
<ul>
<li class=""><strong>✨ feat:</strong> 新增功能時，用一個「閃亮亮」的圖示來告訴大家這是個新功能。</li>
<li class=""><strong>🐛 fix:</strong> 修復錯誤時，直觀地用「小蟲」來表示這是 Bug 的修復。</li>
<li class=""><strong>📝 docs:</strong> 更新文件時，一個簡單的筆記本圖示瞬間傳遞信息。</li>
<li class=""><strong>⚡️ perf:</strong> 提升性能，用一個閃電般的符號突顯出效率提升的改動。</li>
</ul>
<p>以下是一些常見的 Gitmoji ：</p>
<table><thead><tr><th><strong>Emoji</strong></th><th><strong>名稱</strong></th><th><strong>用途</strong></th></tr></thead><tbody><tr><td>✨</td><td><code>:sparkles:</code></td><td>新增功能或重大改進</td></tr><tr><td>🐛</td><td><code>:bug:</code></td><td>修復 Bug</td></tr><tr><td>📝</td><td><code>:memo:</code></td><td>更新文件或增加說明</td></tr><tr><td>💄</td><td><code>:lipstick:</code></td><td>優化程式碼樣式（如格式調整、空白）</td></tr><tr><td>♻️</td><td><code>:recycle:</code></td><td>程式碼重構（非功能性變更）</td></tr><tr><td>⚡️</td><td><code>:zap:</code></td><td>改善效能的變更</td></tr><tr><td>✅</td><td><code>:white_check_mark:</code></td><td>添加或修正測試</td></tr></tbody></table>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>由於 Git 本身只專注於版本控制，並不負責處理格式化或語法轉換，不會自動將這些名稱轉換為 Emoji。如果想在提交訊息中自動顯示 Emoji，需要借助一些工具。例如：</p><ul>
<li class=""><strong>gitmoji-cli</strong> 提供互動式選擇並插入 Emoji。</li>
<li class=""><strong>cz-gitmoji</strong> 或 <strong>cz-customizable</strong> 可以通過配置 Commitizen 來插入 Gitmoji。</li>
</ul></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>雖然 Gitmoji 很酷，但也不是每個團隊都適合引入。這裡是一些你可能需要考慮的點：</p><ul>
<li class=""><strong>成本考量</strong>：Gitmoji 的引入需要額外的工具配置和規範制定，對於輕量型或快速開發的專案可能顯得繁瑣。</li>
<li class=""><strong>團隊文化</strong>：有些團隊偏好嚴謹的文字風格，可能會認為表情符號不夠正式。</li>
</ul><p>基於這些考量，接下來的教學我們會針對「不使用 Gitmoji」和「使用 Gitmoji」分別展示工具配置與實作流程，讓大家能根據團隊需求靈活選擇。</p></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="工具選擇與比較commitizen-適配器概覽"><strong>工具選擇與比較：Commitizen 適配器概覽</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E5%B7%A5%E5%85%B7%E9%81%B8%E6%93%87%E8%88%87%E6%AF%94%E8%BC%83commitizen-%E9%81%A9%E9%85%8D%E5%99%A8%E6%A6%82%E8%A6%BD" class="hash-link" aria-label="Direct link to 工具選擇與比較commitizen-適配器概覽" title="Direct link to 工具選擇與比較commitizen-適配器概覽" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="commitizen-適配器是什麼"><strong>Commitizen 適配器是什麼？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#commitizen-%E9%81%A9%E9%85%8D%E5%99%A8%E6%98%AF%E4%BB%80%E9%BA%BC" class="hash-link" aria-label="Direct link to commitizen-適配器是什麼" title="Direct link to commitizen-適配器是什麼" translate="no">​</a></h3>
<p>Commitizen 本身只是一個框架，想讓它運作起來，需要安裝一個「適配器(adapter)」來定義提交訊息的規則和互動流程。市面上有許多不同的適配器，每個適配器針對不同的場景提供了對應的功能。以下是三個常見的選擇：</p>
<table><thead><tr><th><strong>適配器</strong></th><th><strong>特色</strong></th><th><strong>優點</strong></th><th><strong>缺點</strong></th></tr></thead><tbody><tr><td><a href="https://www.npmjs.com/package/cz-conventional-changelog" target="_blank" rel="noopener noreferrer" class=""><strong>cz-conventional-changelog</strong></a></td><td>1. 社群成熟度高，是 Commitizen 社群內廣泛使用的適配器。 <br> 2. 完全基於 Conventional Commit 規範</td><td>1. 配置簡單，基本零配置即可使用 <br> 2. 遵循廣泛採用的 Angular 規範 <br> 3. 預設配置產生的提交訊息就能通過 <code>@commitlint/config-conventional</code></td><td>1. 不支援自訂 Prompt <br> 2. 無法直接支援 Gitmoji</td></tr><tr><td><a href="https://www.npmjs.com/package/@commitlint/cz-commitlint" target="_blank" rel="noopener noreferrer" class=""><strong>@commitlint/cz-commitlint</strong></a></td><td>1. Commitlint 官方提供的 Commitizen 適配器 <br> 2. 直接根據 <code>commitlint.config.js</code> 配置的規則生成提交訊息，確保規範同步</td><td>1. 結合 Commitlint 配置，提供強大的規範校驗能力 <br> 2. 配置選項豐富- 提供更嚴格的提交信息驗證</td><td>1. 配置相對較多 <br> 2. 不支援 Gitmoji</td></tr><tr><td><a href="https://github.com/leoforfree/cz-customizable" target="_blank" rel="noopener noreferrer" class=""><strong>cz-customizable</strong></a></td><td>高度自訂，允許完全掌控 Prompt 的顯示內容與行為</td><td>1. 適合需要自訂提交規範的團隊，特別是整合 Gitmoji 的情境 <br> 2. 支援中文或其他語言 <br> 3. 適合特殊提交規範的需求</td><td>1. 需要手動編寫自訂配置，學習成本稍高 <br> 2. 團隊需維護自定義規範文檔</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="哪種適配器最適合你的團隊"><strong>哪種適配器最適合你的團隊？</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E5%93%AA%E7%A8%AE%E9%81%A9%E9%85%8D%E5%99%A8%E6%9C%80%E9%81%A9%E5%90%88%E4%BD%A0%E7%9A%84%E5%9C%98%E9%9A%8A" class="hash-link" aria-label="Direct link to 哪種適配器最適合你的團隊" title="Direct link to 哪種適配器最適合你的團隊" translate="no">​</a></h3>
<ol>
<li class="">
<p><strong>如果不需要 Gitmoji</strong>：</p>
<ul>
<li class=""><strong>初學者</strong>：選擇 <strong>cz-conventional-changelog</strong>，簡單高效，適合用於只需快速生成符合 Conventional Changelog 的提交訊息的場景。</li>
<li class=""><strong>進階需求</strong>：選擇 <strong>@commitlint/cz-commitlint</strong>，雖然需另外搭配 <strong>@commitlint/config-conventional</strong> 這類的規則集設定，但與 <strong>commitlint</strong> 整合的靈活性更高，可額外添加自定義的規則，且生成的訊息和驗證規則一致。適合有客製化規範需求的專案。</li>
</ul>
<p>這兩種適配器都基於 Angular 的提交訊息規範，因此兩者 Prompt 的規則非常相似。如果你沒有額外自定義規則的需求，那麼我會推薦配置最為簡單的 <strong>cz-conventional-changelog。</strong></p>
</li>
<li class="">
<p><strong>如果需要 Gitmoji</strong>：</p>
<ul>
<li class="">強烈推薦使用 <strong>cz-customizable</strong>，它能輕鬆整合 Gitmoji，並根據團隊需求自訂提交訊息的 Prompt。</li>
</ul>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實作安裝與配置-commitizen"><strong>實作：安裝與配置 Commitizen</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E5%AF%A6%E4%BD%9C%E5%AE%89%E8%A3%9D%E8%88%87%E9%85%8D%E7%BD%AE-commitizen" class="hash-link" aria-label="Direct link to 實作安裝與配置-commitizen" title="Direct link to 實作安裝與配置-commitizen" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="不使用-gitmoji1-搭配-cz-conventional-changelog"><strong>不使用 Gitmoji(1): 搭配 cz-conventional-changelog</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E4%B8%8D%E4%BD%BF%E7%94%A8-gitmoji1-%E6%90%AD%E9%85%8D-cz-conventional-changelog" class="hash-link" aria-label="Direct link to 不使用-gitmoji1-搭配-cz-conventional-changelog" title="Direct link to 不使用-gitmoji1-搭配-cz-conventional-changelog" translate="no">​</a></h3>
<ol>
<li class="">
<p><strong>安裝依賴</strong>：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev commitizen cz-conventional-changelog</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>配置 Commitizen</strong>：</p>
<ul>
<li class="">
<p>在專案的 <code>package.json</code> 中新增以下內容：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commit"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"config"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commitizen"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"path"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz-conventional-changelog"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>config</code> 欄位用於指定專案的配置設定。其中，<code>commitizen</code> 欄位包含 <code>path</code> 屬性，指向 <code>cz-conventional-changelog</code> 這個適配器</li>
<li class="">使用 <code>npm run commit</code> 即可啟動互動式提交</li>
</ul>
</li>
</ul>
</li>
<li class="">
<p><strong>效果演示</strong>：</p>
<ul>
<li class="">
<p>使用以下命令啟動互動式提交：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm run commit</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>互動式 Prompt 介面如下：<br>
<img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1732979825/Docusaurus%20Blog/Blog/Conventional%20Commit%20with%20Gitmoji/cz-conventional-changelog-1_y6lwy2.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1732979825/Docusaurus%20Blog/Blog/Conventional%20Commit%20with%20Gitmoji/cz-conventional-changelog-2_f5mjcb.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
</li>
</ul>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="不使用-gitmoji2-搭配-commitlintcz-commitlint--commitlintconfig-conventional"><strong>不使用 Gitmoji(2): 搭配 @commitlint/cz-commitlint + @commitlint/config-conventional</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E4%B8%8D%E4%BD%BF%E7%94%A8-gitmoji2-%E6%90%AD%E9%85%8D-commitlintcz-commitlint--commitlintconfig-conventional" class="hash-link" aria-label="Direct link to 不使用-gitmoji2-搭配-commitlintcz-commitlint--commitlintconfig-conventional" title="Direct link to 不使用-gitmoji2-搭配-commitlintcz-commitlint--commitlintconfig-conventional" translate="no">​</a></h3>
<ol>
<li class="">
<p><strong>安裝依賴</strong>：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev commitizen @commitlint/cz-commitlint @commitlint/config-conventional</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>@commitlint/cz-commitlint:</strong> 為 Commitizen 的適配器，依賴於 <code>commitlint.config.js</code> 中的配置</li>
<li class=""><strong>@commitlint/config-conventional:</strong>  提供了預設的規範配置（用來設置於 <code>commitlint.config.js</code> ）</li>
</ul>
</li>
<li class="">
<p><strong>配置 Commitizen</strong>：</p>
<ul>
<li class="">
<p>在 <code>package.json</code> 中新增以下內容：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commit"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"config"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commitizen"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"path"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@commitlint/cz-commitlint"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
</li>
</ul>
</li>
<li class="">
<p><strong>配置 Commitlint</strong>：</p>
<ul>
<li class="">
<p>在專案根目錄新增 <code>commitlint.config.js</code>，並擴展 <code>@commitlint/config-conventional</code>：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">extends</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'@commitlint/config-conventional'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ul>
</li>
<li class="">
<p><strong>效果演示</strong>：</p>
<ul>
<li class="">
<p>使用以下命令啟動互動式提交：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm run commit</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>互動式 Prompt 介面如下：</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1732979825/Docusaurus%20Blog/Blog/Conventional%20Commit%20with%20Gitmoji/commitlint_config-conventional-1_zyu6oq.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1732979825/Docusaurus%20Blog/Blog/Conventional%20Commit%20with%20Gitmoji/commitlint_config-conventional-2_hw2xdf.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
</li>
</ul>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-gitmoji-搭配-cz-customizable"><strong>使用 Gitmoji: 搭配 cz-customizable</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E4%BD%BF%E7%94%A8-gitmoji-%E6%90%AD%E9%85%8D-cz-customizable" class="hash-link" aria-label="Direct link to 使用-gitmoji-搭配-cz-customizable" title="Direct link to 使用-gitmoji-搭配-cz-customizable" translate="no">​</a></h3>
<ol>
<li class="">
<p><strong>安裝依賴</strong>：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm install --save-dev commitizen cz-customizable</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>根目錄創建配置文件</strong>：</p>
</li>
</ol>
<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary> 中文版 .cz-config.js </summary><div><div class="collapsibleContent_i85q"><div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">types</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":sparkles: feat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"✨ feat: 新功能"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":bug: fix"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🐛 fix: 修復 Bug"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":memo: docs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"📝 docs: 文件變更"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":art: style"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🎨 style: 程式碼格式（不影響功能，例如空白、格式化、缺少分號等）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":recycle: refactor"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"♻️  refactor: 程式碼重構（既不是修復錯誤也不是添加功能）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":zap: perf"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"⚡️ perf: 改善效能的程式碼變更"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":white_check_mark: test"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"✅ test: 添加或修正測試"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":package: build"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"📦️ build: 影響構建系統或外部依賴的變更（例如：gulp、npm）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":construction_worker: ci"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"👷 ci: 更改持續整合文件和腳本（例如：Jenkins, Travis、Circle、Github Action 等）"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":wrench: chore"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🔧 chore: 其他不修改 src 或測試文件的變更"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":rewind: revert"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"⏪️ revert: 回退先前的提交"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">scopes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ui"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"backend"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"api"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docs"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tests"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">messages</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"選擇您要提交的變更類型："</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">scope</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"選擇此變更的範圍（可選）："</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">subject</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"簡短描述變更（必填）："</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">body</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"詳細描述（可選）："</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">breaking</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"此提交是否包含重大變更？如果是，請詳細描述（可選）："</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">footer</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"列出此變更相關的 ISSUE，例如：#31, #34（可選）："</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">confirmCommit</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"您確定要提交上述內容嗎？"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">askForBreakingChangeFirst</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">allowBreakingChanges</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"feat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fix"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">allowCustomScopes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">subjectLimit</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div></div></div></details>
<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary> 英文版 .cz-config.js </summary><div><div class="collapsibleContent_i85q"><div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">types</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":sparkles: feat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"✨ feat: New feature"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":bug: fix"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🐛 fix: Bug fix"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":memo: docs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"📝 docs: Documentation changes"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":art: style"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🎨 style: Code style changes (formatting, white spaces, missing semicolons, etc.)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":recycle: refactor"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"♻️  refactor: Code refactoring (neither a fix nor a feature addition)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":zap: perf"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"⚡️ perf: Performance improvement changes"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":white_check_mark: test"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"✅ test: Adding or updating tests"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":package: build"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"📦️ build: Changes that affect the build system or external dependencies (e.g., gulp, npm)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":construction_worker: ci"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"👷 ci: Changes to CI configuration files and scripts (e.g., Jenkins, Travis, Circle, GitHub Actions)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":wrench: chore"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"🔧 chore: Other changes that don't modify src or test files"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">":rewind: revert"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"⏪️ revert: Revert a previous commit"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">scopes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ui"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"backend"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"api"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docs"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tests"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">messages</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Select the type of change you are committing:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">scope</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Select the scope of this change (optional):"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">subject</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Write a short description of the change (required):"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">body</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Provide a longer description of the change (optional):"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">breaking</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Does this commit include a breaking change? If yes, provide details (optional):"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">footer</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"List any issues related to this change, e.g., #31, #34 (optional):"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">confirmCommit</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Are you sure you want to commit the above?"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">askForBreakingChangeFirst</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">allowBreakingChanges</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"feat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"fix"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">allowCustomScopes</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">subjectLimit</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">    </span><br></div></code></pre></div></div></div></div></details>
<ol start="3">
<li class=""><strong>配置 Commitizen</strong>：<!-- -->
<ul>
<li class="">
<p>在 <code>package.json</code> 中新增以下內容：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commit"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"config"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"commitizen"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"path"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cz-customizable"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
</li>
</ul>
</li>
<li class=""><strong>效果演示</strong>：<!-- -->
<ul>
<li class="">
<p>使用以下命令啟動互動式提交：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm run commit</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>互動式 Prompt 介面如下：</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1732979825/Docusaurus%20Blog/Blog/Conventional%20Commit%20with%20Gitmoji/cz-customizable-1_y1zn6h.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1732979826/Docusaurus%20Blog/Blog/Conventional%20Commit%20with%20Gitmoji/cz-customizable-2_yomm0m.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1732979825/Docusaurus%20Blog/Blog/Conventional%20Commit%20with%20Gitmoji/cz-customizable-3_vqhbkr.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
</li>
</ul>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語為後續工作流打下基礎"><strong>結語：為後續工作流打下基礎</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#%E7%B5%90%E8%AA%9E%E7%82%BA%E5%BE%8C%E7%BA%8C%E5%B7%A5%E4%BD%9C%E6%B5%81%E6%89%93%E4%B8%8B%E5%9F%BA%E7%A4%8E" class="hash-link" aria-label="Direct link to 結語為後續工作流打下基礎" title="Direct link to 結語為後續工作流打下基礎" translate="no">​</a></h2>
<p>到這裡，我們已經完成了 Commitizen 的安裝與配置，並且針對 <strong>不使用 Gitmoji</strong> 和 <strong>使用 Gitmoji</strong> 兩種情境，分別展示了適合的工具選擇與實作步驟。這些基礎設置並不僅僅是為了提交訊息的美觀或規範化，更重要的是，這為後續的自動化流程鋪平了道路。未來我們便可以透過規範化的提交歷史，實現自動生成 <strong>Changelog</strong>，甚至結合 <strong>CI/CD</strong> 工具進一步優化開發流程。</p>
<p>如果你在導入這套流程時有其他需求或改進想法，別忘了靈活調整工具和設定，讓它更符合你的專案需求。下一篇文章我將介紹如何使用 Husky 來自動檢查提交訊息，確保提交符合規範。希望這篇文章對你有幫助，為你的開發流程帶來一點效率提升，也增添一些樂趣！</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/gitmoji-x-conventional-commit-workflow-commitizen#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://www.conventionalcommits.org/en/v1.0.0/" target="_blank" rel="noopener noreferrer" class=""><strong>Conventional Commits</strong></a></li>
<li class=""><a href="https://gitmoji.dev/" target="_blank" rel="noopener noreferrer" class=""><strong>Gitmoji</strong></a></li>
<li class=""><a href="https://commitizen.github.io/cz-cli/" target="_blank" rel="noopener noreferrer" class=""><strong>Commitizen</strong></a></li>
<li class=""><a href="https://github.com/leoforfree/cz-customizable" target="_blank" rel="noopener noreferrer" class=""><strong>cz-customizable</strong></a></li>
<li class=""><a href="https://www.npmjs.com/package/cz-conventional-changelog" target="_blank" rel="noopener noreferrer" class=""><strong>cz-conventional-changelog</strong></a></li>
<li class=""><a href="https://www.npmjs.com/package/@commitlint/cz-commitlint" target="_blank" rel="noopener noreferrer" class=""><strong>@commitlint/cz-commitlint</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="實作紀錄" term="實作紀錄"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Koa 與 Express 的核心差異]]></title>
        <id>https://notes.boshkuo.com/en/blog/koa-vs-express</id>
        <link href="https://notes.boshkuo.com/en/blog/koa-vs-express"/>
        <updated>2024-11-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇文章深入探討 Koa 與 Express 這兩個流行的 Node.js 框架之間的核心差異，涵蓋中介層執行機制、錯誤處理方式以及框架設計理念。透過詳細範例和清晰說明，幫助開發者理解它們在實際應用中的不同之處，進而做出更合適的技術選擇。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p><strong>前言:</strong><br>
<!-- -->最近在公司被分派一個 Koa backend 的功能開發任務，雖然我平時多數時間主要負責前端相關的工作，不過因為學生時期有稍微接觸過一點 Express，它與 Koa 都是 Node.js 知名 web 框架，所以閱讀程式碼時並不會太陌生。這兩者雖然有不少相似之處，但實際使用後發現，它們在設計理念和使用體驗上有很大的不同。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="簡介"><strong>簡介</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#%E7%B0%A1%E4%BB%8B" class="hash-link" aria-label="Direct link to 簡介" title="Direct link to 簡介" translate="no">​</a></h2>
<p><strong>Express</strong> 是目前 Node.js 最知名且下載量最多的 web 框架。誕生於 2010 年，它是當時第一批專為 Node.js 設計的 Web 框架之一。因為簡單易學、功能強大，再加上豐富的社群資源，Express 很快成為開發者的首選工具，也推動了 Node.js 生態系統的成長，甚至啟發了後來許多框架的設計。</p>
<p>到了 2013 年，Express 的開發團隊推出了一個新框架：<strong>Koa</strong>。Koa 主打簡潔與輕量，移除了內建中介軟體，讓開發者可以自行選擇需要的模組來打造應用。Koa 的一大特色是原生支援 async/await，解決了 Express 在同步與異步操作上的一些限制。相較之下，Express 的中介軟體基於 callback 設計，內部的 next() 函數是同步執行，無法像 Koa 那樣等待下層中介軟體完成後再繼續執行。這點我們稍後會在後續的章節透過範例來詳細說明。</p>
<p>這篇文章整理了我的學習心得，希望能帶大家深入了解 Express 和 Koa 的核心差異。如果你和我一樣在查資料時曾經有「哦，原來是這樣！」的驚喜，期待這篇文章能再次帶給你那樣的體驗。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="框架的內建功能"><strong>框架的內建功能</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#%E6%A1%86%E6%9E%B6%E7%9A%84%E5%85%A7%E5%BB%BA%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="Direct link to 框架的內建功能" title="Direct link to 框架的內建功能" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="express開箱即用"><strong>Express：開箱即用</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#express%E9%96%8B%E7%AE%B1%E5%8D%B3%E7%94%A8" class="hash-link" aria-label="Direct link to express開箱即用" title="Direct link to express開箱即用" translate="no">​</a></h3>
<p>如果你使用過 Express，那你一定知道它最大的優點就是「方便」。Express 是一個接近於完整的框架，它內建了不少功能，讓開發者可以快速啟動專案，少去找第三方套件的麻煩。Express 還附帶了許多便捷方法來處理各種需求，例如：</p>
<ul>
<li class=""><code>express.Router()</code>：提供靈活的路由管理。</li>
<li class=""><code>express.static()</code>：方便地設定靜態文件服務。</li>
<li class=""><code>express.set()</code>：用於應用設定。</li>
<li class=""><code>express.json()</code> 和 <code>express.urlencoded()</code>：內建的 body parser，讓你能輕鬆解析 JSON 和 URL 編碼的請求體。</li>
</ul>
<p>簡單看個範例：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/index.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> express </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'express'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">express</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">express</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 內建 body parser</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Hello from Express!'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Express server running on http://localhost:3000'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>這段程式碼展示了 Express 如何快速設定一個簡單的 HTTP 伺服器。只需要幾行程式碼，不需要下載額外的第三方套件，就能處理基本的路由和請求解析。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="koa極簡核心"><strong>Koa：極簡核心</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#koa%E6%A5%B5%E7%B0%A1%E6%A0%B8%E5%BF%83" class="hash-link" aria-label="Direct link to koa極簡核心" title="Direct link to koa極簡核心" translate="no">​</a></h3>
<p>反觀 <strong>Koa</strong>，它的設計理念是「保持核心極簡」。換句話說，Koa 的核心不包含任何預設的中介層，連基本的路由和 body parser 都需要你自己安裝。這樣的設計讓開發者能夠有極高的自由度，按照自己的需求去組裝應用程式。</p>
<p>Koa 特別適合那些不依賴大量路徑管理的 HTTP 服務，比如 Webhook 或聊天機器人，因為它不會預設任何結構，開發者可以靈活選擇所需的中介軟體來實現功能。</p>
<p>舉個簡單的例子來看看如何在 Koa 中新增路由和 body parser：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/index.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">Koa</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'koa'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">Router</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'@koa/router'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> bodyParser </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'koa-bodyparser'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Koa</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> router </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Router</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">bodyParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 需要手動引入 body parser</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">router</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Hello from Koa with Router and Body Parser!'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">router</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">routes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">router</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">allowedMethods</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Koa server running on http://localhost:3000'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p>可以看到，使用 Koa 時需要我們手動安裝 <code>@koa/router</code> 和 <code>koa-bodyparser</code> 等套件，來補足 Express 內建的功能。這樣的設計讓 Koa 的核心保持簡潔，並給予開發者極大的掌控權。當然，自由也意味著在搭建應用時需要更多的決策和設置，這對於習慣快速啟動專案的人來說，可能需要適應一下。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="requestresponse-處理方式"><strong>Request/Response 處理方式</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#requestresponse-%E8%99%95%E7%90%86%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to requestresponse-處理方式" title="Direct link to requestresponse-處理方式" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="express使用-req-和-res-物件"><strong>Express：使用 <code>req</code> 和 <code>res</code> 物件</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#express%E4%BD%BF%E7%94%A8-req-%E5%92%8C-res-%E7%89%A9%E4%BB%B6" class="hash-link" aria-label="Direct link to express使用-req-和-res-物件" title="Direct link to express使用-req-和-res-物件" translate="no">​</a></h3>
<p>在 Express 中，處理請求和回應的核心是 <code>req</code>（Request）和 <code>res</code>（Response）物件。這兩個物件由 Express 根據 Node.js 原生的 <code>http.IncomingMessage</code> 和 <code>http.ServerResponse</code> 包裝而來，並添加了更多方便開發的屬性與方法。</p>
<p>req 提供了各種屬性和方法，用於存取請求相關的數據，例如：</p>
<ul>
<li class=""><code>req.body</code>：存取 POST 請求的資料。</li>
<li class=""><code>req.params</code>：存取路由中的參數。</li>
<li class=""><code>req.query</code>：存取 URL 中的查詢參數。</li>
</ul>
<p>res 物件則用來處理回應，包括：</p>
<ul>
<li class=""><code>res.send()</code>：發送回應。</li>
<li class=""><code>res.json()</code>：發送 JSON 格式的回應。</li>
<li class=""><code>res.status()</code>：設置 HTTP 狀態碼。</li>
</ul>
<p>以下是一個簡單的 Express 範例，展示如何處理請求和回應：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/index.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> express </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'express'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">express</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">express</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 解析 JSON 請求體</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/greet/:name'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">200</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Hello, </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">!</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Express server is running on http://localhost:3000'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="koa使用-ctx-上下文物件"><strong>Koa：使用 <code>ctx</code> 上下文物件</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#koa%E4%BD%BF%E7%94%A8-ctx-%E4%B8%8A%E4%B8%8B%E6%96%87%E7%89%A9%E4%BB%B6" class="hash-link" aria-label="Direct link to koa使用-ctx-上下文物件" title="Direct link to koa使用-ctx-上下文物件" translate="no">​</a></h3>
<p>Koa 的 Request/Response 處理則是基於 <code>ctx(Context)</code> 物件。ctx 封裝了 <code>request</code> 和 <code>response</code>，並將它們整合到一個乾淨的 API 中，讓開發者更方便地操作。</p>
<p>每個請求都會生成一個獨立的 ctx，開發者可以直接在這個物件上存取或修改請求與回應。Koa 將 Node.js 原生的 <code>req</code> 和 <code>res</code> 包裝成 <code>ctx.request</code> 和 <code>ctx.response</code>，並提供了額外功能。</p>
<p>在 Koa 中，常見的請求操作包括：</p>
<p>ctx.request.body：存取 POST 請求的數據（需搭配 koa-bodyparser）。
ctx.params：存取路由參數（需搭配路由模組，如 @koa/router）。
ctx.query：存取 URL 查詢參數。
回應操作則包括：</p>
<p>ctx.body：設置回應內容。
ctx.status：設置 HTTP 狀態碼。</p>
<p>以下是一個簡單的 Koa 範例：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/index.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">Koa</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'koa'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">Router</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'@koa/router'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> bodyParser </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'koa-bodyparser'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Koa</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> router </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Router</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">bodyParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 解析 JSON 請求體</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">router</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/greet/:name'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> name </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">params</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">200</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Hello, </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">!</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">router</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">routes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">router</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">allowedMethods</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Koa server is running on http://localhost:3000'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>除了 ctx.request 和 ctx.response，ctx 還包括許多方便的 helper methods 與屬性，讓開發者更輕鬆處理常見任務。以下是一些實用的例子：</p>
<ul>
<li class=""><strong><code>ctx.querystring</code></strong>：返回原始的查詢字符串。例如，對於 URL <code>http://localhost:3000/greet?name=John&amp;age=30</code>，<code>ctx.querystring</code> 會返回 <code>"name=John&amp;age=30"</code>。</li>
<li class=""><strong><code>ctx.throw(status, message)</code></strong>：主動拋出錯誤，簡化錯誤處理。例如，<code>ctx.throw(400, 'Bad Request')</code> 會在錯誤發生處立即中斷執行後續的中介軟體，並返回 400 狀態碼和錯誤訊息。</li>
<li class=""><strong><code>ctx.state</code></strong>：一個共享物件，用於在中介軟體之間傳遞數據。例如，可以在認證中介軟體中設定用戶資訊，供後續的業務邏輯使用。</li>
<li class=""><strong><code>ctx.is(types)</code></strong>：檢查請求的 Content-Type 是否匹配指定類型。例如，<code>ctx.is('json', 'text')</code> 可判斷請求是否為 JSON 或純文本。</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="中介層middleware的執行機制"><strong>中介層（Middleware）的執行機制</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#%E4%B8%AD%E4%BB%8B%E5%B1%A4middleware%E7%9A%84%E5%9F%B7%E8%A1%8C%E6%A9%9F%E5%88%B6" class="hash-link" aria-label="Direct link to 中介層middleware的執行機制" title="Direct link to 中介層middleware的執行機制" translate="no">​</a></h2>
<p>在後端開發中，中介層（middleware）的作用是讓請求在抵達路由處理器之前或回應送出之前進行處理。常見的用途包括日誌記錄、驗證、錯誤處理、解析請求體等。中介層的目標是提升應用的可擴充性和模組化，讓開發和維護更高效。透過一層層「守門員」依序處理請求，應用邏輯可以被拆分成清晰的小步驟，各自負責不同功能。</p>
<p>在設計上，Express 和 Koa 的中介層有一個核心差異：</p>
<ol>
<li class=""><strong>Express</strong> 的中介層基於 <strong>callback</strong>，執行 <code>next()</code> 時是同步的。也就是說，呼叫 <code>next()</code> 後，接下來的程式碼會立即執行，並不會等待後續中介層完成。</li>
<li class=""><strong>Koa</strong> 的中介層基於 <strong>Promise</strong>，允許使用 <code>async/await</code>。當執行 <code>await next()</code> 時，程式會暫停並等待後續中介層完成後，才繼續執行接下來的程式碼。這種設計符合「洋蔥模型」，讓非同步邏輯處理更加直觀自然。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="洋蔥模型"><strong>洋蔥模型</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#%E6%B4%8B%E8%94%A5%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="Direct link to 洋蔥模型" title="Direct link to 洋蔥模型" translate="no">​</a></h3>
<p>在深入了解 Koa 和 Express 的中介層之前，我們先來介紹一個核心概念——<strong>洋蔥模型</strong>，這是 Koa 中介層設計的基礎。洋蔥模型的運作方式就像剝洋蔥：請求先經過最外層的中介層，依次「穿透」到最內層。請求處理完成後，回應再從最內層開始逐層返回到外層。下圖就是一個非常經典的例子：</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1730601978/Docusaurus%20Blog/Blog/Koa%20vs%20Express/onion_model_avous5.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<p>我們可以把中介層的洋蔥模型機制理解為兩個階段：</p>
<ol>
<li class=""><strong>進入階段</strong>：執行該中介層函數 <code>next()</code> 前的邏輯，順序是從最先註冊的中介層執行到最晚註冊的中介層。</li>
<li class=""><strong>返回階段</strong>：執行該中介層函數 <code>next()</code> 後的邏輯，順序則反過來，從最晚註冊的中介層執行到最早註冊的中介層。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="express線性同步執行"><strong>Express：線性、同步執行</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#express%E7%B7%9A%E6%80%A7%E5%90%8C%E6%AD%A5%E5%9F%B7%E8%A1%8C" class="hash-link" aria-label="Direct link to express線性同步執行" title="Direct link to express線性同步執行" translate="no">​</a></h3>
<p>前面我們有提到，<strong>Express</strong> 的中介層採用線性、同步的執行模式。請求進入時，中介層按照註冊順序依次執行，透過 <code>next()</code> 將控制權傳遞給下一個中介層。然而，在處理非同步操作時，這種同步特性可能會導致執行順序看起來有些不直觀。</p>
<p><strong>觀察範例</strong><br>
<!-- -->以下是一段範例程式碼，展示了 Express 中同步與非同步中介層的執行特性：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/middleware/testMiddleware.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Request</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Response</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">NextFunction</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"express"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">syncMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  req</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">NextFunction</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"1. 同步中介層 - Before next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"1. 同步中介層 - After next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">asyncMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  req</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">NextFunction</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - Before next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name builtin">Promise</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name keyword" style="color:#00009f">void</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resolve</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">setTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - Before next wait for 1 second"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 模擬非同步事件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - After next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">terminateMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  req</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">NextFunction</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"3. 終止中介層 - Before termination"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Request terminated by middleware"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 不執行 next()，這裡會終止請求</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>執行結果</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">1. 同步中介層 - Before next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 非同步中介層 - Before next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1. 同步中介層 - After next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 非同步中介層 - Before next wait for 1 second</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. 終止中介層 - Before termination</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 非同步中介層 - After next</span><br></div></code></pre></div></div>
<p>相信對 Express 不熟的讀者第一眼看到這個執行結果可能會覺得有點難以理解，咦？為什麼 <code>1. 同步中介層 - After next</code> 會跑到 <code>2. 非同步中介層 - Before next wait for 1 second</code> 前面執行呢？</p>
<p><strong>解釋執行邏輯</strong><br>
<!-- -->因為 Express 的中介層是基於 <strong>callback</strong> 設計的，當 <code>next()</code> 被執行時，控制權會立刻交給下一個中介層，但 <code>next()</code> 後的程式碼不會等待後續中介層的非同步邏輯完成才執行。如果我們試圖中介層的執行邏輯拆解成 callback 的話大概會像這樣：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"1. 同步中介層 - Before next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - Before next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 呼叫非同步邏輯，進入等待</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - Before next wait for 1 second"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Promise</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">resolve</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resolve</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"3. 終止中介層 - Before termination"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Request terminated by middleware"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - After next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"1. 同步中介層 - After next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 這行在等待非同步結果前就執行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p>若看到這邊你還是覺得有點難理解，以下的時序圖可以幫助你理解 middleware 內部運行順序的邏輯</p>
<div class="ui-theme-mermaid-styles-diagram cursor-zoom-in rounded-lg focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4" role="button" tabindex="0" aria-label="Open Mermaid diagram preview"></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>有些文章提到可以把 Express 的 middleware 理解為一個 <code>FIFO Queue</code>（先進先出），但我認為這種說法存在誤區。因為在實際運行中，執行順序取決於是否涉及非同步操作。</p><p><strong>同步操作下的 LIFO 行為</strong><br>
<!-- -->當中介層中沒有任何非同步操作時，<code>next()</code> 的執行順序更接近 <strong>LIFO</strong>（後進先出）的呼叫堆疊（call stack）。因為 <code>next()</code> 將控制權傳遞給下一個中介層後，當所有中介層的進入邏輯執行完畢，返回階段會從最後一個中介層開始逐層返回，與堆疊的後進先出特性類似。</p><p><strong>非同步操作與 Event Loop 的影響</strong><br>
<!-- -->當中介層中有非同步操作（如 <code>setTimeout</code> 或 <code>Promise</code>）時，執行順序變得更複雜。此時，非同步任務會被放入事件隊列（event queue）中，等到主執行緒空閒時再執行。這意味著中介層執行順序受到 JavaScript <strong>Event Loop</strong> 機制的驅動：</p><ol>
<li class=""><strong>進入階段</strong>：同步邏輯按註冊順序執行，直到遇到非同步操作。</li>
<li class=""><strong>非同步操作</strong>：將任務交給事件隊列等待執行，主執行緒繼續處理其他中介層。</li>
<li class=""><strong>返回階段</strong>：當事件隊列中的非同步任務完成後，主執行緒會取回控制權，繼續執行 <code>next()</code> 後的程式。</li>
</ol><p>因此，Express 的中介層執行邏輯並非簡單的 FIFO 隊列，而是同步情況下的 <strong>LIFO 行為</strong>，結合 Event Loop 的 <strong>非同步調度</strong>。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="koa洋蔥模型"><strong>Koa：洋蔥模型</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#koa%E6%B4%8B%E8%94%A5%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="Direct link to koa洋蔥模型" title="Direct link to koa洋蔥模型" translate="no">​</a></h3>
<p>在了解了 <strong>Express</strong> 線性、同步的中介層執行後，我們來看看 <strong>Koa</strong> 如何處理中介層。Koa 採用了所謂的 <strong>「洋蔥模型」</strong>，這種設計讓中介層的執行順序清晰且可預測：從外到內，再從內到外。</p>
<p><strong>觀察範例</strong><br>
<!-- -->以下是一個簡單的 Koa 中介層範例：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/middleware/testMiddleware.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Context</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Next</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"koa"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">syncMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"1. 同步中介層 - Before next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"1. 同步中介層 - After next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">asyncMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - Before next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name builtin">Promise</span><span class="token class-name operator" style="color:#393A34">&lt;</span><span class="token class-name keyword" style="color:#00009f">void</span><span class="token class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">resolve</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">setTimeout</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - Before next wait for 1 second"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"2. 非同步中介層 - After next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">terminateMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"3. 終止中介層 - Before termination"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Request terminated by middleware"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 不執行 next()，這裡會終止請求</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>執行結果</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">1. 同步中介層 - Before next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 非同步中介層 - Before next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 非同步中介層 - Before next wait for 1 second</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">3. 終止中介層 - Before termination</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">2. 非同步中介層 - After next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">1. 同步中介層 - After next</span><br></div></code></pre></div></div>
<p><strong>解釋執行邏輯</strong><br>
<!-- -->Koa 的中介層基於 <strong>Promise</strong> 設計，執行順序遵循以下邏輯：</p>
<ol>
<li class=""><strong>進入階段（外到內）：</strong> 當執行 <code>await next()</code> 時，控制權會傳遞到下一個中介層。</li>
<li class=""><strong>返回階段（內到外）：</strong> 當內層中介層執行完畢後，控制權返回到當前中介層，繼續執行 <code>next()</code> 之後的程式碼。</li>
</ol>
<p>可以發現，Koa 的 middleware 運行順序相比於 Express 要好預測得多了，不論是同不還是非同步邏輯，程式碼的執行順序都是「外到內，內到外」。對於開發者來說，Koa 的洋蔥模型提供了更可控的執行流程。特別是在處理非同步邏輯時，Koa 的設計讓控制流更清晰，並減少了對開發者的心智負擔。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="中介層middleware執行細節探討"><strong>中介層（Middleware）執行細節探討</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#%E4%B8%AD%E4%BB%8B%E5%B1%A4middleware%E5%9F%B7%E8%A1%8C%E7%B4%B0%E7%AF%80%E6%8E%A2%E8%A8%8E" class="hash-link" aria-label="Direct link to 中介層middleware執行細節探討" title="Direct link to 中介層middleware執行細節探討" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="express回應與中介層執行的陷阱"><strong>Express：回應與中介層執行的陷阱</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#express%E5%9B%9E%E6%87%89%E8%88%87%E4%B8%AD%E4%BB%8B%E5%B1%A4%E5%9F%B7%E8%A1%8C%E7%9A%84%E9%99%B7%E9%98%B1" class="hash-link" aria-label="Direct link to express回應與中介層執行的陷阱" title="Direct link to express回應與中介層執行的陷阱" translate="no">​</a></h3>
<p>在 Express 中，當中介層執行 <code>res.send()</code> 等方法發送回應時，請求流程就會被終止，不會再繼續執行後面的中介層或程式碼。如如果在回應後意外執行 <code>next()</code>，會拋出以下錯誤：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Error: Cannot set headers after they are sent to the client</span><br></div></code></pre></div></div>
<p>這是因為回應頭已經被發送，不能再次修改。因此，開發者需謹慎處理回應後的邏輯，避免不必要的 <code>next()</code> 呼叫。</p>
<p><strong>範例</strong>：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/middleware/testMiddleware.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Request</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Response</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">NextFunction</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"express"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">terminateMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  req</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">NextFunction</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"3. 終止中介層 - Before termination"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Request terminated by middleware"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 將導致錯誤：Cannot set headers after they are sent to the client</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p><strong>預期結果</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">1. 終止中介層 - Before termination</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Express Error: Error: Cannot set headers after they are sent to the client</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>設計注意事項</div><div class="admonitionContent_BuS1"><p>在設計 Express 中介層時，務必確保以下幾點：</p><ol>
<li class=""><strong>終止中介層：</strong> 確認回應邏輯執行完畢後，避免再呼叫 <code>next()</code>。</li>
<li class=""><strong>錯誤處理：</strong> 如果有錯誤回應邏輯，例如 <code>res.status(500).send()</code>，應將後續處理責任中止。</li>
<li class=""><strong>明確流程：</strong> 確保所有中介層對回應的處理是清晰可控的，避免因誤用 <code>next()</code> 而導致不可預測的錯誤。</li>
</ol></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="koa回應後的執行與控制"><strong>Koa：回應後的執行與控制</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#koa%E5%9B%9E%E6%87%89%E5%BE%8C%E7%9A%84%E5%9F%B7%E8%A1%8C%E8%88%87%E6%8E%A7%E5%88%B6" class="hash-link" aria-label="Direct link to koa回應後的執行與控制" title="Direct link to koa回應後的執行與控制" translate="no">​</a></h3>
<p>與 <strong>Express</strong> 不同，<strong>Koa</strong> 的設計允許即使設置了 <code>ctx.body</code> 回應請求，後續的中介層仍然會繼續執行。這種設計為開發者提供了更多的靈活性，可以在設置回應後進行額外的邏輯處理，例如記錄日誌或執行清理操作。</p>
<p><strong>範例</strong>：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/middleware/testMiddleware.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Context</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Next</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"koa"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">terminateMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"3. 終止中介層 - Before termination"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Request terminated by middleware"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 即使設置了 ctx.body，後面的中介層仍會執行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">logMiddleware</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"4. 日誌記錄中介層 - 執行中"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"4. 日誌記錄中介層 - 完成"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>預期結果</strong>：</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token list punctuation" style="color:#393A34">3.</span><span class="token plain"> 終止中介層 - Before termination</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">4.</span><span class="token plain"> 日誌記錄中介層 - 執行中</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">4.</span><span class="token plain"> 日誌記錄中介層 - 完成</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">...</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>關鍵特性</div><div class="admonitionContent_BuS1"><ol>
<li class=""><strong>後續執行：</strong> 即使設置了 <code>ctx.body</code>，後續中介層的邏輯仍會執行，除非中介層中斷了請求流程（如拋出錯誤）。</li>
<li class=""><strong>靈活性：</strong> 開發者可以在回應設置後繼續執行額外邏輯，例如：<!-- -->
<ul>
<li class=""><strong>記錄日誌：</strong> 紀錄請求與回應的相關資訊。</li>
<li class=""><strong>資源清理：</strong> 釋放資料庫連線或清除暫存。</li>
<li class=""><strong>監控處理：</strong> 收集性能數據或追蹤請求。</li>
</ul>
</li>
</ol></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="錯誤處理"><strong>錯誤處理</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#%E9%8C%AF%E8%AA%A4%E8%99%95%E7%90%86" class="hash-link" aria-label="Direct link to 錯誤處理" title="Direct link to 錯誤處理" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="express-的錯誤處理"><strong>Express 的錯誤處理</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#express-%E7%9A%84%E9%8C%AF%E8%AA%A4%E8%99%95%E7%90%86" class="hash-link" aria-label="Direct link to express-的錯誤處理" title="Direct link to express-的錯誤處理" translate="no">​</a></h3>
<p>在 Express 中，有兩種類型的中介層：</p>
<ul>
<li class=""><strong>一般</strong>中介層：參數為 <code>(req, res, next)</code>，處理正常的請求和回應邏輯。</li>
<li class=""><strong>錯誤處理</strong>中介層：參數為 <code>(err, req, res, next)</code>，必須接受 4 個參數。這樣的中介層專門用於捕獲和處理應用程式中的錯誤。Express 會自動識別並執行這種中介層來處理錯誤。</li>
</ul>
<p>當 Express 檢測到一個這個中介層帶有四個參數<code>(err, req, res, next)</code>時，它會將其視為錯誤處理中間件，並且只在以下情況下執行它：</p>
<ul>
<li class="">當中介層或路由處理器呼叫 <code>next(error)</code> 時。</li>
<li class="">當程式碼中主動拋出異常（如 <code>throw new Error(...)</code>）時。</li>
</ul>
<p>當錯誤發生時，Express 會跳過後續的正常中介層，直接執行錯誤處理中介層。</p>
<p><strong>錯誤處理範例</strong></p>
<p>以下我們透過範例來看在 Express 中如何做錯誤處理：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/index.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"第一個中介層 - Before next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"模擬錯誤"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"第一個中介層 - After next"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 這行仍然會被執行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"第二個中介層"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 這行不會被執行，因為錯誤已經傳遞到錯誤處理中間件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 錯誤處理中介層，必須放在所有其他中間件和路由之後</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">err</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"錯誤捕獲:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">500</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"發生錯誤"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>執行結果</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">第一個中間件 - Before next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">第一個中間件 - After next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">錯誤捕獲: 模擬錯誤</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>注意事項</div><div class="admonitionContent_BuS1"><ol>
<li class=""><strong><code>next(error)</code> 的行為：</strong>
<ul>
<li class="">傳遞錯誤後，Express 會跳過正常中介層，直接進入錯誤處理中介層。</li>
<li class="">但調用 <code>next(error)</code> 並不會中止上層中介層的程式碼執行，<code>next()</code> 後的邏輯仍然會執行（如範例中的 <code>After next</code> 日誌）。</li>
</ul>
</li>
<li class=""><strong>錯誤處理中介層的位置：</strong>
<ul>
<li class="">必須放在所有其他中介層和路由之後，否則無法捕獲錯誤。</li>
</ul>
</li>
<li class=""><strong>拋出異常：</strong>
<ul>
<li class="">主動 <code>throw</code> 也會觸發錯誤處理中介層，行為與 <code>next(error)</code> 一致。</li>
</ul>
</li>
</ol></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>為什麼錯誤處理中介層，必須放在所有其他中間件和路由之後？</div><div class="admonitionContent_BuS1"><p>如前所述，因為 Express 採用線性執行模型，<code>next()</code> 會將控制權傳遞給下一個中介層或路由。如果錯誤處理中介層不放在最後，Express 將無法正確捕獲和處理錯誤 <code>next(error)</code> 向後傳遞的錯誤。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="koa-的錯誤處理"><strong>Koa 的錯誤處理</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#koa-%E7%9A%84%E9%8C%AF%E8%AA%A4%E8%99%95%E7%90%86" class="hash-link" aria-label="Direct link to koa-的錯誤處理" title="Direct link to koa-的錯誤處理" translate="no">​</a></h3>
<p>在 <strong>Koa</strong> 中，錯誤處理的機制與 <strong>Express</strong> 有一些根本上的不同。Koa 採用了原生的 <code>async/await</code> 和 <code>try...catch</code> 結構，讓邏輯更加直觀。這種設計使每層中介層能集中捕捉和處理錯誤，而不需要額外設置專用的錯誤處理中介層。</p>
<p><strong>錯誤處理的特性</strong></p>
<ul>
<li class=""><code>try...catch</code> 捕捉異常： 每層中介層都可以使用 <code>try...catch</code> 捕捉異常，未捕捉的錯誤會傳遞到全局錯誤監聽器。</li>
<li class="">自動停止流程： 當發生錯誤時，Koa 會中止 <code>next()</code> 後的程式碼執行，避免不必要的邏輯被執行。</li>
<li class="">全局錯誤監聽器： 使用 <code>ctx.app.emit('error', err, ctx)</code>，可以將錯誤傳遞到全局監聽器進行集中處理。</li>
</ul>
<p><strong>範例</strong>：<br>
<!-- -->讓我們看一個簡單的例子，展示如何在 Koa 中使用 <code>try...catch</code> 來捕獲和處理錯誤：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_OeMC">src/index.ts</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">Koa</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Context</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Next</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'koa'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Koa</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 全局錯誤監聽器</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">on</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'error'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'全局錯誤監聽器捕獲錯誤:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 可以在此處添加日誌記錄或通知系統等處理</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 一般中介層，包含 try...catch 來捕獲錯誤</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'第一個中介層 - Before next'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">next</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 傳遞控制權給下一個中介層</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'第一個中介層 - After next'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 這行在發生錯誤時不會執行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'第一個中介層捕獲錯誤:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">status</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">500</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'伺服器發生錯誤'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ctx</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">emit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'error'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 將錯誤發送到全局監聽器</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 模擬異常的中介層</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Context</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> next</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'第二個中介層 - Before throw'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'模擬錯誤'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 主動拋出錯誤</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'第二個中介層 - After throw'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 不會執行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Koa server is running on http://localhost:3000'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>執行結果</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">第一個中介層 - Before next</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">第二個中介層 - Before throw</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">第一個中介層捕獲錯誤: 模擬錯誤</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">全局錯誤監聽器捕獲錯誤: 模擬錯誤</span><br></div></code></pre></div></div>
<p><strong>客戶端回應</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">伺服器發生錯誤</span><br></div></code></pre></div></div>
<p><strong>關鍵邏輯解析:</strong><br>
<!-- -->在這個範例中，當 <code>第二個中介層</code> 拋出錯誤後，Koa 會立即進入 <code>第一個中介層</code> 的 <code>catch</code> 區塊，設置 HTTP 狀態碼為 500 並回傳錯誤訊息，同時停止執行 <code>第一個中介層</code> 中 <code>next()</code> 後的程式碼。這種機制確保錯誤發生後，流程能夠被中止，並進行適當的錯誤處理。</p>
<p>除了使用 <code>try...catch</code> 來捕捉錯誤，Koa 還提供了 <code>ctx.app.emit('error', err, ctx)</code> 方法來將錯誤事件發送給應用的全局錯誤監聽器。這在大型應用中特別有用，因為它允許集中處理所有錯誤，例如記錄日誌或發送通知，而不需要在每個中介層中重複錯誤處理邏輯。</p>
<p>在這個範例中， <code>ctx.app.emit('error', err, ctx)</code> 被調用時，全局監聽器 <code>app.on('error', …)</code> 會接收到這個錯誤事件，並根據需求進行集中處理。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/koa-vs-express#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://medium.com/@gary92.gs/express-vs-koa-e48458020ce4" target="_blank" rel="noopener noreferrer" class=""><strong>Express vs Koa</strong></a></li>
<li class=""><a href="https://medium.com/cow-say/%E9%87%90%E6%B8%85-express-js-%E5%92%8C-koa-js-%E7%9A%84%E8%A8%AD%E8%A8%88%E5%B7%AE%E7%95%B0-7f95c9f5d596" target="_blank" rel="noopener noreferrer" class=""><strong>釐清 Express.js 和 Koa.js 的設計差異</strong></a></li>
<li class=""><a href="https://johnnywang1994.github.io/book/articles/js/express-vs-koa.html#nodejs-%E8%BC%95%E9%87%8F%E9%96%8B%E7%99%BC%E6%A1%86%E6%9E%B6-expressjs-%E8%88%87-koa2-%E7%9A%84%E5%8D%80%E5%88%A5" target="_blank" rel="noopener noreferrer" class=""><strong>NodeJS 輕量開發框架 Expressjs 與 Koa2 的區別</strong></a></li>
<li class=""><a href="https://blog.xav1er.com/p/middleware-of-koa-and-express/" target="_blank" rel="noopener noreferrer" class=""><strong>浅谈 Koa 和 Express 的中间件设计模式</strong></a></li>
<li class=""><a href="https://github.com/koajs/koa/blob/master/docs/koa-vs-express.md" target="_blank" rel="noopener noreferrer" class=""><strong>Koa vs Express</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="Node.js" term="Node.js"/>
        <category label="Backend" term="Backend"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[React 專案配置 i18n 多國語系]]></title>
        <id>https://notes.boshkuo.com/en/blog/react-i18next</id>
        <link href="https://notes.boshkuo.com/en/blog/react-i18next"/>
        <updated>2024-10-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇文章記錄了我在 React + TypeScript 專案中配置多國語系支持的過程，從工具選擇、需求分析到實際安裝與配置，帶你一步步了解如何使用 i18next 與 react-i18next 來實現應用的多語言切換功能。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p><strong>前言：</strong><br>
<!-- -->最近我在更新公司內部的前端 Codebase，其中一項任務是為應用加入多國語系（i18n）的支持。雖然在舊專案上一直有在使用 i18n 的功能，但這是我第一次接觸 i18n 的配置，正好藉此機會把學習過程和實作細節記錄下來。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="主要工具介紹i18next-與-react-i18next"><strong>主要工具介紹：i18next 與 react-i18next</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E4%B8%BB%E8%A6%81%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%B4%B9i18next-%E8%88%87-react-i18next" class="hash-link" aria-label="Direct link to 主要工具介紹i18next-與-react-i18next" title="Direct link to 主要工具介紹i18next-與-react-i18next" translate="no">​</a></h2>
<p>在研究 React 應用程式中的多國語系解決方案時，<a href="https://www.i18next.com/" target="_blank" rel="noopener noreferrer" class=""><strong>18next</strong></a> 和 <a href="https://react.i18next.com/" target="_blank" rel="noopener noreferrer" class=""><strong>react-i18next</strong></a> 是最常被推薦的組合。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i18next"><strong>i18next</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#i18next" class="hash-link" aria-label="Direct link to i18next" title="Direct link to i18next" translate="no">​</a></h3>
<p><strong>18next</strong> 是一個強大且可擴充的國際化庫，專門用來處理語言轉換。i18next 的特點是可以跨框架使用，這讓它成為前後端通用的國際化解決方案。它的核心功能包括語言偵測、語言切換，以及使用 JSON 語系檔案來管理文本內容。</p>
<p>此外，i18next 支援多種插件，例如：當你想要根據用戶的偏好來設定語言時，可以透過語言檢測插件來判斷用戶的語言喜好。它還支援 HTTP 加載器，可以動態從伺服器端載入翻譯文件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="react-i18next"><strong>react-i18next</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#react-i18next" class="hash-link" aria-label="Direct link to react-i18next" title="Direct link to react-i18next" translate="no">​</a></h3>
<p><strong>react-i18next</strong> 則是 i18next 的 React 封裝。它的目的是讓 React 開發者能更方便地將 i18next 的功能整合進應用程式中。它提供了很多直觀的工具，例如 Hook (<code>useTranslation</code>) 和高階元件 (<code>withTranslation</code>)，讓我們能夠以更符合 React 思維的方式處理翻譯工作。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="專案需求"><strong>專案需求</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E5%B0%88%E6%A1%88%E9%9C%80%E6%B1%82" class="hash-link" aria-label="Direct link to 專案需求" title="Direct link to 專案需求" translate="no">​</a></h2>
<p>大致理解這些工具的作用後，我開始分析專案的需求。根據過去舊專案的 i18n 使用情況，目前的需求至少要包括：</p>
<ul>
<li class=""><strong>支持中英文切換</strong>：應用需要支持用戶隨時切換介面語言。</li>
<li class=""><strong>根據用戶的瀏覽器語言自動設置語言</strong>：提升用戶體驗，讓應用自動選擇最合適的語言。</li>
<li class=""><strong>命名空間來管理翻譯文檔</strong>：不同的功能模組應該有各自獨立的翻譯檔，避免文檔過於混亂。比如，通用部分 (<code>common</code>) 和應用特定部分 (<code>app</code>)。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝與配置"><strong>安裝與配置</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E5%AE%89%E8%A3%9D%E8%88%87%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 安裝與配置" title="Direct link to 安裝與配置" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="套件安裝"><strong>套件安裝</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E5%A5%97%E4%BB%B6%E5%AE%89%E8%A3%9D" class="hash-link" aria-label="Direct link to 套件安裝" title="Direct link to 套件安裝" translate="no">​</a></h3>
<p>開始實作之前，首先要安裝所需的工具。以下是我在專案中使用的插件和它們的作用：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn add i18next i18next-browser-languagedetector i18next-http-backend react-i18next</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>i18next-http-backend</code>：這個插件用於從伺服器端加載翻譯文檔，可以方便我們將翻譯文案分開管理，並根據所需語言動態加載。</li>
<li class=""><code>i18next-browser-languagedetector</code>：這個插件用於自動偵測用戶的瀏覽器語言，例如通過 URL 的查詢參數或瀏覽器的語言設置。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="i18n-配置"><strong>i18n 配置</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#i18n-%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to i18n-配置" title="Direct link to i18n-配置" translate="no">​</a></h3>
<p>安裝完套件之後，下一步就是配置 <strong>i18next</strong>，讓它符合我們的需求。我根據官方文檔做了以下配置：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// i18n.ts</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> initReactI18next </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-i18next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">i18n</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'i18next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">LanguageDetector</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'i18next-browser-languagedetector'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">HttpBackend</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'i18next-http-backend'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 初始化配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">i18n</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">HttpBackend</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 使用 HttpBackend 來載入翻譯文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">LanguageDetector</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 使用語言檢測插件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">initReactI18next</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 初始化 React i18next</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">init</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// lng: 'zh-TW', // 預設語言</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    fallbackLng</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'zh-TW'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 如果檢測不到則使用的預設語言</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    detection</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      order</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'querystring'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'navigator'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 定義語言檢測的順序，例如通過查詢參數或瀏覽器語言</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      caches</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 不緩存語言設定，不使用 localStorage 或 cookie</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    backend</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      loadPath</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/translations/{{ns}}/{{lng}}.json'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 翻譯檔案路徑</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ns</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'common'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 使用的 namespaces</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    defaultNS</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'common'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 預設 namespace</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    interpolation</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      escapeValue</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// React 已自帶 XSS 安全處理，不需額外 escape</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    debug</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">meta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">DEV</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 在開發時期可以開啟以查看 debug 信息</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> i18n</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>在這段程式碼中，我對語言偵測的順序和命名空間做了詳細的設定。例如，我使用了 <code>common</code> 和 <code>app</code> 兩個命名空間，以保持翻譯內容的模組化管理。這樣一來，不同的功能模組就不會互相干擾，維護起來也更加輕鬆。接著我們只需要在案入口文件（如 <code>main.tsx</code>）中引入，我們就可以在所有元件中使用 i18n 的翻譯功能。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>詳細可配置選項與定義可參考 <strong><a href="https://www.i18next.com/overview/configuration-options" target="_blank" rel="noopener noreferrer" class="">@i18next documentation - Configuration Options</a></strong> 。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="準備翻譯文件"><strong>準備翻譯文件</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E6%BA%96%E5%82%99%E7%BF%BB%E8%AD%AF%E6%96%87%E4%BB%B6" class="hash-link" aria-label="Direct link to 準備翻譯文件" title="Direct link to 準備翻譯文件" translate="no">​</a></h3>
<p>在配置完成後，接下來就是準備翻譯文件。在 i18next 的設定中，我把翻譯檔案的路徑設定為 <code>'/translations/{{ns}}/{{lng}}.json'</code>，因此我建立了以下目錄結構：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">public/translations</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└── common</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ├── en.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    └── zh-TW.json</span><br></div></code></pre></div></div>
<p>每個 JSON 文件都包含 key-value 形式的翻譯文字，例如：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// zh-TW.json</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"login"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"登入"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"logout"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"登出"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-react-i18next"><strong>使用 <code>react-i18next</code></strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E4%BD%BF%E7%94%A8-react-i18next" class="hash-link" aria-label="Direct link to 使用-react-i18next" title="Direct link to 使用-react-i18next" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際處理翻譯內容"><strong>實際處理翻譯內容</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E5%AF%A6%E9%9A%9B%E8%99%95%E7%90%86%E7%BF%BB%E8%AD%AF%E5%85%A7%E5%AE%B9" class="hash-link" aria-label="Direct link to 實際處理翻譯內容" title="Direct link to 實際處理翻譯內容" translate="no">​</a></h3>
<p>在 <code>react-i18next</code> 中，最常用的方式是使用 <code>useTranslation</code> hook 來處理翻譯：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useTranslation </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-i18next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">MyComponent</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access maybe-class-name" style="color:#d73a49">FC</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> t </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useTranslation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'app'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 使用 'app' 命名空間</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token function" style="color:#d73a49">t</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'welcome_message'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">h1</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">MyComponent</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>在這段程式碼中，我使用 useTranslation 來取得 <code>t</code> 函數。這個 <code>t()</code> 函數可以根據當前語言和鍵值來取得相應的翻譯文字，讓元件渲染時可以自動切換語言，實現真正的多語系支援。</p>
<p>此外，在使用 useTranslation 時可以指定特定的 namespace，例如 <code>useTranslation('common')</code> 或 <code>useTranslation('app')</code>。這樣做可以有效地組織和管理不同領域的翻譯內容。如果不指定的話，i18n 則會自動使用預設的 namespace 來翻譯。</p>
<p>除了 useTranslation，還有一些其他的 API，如 <a href="https://react.i18next.com/latest/withtranslation-hoc" target="_blank" rel="noopener noreferrer" class=""><strong>withTranslation</strong></a> HOC 和 <a href="https://react.i18next.com/latest/translation-render-prop" target="_blank" rel="noopener noreferrer" class=""><strong>Translation</strong></a> render prop，但由於它們較少用到，我這裡就不做深入介紹。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="語言切換功能"><strong>語言切換功能</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#%E8%AA%9E%E8%A8%80%E5%88%87%E6%8F%9B%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="Direct link to 語言切換功能" title="Direct link to 語言切換功能" translate="no">​</a></h3>
<p>除了翻譯，語言切換也是多國語系的核心功能之一。在這裡，我使用了 i18next 提供的 <code>changeLanguage</code> 方法，讓使用者能夠隨時切換語言：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useTranslation </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react-i18next'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">LanguageSwitcher</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access maybe-class-name" style="color:#d73a49">FC</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> i18n </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useTranslation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">changeLanguage</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">lng</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    i18n</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">changeLanguage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">lng</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript function" style="color:#d73a49">changeLanguage</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript string" style="color:#e3116c">'en'</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">English</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript function" style="color:#d73a49">changeLanguage</span><span class="token tag script language-javascript punctuation" style="color:#393A34">(</span><span class="token tag script language-javascript string" style="color:#e3116c">'zh'</span><span class="token tag script language-javascript punctuation" style="color:#393A34">)</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">中文</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">LanguageSwitcher</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/react-i18next#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://react.i18next.com/" target="_blank" rel="noopener noreferrer" class=""><strong>react-i18next documentation</strong></a></li>
<li class=""><a href="https://www.i18next.com/" target="_blank" rel="noopener noreferrer" class=""><strong>i18next documentation</strong></a></li>
<li class=""><a href="https://github.com/i18next/i18next-browser-languageDetector" target="_blank" rel="noopener noreferrer" class=""><strong>i18next-browser-languageDetector</strong></a></li>
<li class=""><a href="https://github.com/i18next/i18next-http-backend" target="_blank" rel="noopener noreferrer" class=""><strong>i18next-http-backend</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="React" term="React"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[新舊時代 JS Bundler 的世代交替 - Vite vs. Webpack 的詳細比較]]></title>
        <id>https://notes.boshkuo.com/en/blog/vite-vs-webpack</id>
        <link href="https://notes.boshkuo.com/en/blog/vite-vs-webpack"/>
        <updated>2024-08-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[透過本文的探討，我們澄清了「萬物皆物件」這一說法的真實含義。JavaScript 中的基本資料類型和物件有著本質的區別，儘管基本資料類型在某些情況下可以表現出物件的行為，但這只是因為 JavaScript 的臨時包裝機制。]]></summary>
        <content type="html"><![CDATA[<p>在過去，當我們談論到 JavaScript 前端開發環境時，很難不提到 <code>Webpack</code> 。這款在 2012 年誕生的強大工具，在過去的 10 年內一直是最主流的前端打包工具 。然而，在 2020 年一個名為 <code>Vite</code> 的新興工具迅速崛起，挑戰著 Webpack 的霸主地位。根據 2023 年 State of JavaScript 網站所統計的資料顯示，Vite 僅花了短短三年就成為使用規模第二大的 Build Tools，如果單看 Interest 或 Positivity 指標的話，甚至都穩坐第一名的位置。</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1722742737/Docusaurus%20Blog/Blog/Vite%20vs%20Webpack/state_of_js_build_tools_ufwlgt.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<p>持續關注 JS 前端生態圈的讀者一定有感受到，近兩年 Vite 人氣竄升的速度之快，在 Vite 快速竄紅的一段時間裡甚至瀰漫著「Webpack 已死」的氛圍。由於我目前的公司所使用的工具是 Webpack，而我自己開專案練習時則會選擇 Vite，因此我能明白兩者之間易用性的差距，以及為什麼現在的前端開發者越來越偏好使用 Vite。但撇除像是開箱及用、快速的 HMR 等那些 Vite 被廣為人知的優點外，我還是想要知道兩者在更深層次的技術細節上有何不同。這篇文章整理了我對 Vite 與 Webpack 兩者如何提供現代 Bundler 所需具備的功能的理解，並探討它們各自的適用場景和潛在限制。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>本篇文章內容整理自 <a href="https://kinsta.com/blog/vite-vs-webpack/" target="_blank" rel="noopener noreferrer" class="">Vite vs. Webpack: A Head-to-Head Comparison</a> 與 <a href="https://vitejs.dev/" target="_blank" rel="noopener noreferrer" class="">Vite 官方網站</a></p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="何謂-bundler"><strong>何謂 Bundler?</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E4%BD%95%E8%AC%82-bundler" class="hash-link" aria-label="Direct link to 何謂-bundler" title="Direct link to 何謂-bundler" translate="no">​</a></h2>
<p>在探討 Vite 與 Webpack 這兩個工具包山包海的的功能之前，我們得先了解這兩個工具在 Web 應用中所代表的角色：<code>Bundler</code></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="bundler-的定義與功能"><strong>Bundler 的定義與功能</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#bundler-%E7%9A%84%E5%AE%9A%E7%BE%A9%E8%88%87%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="Direct link to bundler-的定義與功能" title="Direct link to bundler-的定義與功能" translate="no">​</a></h3>
<p>JavaScript Module Bundler 是一種用於網頁開發的工具，旨在將多個 JavaScript 檔案合併成一個或少數幾個檔案，這個合併檔案稱為 <strong>bundle</strong> 。這樣的合併過程可以減少網頁應用程式所需的請求數量，從而提升效能和加快載入速度。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="實例"><strong>實例</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E5%AF%A6%E4%BE%8B" class="hash-link" aria-label="Direct link to 實例" title="Direct link to 實例" translate="no">​</a></h3>
<p>假設我們有兩個獨立的 JavaScript 檔案：module1.js 和 module2.js。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// module1.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">greet</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Hello, </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">!</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// module2.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">farewell</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">name</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Goodbye, </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">name</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">!</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>透過使用 JavaScript bundler（如 <strong>Rollup</strong>、<strong>Webpack</strong> 或 <strong>Parcel</strong>），我們可以將這些模組與一個 index.js 檔案合併：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// index.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> greet </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./module1.js'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> farewell </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./module2.js'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">greet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Kinsta'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">farewell</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Server Troubles'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>Bundler 會將 module1.js、module2.js 和 index.js 合併為一個最佳化後的 bundle，方便在網頁應用程式中使用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="優點"><strong>優點</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E5%84%AA%E9%BB%9E" class="hash-link" aria-label="Direct link to 優點" title="Direct link to 優點" translate="no">​</a></h3>
<ol>
<li class=""><strong>減少 HTTP 請求</strong>：合併多個檔案，減少請求數量。</li>
<li class=""><strong>程式碼最佳化</strong>：進行程式碼壓縮、混淆等，減少檔案大小。</li>
<li class=""><strong>跨瀏覽器相容性</strong>：解決瀏覽器特定問題，提供一致的使用者體驗。</li>
<li class=""><strong>程式碼轉換</strong>：進行必要的程式碼轉換，如轉譯（transpilation），以確保在所有目標環境中運行。</li>
</ol>
<blockquote>
<p>儘管現代瀏覽器支援 ES modules 及 HTTP/2 技術，這些技術能解決請求開銷問題，但 JavaScript bundlers 依然不可或缺，因為它們可以執行關鍵的程式碼轉換和最佳化。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="簡單認識-vite-與-webpack"><strong>簡單認識 Vite 與 Webpack</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E7%B0%A1%E5%96%AE%E8%AA%8D%E8%AD%98-vite-%E8%88%87-webpack" class="hash-link" aria-label="Direct link to 簡單認識-vite-與-webpack" title="Direct link to 簡單認識-vite-與-webpack" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite---最佳開發體驗的現代化工具"><strong>Vite - 最佳開發體驗的現代化工具</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite---%E6%9C%80%E4%BD%B3%E9%96%8B%E7%99%BC%E9%AB%94%E9%A9%97%E7%9A%84%E7%8F%BE%E4%BB%A3%E5%8C%96%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="Direct link to vite---最佳開發體驗的現代化工具" title="Direct link to vite---最佳開發體驗的現代化工具" translate="no">​</a></h3>
<p>Vite 是一個現代化的前端建構工具，旨在提供快速的開發體驗和高效的建構效能。由於它使用了原生 ES 模組和 Rollup 作為底層技術，Vite 可以在開發階段實現快速的<strong>熱更新（Hot Module Replacement，HMR）</strong>，並在生產環境中進行高效的程式碼打包。</p>
<p><strong>Vite 的特色</strong></p>
<ol>
<li class=""><strong>快速冷啟動</strong>：利用瀏覽器的原生 ES 模組支援，Vite 只需啟動必要的部分，避免了打包整個應用程式的時間。</li>
<li class=""><strong>即時模組熱更新</strong>：Vite 的 HMR 機制能夠在程式碼改變時快速更新頁面，提升開發效率。</li>
<li class=""><strong>現代化語法支援</strong>：內建支援 TypeScript、JSX 和 CSS 前置處理器（如 Sass、Less）。</li>
<li class=""><strong>輕量且易於配置</strong>：相較於傳統的打包工具，Vite 的配置更加簡單直觀，減少了學習成本。</li>
<li class=""><strong>高度擴展性</strong>：基於外掛系統，可以輕鬆擴展 Vite 的功能，以滿足不同專案需求。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webpack---全能的模組打包工具"><strong>Webpack - 全能的模組打包工具</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#webpack---%E5%85%A8%E8%83%BD%E7%9A%84%E6%A8%A1%E7%B5%84%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7" class="hash-link" aria-label="Direct link to webpack---全能的模組打包工具" title="Direct link to webpack---全能的模組打包工具" translate="no">​</a></h3>
<p>Webpack 是一個功能強大的模組打包工具，曾經是前端開發的主流選擇。它能夠處理多種資源類型（如 JavaScript、CSS、影像等），並提供豐富的配置選項和外掛系統。</p>
<p><strong>Webpack 的特色</strong></p>
<ol>
<li class=""><strong>靈活性和擴展性</strong>：Webpack 的配置文件支援多種選項和外掛，能夠處理各種複雜的應用場景。</li>
<li class=""><strong>模組化管理</strong>：支援多種模組格式（如 CommonJS、AMD、ES Modules），有效管理程式碼相依性。</li>
<li class=""><strong>強大的社群支援</strong>：擁有廣泛的使用者基礎和豐富的第三方外掛資源。</li>
<li class=""><strong>豐富的功能</strong>：提供從開發到生產環境的全面支援，包括熱模組替換、程式碼壓縮、程式碼分割(code split)、懶載入(lazy loading)和混淆等。</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="架構與設計哲學"><strong>架構與設計哲學</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E6%9E%B6%E6%A7%8B%E8%88%87%E8%A8%AD%E8%A8%88%E5%93%B2%E5%AD%B8" class="hash-link" aria-label="Direct link to 架構與設計哲學" title="Direct link to 架構與設計哲學" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-的設計哲學"><strong>Vite 的設計哲學</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite-%E7%9A%84%E8%A8%AD%E8%A8%88%E5%93%B2%E5%AD%B8" class="hash-link" aria-label="Direct link to vite-的設計哲學" title="Direct link to vite-的設計哲學" translate="no">​</a></h3>
<p>Vite 的設計哲學圍繞輕量化和可擴展性，強調簡潔和長期的專案可維護性。Vite 的核心架構分為開發階段和生產建構階段，透過利用現代瀏覽器的原生對 ES 模組的支持和快速的模組熱更新（HMR）機制，提升開發效率。</p>
<ol>
<li class="">
<p><strong>開發階段</strong>：Vite 利用瀏覽器的原生 ES 模組支援，只需啟動必要部分，降低整個應用程式的冷啟動時間。當程式碼改變時，Vite 只重新編譯改動部分，並通過模組熱更新（HMR）快速反映在瀏覽器中，極大提升開發效率。</p>
</li>
<li class="">
<p><strong>生產建構階段</strong>：在建構生產環境程式碼時，Vite 使用 Rollup 進行打包。Rollup 擅長生成高效的程式碼，並提供豐富的外掛系統，可以對程式碼進行各種最佳化，如：程式碼分割、壓縮、混淆等。</p>
</li>
<li class="">
<p><strong>外掛系統</strong>：Vite 的外掛系統基於 Rollup，擴展性強。開發者可以輕鬆地新增或自訂外掛，以滿足特定的開發需求，防止核心臃腫。Vite 積極與 Rollup 專案合作，保持相容性和共享外掛生態系統。</p>
</li>
<li class="">
<p><strong>設計實踐</strong>：Vite 的設計哲學體現在它對現代 JavaScript 特性的支援上，如 ES 模組和 Worker 語法。例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// app.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> greet </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./utilities.js'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> worker </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Worker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./worker.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">meta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">url</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">input</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">42</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access" style="color:#d73a49">onmessage</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">result</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Result from the web worker: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">result</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> message </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">greet</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Hello, Vite!'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">message</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>這段程式碼展示了 Vite 如何無縫支援 ES 模組和 Worker 語法。在這個例子中，我們使用 ES 模組語法從 utilities.js 模組中匯入 greet 函數，並且通過 new URL('./worker.js', import.meta.url) 建立了一個新的 Web Worker，這在 Vite 中得到原生支援而不需要額外配置。這樣的設計大大簡化了開發過程，使得開發者可以更專注於功能實現而非工具配置。</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webpack-的設計哲學"><strong>Webpack 的設計哲學</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#webpack-%E7%9A%84%E8%A8%AD%E8%A8%88%E5%93%B2%E5%AD%B8" class="hash-link" aria-label="Direct link to webpack-的設計哲學" title="Direct link to webpack-的設計哲學" translate="no">​</a></h3>
<p>Webpack 的設計哲學是以模組化為核心，通過強大的配置和外掛系統，實現高度靈活的打包和資源管理。Webpack 被設計為一個功能齊全的模組打包工具，適用於各種複雜的應用場景。</p>
<ol>
<li class="">
<p><strong>模組化管理</strong>：Webpack 支援多種模組格式（如 ES2015 的 import 語句、CommonJS 的 require 語句、AMD 的 define 和 require 語句，以及 CSS/Sass/Less 文件中的 @import 語句等），通過 Loader 將它們轉換為可以在 JavaScript 中引用的模組。這種模組化的處理方式類似於使用樂高積木搭建專案，提升程式碼的可讀性和維護性。</p>
</li>
<li class="">
<p><strong>配置靈活</strong>：Webpack 提供了高度可配置的選項，開發者可以通過配置文件詳細定義打包過程中的各種行為，如入口文件、輸出文件、模組解析規則等。</p>
</li>
<li class="">
<p><strong>外掛系統</strong>：Webpack 擁有豐富的外掛系統，幾乎所有的打包過程都可以通過外掛進行擴展和自訂，這使得 Webpack 能夠滿足各種需求，保持靈活性和可擴展性。</p>
</li>
<li class="">
<p><strong>程式碼最佳化</strong>：Webpack 提供了多種程式碼最佳化技術，如程式碼分割、Tree Shaking、程式碼壓縮和混淆，這些技術有助於生成高效的生產環境程式碼。</p>
</li>
<li class="">
<p><strong>設計實踐</strong>：Webpack 的模組化設計特別適合大型專案。透過配置和使用 loader，開發者可以將專案組織成模組，提高可讀性和維護性。例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./app.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'bundle.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dist'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">js</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'babel-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">exclude</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex" style="color:#36acaa">node_modules</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>這個例子展示了 Webpack 如何配置建構過程，最佳化程式碼並高效處理資源，通過使用 Babel Loader 寫出清晰、模組化的程式碼，提高使用者體驗。</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-與-webpack-的共通點"><strong>Vite 與 Webpack 的共通點</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite-%E8%88%87-webpack-%E7%9A%84%E5%85%B1%E9%80%9A%E9%BB%9E" class="hash-link" aria-label="Direct link to vite-與-webpack-的共通點" title="Direct link to vite-與-webpack-的共通點" translate="no">​</a></h3>
<p>儘管 Vite 和 Webpack 在設計哲學上有所不同，也存在一些共通點：</p>
<ul>
<li class="">
<p><strong>模組化設計：</strong> 兩者都採用模組化設計哲學，鼓勵將應用程式拆分成小的、可重用的模組。這種設計方式有助於提高程式碼的可維護性和可擴展性。</p>
</li>
<li class="">
<p><strong>外掛驅動架構：</strong> Vite 和 Webpack 都使用外掛驅動的架構。這種架構允許開發者根據需求擴展工具的功能，而不需要改變其核心程式碼。外掛機制提供了極大的靈活性，讓開發者可以自訂打包流程。</p>
</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="配置和易用性"><strong>配置和易用性</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E9%85%8D%E7%BD%AE%E5%92%8C%E6%98%93%E7%94%A8%E6%80%A7" class="hash-link" aria-label="Direct link to 配置和易用性" title="Direct link to 配置和易用性" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="精簡的-vite-配置"><strong>精簡的 Vite 配置</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E7%B2%BE%E7%B0%A1%E7%9A%84-vite-%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 精簡的-vite-配置" title="Direct link to 精簡的-vite-配置" translate="no">​</a></h3>
<p>Vite 的設計目標之一是提供簡單易用的配置，使開發者能夠快速上手並專注於開發工作。Vite 通常需要極少的配置或幾乎不需要配置，這是其「開箱即用」的關鍵所在。</p>
<ol>
<li class="">
<p><strong>默認配置即用</strong>：Vite 提供了合理的默認配置，涵蓋了大多數常見的開發需求。開發者無需進行大量配置即可啟動和運行項目。</p>
</li>
<li class="">
<p><strong>簡單的配置文件</strong>：如果需要自定義配置，可以通過在項目根目錄下創建 <code>vite.config.js</code> 文件來完成。配置文件結構簡單直觀，例如：在上述例子中，我們僅導入並安裝了 Vite 的 Vue.js 官方插件。Vite 的優勢在於其自動檢測大多數項目的正確設置，減少配置的麻煩。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// vite.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> defineConfig </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vite'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">vue</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@vitejs/plugin-vue'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defineConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token function" style="color:#d73a49">vue</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>插件系統</strong>：Vite 的插件系統基於 Rollup，使得配置和擴展變得簡單。只需安裝並引用相應的插件，即可輕鬆添加所需功能。</p>
</li>
<li class="">
<p><strong>開發伺服器配置</strong>：Vite 的開發伺服器配置也非常簡單，可以在 <code>vite.config.js</code> 中進行設置，如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defineConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">server</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">port</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="複雜的-webpack-配置"><strong>複雜的 Webpack 配置</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E8%A4%87%E9%9B%9C%E7%9A%84-webpack-%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 複雜的-webpack-配置" title="Direct link to 複雜的-webpack-配置" translate="no">​</a></h3>
<p>相比之下，Webpack 的配置更為複雜和靈活，這是由於其強大的功能和廣泛的應用場景所決定的。雖然 Webpack 在最近的版本中也朝向零配置方向發展，但仍然不如 Vite 自動化。Webpack 可以處理各種不同的需求，但相應的配置也變得更加繁瑣和詳細。</p>
<ol>
<li class="">
<p><strong>詳細的配置文件</strong>：Webpack 的配置文件結構複雜，涵蓋了入口點、輸出、模組解析規則、插件等多個方面。例如：這段配置文件展示了 Webpack 如何配置構建過程，優化代碼並高效處理資源。相比 Vite，Webpack 的配置涉及更多手動設置，包括指定入口和輸出路徑、配置 Loader 處理不同類型的文件、設置插件以實現特定功能。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> webpack </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'webpack'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">HotModuleReplacementPlugin</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'webpack'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">VueLoaderPlugin</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'vue-loader'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/main.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./build'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'bundle.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">js</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">exclude</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">(</span><span class="token regex regex-source language-regex" style="color:#36acaa">node_modules</span><span class="token regex regex-source language-regex alternation keyword" style="color:#00009f">|</span><span class="token regex regex-source language-regex" style="color:#36acaa">bower_components</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">)</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token literal-property property" style="color:#36acaa">loader</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'babel-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token literal-property property" style="color:#36acaa">options</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token literal-property property" style="color:#36acaa">presets</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'@babel/preset-env'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">vue</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token literal-property property" style="color:#36acaa">loader</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">css</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'vue-style-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'css-loader'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">resolve</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">alias</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token literal-property property" style="color:#36acaa">vue</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue/dist/vue.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">HotModuleReplacementPlugin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">VueLoaderPlugin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>豐富的插件和 Loader</strong>：Webpack 擁有大量的插件和 Loader，可以滿足各種需求，但這也增加了配置的複雜性。開發者需要根據具體需求選擇和配置合適的插件和 Loader。</p>
</li>
<li class="">
<p><strong>靈活的自定義</strong>：Webpack 允許開發者自定義構建流程的各個方面，這意味著開發者需要對配置有深入的理解，以充分利用其強大的功能。</p>
</li>
<li class="">
<p><strong>多配置文件</strong>：對於大型項目，通常需要多個配置文件來處理開發、測試和生產環境，這進一步增加了配置的複雜性。</p>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="開發端伺服器"><strong>開發端伺服器</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E9%96%8B%E7%99%BC%E7%AB%AF%E4%BC%BA%E6%9C%8D%E5%99%A8" class="hash-link" aria-label="Direct link to 開發端伺服器" title="Direct link to 開發端伺服器" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="伺服器配置"><strong>伺服器配置</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E4%BC%BA%E6%9C%8D%E5%99%A8%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 伺服器配置" title="Direct link to 伺服器配置" translate="no">​</a></h3>
<p>Vite 和 Webpack 都提供了強大的開發伺服器功能，但在配置和使用上有所不同，對開發效率和生產力有直接影響。</p>
<ol>
<li class="">
<p><strong>Vite 的伺服器配置</strong>：Vite 的開發伺服器是內建的，開箱即用，通常不需要額外配置。只需在 <code>vite.config.js</code> 中進行少量設定即可。例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// vite.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> defineConfig </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vite'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">vue</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@vitejs/plugin-vue'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defineConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token function" style="color:#d73a49">vue</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">server</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">port</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">open</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 啟動伺服器時自動打開瀏覽器</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">proxy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">'/api'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'http://localhost:5000'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>Webpack 的伺服器配置</strong>：Webpack 提供了多種選項，如 <code>webpack-dev-server</code> 和 <code>webpack-dev-middleware</code>，需要額外設定才能自動編譯程式碼並處理變更。配置相對複雜，例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> webpack </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'webpack'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">HotModuleReplacementPlugin</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'webpack'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">VueLoaderPlugin</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'vue-loader'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/main.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./build'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'bundle.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">js</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">exclude</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">(</span><span class="token regex regex-source language-regex" style="color:#36acaa">node_modules</span><span class="token regex regex-source language-regex alternation keyword" style="color:#00009f">|</span><span class="token regex regex-source language-regex" style="color:#36acaa">bower_components</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">)</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token literal-property property" style="color:#36acaa">loader</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'babel-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token literal-property property" style="color:#36acaa">options</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                        </span><span class="token literal-property property" style="color:#36acaa">presets</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'@babel/preset-env'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">vue</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token literal-property property" style="color:#36acaa">loader</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">css</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'vue-style-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'css-loader'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">resolve</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">alias</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token literal-property property" style="color:#36acaa">vue</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue/dist/vue.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">HotModuleReplacementPlugin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">VueLoaderPlugin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">devServer</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">contentBase</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">join</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'public'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">port</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3000</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">hot</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">open</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">proxy</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token string-property property" style="color:#36acaa">'/api'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'http://localhost:5000'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="冷啟動速度"><strong>冷啟動速度</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E5%86%B7%E5%95%9F%E5%8B%95%E9%80%9F%E5%BA%A6" class="hash-link" aria-label="Direct link to 冷啟動速度" title="Direct link to 冷啟動速度" translate="no">​</a></h3>
<ol>
<li class=""><strong>Vite 的冷啟動速度</strong>：
Vite 通過使用 esbuild（基於 Go 的高性能打包工具）來預打包依賴項，大幅減少初始化時間。Vite 的設計利用原生 ES 模組，只在瀏覽器請求時進行程式碼轉換和服務，從而顯著提高了伺服器啟動速度。<!-- -->
<ul>
<li class=""><strong>高效的依賴處理</strong>：Vite 預打包依賴項，例如將 <code>lodash-es</code> 的 600 多個內部模組合併為一個模組，減少 HTTP 請求數量，提升開發伺服器性能。</li>
<li class=""><strong>按需載入</strong>：Vite 使用原生 ES 模組按需載入原始碼，最小化伺服器負載和延遲。</li>
</ul>
</li>
<li class=""><strong>Webpack 的冷啟動速度</strong>：
Webpack 傾向於在啟動時預打包原始碼和依賴項，這種捆綁方法在開發過程中延長了伺服器啟動時間。相比之下，Webpack 的伺服器設定時間通常比 Vite 更長。<!-- -->
<ul>
<li class=""><strong>bundle 方法</strong>：Webpack 的 bundle 方法確保所有站點資料都可用，使開發伺服器中的頁面導航速度更快，但初始化時間較長。</li>
</ul>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hmr-hot-module-replacement"><strong>HMR (Hot Module Replacement)</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#hmr-hot-module-replacement" class="hash-link" aria-label="Direct link to hmr-hot-module-replacement" title="Direct link to hmr-hot-module-replacement" translate="no">​</a></h3>
<ol>
<li class=""><strong>Vite 的 HMR</strong>：Vite 使用原生 ESM 實現 HMR，通過將部分捆綁工作轉移到瀏覽器，減少伺服器負載和延遲。這確保了快速更新而無需整頁重新載入，對開發過程中的即時反饋至關重要。</li>
<li class=""><strong>Webpack 的 HMR</strong>：Webpack 也支援 HMR，實現即時更新並保留開發過程中的應用狀態。與 Vite 不同，Webpack 需要在配置文件中啟用相關外掛和設定。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="快取效能"><strong>快取效能</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E5%BF%AB%E5%8F%96%E6%95%88%E8%83%BD" class="hash-link" aria-label="Direct link to 快取效能" title="Direct link to 快取效能" translate="no">​</a></h3>
<p><strong>1. Vite 的快取效能：</strong></p>
<ul>
<li class=""><strong>瀏覽器原生快取</strong>：Vite 利用瀏覽器對 ES 模組的原生支援，通過 HTTP 提供模組，瀏覽器能夠自行快取這些模組，避免了重複加載，提高了開發環境中的響應速度。</li>
<li class=""><strong>依賴預構建快取</strong>：Vite 會在第一次啟動時預構建並快取第三方依賴（如 npm 包），這些依賴會被儲存在本地快取中，隨後的啟動只需要重用這些預構建的快取，減少了重新解析和打包的時間。</li>
<li class=""><strong>模組熱替換 (HMR) 快取</strong>：在開發過程中，Vite 的 HMR 只會更新實際變動的模組，而不會重新加載整個應用程式。這種精細的快取策略確保了更快的開發迭代速度。</li>
</ul>
<p><strong>2. Webpack 的快取效能：</strong></p>
<ul>
<li class=""><strong>持久快取</strong>：Webpack 支持持久化快取（Persistent Caching），可以將編譯過程中生成的模組和資產緩存在磁碟中。在隨後的構建中，Webpack 可以重用這些快取，顯著減少了編譯時間。</li>
<li class=""><strong>模組快取 (Module Caching)</strong>：Webpack 在開發過程中會將已編譯的模組快取起來，當模組未發生變化時，會直接使用快取的結果，而不是重新編譯，從而提高建構速度。</li>
<li class=""><strong>DLL 外掛 (DLL Plugin)</strong>：Webpack 提供了 DLL 外掛，用於提前打包和快取不常變化的第三方庫。這些庫可以在開發和建構過程中重複使用，減少重複編譯的開銷。</li>
<li class=""><strong>最佳化輸出快取 (Output Caching)</strong>：Webpack 可以根據文件內容生成文件名的 hash 值，從而實現瀏覽器層面的快取。當文件內容未變化時，文件名不變，瀏覽器可以直接使用快取的文件，提升使用者的載入體驗。</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="最佳化打包流程"><strong>最佳化打包流程</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E6%9C%80%E4%BD%B3%E5%8C%96%E6%89%93%E5%8C%85%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="Direct link to 最佳化打包流程" title="Direct link to 最佳化打包流程" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="預載指令生成"><strong>預載指令生成</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E9%A0%90%E8%BC%89%E6%8C%87%E4%BB%A4%E7%94%9F%E6%88%90" class="hash-link" aria-label="Direct link to 預載指令生成" title="Direct link to 預載指令生成" translate="no">​</a></h3>
<p>預載指令（Preload Directives）是用於告訴瀏覽器哪些資源應該優先載入的技術，以加速網頁的首次渲染。</p>
<ol>
<li class="">
<p><strong>Vite 的預載指令生成</strong>：
Vite 自動在構建的 HTML 中為入口塊(entry chunks)生成 <code>&lt;link rel="modulepreload"&gt;</code> 指令，並直接匯入他們，從而提高載入速度。例如：</p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Vite - Module Preloading --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">link</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">rel</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">modulepreload</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/module-a.js</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>Webpack 的預載指令生成</strong>：
Webpack 最初並不原生支持資源提示（Resource Hints），但從 v4.6.0 開始，它增加了對預加載（Prefetching）和預載（Preloading）的支持。通過在導入模組時使用內嵌指令，Webpack 可以輸出資源提示，告訴瀏覽器何時加載要導入的模組文件。例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token comment" style="color:#999988;font-style:italic">/* webpackPreload: true */</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/module-a.js'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>這會輸出：</p>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- Webpack - Manual Module Preloading --&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">link</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">rel</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">preload</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">as</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">script</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">href</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/module-a.js</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="css-code-splitting"><strong>CSS Code Splitting</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#css-code-splitting" class="hash-link" aria-label="Direct link to css-code-splitting" title="Direct link to css-code-splitting" translate="no">​</a></h3>
<p>CSS Code Splitting 是將 CSS 程式碼拆分為多個文件，以便按需載入，提高頁面載入性能。</p>
<ol>
<li class="">
<p><strong>Vite 的 CSS Code Splitting</strong>：</p>
<p>Vite 能夠自動提取異步塊(async chunks)中的 CSS 並生成單獨的文件，這意味著當加載相關的異步塊時，只有必要的 CSS 會通過 <code>&lt;link&gt;</code> 標籤被加載。此外， Vite 會確保在 CSS 加載完成後才開始解析異步塊避免了 Flash of Unstyled Content（FOUC）的問題。</p>
<p>由於這些功能是 Vite 預先配置的，我們只需像平常一樣導入 CSS 文件，Vite 就會自動處理 CSS 的拆分和加載，不需要額外的配置。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./main.css'</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>Webpack 的 CSS Code Splitting</strong>：
Webpack 提供了更多的靈活性，但需要更多的配置來實現 CSS Code Splitting。開發者可以使用多種插件和配置選項來拆分 CSS，比如 <code>mini-css-extract-plugin</code>。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">MiniCssExtractPlugin</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'mini-css-extract-plugin'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...其他配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">css</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token maybe-class-name">MiniCssExtractPlugin</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">loader</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'css-loader'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">MiniCssExtractPlugin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'[name].css'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">chunkFilename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'[id].css'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-splitting-and-chunks-loading"><strong>Code Splitting and Chunks Loading</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#code-splitting-and-chunks-loading" class="hash-link" aria-label="Direct link to code-splitting-and-chunks-loading" title="Direct link to code-splitting-and-chunks-loading" translate="no">​</a></h3>
<p>Code splitting 是一種將程式碼拆分為較小的、可管理的塊（chunks）並按需載入的技術。這種方法顯著減少了初始載入時間，節省了資源，提高了應用程式的性能。</p>
<ol>
<li class="">
<p><strong>Vite 的 Code Splitting</strong>：</p>
<p>Vite 使用 Rollup 進行程式碼拆分，在動態載入或多入口點的情況下，會自動將程式碼拆分為多個塊（chunks）。這有助於減少初次載入時間，提升應用的效能和使用者體驗。</p>
<p><strong>自動程式碼拆分</strong></p>
<p>Vite 通過分析模組之間的依賴關係，自動將應用程式拆分為多個塊。這些塊在應用執行階段會按需載入，避免了整個應用一次性載入帶來的性能問題。</p>
<p><strong>手動配置拆分</strong></p>
<p>你可以通過 Rollup 的 <code>output.manualChunks</code> 選項顯式指定哪些模組應該拆分為單獨的塊。例如，將第三方庫拆分到一個單獨的 vendor 塊中：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// vite.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defineConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">build</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rollupOptions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">manualChunks</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token literal-property property" style="color:#36acaa">vendor</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'vue'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue-router'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>動態匯入</strong></p>
<p>Vite 支援動態匯入 (Dynamic Import)，允許在執行階段按需載入模組。這樣可以進一步減少初次載入時間，提高應用的響應速度。使用動態匯入時，可以通過變數來進行模組載入：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> module </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">./dir/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">moduleName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">.js</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>拆分 Vendor Chunk</strong></p>
<p>Vite 提供了官方的 <code>splitVendorChunkPlugin</code> 外掛，用於自動拆分第三方庫，將其放入單獨的 vendor chunk 中，從而最佳化載入性能：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> splitVendorChunkPlugin </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vite'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defineConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token function" style="color:#d73a49">splitVendorChunkPlugin</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>Webpack 的 Code Splitting</strong>：
Webpack 提供了多種程式碼拆分技術，包括入口點、SplitChunks 外掛和動態匯入。</p>
<p><strong>入口點</strong>：通過定義多個入口點來拆分程式碼，適合簡單的應用場景。然而，這種方法有其限制。如果一模組在不同的入口區塊中導入，它們最終會出現在兩個塊（chunk）中，從而導致重複的程式碼出現在多個塊（chunks）中 。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">mode</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'development'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">index</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/index.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">another</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/separate-module.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'[name].bundle.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dist'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>防止重複</strong>：使用 SplitChunks 外掛或入口依賴來減少冗餘，確保共享的模組只載入一次。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">mode</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'development'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">index</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword module" style="color:#00009f">import</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/index.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">dependOn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'shared'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">another</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword module" style="color:#00009f">import</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/another-module.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">dependOn</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'shared'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">shared</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'lodash'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'[name].bundle.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dist'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">optimization</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">runtimeChunk</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'single'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p><strong>動態匯入</strong>：動態匯入支援按需載入程式碼，並允許使用 Webpack 的魔法註釋來設定塊名、懶載入（lazy-load）和設定載入優先順序。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> _ </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'lodash'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* webpackChunkName: "my-chunk-name" */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* webpackMode: "lazy" */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* webpackExports: ["default", "named"] */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">/* webpackFetchPriority: "high" */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string" style="color:#e3116c">'module'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tree-shaking"><strong>Tree-Shaking</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#tree-shaking" class="hash-link" aria-label="Direct link to tree-shaking" title="Direct link to tree-shaking" translate="no">​</a></h3>
<p>Tree-Shaking 是移除未使用程式碼的技術，從而減少打包後文件的大小，提高載入性能。</p>
<ol>
<li class="">
<p><strong>Vite 的 Tree-Shaking</strong>：</p>
<p>Vite 使用 Rollup 作為其打包工具，Rollup 以其優秀的 Tree-shaking 能力而著稱。Rollup 在打包過程中會分析模組的依賴關係和引用情況，移除未使用的程式碼，僅保留應用實際需要的部分。</p>
<ol>
<li class=""><strong>基於 ES 模組</strong>：Vite 和 Rollup 都使用 ES 模組標準，這種標準原生支援靜態分析，能夠精確地識別並移除未使用的程式碼。</li>
<li class=""><strong>無需配置</strong>：Vite 的 Tree-shaking 是開箱即用的，開發者不需要進行額外配置，Rollup 會自動處理這些最佳化。</li>
</ol>
</li>
<li class="">
<p><strong>Webpack 的 Tree-Shaking</strong>：</p>
<p>Webpack 同樣支援 Tree-shaking，但需要依賴於一些配置和條件才能充分發揮其效用。</p>
<ol>
<li class="">
<p><strong>基於 ES 模組</strong>：與 Vite 一樣，Webpack 的 Tree-shaking 也依賴於 ES 模組語法。使用 ES 模組的程式碼可以被靜態分析，從而識別未使用的部分。</p>
</li>
<li class="">
<p><strong>配置要求</strong>：</p>
<ul>
<li class=""><strong>mode 設定為 production：</strong> Webpack 在生產模式下會自動啟用 Tree-shaking。確保在配置文件中設定 <code>mode: 'production'</code>。</li>
<li class=""><strong>sideEffects 設定：</strong> 需要在 <code>package.json</code> 或 Webpack 配置文件中設定 <code>sideEffects</code> 屬性來告訴 Webpack 哪些文件沒有副作用，可以安全地進行 Tree-shaking。</li>
</ul>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// package.json</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"your-project"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"sideEffects"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>或配置文件設置：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">mode</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'production'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 其他配置</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">optimization</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">usedExports</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// 其他規則</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">js</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">sideEffects</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="靜態檔案處理"><strong>靜態檔案處理</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#%E9%9D%9C%E6%85%8B%E6%AA%94%E6%A1%88%E8%99%95%E7%90%86" class="hash-link" aria-label="Direct link to 靜態檔案處理" title="Direct link to 靜態檔案處理" translate="no">​</a></h2>
<p>靜態資源如圖像、字體和其他文件是網頁開發中不可或缺的一部分。Vite 和 Webpack 在處理這些資源時採用了不同的方法，各有優勢和優化方式。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-的靜態檔案處理方式"><strong>Vite 的靜態檔案處理方式</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite-%E7%9A%84%E9%9D%9C%E6%85%8B%E6%AA%94%E6%A1%88%E8%99%95%E7%90%86%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to vite-的靜態檔案處理方式" title="Direct link to vite-的靜態檔案處理方式" translate="no">​</a></h3>
<p>Vite 的靜態資源處理方式簡單高效。在開發過程中，當你導入靜態資源時，Vite 會返回解析後的公共 URL。例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">kinstaImage</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./kinsta-image.png'</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>在開發過程中，<code>kinstaImage</code> 會解析為 <code>/img.png</code>。在生產環境中，URL 會變成類似 <code>/assets/img.2d8efhg.png</code> 的形式，以最佳化快取和性能。</p>
<ol>
<li class=""><strong>靜態資源目錄</strong>：
靜態資源可以放置在 <code>public</code> 目錄下，這些資源會在構建過程中自動拷貝到輸出目錄。資源可以通過絕對公共路徑或相對路徑引用。這種行為也延伸到 CSS 中的 URL 引用</li>
<li class=""><strong>資源類型處理</strong>：
Vite 能夠檢測常見的圖像、媒體和字體文件類型，並將它們作為資產處理。這些資源在構建過程中會被包含在資產圖（build assets graph）中，並生成帶有雜湊的文件名，以進行優化處理。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webpack-的靜態檔案處理方式"><strong>Webpack 的靜態檔案處理方式</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#webpack-%E7%9A%84%E9%9D%9C%E6%85%8B%E6%AA%94%E6%A1%88%E8%99%95%E7%90%86%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to webpack-的靜態檔案處理方式" title="Direct link to webpack-的靜態檔案處理方式" translate="no">​</a></h3>
<p>Webpack 在處理靜態資源時採用了不同的方法，提供了靈活的配置選項。通過 loader 和插件，Webpack 可以高效地處理和優化靜態資源。</p>
<ol>
<li class="">
<p><strong>資源導入</strong>：
和 Vite 一樣，Webpack 也支持通過導入靜態資源來進行處理：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">kinstaImage</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./kinsta-image.png'</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>Webpack 會將該圖像添加到輸出目錄，並為你提供最終的圖像 URL。</p>
</li>
<li class="">
<p><strong>資源模塊</strong>：
Webpack 5 引入了資產模塊，可以處理各種類型的資源，不僅限於圖像。例如，可以配置 Webpack 來處理字體文件：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">(</span><span class="token regex regex-source language-regex" style="color:#36acaa">woff</span><span class="token regex regex-source language-regex alternation keyword" style="color:#00009f">|</span><span class="token regex regex-source language-regex" style="color:#36acaa">woff2</span><span class="token regex regex-source language-regex alternation keyword" style="color:#00009f">|</span><span class="token regex regex-source language-regex" style="color:#36acaa">eot</span><span class="token regex regex-source language-regex alternation keyword" style="color:#00009f">|</span><span class="token regex regex-source language-regex" style="color:#36acaa">ttf</span><span class="token regex regex-source language-regex alternation keyword" style="color:#00009f">|</span><span class="token regex regex-source language-regex" style="color:#36acaa">otf</span><span class="token regex regex-source language-regex group punctuation" style="color:#393A34">)</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'asset/resource'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>這樣的配置允許你通過 <code>@font-face</code> 聲明將字體文件整合到項目中。</p>
</li>
<li class="">
<p><strong>CSS 和 HTML 處理</strong>：
使用 <code>css-loader</code> 和 <code>html-loader</code>，Webpack 能夠在處理 CSS 和 HTML 文件時自動解析和替換資源 URL，確保輸出目錄中的路徑正確。</p>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="json-支援"><strong>JSON 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#json-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to json-支援" title="Direct link to json-支援" translate="no">​</a></h2>
<p>Vite 和 Webpack 都提供了對 JSON 文件的良好支援，兩者都能自動解析 JSON 文件並將其轉換為 JavaScript 物件，讓開發者可以輕鬆地在專案中使用 JSON 資料。此外，還支持 JSON 命名導入，這有助於進行 Tree-Shaking。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// import an object</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">json</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./example.json'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// import a root field as named exports.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> test </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./example.json'</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="typescript-支援"><strong>TypeScript 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#typescript-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to typescript-支援" title="Direct link to typescript-支援" translate="no">​</a></h2>
<p>TypeScript 是 JavaScript 的超集，增加了靜態類型支援，能夠提升程式碼的可維護性和開發效率。Vite 和 Webpack 都提供了對 TypeScript 的良好支援</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-的-typescript-支援"><strong>Vite 的 TypeScript 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite-%E7%9A%84-typescript-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to vite-的-typescript-支援" title="Direct link to vite-的-typescript-支援" translate="no">​</a></h3>
<p>Vite 提供了開箱即用的 TypeScript 支援，無需額外的配置即可使用 TypeScript 進行開發。Vite 使用 <code>esbuild</code> 作為默認的 TypeScript 編譯器，這使得 TypeScript 文件的編譯速度非常快。然而，Vite 主要關注於程式碼的轉譯（transpilation），而不是類型檢查。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webpack-的-typescript-支援"><strong>Webpack 的 TypeScript 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#webpack-%E7%9A%84-typescript-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to webpack-的-typescript-支援" title="Direct link to webpack-的-typescript-支援" translate="no">​</a></h3>
<p>Webpack 對 TypeScript 的支援需要通過 <code>ts-loader</code> 或 <code>babel-loader</code> 來進行配置。這使得 Webpack 能夠編譯和處理 TypeScript 文件。雖然這需要額外的配置步驟，但它提供了靈活性和與其他 Webpack 特性的相容性。</p>
<ol>
<li class="">
<p><strong>使用 ts-loader</strong>：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/index.ts'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">ts</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ts-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">exclude</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex" style="color:#36acaa">node_modules</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">resolve</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">extensions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'.ts'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'.js'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'bundle.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dist'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>使用 babel-loader</strong>：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/index.ts'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">ts</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">exclude</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex" style="color:#36acaa">node_modules</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token literal-property property" style="color:#36acaa">loader</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'babel-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token literal-property property" style="color:#36acaa">options</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token literal-property property" style="color:#36acaa">presets</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token string" style="color:#e3116c">'@babel/preset-env'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token string" style="color:#e3116c">'@babel/preset-typescript'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">resolve</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">extensions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'.ts'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'.js'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'bundle.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dist'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="glob-import-支援"><strong>Glob Import 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#glob-import-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to glob-import-支援" title="Direct link to glob-import-支援" translate="no">​</a></h2>
<p>Glob Import 是一種強大的功能，允許開發者使用通配符模式來導入多個模組。Vite 和 Webpack 都提供了對 Glob Import 的支持，但它們的實現方式有所不同。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-的-glob-import-支援"><strong>Vite 的 Glob Import 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite-%E7%9A%84-glob-import-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to vite-的-glob-import-支援" title="Direct link to vite-的-glob-import-支援" translate="no">​</a></h3>
<p>Vite 通過內建的 <code>import.meta.glob</code> 方法提供對 Glob Import 的支持</p>
<ol>
<li class="">
<p><strong>使用 Glob Import</strong>：
使用 Vite 的 <code>import.meta.glob</code> 來導入多個模組：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> modules </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">meta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">glob</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./kinsta/*.js'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 這會輸出如下結構：</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> modules </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">'./kinsta/isCool.js'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./kinsta/isCool.js'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">'./kinsta/isAwesome.js'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./kinsta/isAwesome.js'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">'./kinsta/isFun.js'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./kinsta/isFun.js'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 動態導入模組</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword control-flow" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> modules</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  modules</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">path</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">module</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> module</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>Glob Import As 支持</strong>：
Vite 支持將文件作為字符串導入，使用 <code>import.meta.glob</code> 的 <code>as</code> 選項：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> modules </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">meta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">glob</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./kinsta/*.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">as</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'raw'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">eager</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 這會被轉換成如下結構：</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> modules </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">'./kinsta/rocks.js'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'export default "rocks"\n'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">'./kinsta/rules.js'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'export default "rules"\n'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>同樣，Vite 也支持 <code>{ as: 'url' }</code> 選項來將 assets 作為 URL 加載。</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webpack-的-glob-import-支援"><strong>Webpack 的 Glob Import 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#webpack-%E7%9A%84-glob-import-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to webpack-的-glob-import-支援" title="Direct link to webpack-的-glob-import-支援" translate="no">​</a></h3>
<p>Webpack 通過插件來實現 Glob Import，例如 <code>webpack-import-glob-loader</code> 和 <code>glob-import-loader</code>。</p>
<ol>
<li class="">
<p><strong>使用插件進行 Glob Import</strong>：
在 Webpack 配置文件中配置這些插件來實現 Glob Import：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">js</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">loader</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'webpack-import-glob-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>導入多個模組</strong>：
使用插件後，可以使用通配符模式導入多個模組：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// src/index.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports operator" style="color:#393A34">*</span><span class="token imports"> </span><span class="token imports keyword module" style="color:#00009f">as</span><span class="token imports"> modules</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./modules/**/*.js'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">modules</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>假設 <code>./modules</code> 目錄結構如下：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">modules/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── module1.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── module2.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└── subfolder/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    └── module3.js</span><br></div></code></pre></div></div>
<p>以上導入會將 <code>modules/module1.js</code>、<code>modules/module2.js</code> 和 <code>modules/subfolder/module3.js</code> 這三個模組都導入到 <code>modules</code> 物件中。</p>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="web-workers-支援"><strong>Web Workers 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#web-workers-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to web-workers-支援" title="Direct link to web-workers-支援" translate="no">​</a></h2>
<p>Web Workers 是一種強大的技術，允許在背景執行緒中執行 JavaScript，從而避免阻塞主執行緒，提升應用的性能和響應速度。Vite 和 Webpack 都提供了對 Web Workers 的良好支援。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-的-web-workers-支援"><strong>Vite 的 Web Workers 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite-%E7%9A%84-web-workers-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to vite-的-web-workers-支援" title="Direct link to vite-的-web-workers-支援" translate="no">​</a></h3>
<p>Vite 提供了開箱即用的 Web Workers 支援，無需額外配置即可使用。Vite 提供了兩種方式來匯入 Web Worker 文件。</p>
<ol>
<li class="">
<p><strong>建立 Web Worker</strong>：
建立一個 Web Worker 文件，例如 <code>worker.js</code>：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// worker.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>使用 Worker 建構函式</strong>：
使用 <code>new Worker()</code> 或 <code>new SharedWorker()</code> 建構函式來建立 Web Worker：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 使用 Worker</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> worker </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Worker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./worker.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">meta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">url</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 使用 SharedWorker</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> sharedWorker </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">SharedWorker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./worker.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">meta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">url</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>使用 ?worker 或 ?sharedworker 後綴</strong>：
直接匯入 Web Worker 文件，並在文件路徑後新增 <code>?worker</code> 或 <code>?sharedworker</code> 後綴：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">MyWorker</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./worker?worker'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> worker </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">MyWorker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Hello from the main thread!'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webpack-的-web-workers-支援"><strong>Webpack 的 Web Workers 支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#webpack-%E7%9A%84-web-workers-%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to webpack-的-web-workers-支援" title="Direct link to webpack-的-web-workers-支援" translate="no">​</a></h3>
<p>Webpack 從 5.0 版本開始，對 Web Workers 提供了原生支援，不再需要使用特定的 loader。這使得在 Webpack 項目中使用 Web Workers 變得更加方便。</p>
<ol>
<li class="">
<p><strong>建立 Web Worker</strong>：
建立一個 Web Worker 文件，例如 <code>worker.js</code>：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// worker.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>使用 Worker 建構函式在主執行緒中使用 Web Worker</strong>：
使用 <code>new Worker()</code> 建立 Web Worker：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> worker </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Worker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">URL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./worker.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">meta</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">url</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">postMessage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'message'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">e</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Worker result:'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ol>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="library-開發支援"><strong>Library 開發支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#library-%E9%96%8B%E7%99%BC%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to library-開發支援" title="Direct link to library-開發支援" translate="no">​</a></h2>
<p>Vite 和 Webpack 都提供了豐富的功能來支持函式庫的開發，但它們的設計哲學和特性有所不同。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vite-的-library-開發支援"><strong>Vite 的 Library 開發支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#vite-%E7%9A%84-library-%E9%96%8B%E7%99%BC%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to vite-的-library-開發支援" title="Direct link to vite-的-library-開發支援" translate="no">​</a></h3>
<p>Vite 專為快速開發和現代化構建體系設計，對於庫開發提供了高效且簡單的支持。Vite 通過其專門的<strong>庫模式（Library Mode）</strong>，簡化了創建函式庫的過程。</p>
<ol>
<li class=""><strong>快速轉譯 TypeScript</strong>：
Vite 使用 <code>esbuild</code> 作為 TypeScript 編譯器，省略了型別檢查，能夠快速將 TypeScript 轉譯成 JavaScript 文件。</li>
<li class=""><strong>外部化依賴</strong>：
Vite 允許外部化特定的依賴，例如 Vue 或 React，使得這些依賴不包含在函式庫的打包結果中，而是在使用時從外部獲取，減少韓式庫的大小。</li>
<li class=""><strong>多模組格式支持</strong>：
Vite 原生支持 ES 模組，且能夠輕鬆地打包成多種格式（如 ESM 和 CJS 和 UMD），滿足不同應用場景的需求。</li>
</ol>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// vite.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> defineConfig </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vite'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">defineConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">build</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">lib</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'src/main.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'MyLibrary'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function-variable function" style="color:#d73a49">fileName</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">format</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">my-library.</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">format</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">.js</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rollupOptions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">external</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'vue'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">globals</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token literal-property property" style="color:#36acaa">vue</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Vue'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webpack-的-library-開發支援"><strong>Webpack 的 Library 開發支援</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#webpack-%E7%9A%84-library-%E9%96%8B%E7%99%BC%E6%94%AF%E6%8F%B4" class="hash-link" aria-label="Direct link to webpack-的-library-開發支援" title="Direct link to webpack-的-library-開發支援" translate="no">​</a></h3>
<p>Webpack 是一個功能強大且靈活的打包工具，也適合於建構各種 JavaScript 韓式庫。使用 Webpack 建立 JavaScript 庫時，可以根據需要配置如何打包函式庫的程式碼。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// webpack.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'path'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">entry</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./src/index.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">output</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">__dirname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'dist'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'my-library.js'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">library</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'MyLibrary'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">libraryTarget</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'umd'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">globalObject</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'this'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">externals</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 將 Vue 排除在外，不打包進庫</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">vue</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">root</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'Vue'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">commonjs2</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">commonjs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">amd</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'vue'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">module</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">rules</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">test</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex special-escape escape" style="color:#36acaa">\.</span><span class="token regex regex-source language-regex" style="color:#36acaa">js</span><span class="token regex regex-source language-regex anchor function" style="color:#d73a49">$</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">exclude</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token regex regex-source language-regex" style="color:#36acaa">node_modules</span><span class="token regex regex-delimiter" style="color:#36acaa">/</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">use</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token literal-property property" style="color:#36acaa">loader</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'babel-loader'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/vite-vs-webpack#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://kinsta.com/blog/vite-vs-webpack/" target="_blank" rel="noopener noreferrer" class=""><strong>Vite vs. Webpack: A Head-to-Head Comparison</strong></a></li>
<li class=""><a href="https://medium.com/starbugs/vite-vs-turbopack-1e139c222557" target="_blank" rel="noopener noreferrer" class=""><strong>兩大 bundler 交鋒：Vite 與 Turbopack 的技術角力賽</strong></a></li>
<li class=""><a href="https://vocus.cc/article/6603a1f6fd89780001fc872c" target="_blank" rel="noopener noreferrer" class=""><strong>為什麼前端不愛用 Webpack 了? Vite 簡介</strong></a></li>
<li class=""><a href="https://segmentfault.com/a/1190000025137845" target="_blank" rel="noopener noreferrer" class=""><strong>聊聊 ESM、Bundle 、Bundleless 、Vite 、Snowpack</strong></a></li>
<li class=""><a href="https://juejin.cn/post/7106136866381889573" target="_blank" rel="noopener noreferrer" class=""><strong>如果能重来，你要选 Vite 还是 Webpack ？</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="Bundler" term="Bundler"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用 Gitlab package registry 發布與下載私人 npm 套件全記錄]]></title>
        <id>https://notes.boshkuo.com/en/blog/gitlab-package-registry</id>
        <link href="https://notes.boshkuo.com/en/blog/gitlab-package-registry"/>
        <updated>2024-07-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇技術筆記詳細介紹了如何使用 Gitlab Package Registry 來發布和下載私人 npm 套件。文章涵蓋了 Gitlab Package Registry 的基本概念、如何進行身份驗證、權限管理以及 package.json 的設定。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p><strong>前情提要：</strong><br>
<!-- -->近期在幫公司開發一個提供內部前端成員使用的 npm package。在開發的過程中有一個比較麻煩的點是，我過去從來沒有開發過 npm package 的經驗，對於如何設置開發環境、打包、發布、維護可說是完全從零開始研究。再加上公司不希望這個 package 開源給外部使用，也沒有計劃要付費使用 npm Orgs 的私人 npm 功能。因此，除了研究如何開發 npm package 之外，同時還得研究是否有免費且適合我們公司的私人 npm library 的解決方案。</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="問題"><strong>問題</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E5%95%8F%E9%A1%8C" class="hash-link" aria-label="Direct link to 問題" title="Direct link to 問題" translate="no">​</a></h2>
<p>我研究了公司至今為止發布 npm library 的作法，發現公司至今為止所有共用函式庫的開發與發布流程大致如下：</p>
<ul>
<li class="">開發端：</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">修改原始碼 → 在本機打包 → 將原始碼與打包後的檔案一併推到 gitlab 上 → 根據改動程度決定是否需要上 tag。</span><br></div></code></pre></div></div>
<ul>
<li class="">用戶端：</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">在本地專案中手動指定 package.json 中套件的專案路徑與版本 -&gt; 把整個函式庫專案從 gitlab 上 clone 到本地端的 node_module</span><br></div></code></pre></div></div>
<p>我覺得目前這樣的開發與發布流程隱藏了一些問題：</p>
<ol>
<li class="">使用端下載套件時，會將原始碼以及其他靜態檔案一同下載下來，造成 node_module 體積過大。</li>
<li class="">供使用端下載的打包檔案沒有錨定於套件版號。</li>
<li class="">發布流程（打包、更新版號、撰寫 CHANGELOG）全手動，不易維護且容易產生疏漏，比如更新版號時忘記打包最新版本的程式碼</li>
</ol>
<blockquote>
<p>總結上述問題中，我認為核心癥結在於：供使用端下載的打包文件應該與原始碼分開託管。</p>
</blockquote>
<p>一般來說，我們使用 npm install 指令下載的套件都是託管在公開的 npm registry 中。然而，免費方案下的 npm registry 只能託管開源的套件，這無法滿足公司想要的私人 npm 需求。</p>
<p>經過一番搜尋後，我發現 Gitlab 提供了一個非常方便的解決方案: <code>Gitlab Package Registry</code>，它可以像 npm registry 一樣運作，但同時保留了私人託管的特性。經過嘗試，我發現這個工具非常強大且易於使用。本篇文章將記錄如何將私人用的 npm library 發布到 Gitlab，以及如何從 Gitlab 下載 npm library。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gitlab-package-registry-簡介"><strong>Gitlab package registry 簡介</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#gitlab-package-registry-%E7%B0%A1%E4%BB%8B" class="hash-link" aria-label="Direct link to gitlab-package-registry-簡介" title="Direct link to gitlab-package-registry-簡介" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="package-registry-是什麼"><strong>Package Registry 是什麼？</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#package-registry-%E6%98%AF%E4%BB%80%E9%BA%BC" class="hash-link" aria-label="Direct link to package-registry-是什麼" title="Direct link to package-registry-是什麼" translate="no">​</a></h3>
<p>首先，我們來談談 <strong>package registry</strong> 到底是什麼。簡單來說，package registry 是一個可以儲存和管理軟體套件的地方。我們可以把它想像成一個專門放軟體套件的倉庫，讓開發者們可以方便地分享和重複使用這些程式碼與模組。</p>
<p><code>Gitlab Package Registry</code> 是 Gitlab 提供的一個功能，讓我們可以在 Gitlab 的平台上管理自己的 npm packages。如此不僅可以把程式碼放在 Gitlab 上，還可以把 npm 套件一同託管於 Gitlab，這對於在使用 Gitlab 的公司或私人開發團隊來說非常方便。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gitlab-如何作為-npm-package-的容器"><strong>Gitlab 如何作為 npm package 的容器？</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#gitlab-%E5%A6%82%E4%BD%95%E4%BD%9C%E7%82%BA-npm-package-%E7%9A%84%E5%AE%B9%E5%99%A8" class="hash-link" aria-label="Direct link to gitlab-如何作為-npm-package-的容器" title="Direct link to gitlab-如何作為-npm-package-的容器" translate="no">​</a></h3>
<p>在介紹如何發布 library 到 Gitlab 之前，讓我們來看看 Gitlab 如何作為 npm package 的容器。</p>
<p>首先，每個 Gitlab 專案（project）都有自己的 Package Registry，也就是說，每個 Gitlab 專案都擁有自己獨立的 npm packages 存儲空間用來存儲和管理多個 npm packages。而每個專案的 Package Registry 都有一個類似地址的唯一 URL，這個 URL 通常是以專案 ID 為基礎構建的。比如說，我在範例中建立的專案 ID 是 123，那麼 Package Registry URL 可能看起來像這樣：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https</span><span class="token operator" style="color:#393A34">:</span><span class="token comment" style="color:#999988;font-style:italic">//&lt;your_domain_name&gt;/api/v4/projects/123/packages/npm/</span><br></div></code></pre></div></div>
<p>這個 URL 主要是用來告訴 npm 使用者從哪裡下載或上傳套件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scope-與-gitlab-的套件命名規則"><strong>Scope 與 Gitlab 的套件命名規則</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#scope-%E8%88%87-gitlab-%E7%9A%84%E5%A5%97%E4%BB%B6%E5%91%BD%E5%90%8D%E8%A6%8F%E5%89%87" class="hash-link" aria-label="Direct link to scope-與-gitlab-的套件命名規則" title="Direct link to scope-與-gitlab-的套件命名規則" translate="no">​</a></h3>
<p>接著，我們來談談 npm 如何知道哪些套件要從上述的 URL 上下載下來。<br>
<!-- -->在 npm 中，<strong>Scope（命名空間）</strong> 是用來組織套件的一種方式，它允許開發者將套件歸類到特定的組織或團隊之下，從而避免命名衝突。例如，<code>@my-org/my-package</code> 表示這個套件屬於 <code>@my-org</code> 組織。</p>
<p>若我們將 GitLab 作為託管 npm 套件的 package registry，package.json 的套件名稱就需要遵循 Gitlab 的命名規則來命名：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@scope/package-name"</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p><strong>@scope</strong> 的值應該是<strong>託管套件的專案根目錄</strong>，而非套件原始碼的專案根目錄，且應使用小寫字母。</p><p>舉例來說，如果將 npm 套件託管於：</p><ul>
<li class="">http://&lt;your_domain_name&gt;/<strong>&lt;instance_name&gt;</strong>/&lt;group_name&gt;/&lt;project_name&gt;<!-- -->
<ul>
<li class="">@scope 的值應為 <code>@&lt;instance_name&gt;</code></li>
</ul>
</li>
<li class="">http://&lt;your_domain_name&gt;/<strong>&lt;group_name&gt;</strong>/&lt;project_name&gt;<!-- -->
<ul>
<li class="">@scope 的值應為 <code>@&lt;group_name&gt;</code></li>
</ul>
</li>
<li class="">http://&lt;your_domain_name&gt;/<strong>&lt;user_name&gt;</strong>/&lt;project_name&gt;<!-- -->
<ul>
<li class="">@scope 的值應為 <code>@&lt;user_name&gt;</code></li>
</ul>
</li>
</ul><p><strong>package-name</strong> 則可以任意命名</p></div></div>
<p>當使用端在專案底下新增新增一個 <code>.npmrc</code> 檔案，並加入以下內容後：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Set URL for your scoped packages.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># For example package with name `@foo/bar` will use this URL for download</span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">@&lt;scope&gt;:registry &lt;your domain name&gt;/api/v4/projects/&lt;project id&gt;/packages/npm/</span><br></div></code></pre></div></div>
<p>當我們下載名稱以 <code>@&lt;scrop&gt;</code> 開頭的套件時，npm 就會知道要去指定的 registry URL 下載</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="npm-registry-身份驗證"><strong>Npm registry 身份驗證</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#npm-registry-%E8%BA%AB%E4%BB%BD%E9%A9%97%E8%AD%89" class="hash-link" aria-label="Direct link to npm-registry-身份驗證" title="Direct link to npm-registry-身份驗證" translate="no">​</a></h3>
<p>在使用 Gitlab Package Registry 發布或下載 npm 套件的時候，Npm 會先進行身份驗證，確認對於某個 registry 來說是否有發布或下載的權限。</p>
<ul>
<li class="">發布套件時： npm 會透過 npm config / .npmrc 與 package.json 的資訊進行身分驗證；</li>
<li class="">下載私人套件時：npm 也會透過 npm config / .npmrc 進行身分驗證。</li>
</ul>
<p>Gitlab 提供了以下幾種驗證方法：</p>
<ul>
<li class=""><a href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html" target="_blank" rel="noopener noreferrer" class="">Personal access token</a>: 以使用者權限進行身份驗證，適用於個人</li>
<li class=""><a href="https://docs.gitlab.com/ee/user/project/deploy_tokens/index.html" target="_blank" rel="noopener noreferrer" class="">Project deploy token</a>: 為特定 project 創建的 token，適用於多個使用者，提供專案範圍內所有 packages 的訪問權限。</li>
<li class=""><a href="https://docs.gitlab.com/ee/user/project/deploy_tokens/index.html" target="_blank" rel="noopener noreferrer" class="">Group deploy token</a>: 為特定群組創建的 token，適用於多個使用者，可以用來存取該群組下的所有 packages。</li>
<li class=""><a href="https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html" target="_blank" rel="noopener noreferrer" class="">Job token</a>:  適用於在 CI/CD pipeline 中發佈 package，如果要通過 CI/CD pipelines 發佈 npm 套件，這是唯一可以使用的選擇。</li>
</ul>
<div class="theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>danger</div><div class="admonitionContent_BuS1"><p>如果在專案設置中關閉了 “Package registry” 功能，無論使用哪種驗證方式都會收到 403 Forbidden</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gitlab-package-registry-權限"><strong>Gitlab package registry 權限</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#gitlab-package-registry-%E6%AC%8A%E9%99%90" class="hash-link" aria-label="Direct link to gitlab-package-registry-權限" title="Direct link to gitlab-package-registry-權限" translate="no">​</a></h3>
<p>在 Gitlab 中，不同的專案角色對 Package Registry 的權限也有所不同，以下表格列出不同權限的最低角色要求：</p>
<table><thead><tr><th>Project visibility</th><th>操作</th><th>最低角色要求</th></tr></thead><tbody><tr><td>公開 (Public)</td><td>檢視 package registry</td><td>無</td></tr><tr><td></td><td>發佈 package</td><td>Developer</td></tr><tr><td></td><td>拉取 package</td><td>無</td></tr><tr><td>內部 (Internal)</td><td>檢視 package registry</td><td>Guest</td></tr><tr><td></td><td>發佈 package</td><td>Developer</td></tr><tr><td></td><td>拉取 package</td><td>Guest</td></tr><tr><td>私有 (Private)</td><td>檢視 package registry</td><td>Reporter</td></tr><tr><td></td><td>發佈 package</td><td>Developer</td></tr><tr><td></td><td>拉取 package</td><td>Reporter</td></tr></tbody></table>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_BuS1"><p>Gitlab 也允許設定不論 <strong>Project visibility</strong> 與<strong>最低角色要求</strong>，使所有人都具有 pull package 的權限：</p><ol>
<li class=""><strong>Settings &gt; General</strong>.</li>
<li class="">&gt;&nbsp;<strong>Visibility, project features, permissions</strong>.</li>
<li class="">→ <strong>Allow anyone to pull from Package Registry</strong></li>
<li class="">→ <strong>Save changes</strong>.</li>
</ol></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="準備-gitlab-package-registry"><strong>準備 GitLab Package Registry</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E6%BA%96%E5%82%99-gitlab-package-registry" class="hash-link" aria-label="Direct link to 準備-gitlab-package-registry" title="Direct link to 準備-gitlab-package-registry" translate="no">​</a></h2>
<p>具備 Gitlab package registry 的基本知識後，我們就可以開始嘗試來發布一個 npm library 到 Gitlab 上。首先我們先來處理 Gitlab 的部分。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="建立-gitlab-project"><strong>建立 Gitlab Project</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E5%BB%BA%E7%AB%8B-gitlab-project" class="hash-link" aria-label="Direct link to 建立-gitlab-project" title="Direct link to 建立-gitlab-project" translate="no">​</a></h3>
<p>在這篇範例中，我在我個人的專案目錄下建立了一個新的 Gitlab 專案，專案 ID 為 691</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1721665264/Docusaurus%20Blog/Node.js/Gitlab%20package%20registry%20%E7%99%BC%E5%B8%83%E8%88%87%E4%B8%8B%E8%BC%89%E7%A7%81%E4%BA%BA%20npm/gitlab_project_kd7zeb.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="設定-deploy-token"><strong>設定 Deploy token</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E8%A8%AD%E5%AE%9A-deploy-token" class="hash-link" aria-label="Direct link to 設定-deploy-token" title="Direct link to 設定-deploy-token" translate="no">​</a></h3>
<p>接著前往 Setting &gt; Repository &gt; Deploy tokens 設定 <a href="https://docs.gitlab.com/ee/user/project/deploy_tokens/index.html" target="_blank" rel="noopener noreferrer" class="">Project deploy token</a>。點擊 Create deploy 後會得到一組 Token，記得要把它記下來。</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1721665263/Docusaurus%20Blog/Node.js/Gitlab%20package%20registry%20%E7%99%BC%E5%B8%83%E8%88%87%E4%B8%8B%E8%BC%89%E7%A7%81%E4%BA%BA%20npm/deploy_token_tzkyrw.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="發布本地端的-npm-專案"><strong>發布本地端的 npm 專案</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E7%99%BC%E5%B8%83%E6%9C%AC%E5%9C%B0%E7%AB%AF%E7%9A%84-npm-%E5%B0%88%E6%A1%88" class="hash-link" aria-label="Direct link to 發布本地端的-npm-專案" title="Direct link to 發布本地端的-npm-專案" translate="no">​</a></h2>
<p>準備好 Gitlab 後，接著就可以準備將本地端的 npm library 發布到 Gitlab 上了。在這篇教學範例中，我在根目錄的 <code>index.js</code> 下創建一個簡單的 function 並且匯出它，用來簡單模擬要發布的 library：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">sum</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">a</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> b</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> a </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> b</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> sum</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="準備-npmrc"><strong>準備 .npmrc</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E6%BA%96%E5%82%99-npmrc" class="hash-link" aria-label="Direct link to 準備-npmrc" title="Direct link to 準備-npmrc" translate="no">​</a></h3>
<p><code>.npmrc</code> 在發布套件時的作用有兩個：</p>
<ol>
<li class="">指定以 <strong>@&lt;scope&gt;</strong> 為套件命名開頭的套件要發布到的 package registry 位置</li>
<li class="">發布套件到指定 package registry url 時，提供 <strong>authToken</strong> 進行身份驗證</li>
</ol>
<p>在套件專案底下新增一個 . npmrc 檔，並加入以下內容：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Set URL for your scoped packages.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># For example package with name `@foo/bar` will use this URL for download</span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">@&lt;scope&gt;:registry &lt;your domain name&gt;/api/v4/projects/&lt;project id&gt;/packages/npm/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Add the token for the scoped packages URL. Replace &lt;your_project_id&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># with the project where your package is located.</span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">//&lt;your domain name&gt;/api/v4/projects/&lt;project id&gt;/packages/npm/:_authToken=&lt;your token&gt;</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>@&lt;scope&gt;</strong>: 由於這個 Gitlab 專案建立於我的個人目錄專案底下，因此在此處為我的 gitlab user-name： @bosh.kuo</li>
<li class=""><strong>&lt;your domain name&gt;</strong>: 為公司的 gitlab domain</li>
<li class=""><strong>&lt;project id&gt;</strong>: 691</li>
<li class=""><strong>&lt;your token&gt;</strong>: 即剛剛創建的 project deploy token</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="準備-packagejson"><strong>準備 package.json</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E6%BA%96%E5%82%99-packagejson" class="hash-link" aria-label="Direct link to 準備-packagejson" title="Direct link to 準備-packagejson" translate="no">​</a></h3>
<p>在發布套件前，，需要先確保以下幾個選項是否正確設定，這些選項將直接影響套件的發布和使用。</p>
<ul>
<li class=""><strong>name</strong>: 套件名稱，須遵循上述的 Gitlab 命名規則</li>
<li class=""><strong>version</strong>: 套件的版本號，每次發布新版本時，記得更新這個版本號。</li>
<li class=""><strong>publishConfig</strong>: 這個選項用來配置發布時的一些參數，特別是 registry 的 URL。我們需要將這個設定為 Gitlab 的 Package Registry URL。</li>
<li class=""><strong>files</strong>: 這個選項用來指定哪些文件應該包含在發布的套件中。通常我們會包括 <code>lib</code> 或 <code>dist</code> 目錄（打包後檔案的目標目錄），這取決於我們的專案結構。</li>
<li class=""><strong>main</strong>: 這個選項指定了套件的主入口文件。當其他人安裝並使用你的套件時，這個文件將作為入口。</li>
<li class=""><strong>module</strong>: 如果套件是用 ES module 編寫的，可以使用這個選項來指定 ES module 的入口文件。</li>
<li class=""><strong>types</strong>: 如果專案使用 TypeScript，這個選項用來指定 TypeScript 的定義文件。這樣用戶在使用套件時，可以獲得完整的型別支援。</li>
</ul>
<p>本篇範例中的 package.json 如下：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@bosh.kuo/published-package"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0.0.0"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"publishConfig"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"registry"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://gitlab.xxx.com/api/v4/projects/691/packages/npm/"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"main"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"index.js"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"files"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"index.js"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="發布"><strong>發布</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E7%99%BC%E5%B8%83" class="hash-link" aria-label="Direct link to 發布" title="Direct link to 發布" translate="no">​</a></h3>
<p>一切準備就緒後就可以透過以下指令發布 npm 套件</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># for npm user</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm publish</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># for yarn user</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn publish</span><br></div></code></pre></div></div>
<p>發布完成後就可以在 <strong>Gitlab</strong> <strong>Package Registry</strong> 中看到剛剛發佈的套件了</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1721665263/Docusaurus%20Blog/Node.js/Gitlab%20package%20registry%20%E7%99%BC%E5%B8%83%E8%88%87%E4%B8%8B%E8%BC%89%E7%A7%81%E4%BA%BA%20npm/npm_package_eypqeo.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<blockquote>
<p>圖上顯示 @bosh.kuo/published-package1 與 @bosh.kuo/published-package2 為後來嘗試用 monorepo 管理多個 npm library 時發布的，並非本篇文章範例中發布的套件，僅用於示例。</p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-gitlab-package-registry-上的-npm-套件"><strong>使用 GitLab Package Registry 上的 npm 套件</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#%E4%BD%BF%E7%94%A8-gitlab-package-registry-%E4%B8%8A%E7%9A%84-npm-%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="Direct link to 使用-gitlab-package-registry-上的-npm-套件" title="Direct link to 使用-gitlab-package-registry-上的-npm-套件" translate="no">​</a></h2>
<p>完成了上述發布流程後，我們可以在本地端建立一個使用端專案，嘗試把剛剛發佈的套件下載下來使用。</p>
<ol>
<li class="">
<p>建立使用端測試專案</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mkdir consumer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd consumer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn init -y</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Npm registry 身份驗證</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Set URL for your scoped packages.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># For example package with name `@foo/bar` will use this URL for download</span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">@&lt;scope&gt;:registry &lt;your domain name&gt;/api/v4/projects/&lt;project id&gt;/packages/npm/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Add the token for the scoped packages URL. Replace &lt;your_project_id&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># with the project where your package is located.</span><br></div><div class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">//&lt;your domain name&gt;/api/v4/projects/&lt;project id&gt;/packages/npm/:_authToken=&lt;your token&gt;</span><br></div></code></pre></div></div>
<p>同上章節的 .npmrc 檔。</p>
<p>此外，也可以透過 npm config set 指令的方式來設定：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm config set @&lt;scope&gt;:registry &lt;your domain name&gt;/api/v4/packages/&lt;project id&gt;/npm/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm config set -- '//&lt;your domain name&gt;/api/v4/packages/npm/:_authToken' "&lt;your_token&gt;"</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>下載專案</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn add @bosh.kuo/published-package</span><br></div></code></pre></div></div>
<p>若下載成功的話，就可以在 <code>node_modules</code> 底下看到套件，其包含的目錄與檔案會與前章節中設定於 <code>package.json</code> 內的 <code>files</code> 一致。</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1721665263/Docusaurus%20Blog/Node.js/Gitlab%20package%20registry%20%E7%99%BC%E5%B8%83%E8%88%87%E4%B8%8B%E8%BC%89%E7%A7%81%E4%BA%BA%20npm/package_in_node_modules_qa60do.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
</li>
<li class="">
<p>測試套件</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// index.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> sum </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"@bosh.kuo/published-package"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">sum</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>如果所有環節都沒有出錯，應該會看到 <code>30</code> 的輸出。</p>
</li>
</ol>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>如果你看到正確的輸出結果，那麼恭喜你！你的套件已成功發布並可以正常使用。這表示你已經成功地將一個私人 npm 套件發布到了 Gitlab Package Registry，並成功地下載並使用了它。</p></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/gitlab-package-registry#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://docs.gitlab.com/ee/user/packages/package_registry/" target="_blank" rel="noopener noreferrer" class=""><strong>Package registry</strong></a></li>
<li class=""><a href="https://docs.gitlab.com/ee/user/packages/npm_registry/#404-not-found-errors-are-happening-on-npm-install-or-yarn" target="_blank" rel="noopener noreferrer" class=""><strong>npm packages in the package registry</strong></a></li>
<li class=""><a href="https://hackmd.io/@RdUg6kDRTcKHmb2WGCucdA/HyxdsQo-7q" target="_blank" rel="noopener noreferrer" class=""><strong>手把手將vue component打包成套件，丟到gitlab package registry-內網篇</strong></a></li>
<li class=""><a href="https://xenby.com/b/288-%E6%95%99%E5%AD%B8-%E5%BB%BA%E7%AB%8B%E8%88%87%E4%BD%BF%E7%94%A8-gitlab-%E7%A7%81%E4%BA%BA-npm-package-registry" target="_blank" rel="noopener noreferrer" class=""><strong>[教學] 建立與使用 GitLab 私人 npm package registry</strong></a></li>
<li class=""><a href="https://docs.npmjs.com/cli/v10/configuring-npm/package-json" target="_blank" rel="noopener noreferrer" class=""><strong>@npm - package.json</strong></a></li>
<li class=""><a href="https://www.youtube.com/watch?v=Yn0N8_Gfcyw&amp;t=540s&amp;ab_channel=JackDo" target="_blank" rel="noopener noreferrer" class=""><strong>Publish private NPM package to Gitlab</strong></a></li>
<li class=""><a href="https://www.youtube.com/watch?v=jIA_ksPKR9w&amp;t=235s&amp;ab_channel=SmrutiFy" target="_blank" rel="noopener noreferrer" class=""><strong>Publishing Private NPM Package to Gitlab | Using Manual Command &amp; Gitlab Action | SmrutiFy</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="GitLab" term="GitLab"/>
        <category label="npm" term="npm"/>
        <category label="yarn" term="yarn"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[剖析JS 「萬物皆物件」的迷思]]></title>
        <id>https://notes.boshkuo.com/en/blog/js-all-types-object</id>
        <link href="https://notes.boshkuo.com/en/blog/js-all-types-object"/>
        <updated>2024-05-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[透過本文的探討，我們澄清了「萬物皆物件」這一說法的真實含義。JavaScript 中的基本資料類型和物件有著本質的區別，儘管基本資料類型在某些情況下可以表現出物件的行為，但這只是因為 JavaScript 的臨時包裝機制。]]></summary>
        <content type="html"><![CDATA[<p>初學 JavaScript 時，偶爾會在教學文或討論區中看到這樣的說法：</p>
<blockquote>
<p><em>「在 JavaScript 中，萬物皆為物件。」</em></p>
</blockquote>
<p>便在潛意識中埋下一個 JS 中所有變數都是物件的種子。如今因為工作上大量使用 JS 這個語言，對 JS 這個語言有比較深一點的了解後，便想要回來探討這個議題。</p>
<p><strong>先講結論，這個說法是不正確的。</strong></p>
<p>但是，我相信正在看這篇文章的你應該也跟我一樣，會想要了解為什麼訪間會有 <strong>「JavaScript 萬物皆為物件」</strong> 的說法，以及這個說法背後的論點是什麼？反對這個說法的論點是什麼？本篇文章將帶大家探討這個議題，挖掘正反兩方的論點，並釐清一些 JS 的觀念。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="為何會有萬物皆物件的說法"><strong>為何會有「萬物皆物件」的說法？</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#%E7%82%BA%E4%BD%95%E6%9C%83%E6%9C%89%E8%90%AC%E7%89%A9%E7%9A%86%E7%89%A9%E4%BB%B6%E7%9A%84%E8%AA%AA%E6%B3%95" class="hash-link" aria-label="Direct link to 為何會有萬物皆物件的說法" title="Direct link to 為何會有萬物皆物件的說法" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="javascript-的基本資料型別"><strong>JavaScript 的基本資料型別</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#javascript-%E7%9A%84%E5%9F%BA%E6%9C%AC%E8%B3%87%E6%96%99%E5%9E%8B%E5%88%A5" class="hash-link" aria-label="Direct link to javascript-的基本資料型別" title="Direct link to javascript-的基本資料型別" translate="no">​</a></h3>
<p>在探討 <strong>「萬物皆為物件」</strong> 這個議題之前，我們首先需要了解 JavaScript 中的基本資料型別。JavaScript 中有七種資料型別，Undefined、Null、Boolean、Number、String、Symbol（ES6新增）、Object。其中，前六種是<strong>基本資料型別（Primitive Types）</strong>，而 Object 是<strong>複雜資料型別</strong>。</p>
<p>由此可見，在 JavaScript 的世界中，並非所有的資料型別本質上都是物件。基本資料型別是獨立的，它們並不具備物件的屬性和方法。然而，在某些情況下，基本資料型別也可以表現出類似物件的行為，這正是 <strong>「萬物皆物件」</strong> 這種說法產生的原因之一。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="萬物皆物件說法的起源"><strong>「萬物皆物件」說法的起源</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#%E8%90%AC%E7%89%A9%E7%9A%86%E7%89%A9%E4%BB%B6%E8%AA%AA%E6%B3%95%E7%9A%84%E8%B5%B7%E6%BA%90" class="hash-link" aria-label="Direct link to 萬物皆物件說法的起源" title="Direct link to 萬物皆物件說法的起源" translate="no">​</a></h3>
<p>雖然基本資料型別和物件有著明顯的區別，但在實際使用中，我們常常發現基本資料型別可以像物件一樣操作。例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> a </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"123"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">a</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">__proto__</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/* String {</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    anchor: ƒ anchor()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    at: ƒ at()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    big: ƒ big()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    blink: ƒ blink()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    bold: ƒ bold()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    charAt: ƒ charAt()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    charCodeAt: ƒ charCodeAt()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    codePointAt: ƒ codePointAt()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    concat: ƒ concat()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    constructor: ƒ String()</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">    ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">} */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> b </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"456"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">b</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">substr</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// "56"</span><br></div></code></pre></div></div>
<p>從上述例子中可以看到，變數 a 和 b 雖然是基本資料型別 <strong>String</strong>，但它們都有 <code>__proto__</code> 屬性，並且可以調用多種方法。這似乎印證了 <strong>「基本型別也是一種物件」</strong> 的說法。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="萬物皆物件說法的誤區"><strong>「萬物皆物件」說法的誤區</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#%E8%90%AC%E7%89%A9%E7%9A%86%E7%89%A9%E4%BB%B6%E8%AA%AA%E6%B3%95%E7%9A%84%E8%AA%A4%E5%8D%80" class="hash-link" aria-label="Direct link to 萬物皆物件說法的誤區" title="Direct link to 萬物皆物件說法的誤區" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="包裝物件wrapper-object"><strong>包裝物件(Wrapper Object)</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#%E5%8C%85%E8%A3%9D%E7%89%A9%E4%BB%B6wrapper-object" class="hash-link" aria-label="Direct link to 包裝物件wrapper-object" title="Direct link to 包裝物件wrapper-object" translate="no">​</a></h3>
<p>然而，這種理解其實是不正確的。事實上，這涉及到 JavaScript 中的<code>包裝物件（Wrapper Object）</code>機制。當我們對基本資料型別的變數調用方法時，JavaScript 會臨時創建一個對應的<strong>包裝物件，<strong>或稱作</strong>基本型別包裹器 (Primitive Wrapper)</strong>，使其具有物件的行為。例如：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> str </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"hello"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">str</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toUpperCase</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// "HELLO"</span><br></div></code></pre></div></div>
<p>在這裡，str 是一個字串，但調用 <strong>toUpperCase</strong> 方法時，JavaScript 會臨時創建一個 <strong>String 包裝物件</strong>，使其具有物件的行為。內部運行過程如下：</p>
<ol>
<li class="">建立一個 <strong>String</strong> 的實例。</li>
<li class="">在實例上呼叫指定的方法。</li>
<li class="">銷毀這個實例。</li>
</ol>
<p>具體過程如下所示：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> tempStr </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"hello"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> tempStr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toUpperCase</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">tempStr </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// "HELLO"</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="驗證包裝物件"><strong>驗證包裝物件</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#%E9%A9%97%E8%AD%89%E5%8C%85%E8%A3%9D%E7%89%A9%E4%BB%B6" class="hash-link" aria-label="Direct link to 驗證包裝物件" title="Direct link to 驗證包裝物件" translate="no">​</a></h3>
<p>這種包裝物件的機制使得基本資料型別在某些情況下看起來像物件，但實際上它們並不是物件，這只是短暫的包裝過程。這一點可以通過以下例子得到印證：</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> str1 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"hello"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> str2 </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"你好"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">str1</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">__proto__</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> str2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">__proto__</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">str1 </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// false </span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">str2 </span><span class="token keyword" style="color:#00009f">instanceof</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// true </span><br></div></code></pre></div></div>
<p>在這個例子中，str1 在調用 <code>__proto__</code> 屬性的瞬間，JavaScript 會用 <strong>new String()</strong> 臨時實例化一個物件，因此在那一瞬間，它們的<strong>建構函式</strong>和<strong>原型(prototype)</strong> 是相同的。但這並不意味著 str1 是一個真正的物件。</p>
<p>這種臨時包裝的特性常常導致開發者誤以為基本資料型別也是物件。因此，我們需要清楚地認識到，JavaScript 中的基本資料型別和物件有本質上的區別，<strong>「萬物皆物件」</strong> 這種說法並不完全準確。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結"><strong>總結</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="Direct link to 總結" title="Direct link to 總結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="常見誤解整理"><strong>常見誤解整理</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#%E5%B8%B8%E8%A6%8B%E8%AA%A4%E8%A7%A3%E6%95%B4%E7%90%86" class="hash-link" aria-label="Direct link to 常見誤解整理" title="Direct link to 常見誤解整理" translate="no">​</a></h3>
<p><strong>誤解一：所有資料型別都是物件</strong><br>
<!-- -->事實上，JavaScript 中的基本資料型別（Primitive Types）並不是物件。這些基本資料型別包括：Undefined、Null、Boolean、Number、String、Symbol。它們在本質上與物件不同，雖然在某些情況下會被臨時包裝成物件，但這並不意味著它們本質上是物件。</p>
<p><strong>誤解二：基本資料型別具有物件的方法和屬性</strong>
這個誤解源於 JavaScript 在運行時對基本資料型別的臨時包裝行為。當我們對基本資料型別調用方法時，JavaScript 會臨時創建一個包裝物件，使其具有物件的行為。然而，這只是一個短暫的過程，並不意味著基本資料型別本身具有物件的方法和屬性。</p>
<p><strong>誤解三：所有變數都可以使用 <strong>proto</strong> 屬性</strong><br>
<strong><strong>proto</strong></strong> 屬性是用來訪問物件的原型的。在基本資料型別被臨時包裝成物件時，這些變數也可以訪問 <strong><strong>proto</strong></strong> 屬性。但這並不意味著基本資料型別本身具有 <strong><strong>proto</strong></strong> 屬性，這只是臨時包裝物件的特性。</p>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/js-all-types-object#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://segmentfault.com/a/1190000012037062" target="_blank" rel="noopener noreferrer" class=""><strong>你还认为JS中万物皆对象？</strong></a></li>
<li class=""><a href="https://ithelp.ithome.com.tw/articles/10193902?sc=iThelpR" target="_blank" rel="noopener noreferrer" class=""><strong>重新認識 JavaScript: Day 23 基本型別包裹器 Primitive Wrapper</strong></a></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="Javascript" term="Javascript"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[[實作紀錄] React + TypeScript + Jest + React Testing Library + MSW 前端測試工具安裝與配置]]></title>
        <id>https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup</id>
        <link href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup"/>
        <updated>2024-05-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[這篇技術筆記文章將詳細介紹如何在現代前端開發環境中整合 React、TypeScript、Jest、React Testing Library 及 MSW（Mock Service Worker）。文章將指導讀者從零開始安裝和組態這些工具，展示它們如何配合使用來建構一個健壯的測試環境。]]></summary>
        <content type="html"><![CDATA[<p>這篇文章主要記錄近期在工作時嘗試在手上的其中一個專案中安裝與配置的測試工具，我在這個專案中選擇使用了 <strong><code>Jest + React Testing Library + MSW</code></strong> 這幾個測試工具，並使用 <strong><code>Husky + lint-staged</code></strong> 設定在 <strong>pre-commit</strong> 時自動執行相關測試文件的自動測試。除了本文章所介紹的測試工具以外，React 生態系還有許多受歡迎的測試工具，像是近期有取代 <strong>Jest</strong> 勢頭的 <strong>Vitest</strong>，以及用來做 E2E 測試的 <strong>Cypress</strong> 等，都很值得嘗試與研究。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="套件安裝"><strong>套件安裝</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#%E5%A5%97%E4%BB%B6%E5%AE%89%E8%A3%9D" class="hash-link" aria-label="Direct link to 套件安裝" title="Direct link to 套件安裝" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="前端基本開發環境"><strong>前端基本開發環境</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#%E5%89%8D%E7%AB%AF%E5%9F%BA%E6%9C%AC%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83" class="hash-link" aria-label="Direct link to 前端基本開發環境" title="Direct link to 前端基本開發環境" translate="no">​</a></h3>
<ul>
<li class="">
<p>React 18</p>
</li>
<li class="">
<p>TypeScript</p>
</li>
<li class="">
<p>Webpack</p>
</li>
<li class="">
<p>Babel</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// .babelrc 設定</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"presets"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"@babel/preset-env"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"@babel/preset-react"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"@babel/preset-typescript"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="jest-相關套件"><strong>Jest 相關套件</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#jest-%E7%9B%B8%E9%97%9C%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="Direct link to jest-相關套件" title="Direct link to jest-相關套件" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn add --dev jest babel-jest jest-environment-jsdom jest-svg-transformer identity-obj-proxy</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="react-testing-library-相關套件"><strong>React Testing Library 相關套件</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#react-testing-library-%E7%9B%B8%E9%97%9C%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="Direct link to react-testing-library-相關套件" title="Direct link to react-testing-library-相關套件" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn add --dev @testing-library/react @testing-library/jest-dom @testing-library/user-event @testing-library/dom</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mock-service-worker-相關套件"><strong>Mock Service Worker 相關套件</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#mock-service-worker-%E7%9B%B8%E9%97%9C%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="Direct link to mock-service-worker-相關套件" title="Direct link to mock-service-worker-相關套件" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn add --dev msw@latest dotenv undici@5</span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="jest-相關套件介紹與配置"><strong>Jest 相關套件介紹與配置</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#jest-%E7%9B%B8%E9%97%9C%E5%A5%97%E4%BB%B6%E4%BB%8B%E7%B4%B9%E8%88%87%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to jest-相關套件介紹與配置" title="Direct link to jest-相關套件介紹與配置" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="jest"><strong>Jest</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#jest" class="hash-link" aria-label="Direct link to jest" title="Direct link to jest" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://jestjs.io/" target="_blank" rel="noopener noreferrer" class="">@Jest</a></strong><br>
<strong><a href="https://jestjs.io/docs/configuration#coveragedirectory-string" target="_blank" rel="noopener noreferrer" class="">@Jest - Configuring Jest</a></strong></p>
</blockquote>
<p><strong><code>Jest</code></strong> 是一個由 Facebook 開發的開源 JavaScript 測試框架，它提供了完整的單元測試環境，包括斷言、模擬、測試監視和報告等功能。<strong>Jest</strong> 使用 <code>describe</code> 和 <code>it</code> 塊來組織測試。<code>describe</code> 塊用於描述測試的大組別，而 <code>it</code> 塊用於描述單個測試。每個測試都應該包含一個或多個斷言，用於驗證測試的結果。</p>
<p><strong>jest.config.js 配置文檔</strong></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">collectCoverage</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 啟用覆蓋率收集</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token literal-property property" style="color:#36acaa">collectCoverageFrom</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 指定收集覆蓋率的檔案範圍</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"src/**/*.{js,jsx,ts,tsx}"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"!src/**/*.d.ts"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">coverageDirectory</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'coverage'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>package.json 配置文檔</strong></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token string-property property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"test"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"jest --verbose"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"test:coverage"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"yarn test --coverage"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"test:watch"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"yarn test --watch"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"test:watch:coverage"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"yarn test --watch --coverage"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Code Coverage</div><div class="admonitionContent_BuS1"><p>1.&nbsp;<strong>Statements</strong>&nbsp;: 有多少比例語句被執行到，一個&nbsp; console.log(); 就算是一個語句，一行中可以有多個 Statements。</p><p>2.&nbsp;<strong>Branches</strong>：條件語句，像是 if ... else 或是 switch，每個情況都是一個 Branch。</p><p>3.&nbsp;<strong>Functions</strong>：一個檔案有多少比例的函式被執行到。</p><p>4.&nbsp;<strong>Lines</strong>：有幾行的程式碼被執行到，基本上 Lines 的數量會小於等於 Statements 的數量</p></div></div>
<div class="theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>安裝 Jest 時遇到的 Bug</div><div class="admonitionContent_BuS1"><p>安裝 <strong>jest</strong> 後我在 src/test/ 目錄下建立了一個簡單的 sum.ts 和 sum.test.ts 來測試，並在 package.json 中設置了：</p><div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"test"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"jest"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div><p>當我下 yarn test 指令後卻出現以下錯誤訊息</p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/strip-ansi/index.js from /Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/string-width/index.js not supported.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Instead change the require of /Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/strip-ansi/index.js in /Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/string-width/index.js to a dynamic import() which is available in all CommonJS modules.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at Object.&lt;anonymous&gt; (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/string-width/index.js:2:19)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at Object.&lt;anonymous&gt; (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/cliui/build/index.cjs:291:21)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at Object.&lt;anonymous&gt; (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/yargs/build/index.cjs:1:60678)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at Object.&lt;anonymous&gt; (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/yargs/index.cjs:5:30)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at _yargs (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/jest-cli/build/run.js:30:39)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at buildArgv (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/jest-cli/build/run.js:149:26)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at Object.run (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/jest-cli/build/run.js:124:24)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at Object.&lt;anonymous&gt; (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/jest-cli/bin/jest.js:16:17)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    at Object.&lt;anonymous&gt; (/Users/boshkuo/Desktop/D8AI/ctbc-tc-test/frontend/node_modules/jest/bin/jest.js:12:3)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">error Command failed with exit code 1.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.</span><br></div></code></pre></div></div><p>神奇的是如果我直接執行 <strong>npx jest</strong> 是不會跳出這個錯誤的。看起來這個錯誤應該跟 <strong>yarn</strong> 脫不了關係</p><p>查了一下發現 github 社群上有很多人遇到相同的問題：</p><ul>
<li class=""><strong><a href="https://github.com/yarnpkg/yarn/issues/8994" target="_blank" rel="noopener noreferrer" class="">[Bug?]: Error [ERR_REQUIRE_ESM]: require() of ES Module string-width/index.js&nbsp;#8994</a></strong></li>
<li class=""><a href="https://github.com/storybookjs/storybook/issues/22431" target="_blank" rel="noopener noreferrer" class=""><strong>[Bug]: string-width dependency stops storybook from executing</strong></a></li>
</ul><p>參考社群上的解法，把 <strong>yarn.lock</strong> 刪除後重新 yarn 一次就沒有報錯了</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="babel-jest"><strong>babel-jest</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#babel-jest" class="hash-link" aria-label="Direct link to babel-jest" title="Direct link to babel-jest" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://jestjs.io/docs/getting-started#using-babel" target="_blank" rel="noopener noreferrer" class="">@Jest - Using Babel</a></strong></p>
</blockquote>
<p>由於 Jest 原生支持 <strong>CommonJS</strong> 模塊，當使用 <strong>ES6</strong>、<strong>TypeScript</strong> 或其他編譯語言時，我們需要 <strong><code>babel-jest</code></strong> 來轉換這些語法。</p>
<p><strong>對應配置</strong></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// jest.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">transform</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">'^.+\\.(ts|tsx|js|jsx)$'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'babel-jest'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 使用 babel-jest 轉換 ts, tsx, js, jsx 文件</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>注意事項：</strong></p>
<ul>
<li class="">實際測試發現，沒有裝也沒有發生錯誤，這是因為目前開發環境使用的 Node.js V20 以支援 ES6 語法，因此在本專案可以不裝。</li>
<li class="">若同時使用 <code>ts-jest</code> ，建議配置中設定 <strong>transform</strong> 的檔案類型不要包括 <strong>.ts</strong>, <strong>.tsx</strong> 檔，原因參考 <code>ts-jest</code> 章節</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ts-jest"><strong>ts-jest</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#ts-jest" class="hash-link" aria-label="Direct link to ts-jest" title="Direct link to ts-jest" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://jestjs.io/docs/getting-started#via-ts-jest" target="_blank" rel="noopener noreferrer" class="">@Jest - TypeScript-Via - ts-jest</a></strong><br>
<strong><a href="https://kulshekhar.github.io/ts-jest/" target="_blank" rel="noopener noreferrer" class="">@ts-jest</a></strong></p>
</blockquote>
<p>雖然 Jest 支援透過 <strong>Babel (babel-jest)</strong> 編譯 TypeScript 測試檔，但畢竟 Babel 僅純粹負責將 TS 轉成 JS，因此執行測試時 Jest 並不會檢查測到試程式碼內的型別錯誤。<strong><code>ts-jest</code></strong> 是一個讓 Jest 能夠測試 TypeScript 程式碼 的 Jest 插件，他不僅能讓 Jest 能夠轉譯 TypeScript 程式碼，還會在執行測試之前先對 TypeScript 程式碼進行靜態類型檢查，幫助發現潛在的型別錯誤。</p>
<p>當執行測試時，檢查到測試檔案的型別錯誤，會顯示如下錯誤訊息，並且在該行終止該測試檔案的測試任務，不繼續往下執行該測試檔案中的其他測試。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">src/test/sum.test.ts:9:17 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.</span><br></div></code></pre></div></div>
<p><strong>對應配置</strong></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// jest.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">preset</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ts-jest'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意事項</div><div class="admonitionContent_BuS1"><p>經過測試後發現，若專案中同時安裝並配置 <code>babel-jest</code> 轉譯 <strong>.ts</strong>, <strong>.tsx</strong> 檔案， <strong>babel-jest</strong> 會幫我們把測試 <strong>.ts</strong>, <strong>.tsx</strong> 檔編譯成 <strong>.js</strong> 檔，因此即便測試程式碼中有型別錯誤的地方，執行 yarn test 後也沒有特別被標出來。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="jest-environment-jsdom"><strong>jest-environment-jsdom</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#jest-environment-jsdom" class="hash-link" aria-label="Direct link to jest-environment-jsdom" title="Direct link to jest-environment-jsdom" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://jestjs.io/docs/configuration#testenvironmentoptions-object" target="_blank" rel="noopener noreferrer" class="">@Jest - Configuring - testEnvironmentOptions</a></strong></p>
</blockquote>
<p><strong><code>jest-environment-jsdom</code></strong> 是一個測試環境，模擬了一個瀏覽器的 API 環境。它允許 Jest 在 Node.js 環境中運行，但提供了像 <strong>window</strong> 或 <strong>document</strong> 這樣的瀏覽器全局變數。使用 <strong>jest-environment-jsdom</strong> 進行 Jest 測試時，不需要額外安裝 <code>jsdom</code>，因為 <strong>jest-environment-jsdom</strong> 已經包含了 <strong>jsdom</strong>。</p>
<p>該套件主要是用來提供 <strong><code>@testing-library</code></strong> 一個模擬瀏覽器的執行環境。<strong>jest-environment-jsdom</strong> 確保了 <strong>render</strong> 和 <strong>fireEvent</strong> 這些 <strong>@testing-library</strong> 提供的方法能在一個類似瀏覽器的環境中正確執行。</p>
<p><strong>對應配置</strong></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// jest.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">testEnvironment</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'jsdom'</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 預設為 'node'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意事項</div><div class="admonitionContent_BuS1"><p>如果測試中涉及到 DOM 或需要模擬瀏覽器行為，但沒有設置 <code>jsdom</code> 環境，那麼測試可能會因為缺少必要的瀏覽器物件而失敗。例如，試圖訪問 <strong>document</strong> 或 <strong>window</strong> 可能會導致錯誤，因為在 Node 環境中這些物件是不存在的。</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="jest-svg-transformer"><strong>jest-svg-transformer</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#jest-svg-transformer" class="hash-link" aria-label="Direct link to jest-svg-transformer" title="Direct link to jest-svg-transformer" translate="no">​</a></h3>
<p><strong><code>jest-svg-transformer</code></strong> 用於處理 SVG 檔案的導入，在測試時，Jest 需要適當地處理這些 SVG 檔案，以避免導入錯誤或解析問題。它能將 SVG 檔案轉換成 React 元件，使其在 Jest 測試中可以正常工作。</p>
<p><strong>對應配置</strong></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// jest.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">'\\.svg$'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'jest-svg-transformer'</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 也可將 SVG 檔案映射到 jest-svg-transformer</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="identity-obj-proxy"><strong>identity-obj-proxy</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#identity-obj-proxy" class="hash-link" aria-label="Direct link to identity-obj-proxy" title="Direct link to identity-obj-proxy" translate="no">​</a></h3>
<p><strong><code>identity-obj-proxy</code></strong> 用於模擬 CSS 模塊的導入。當我們在 React 元件中使用 CSS 模塊（CSS-in-JS）時，直接導入 CSS 檔案將導致 Jest 測試因無法解析 CSS 而失敗。<strong>identity-obj-proxy</strong> 允許 Jest 忽略 CSS 導入的具體內容，而是將導入的 CSS 檔案視為一個空的物件或代理，從而不影響測試的運行。</p>
<p><strong>對應配置</strong></p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// jest.config.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">'\\.(css|less|scss|sass)$'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'identity-obj-proxy'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 對 CSS 模塊進行模擬</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vscode-jestvscode-plugin"><strong>vscode-jest(vscode plugin)</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#vscode-jestvscode-plugin" class="hash-link" aria-label="Direct link to vscode-jestvscode-plugin" title="Direct link to vscode-jestvscode-plugin" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://github.com/jest-community/vscode-jest" target="_blank" rel="noopener noreferrer" class="">@vscode-jest</a></strong></p>
</blockquote>
<p>於 <code>.vscode/settings.json</code> 設定 jest plugin</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"jest.virtualFolders"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"frontend"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"rootPath"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./frontend"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"backend"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"rootPath"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./backend"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"jest.runMode"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"on-demand"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="react-testing-library-相關套件介紹與配置"><strong>React Testing Library 相關套件介紹與配置</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#react-testing-library-%E7%9B%B8%E9%97%9C%E5%A5%97%E4%BB%B6%E4%BB%8B%E7%B4%B9%E8%88%87%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to react-testing-library-相關套件介紹與配置" title="Direct link to react-testing-library-相關套件介紹與配置" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-libraryreact"><strong>@testing-library/react</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#testing-libraryreact" class="hash-link" aria-label="Direct link to testing-libraryreact" title="Direct link to testing-libraryreact" translate="no">​</a></h3>
<blockquote>
<p><a href="https://testing-library.com/docs/react-testing-library/intro" target="_blank" rel="noopener noreferrer" class=""><strong>@React Testing Library</strong></a></p>
</blockquote>
<p><strong><code>@testing-library/react</code></strong> 基於&nbsp;<strong><code>@testing-library/dom</code></strong>，主要功能為：</p>
<ul>
<li class=""><strong>render()</strong>: 在測試檔案中渲染 React 元件</li>
<li class=""><strong>screen()</strong>: 在測試模擬環境中查找渲染元件的 DOM 元素</li>
<li class=""><strong>fireEvent()</strong>：支持事件模擬，允許模擬用戶觸發如點擊、輸入等事件</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-libraryjest-dom"><strong>@testing-library/jest-dom</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#testing-libraryjest-dom" class="hash-link" aria-label="Direct link to testing-libraryjest-dom" title="Direct link to testing-libraryjest-dom" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://testing-library.com/docs/react-testing-library/migrate-from-enzyme/#simulate-user-events" target="_blank" rel="noopener noreferrer" class="">@React Testing Library - Simulate user events?</a></strong><br>
<strong><a href="https://testing-library.com/docs/ecosystem-jest-dom" target="_blank" rel="noopener noreferrer" class="">@jest-dom</a></strong></p>
</blockquote>
<p><strong>Jest</strong> 本身就提供了許多<strong>斷言器(Matchers)</strong>，如： <code>toBe()</code>, <code>toMatch()</code> 等。<br>
<strong><code>@testing-library/jest-dom</code></strong> 擴充了 Jest 更多的跟 <strong>DOM</strong> 有關的斷言方法，如：<code>toBeDisabled()</code>, <code>toBeVisible()</code>, <code>toHaveTextContent()</code> 等。</p>
<p><strong>對應配置</strong></p>
<p>為了讓所有測試檔案皆可以使用 <strong>@testing-library/jest-dom</strong> 擴充的斷言器，通常建議在 Jest 的設置文件設置建立 Jest 測試境前直接引入這些斷言器</p>
<ul>
<li class="">創建 <code>jest.setup.ts</code>，：</li>
</ul>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'@testing-library/jest-dom'</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<ul>
<li class="">更新 <code>tsconfig.json</code>：</li>
</ul>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token string-property property" style="color:#36acaa">"include"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"src/**/*"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".eslintrc.js"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"jest.setup.ts"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<ul>
<li class="">更新 <code>jest.config.js</code> 以包括設置檔案：</li>
</ul>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">setupFilesAfterEnv</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'&lt;rootDir&gt;/jest.setup.js'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">testEnvironment</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'jsdom'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>注意事項</div><div class="admonitionContent_BuS1"><ul>
<li class="">
<p>在 <code>jest.config.js</code> 檔案中使用 <code>&lt;rootDir&gt;</code> 是 Jest 提供的一個特殊標記，它代表專案的根目錄，當 Jest 執行階段，它會自動將 <code>&lt;rootDir&gt;</code> 取代為實際的根目錄路徑。</p>
</li>
<li class="">
<p><strong>@testing-library/jest-dom</strong> 與 <strong>@testing-library/dom</strong> 雖然名稱看上去很相近，但實際的功能卻完全不同。</p>
<ul>
<li class=""><code>@testing-library/dom</code> 是 Testing Library 的核心，主要功能為提供 DOM 查找</li>
<li class=""><code>@testing-library/jest-dom</code> 則是擴充 Jest 的斷言方法，提供 Jest 更多與 DOM 相關的 Matcher</li>
</ul>
</li>
</ul></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-libraryuser-event"><strong>@testing-library/user-event</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#testing-libraryuser-event" class="hash-link" aria-label="Direct link to testing-libraryuser-event" title="Direct link to testing-libraryuser-event" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://testing-library.com/docs/react-testing-library/migrate-from-enzyme/#simulate-user-events" target="_blank" rel="noopener noreferrer" class="">@React Testing Library - What is React Testing Library?</a></strong><br>
<strong><a href="https://github.com/testing-library/user-event" target="_blank" rel="noopener noreferrer" class="">user-event</a></strong></p>
</blockquote>
<p><strong><code>@testing-library/user-event</code></strong> 是一個用於模擬真實使用者行為的 library，它提供了一個更高層級的 API 來模擬使用者操作。這個套件可以模擬更接近真實使用者行為的事件，比如輸入文字、點選連結、選擇選項等，它會觸發相應的事件，包括鼠標事件和鍵盤事件，並考慮這些事件的順序。且當模擬事件時，會等待 DOM 的更新完成，使得測試更加可靠和符合實際的應用場景。相對於 <strong>@testing-library/react</strong> 中的 <strong>fireEvent</strong>，提供了更接近於使用者的交互方式。</p>
<ul>
<li class=""><strong>fireEvent</strong> 直接在 DOM 元素上觸發指定的事件，而不模擬使用者實際操作的前置步驟或後續事件。例如，使用 <strong>fireEvent.click</strong> 只會觸發點擊事件，而不會模擬鼠標移動到元素上、按下和釋放的過程。</li>
<li class=""><strong>userEvent</strong> 則模擬整個使用者操作的流程，例如在使用 <strong>userEvent.type</strong> 模擬輸入文字時，會觸發鍵盤按下、鍵盤釋放等一系列事件。</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="msw-介紹與配置"><strong>MSW 介紹與配置</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#msw-%E4%BB%8B%E7%B4%B9%E8%88%87%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to msw-介紹與配置" title="Direct link to msw-介紹與配置" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="建立-api-模擬"><strong>建立 API 模擬</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#%E5%BB%BA%E7%AB%8B-api-%E6%A8%A1%E6%93%AC" class="hash-link" aria-label="Direct link to 建立-api-模擬" title="Direct link to 建立-api-模擬" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://mswjs.io/docs/concepts/response-resolver" target="_blank" rel="noopener noreferrer" class="">@MSW - Response resolver</a></strong></p>
</blockquote>
<p>在 <code>src/mocks</code> 目錄下建立一個 <code>handlers.ts</code> 檔案來定義模擬後端的 API Response。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// example</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> http</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">HttpResponse</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'msw'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handlers </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Describe what request to intercept...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  http</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/greeting'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ...and how to respond to it.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">HttpResponse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'Hello world!'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="開發環境配置"><strong>開發環境配置</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 開發環境配置" title="Direct link to 開發環境配置" translate="no">​</a></h3>
<blockquote>
<p><strong><a href="https://mswjs.io/docs/integrations/browser" target="_blank" rel="noopener noreferrer" class="">@MSW - Browser integration</a>
<a href="https://mswjs.io/docs/cli/init" target="_blank" rel="noopener noreferrer" class="">@MSW - init</a></strong></p>
</blockquote>
<p>在<strong>開發環境</strong>中，前端程式碼是在<code>瀏覽器</code>中運行的。 <strong>MSW</strong> 提供的 <strong>Service Worker</strong> 能夠在瀏覽器環境中自然地攔截和修改 HTTP 請求，且可以直接在瀏覽器中看到模擬 API 的及時反應。在產品規劃初期的開發階段，當 API 的 payload 與回傳規格尚未定義好之前，前端工程師可以使用 <strong>MSW</strong> 的 <strong>Service Worker</strong> 作為 <strong>mock API Server</strong>，在後端 API ready 之前就可以先行開發資料流與狀態管理的邏輯，可以節省許多等待的時間。以下將一步步講解 <strong>MSW</strong> 用於開發環境的配置流程。</p>
<p><strong>1. 初始化 Service Worker</strong></p>
<p>當我們希望前端在開發環境下使用 <strong>MSW</strong> 模擬後端 API 時，為了使 <strong>MSW</strong> 正常工作，我們需要將 <strong>Service Worker</strong> 腳本（<code>mockServiceWorker.js</code>）放置到應用的可訪問目錄中。這是因為 <strong>Service Worker</strong> 必須作為靜態資源被客戶端瀏覽器訪問和註冊。以下指令會自動將 <code>mockServiceWorker.js</code>從 <strong>MSW</strong> 的 npm 包複製到指定的公共目錄（在本專案中為 <code>./public</code> 目錄）。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx msw init ./public --save</span><br></div></code></pre></div></div>
<p>也可以將此命令添加到 <strong>package.json</strong> 的 scripts 部分，以便輕鬆重複使用：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">"scripts": {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "init:msw": "msw init ./public --save"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p><strong>2. 設置 Service Worker</strong></p>
<p>在 <code>src/mocks</code> 目錄下建立一個 <code>browser.ts</code> 檔案來設定 <strong>Service Worker</strong>。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> setupWorker </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'msw/browser'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> handlers </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./handlers'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> worker </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setupWorker</span><span class="token punctuation" style="color:#393A34">(</span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">handlers</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>3. Conditionally enable mocking</strong></p>
<p>若要在開發的執行階段使用 <strong>Service Worker</strong> 模擬後端 API，我們需要透過呼叫 <code>worker.start()</code> 來啟動 worker。我們可以透過設定環境變數來控制是否要啟動 <strong>Server Worker</strong>，如下列範例：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">enableMocking</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token maybe-class-name">ServiceWorkerRegistration</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">|</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">undefined</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">NODE_ENV</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'development'</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> process</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">ENABLE_MOCKING_SERVER</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'true'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> worker </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">import</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'./mocks/browser'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// `worker.start()` returns a Promise that resolves</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// once the Service Worker is up and ready to intercept requests.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> worker</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">start</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> container </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">getElementById</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'root'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">container</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> root </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createRoot</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">container</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">enableMocking</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    root</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">AppProviders</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">App</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">AppProviders</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'root container element not found'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="測試環境配置"><strong>測試環境配置</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#%E6%B8%AC%E8%A9%A6%E7%92%B0%E5%A2%83%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 測試環境配置" title="Direct link to 測試環境配置" translate="no">​</a></h3>
<p>與<strong>開發環境</strong>和<strong>正式環境</strong>不同的是，在<strong>測試環境</strong>中，測試通常不是在真實的<code>瀏覽器環境</code>中執行，而是在 <code>Node.js 環境</code>中運行。因此，使用 <strong>Node.js 版的 MSW</strong> 才能夠再執行測試時模擬 API 請求和回應。以下將一步步講解 MSW 用於測試環境的配置流程。</p>
<p><strong>設置測試環境 Mock Server</strong></p>
<p>在 <code>src/mocks</code> 目錄下創建  <code>server.ts</code>初始化 MSW 的 <strong>Node Mock Server</strong>。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> setupServer </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'msw/node'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> handlers </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'./handlers'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> server </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setupServer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">handlers</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><strong>配置 <code>jest.setup.ts</code> 文件</strong></p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@testing-library/jest-dom"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> server </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./mocks/server"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">beforeAll</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> server</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">afterEach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> server</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">resetHandlers</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">afterAll</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> server</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">close</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary>配置 MOCK SERVER 時遇到的 BUG [TL;DR]</summary><div><div class="collapsibleContent_i85q"><div><div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>配置 Mock Server 時遇到的 Bug</div><div class="admonitionContent_BuS1"><p><strong>錯誤訊息</strong></p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Test suite failed to run</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ReferenceError: TextEncoder is not defined</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">&gt; 1 | import { setupServer } from 'msw/node';</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | ^</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  2 | import { handlers } from './handlers';</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  3 |</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  4 | export const server = setupServer(...handlers);</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  at Object.&lt;anonymous&gt; (node_modules/@mswjs/interceptors/src/utils/bufferUtils.ts:1:17)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  at Object.&lt;anonymous&gt; (node_modules/@mswjs/interceptors/lib/node/chunk-K74ZLSG6.js:5:24)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  at Object.&lt;anonymous&gt; (node_modules/@mswjs/interceptors/lib/node/interceptors/XMLHttpRequest/index.js:3:24)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  at Object.&lt;anonymous&gt; (node_modules/msw/src/node/SetupServerApi.ts:3:43)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  at Object.&lt;anonymous&gt; (src/mocks/server.ts:1:1)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  at Object.&lt;anonymous&gt; (jest.setup.ts:2:1)</span><br></div></code></pre></div></div><p><strong>原因：參考以下連結</strong></p><ul>
<li class=""><strong><a href="https://mswjs.io/docs/migrations/1.x-to-2.x/#requestresponsetextencoder-is-not-defined-jest" target="_blank" rel="noopener noreferrer" class="">@MSW - Frequent issues - <code>Request</code>/<code>Response</code>/<code>TextEncoder</code>&nbsp;is not defined (Jest)</a></strong></li>
</ul><p>根據官方解法配置  <code>jest.polyfill.js</code> 又出現以下錯誤：</p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ReferenceError: ReadableStream is not defined</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  20 |</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  21 | const { Blob, File } = require('node:buffer');</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">&gt; 22 | const { fetch, Headers, FormData, Request, Response } = require('undici');</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     |                                                         ^</span><br></div></code></pre></div></div><p><strong>解法：參考以下連結</strong></p><ul>
<li class=""><strong><a href="https://github.com/mswjs/msw/discussions/1934" target="_blank" rel="noopener noreferrer" class="">ReferenceError: ReadableStream is not defined&nbsp;#1934</a></strong></li>
</ul><p>在 <code>jest.setup.ts</code> 同層級目錄下新增 <code>jest.polyfill.js</code></p><div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// jest.polyfill.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// https://mswjs.io/docs/migrations/1.x-to-2.x/#requestresponsetextencoder-is-not-defined-jest</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">/**</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * @note The block below contains polyfills for Node.js globals</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * required for Jest to function when running JSDOM tests.</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * These HAVE to be require's and HAVE to be in this exact</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * order, since "undici" depends on the "TextEncoder" global API.</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> *</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * Consider migrating to a more modern test runner if</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> * you don't want to deal with this.</span><br></div><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"> */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">TextDecoder</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">TextEncoder</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">ReadableStream</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'node:util'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token known-class-name class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">defineProperties</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">globalThis</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">TextDecoder</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">TextDecoder</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">TextEncoder</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">TextEncoder</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ReadableStream: { value: ReadableStream },</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">Blob</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">File</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'node:buffer'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> fetch</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">Headers</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">FormData</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">Request</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">require</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'undici'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token known-class-name class-name">Object</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">defineProperties</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">globalThis</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">fetch</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> fetch</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">writable</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">Blob</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Blob</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">File</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">File</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">Headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Headers</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">FormData</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">FormData</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">Request</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Request</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">Response</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">Response</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div><p>配置 <code>jest.config.ts</code></p><div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">module</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">exports</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">setupFiles</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'./jest.polyfills.js'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">preset</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'ts-jest'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">testEnvironment</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'jsdom'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">moduleNameMapper</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">'^~/(.*)$'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'&lt;rootDir&gt;/src/$1'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 匹配在 tsconfig.json 中定義的路徑別名。</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">'\\.svg$'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'jest-svg-transformer'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 將 SVG 檔案映射到 jest-svg-transformer</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string-property property" style="color:#36acaa">'\\.(css|less|scss|sass)$'</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'identity-obj-proxy'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 對 CSS 模塊進行模擬</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">testEnvironmentOptions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// https://mswjs.io/docs/migrations/1.x-to-2.x/#cannot-find-module-mswnode-jsdom</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token literal-property property" style="color:#36acaa">customExportConditions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">''</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">setupFilesAfterEnv</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'&lt;rootDir&gt;/jest.setup.ts'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">collectCoverageFrom</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 指定收集覆蓋率的檔案範圍</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">'src/**/*.{js,jsx,ts,tsx}'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">'!src/**/*.d.ts'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">coverageDirectory</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'coverage'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div></div></div></div></div></div></details>
<details class="details_lb9f alert alert--info details_b_Ee" data-collapsed="true"><summary>MSW V2 + Jest + JSDOM: Network error Bug [TL;DR]</summary><div><div class="collapsibleContent_i85q"><div><div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>MSW V2 + Jest + JSDOM: Network error Bug</div><div class="admonitionContent_BuS1"><p><strong>問題：</strong></p><ul>
<li class=""><strong><a href="https://github.com/mswjs/msw/discussions/1915" target="_blank" rel="noopener noreferrer" class="">Network error with Axios in Jest tests with MSW 2.x&nbsp;#1915</a></strong></li>
<li class=""><strong><a href="https://github.com/mswjs/msw/issues/1916" target="_blank" rel="noopener noreferrer" class="">Undici 6.x - Request/Response/TextEncoder is not defined (Jest) no longer works</a></strong></li>
</ul><p><strong>原因：</strong></p><ul>
<li class="">與上述 <strong><code>配置 Mock Server 時遇到的 Bug</code></strong> 問題相關，主要跟 Jest 內部程式碼有關，不是 MSW 本身的問題，可參考 <a href="https://github.com/mswjs/msw/issues/1916#issuecomment-1908544946%E3%80%82" target="_blank" rel="noopener noreferrer" class="">https://github.com/mswjs/msw/issues/1916#issuecomment-1908544946。</a></li>
</ul><p><strong>解法：</strong></p><ul>
<li class="">MSW 作者建議使用 <strong>Vitest</strong> 取代 <strong>Jest</strong>，暫時不考慮。</li>
<li class="">根據上述 <strong><code>配置 Mock Server 時遇到的 Bug</code></strong> 問題解法的配置，將 undici 從 v6 降版到 v5</li>
</ul></div></div></div></div></div></details>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="搭配-lint-staged-在-pre-commit-階段對更動的程式碼執行自動測試"><strong>搭配 lint-staged 在 pre-commit 階段對更動的程式碼執行自動測試</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#%E6%90%AD%E9%85%8D-lint-staged-%E5%9C%A8-pre-commit-%E9%9A%8E%E6%AE%B5%E5%B0%8D%E6%9B%B4%E5%8B%95%E7%9A%84%E7%A8%8B%E5%BC%8F%E7%A2%BC%E5%9F%B7%E8%A1%8C%E8%87%AA%E5%8B%95%E6%B8%AC%E8%A9%A6" class="hash-link" aria-label="Direct link to 搭配-lint-staged-在-pre-commit-階段對更動的程式碼執行自動測試" title="Direct link to 搭配-lint-staged-在-pre-commit-階段對更動的程式碼執行自動測試" translate="no">​</a></h2>
<p>導入測試的其中一個目的就是為了防止工程師之間 co-work 時不小心改壞了別人負責的功能。假設我目前所開發的功能需要依賴同事A的某個函數，在沒有寫測試的情況下，同事A把函數的參數介面或回傳值換掉都很可能造成我開發的功能無法正常運作；相反地，如果我有為我開發的功能寫測試，當同事A改動該函數而造成錯誤就能在測試中被提早發現。</p>
<p>然而，以上測試所帶來的好處的前提是建立在同事A有乖乖地在 commit 程式碼前先跑一次專案的測試，若同事A忘記執行專案測試，有問題的程式碼還是會被推送到雲端。此外，每次 commit 都執行所有的測試檔案顯然不是個有效率的工作流程。</p>
<p><strong><code>Husky + lint-staged</code></strong> 這兩個工具可以幫助我們建立 <strong>Git pre-commit hooks</strong> ，針對與提交的程式碼相關的所有測試檔案進行自動化測試，這將能大大地改善開發過程中<strong>改A錯B</strong>的情況，且能節省執行不必要的測試的時間。關於 <strong>Husky</strong> 與 <strong>lint-staged</strong> 的安裝與更詳細的介紹，在我之前寫的 <a href="https://notes.boshkuo.com/blog/eslint-prettier-lintstaged" target="_blank" rel="noopener noreferrer" class="">整合 ESLint, Prettier, Husky, lint-staged 維護團隊開發品質</a> 這篇文章中有比較詳細的介紹，歡迎對如何配置專案統一程式碼風格與規範有興趣的讀者閱讀這篇文章。</p>
<p><strong>設置 <code>.lintstagedrc</code></strong>:</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string-property property" style="color:#36acaa">"frontend/src/**/*.{js,jsx,ts,tsx}"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">    </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">	  </span><span class="token string" style="color:#e3116c">"jest --findRelatedTests --bail --config='./frontend/jest.config.ts' "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">	</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>—bail</strong>: 遇到運行失敗的測驗立即退出</li>
<li class="">—<strong>findRelatedTests</strong>: 用來運行與指定的源文件相關的測試。這個命令接受一個或多個文件路徑（空格分隔），Jest 會識別出哪些測試檔案是這些源文件的相關測試，並只執行這些測試。適合與 pre-commit hook 工具搭配使用</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/react-ts-jest-rtl-msw-setup#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><strong><a href="https://jestjs.io/" target="_blank" rel="noopener noreferrer" class="">Jest</a></strong></li>
<li class=""><strong><a href="https://kulshekhar.github.io/ts-jest/" target="_blank" rel="noopener noreferrer" class="">ts-jest</a></strong></li>
<li class=""><strong><a href="https://testing-library.com/" target="_blank" rel="noopener noreferrer" class="">Testing Library</a></strong></li>
<li class=""><strong><a href="https://ithelp.ithome.com.tw/articles/10322782" target="_blank" rel="noopener noreferrer" class="">[Day 05] React 測試安裝介紹 ( CRA / Vite / Next.js )</a></strong></li>
<li class=""><strong><a href="https://dev.to/ivadyhabimana/setup-jest-and-react-testing-library-in-a-react-project-a-step-by-step-guide-1mf0" target="_blank" rel="noopener noreferrer" class="">Setup Jest and React Testing Library in a React project | a step-by-step guide</a></strong></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="React" term="React"/>
        <category label="Testing" term="Testing"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[以 React + Express 為範例探討 Web 應用中的檔案上傳機制]]></title>
        <id>https://notes.boshkuo.com/en/blog/upload_file</id>
        <link href="https://notes.boshkuo.com/en/blog/upload_file"/>
        <updated>2024-04-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本篇技術筆記將以 React + Express 為例，探討 Web 應用中的檔案上傳機制。本文章將介紹如何使用 React 搭配 react-dropzone 套件建立前端上傳介面，以及如何使用 Express 搭配 multer 套件在後端處理上傳的檔案。]]></summary>
        <content type="html"><![CDATA[<p>最近在工作遇到了需要處理上傳檔案的需求，借此機會完整地學習 Web 前後端處理檔案上傳的機制。本篇筆記以一個簡單範例輔助學習，該範例提供使用者在畫面中上傳任意圖片格式的單一圖片檔案，並儲存於伺服器端的特定資料夾中。使用者可以透過拖放的方式將檔案上傳至網站上，亦可以用點擊的方式打開資料總管，選擇要上傳的檔案。</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>本篇筆記專注於探討 React 專案中檔案上傳的各種機制與資料結構。本文不會深入討論元件的樣式問題。本筆記提供的範例程式碼將專注於功能實現。</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例"><strong>範例</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="Direct link to 範例" title="Direct link to 範例" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1713414104/Docusaurus%20Blog/Web/%E6%AA%94%E6%A1%88%E4%B8%8A%E5%82%B3%E6%A9%9F%E5%88%B6/upload-app_espdgo.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-code-react"><strong>Example Code: React</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#example-code-react" class="hash-link" aria-label="Direct link to example-code-react" title="Direct link to example-code-react" translate="no">​</a></h3>
<ul>
<li class="">使用 Vite 在 <strong>frontend</strong> 資料夾建立一個新的 React 專案</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn create vite frontend --template react-ts</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd frontend</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn</span><br></div></code></pre></div></div>
<ul>
<li class="">安裝必要套件</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn add @mui/material @emotion/react @emotion/styled</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn add axios react-dropzone</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>frontend/src/App.tsx</strong></li>
</ul>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useState </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"react"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useDropzone </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"react-dropzone"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">axios</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"axios"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">LinearProgress</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@mui/material/LinearProgress"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Box</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Typography</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Paper</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Button</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Snackbar</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@mui/material"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token maybe-class-name">App</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access maybe-class-name" style="color:#d73a49">FC</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setFile</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useState</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name">File </span><span class="token generic-function generic class-name operator" style="color:#393A34">|</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name keyword" style="color:#00009f">null</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">fileInfo</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setFileInfo</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useState</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">{</span><span class="token generic-function generic class-name"></span><br></div><div class="token-line" style="color:#393A34"><span class="token generic-function generic class-name">    name</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">string</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">;</span><span class="token generic-function generic class-name"></span><br></div><div class="token-line" style="color:#393A34"><span class="token generic-function generic class-name">    size</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">number</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">;</span><span class="token generic-function generic class-name"></span><br></div><div class="token-line" style="color:#393A34"><span class="token generic-function generic class-name">    type</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">string</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">;</span><span class="token generic-function generic class-name"></span><br></div><div class="token-line" style="color:#393A34"><span class="token generic-function generic class-name">    preview</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">string</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">;</span><span class="token generic-function generic class-name"></span><br></div><div class="token-line" style="color:#393A34"><span class="token generic-function generic class-name">  </span><span class="token generic-function generic class-name punctuation" style="color:#393A34">}</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name operator" style="color:#393A34">|</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name keyword" style="color:#00009f">null</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">uploadProgress</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setUploadProgress</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useState</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name builtin">number</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">openSnackbar</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setOpenSnackbar</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">useState</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name builtin">boolean</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">simulateUploadProgress</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">setUploadProgress</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> interval </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">setInterval</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setUploadProgress</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">oldProgress</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">oldProgress </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token function" style="color:#d73a49">clearInterval</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">interval</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> oldProgress </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> oldProgress </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> oldProgress </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 每次增加 10%，可根據需求調整</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 每 100 毫秒更新一次進度</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handleFileChange</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">acceptedFiles</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">File</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">acceptedFiles</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> selectedFile </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> acceptedFiles</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">setFile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">selectedFile</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">setFileInfo</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> selectedFile</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      size</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> selectedFile</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">size</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> selectedFile</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      preview</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">createObjectURL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">selectedFile</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">simulateUploadProgress</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 模擬上傳進度</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">uploadFile</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">alert</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"請先選擇一個檔案。"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> formData </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">FormData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    formData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"file"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"file"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"formData"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> formData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    axios</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"http://localhost:5000/upload"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> formData</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">then</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setOpenSnackbar</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// 顯示上傳成功的通知</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">catch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">error</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handleCloseSnackbar</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">setOpenSnackbar</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> getRootProps</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> getInputProps </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useDropzone</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// accept: { "image/jpeg": [], "image/png": [] },</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    accept</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token string-property property" style="color:#36acaa">"image/*"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    onDrop</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> handleFileChange</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">onDragEnter</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setUploadProgress</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function-variable function" style="color:#d73a49">onFileDialogOpen</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setUploadProgress</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"getRootProps"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getRootProps</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"getInputProps"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">getInputProps</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Box</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">        display</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"flex"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">        flexDirection</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"column"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">        alignItems</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"center"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">        paddingTop</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"20px"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">      </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">    </span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Paper</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">elevation</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">3</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> width</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"50%"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> padding</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"20px"</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Box</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag spread punctuation" style="color:#393A34">{</span><span class="token tag spread operator" style="color:#393A34">...</span><span class="token tag spread method function property-access" style="color:#d73a49">getRootProps</span><span class="token tag spread punctuation" style="color:#393A34">(</span><span class="token tag spread punctuation" style="color:#393A34">)</span><span class="token tag spread punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> border</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"2px dashed #ccc"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> p</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript number" style="color:#36acaa">3</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> mb</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript number" style="color:#36acaa">3</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag spread punctuation" style="color:#393A34">{</span><span class="token tag spread operator" style="color:#393A34">...</span><span class="token tag spread method function property-access" style="color:#d73a49">getInputProps</span><span class="token tag spread punctuation" style="color:#393A34">(</span><span class="token tag spread punctuation" style="color:#393A34">)</span><span class="token tag spread punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> textAlign</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"center"</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            將檔案拖曳到這裡，或點擊選擇檔案</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Box</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">LinearProgress</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag attr-value" style="color:#e3116c">determinate</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">value</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">uploadProgress</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> marginTop</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"10px"</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">uploadProgress </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> fileInfo </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Box</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">            </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">              display</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"flex"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">              alignItems</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"center"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">              justifyContent</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"center"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">              paddingTop</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"20px"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">            </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">img</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">              </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">fileInfo</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">preview</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">              </span><span class="token tag attr-name" style="color:#00a4db">alt</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag attr-value" style="color:#e3116c">Preview</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">              </span><span class="token tag attr-name" style="color:#00a4db">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">                maxWidth</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"100px"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">                maxHeight</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"100px"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">                marginRight</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"10px"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag script language-javascript" style="color:#00009f">              </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">            </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> flexGrow</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript number" style="color:#36acaa">1</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">fileInfo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> mx</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript number" style="color:#36acaa">1</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">fileInfo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">type</span><span class="token punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> mx</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript number" style="color:#36acaa">1</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">              </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">fileInfo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">size</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toFixed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"> KB</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">            </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Typography</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Box</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">variant</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag attr-value" style="color:#e3116c">contained</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">uploadFile</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag attr-name" style="color:#00a4db">sx</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f"> width</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"100%"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">,</span><span class="token tag script language-javascript" style="color:#00009f"> marginTop</span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"10px"</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">          上傳檔案</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Paper</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Snackbar</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">open</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">openSnackbar</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">autoHideDuration</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript number" style="color:#36acaa">6000</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">onClose</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleCloseSnackbar</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">        </span><span class="token tag attr-name" style="color:#00a4db">message</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag attr-value" style="color:#e3116c">上傳成功！</span><span class="token tag attr-value punctuation" style="color:#393A34">'</span><span class="token tag" style="color:#00009f"></span><br></div><div class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">      </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Box</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">App</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-code-express"><strong>Example Code: Express</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#example-code-express" class="hash-link" aria-label="Direct link to example-code-express" title="Direct link to example-code-express" translate="no">​</a></h3>
<ul>
<li class="">在 <strong>backend</strong> 資料夾建立一個新的 Express 專案</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mkdir backend</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd backend</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn init -y</span><br></div></code></pre></div></div>
<ul>
<li class="">安裝必要套件</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">yarn add express multer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">yarn add @types/node @types/express @types/multer ts-node-dev typescript -D</span><br></div></code></pre></div></div>
<ul>
<li class="">在 backend 目錄中，創建 <strong>tsconfig.json</strong>：</li>
</ul>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"compilerOptions"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"target"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"esnext"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"module"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"commonjs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"moduleResolution"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"node"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"outDir"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"./dist"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"esModuleInterop"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"strict"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"include"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"src/**/*"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<ul>
<li class="">打開 <strong>backend/package.json</strong> 文件，並在其中添加如下的 <strong>scripts</strong> 部分：</li>
</ul>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"scripts"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"start"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ts-node-dev src/index.ts"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"build"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tsc"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"serve"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"node dist/index.js"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>backend/src/index.ts</strong></li>
</ul>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">express</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"express"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">multer</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"multer"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">cors</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cors"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">path</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"path"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> app </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">express</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> port </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5000</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">use</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">cors</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> storage </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> multer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">diskStorage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">destination</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> file</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cb</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">cb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"uploads/"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">filename</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> file</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cb</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 先轉換檔案名編碼</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> correctedName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token maybe-class-name">Buffer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">from</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">originalname</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"latin1"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"utf8"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> ext </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">extname</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">correctedName</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> baseName </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">basename</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">correctedName</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> ext</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">cb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">baseName</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">-</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">Date</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation function" style="color:#d73a49">now</span><span class="token template-string interpolation punctuation" style="color:#393A34">(</span><span class="token template-string interpolation punctuation" style="color:#393A34">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">ext</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> upload </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">multer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> storage </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/upload"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> upload</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">single</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"file"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Received file: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">req</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">file</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation">path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"檔案上傳成功"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">file</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">path</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">400</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"檔案上傳失敗"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">port</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Server is running at http://localhost:</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">port</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前端react上傳檔案機制"><strong>前端(React)上傳檔案機制</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E5%89%8D%E7%AB%AFreact%E4%B8%8A%E5%82%B3%E6%AA%94%E6%A1%88%E6%A9%9F%E5%88%B6" class="hash-link" aria-label="Direct link to 前端react上傳檔案機制" title="Direct link to 前端react上傳檔案機制" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="html-元素-input-typefile"><strong>HTML 元素 &lt;input type="file"&gt;</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#html-%E5%85%83%E7%B4%A0-input-typefile" class="hash-link" aria-label="Direct link to html-元素-input-typefile" title="Direct link to html-元素-input-typefile" translate="no">​</a></h3>
<p>在 Web 應用中，檔案上傳功能的實現通常依賴於 <code>&lt;input type="file"&gt;</code> 這個 HTML 元素。當使用這種類型的輸入時，瀏覽器會提供一個按鈕，用戶點擊後會打開標準的系統文件選擇對話框，用戶可以從本地電腦中選擇一個或多個文件。</p>
<blockquote>
<p>參閱 <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#using_file_inputs" target="_blank" rel="noopener noreferrer" class=""><strong>@MDN-&lt;input type="file"&gt;</strong></a></p>
</blockquote>
<ul>
<li class=""><strong>基本用法</strong></li>
</ul>
<p>基本的文件輸入元素看起來像這樣：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">file</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">myfile</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">name</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">myfile</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>這個元素會在網頁上創建一個按鈕，用戶點擊後可以選擇文件。</p>
<ul>
<li class=""><strong>接受特定類型的文件</strong></li>
</ul>
<p><strong>&lt;input&gt;</strong> 標籤的 <code>accept</code> 屬性允許我們限制用戶可以選擇的文件類型。例如，如果只希望用戶上傳圖片，可以這樣設定：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">file</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">imagefile</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">name</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">imagefile</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">accept</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">image/*</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>多文件選擇</strong></li>
</ul>
<p>如果希望允許用戶一次選擇多個文件，可以添加 <code>multiple</code> 屬性：</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">file</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">multiplefiles</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">name</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">files</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">accept</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">image/*</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">multiple</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="檔案資料類型file-filelist"><strong>檔案資料類型：File, FileList</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E6%AA%94%E6%A1%88%E8%B3%87%E6%96%99%E9%A1%9E%E5%9E%8Bfile-filelist" class="hash-link" aria-label="Direct link to 檔案資料類型file-filelist" title="Direct link to 檔案資料類型file-filelist" translate="no">​</a></h3>
<p>當用戶通過 <strong>&lt;input type="file"&gt;</strong> 選擇了文件後，這些文件將會作為 DOM 元素的一部分存儲在其 <code>files</code> 屬性中。我們在前端可以寫一個事件處理函數綁定於當用戶選擇了新的檔案時觸發的 onChange 事件，這樣我們就可以從事件目標(<strong>event.target</strong>)中獲取檔案資料進行進一步的操作。</p>
<ul>
<li class=""><strong>FileList</strong></li>
</ul>
<p>當使用者選擇檔案後，事件的 <strong>target.files</strong> 屬性是一個 <strong><code>FileList</code></strong> 物件。<strong><code>FileList</code></strong> 是一個類似陣列的物件，包含了所有選擇的檔案。每個檔案都被封裝為一個 <strong><code>File</code></strong> 物件。</p>
<blockquote>
<p>參閱 <strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList" target="_blank" rel="noopener noreferrer" class="">@MDN-FileList</a></strong></p>
</blockquote>
<ul>
<li class=""><strong>File</strong></li>
</ul>
<p><strong><code>File</code></strong> 物件包含在 <strong><code>FileList</code></strong> 中，提供了檔案的基本訊息，它繼承自 <strong><code>Blob</code></strong>，並添加了檔案相關的屬性，像是：</p>
<ul>
<li class=""><strong>name</strong>：檔案的名稱（包括擴展名）。</li>
<li class=""><strong>size</strong>：檔案的大小，以 byte 為單位。</li>
<li class=""><strong>type</strong>：檔案的 MIME 類型，例如 "image/jpeg"。</li>
<li class=""><strong>lastModified</strong>：檔案最後修改的時間戳，以毫秒為單位。這個數值表示自 1970 年 1 月 1 日午夜 (00:00 UTC) 至文件最後修改時間的毫秒數。</li>
<li class=""><strong>lastModifiedDate</strong>：檔案最後修改的日期，為一個 <strong>Date</strong> 物件。這個屬性是對 <strong>lastModified</strong> 毫秒時間戳的一個更直觀的日期表示，便於讀取和顯示。</li>
</ul>
<blockquote>
<p>參閱 <strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/File" target="_blank" rel="noopener noreferrer" class="">@MDN-File</a> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" target="_blank" rel="noopener noreferrer" class="">@MDN-Blob</a> <a href="https://developer.mozilla.org/en-US/docs/Glossary/MIME_type" target="_blank" rel="noopener noreferrer" class="">@MDN-MIME type</a></strong></p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-react-dropzone-以-drag--drop-方式上傳檔案"><strong>使用 react-dropzone 以 Drag &amp; Drop 方式上傳檔案</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E4%BD%BF%E7%94%A8-react-dropzone-%E4%BB%A5-drag--drop-%E6%96%B9%E5%BC%8F%E4%B8%8A%E5%82%B3%E6%AA%94%E6%A1%88" class="hash-link" aria-label="Direct link to 使用-react-dropzone-以-drag--drop-方式上傳檔案" title="Direct link to 使用-react-dropzone-以-drag--drop-方式上傳檔案" translate="no">​</a></h3>
<p>比起傳統的點擊上傳按鈕，現在的 Web 應用程式通常允許用戶直接從他們的檔案管理器中把文件拖到網頁的指定區域，本文章提供的範例中使用了 <strong><code>react-dropzone</code></strong> 套件來實現這一功能。 <strong>react-dropzone</strong> 封裝了很多細節和事件處理，讓開發者可以輕鬆地實現拖放功能，它提供了一個簡單的 <strong>useDropzone API</strong> ，讓開發者可以輕鬆的設定並管理拖放區域所需的所有屬性和方法。</p>
<blockquote>
<p>詳細API 使用方法參閱 <strong><a href="https://react-dropzone.js.org/#src" target="_blank" rel="noopener noreferrer" class="">@react-dropzone</a></strong></p>
</blockquote>
<p>以下簡單介紹 <strong><code>useDropzone</code></strong> 的幾個常用的參數定義與回傳</p>
<ul>
<li class="">選項<!-- -->
<ul>
<li class=""><strong>accept</strong>：指定接受的檔案類型。</li>
<li class=""><strong>onDrop</strong>：當檔案被拖放到區域時的回呼，參數為接受和拒絕的檔案。</li>
<li class=""><strong>multiple</strong>：是否允許選擇多個檔案。</li>
<li class=""><strong>minSize、maxSize</strong>：設定檔案的最小和最大大小。</li>
<li class=""><strong>disabled</strong>：停用拖放功能。</li>
<li class=""><strong>maxFiles</strong>：限制用戶一次性能夠上傳的最大文件數量</li>
<li class=""><strong>noClick</strong>：當設置為 <strong>true</strong> 時，拖放區域將不會響應點擊事件，這意味著用戶不能通過點擊來打開文件選擇對話框。</li>
<li class=""><strong>noDrag</strong>：當設置為 <strong>true</strong> 時，禁用拖放功能。這意味著用戶不能將文件拖到指定區域來上傳，但仍可以通過點擊來觸發文件選擇。</li>
</ul>
</li>
<li class=""><strong>回傳</strong>
<ul>
<li class=""><strong>getRootProps()</strong>：返回應用於拖放區域的 props，包括 onClick, onDrop 等事件處理器等</li>
<li class=""><strong>getInputProps()</strong>：返回應用於 <strong>&lt;input type="file"&gt;</strong> 的 props，如 <code>{accept: 'image/*', multiple: true, type: 'file', style: {display:&nbsp;'none'}, onChange: ƒ,&nbsp;…}</code>。</li>
<li class=""><strong>acceptedFiles</strong>：一個包含所有被接受檔案的陣列。</li>
<li class=""><strong>fileRejections</strong>：一個包含所有被拒絕檔案的陣列，每個對象包含檔案和錯誤資訊。</li>
<li class=""><strong>open()</strong>：一個函數，可以用來打開檔案選擇器，通常用於自訂點選行為時。</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="預覽圖片"><strong>預覽圖片</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E9%A0%90%E8%A6%BD%E5%9C%96%E7%89%87" class="hash-link" aria-label="Direct link to 預覽圖片" title="Direct link to 預覽圖片" translate="no">​</a></h3>
<p>當我們需要在畫面上顯示一張圖片時，會需要在前端程式碼中添加一個 <strong>&lt;img&gt;</strong> 元素， 並指定元素 <strong>src</strong> 為圖片的來源 URL 。這個 URL 可以來自後端伺服器或公開的網路資源。</p>
<blockquote>
<p>然而，在前端在把照片檔案上傳至後端之前，這個 URL 由誰來提供呢？</p>
</blockquote>
<p><strong>答案就是前端自己！</strong></p>
<p>前端可以透過以下兩種常見的方法來獲取本地圖片檔案的URL，以便在上傳前進行預覽</p>
<ul>
<li class="">
<p><strong>方法一: 使用 FileReader</strong></p>
<blockquote>
<p>參閱 <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader" target="_blank" rel="noopener noreferrer" class=""><strong>@MDN-FileReader</strong></a></p>
</blockquote>
<p><strong><code>FileReader</code></strong> 是 Web API 的一部分，它允許 Web 應用非同步讀取儲存在用戶系統上的文件（或原始數據）內容，無論是一般檔案(<strong><code>File</code></strong> 物件)或是二進位資料(<strong><code>Blob</code></strong> 物件)。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useState </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">ImagePreview</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">preview</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setPreview</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handleFileChange</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">event</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> file </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">target</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">files</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> reader </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">FileReader</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      reader</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method-variable function-variable method function property-access" style="color:#d73a49">onload</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token function" style="color:#d73a49">setPreview</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">reader</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">result</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 使用讀取到的 Base64 URL</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      reader</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">readAsDataURL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// 讀取文件並轉換成 Base64 編碼的 URL</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setPreview</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">file</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onChange</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleFileChange</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">preview </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">img</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">preview</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">alt</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">Preview</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">ImagePreview</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<ul>
<li class=""><code>readAsDataURL</code> 方法讀取文件並將其轉換為一個 Base64 編碼的 URL</li>
<li class="">當文件讀取完成後，<strong>onload</strong> 事件被觸發，<strong>FileReader.result</strong> 屬性將包含所讀取檔案的Data URI，並將其設為預覽圖的 source URL圖片顯示在畫面上。</li>
</ul>
</li>
<li class="">
<p><strong>方法二: URL.createObjectURL</strong></p>
<blockquote>
<p>參閱 <strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static#examples" target="_blank" rel="noopener noreferrer" class="">@URL: createObjectURL() static method</a></strong></p>
</blockquote>
<p><code>URL.createObjectURL</code> 是一個創建一個包含指定文件內容的 URL 的方法。這個 URL 的壽命為新窗口的生命周期，直到被撤銷(<code>URL.revokeObjectURL</code>)。這個方法比 <strong>FileReader</strong> 更為高效，因為它不需要讀取文件內容，直接生成一個指向文件的 URL。</p>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> useState</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> useEffect </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'react'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">ImagePreview</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">preview</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> setPreview</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">useEffect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 這個 effect 會在組件卸載時執行</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> preview </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">revokeObjectURL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">preview</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">preview</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:#d73a49">handleFileChange</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">event</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> file </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">target</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">files</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> objectUrl </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">URL</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">createObjectURL</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setPreview</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">objectUrl</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token function" style="color:#d73a49">setPreview</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">file</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onChange</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleFileChange</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">preview </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">img</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">src</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">preview</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">alt</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">Preview</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">default</span><span class="token plain"> </span><span class="token maybe-class-name">ImagePreview</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-formdata-資料格式傳輸資料給後端"><strong>使用 FormData 資料格式傳輸資料給後端</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E4%BD%BF%E7%94%A8-formdata-%E8%B3%87%E6%96%99%E6%A0%BC%E5%BC%8F%E5%82%B3%E8%BC%B8%E8%B3%87%E6%96%99%E7%B5%A6%E5%BE%8C%E7%AB%AF" class="hash-link" aria-label="Direct link to 使用-formdata-資料格式傳輸資料給後端" title="Direct link to 使用-formdata-資料格式傳輸資料給後端" translate="no">​</a></h3>
<p>在網路的世界裡，服務之間透過 protocol 來規範資料的傳遞規則、形式。HTTP 協議定義了多種數據傳輸的內容類型(Content-Types)。<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types" target="_blank" rel="noopener noreferrer" class="">MIME Type</a>&nbsp;定義了傳輸格式的種類：</p>
<ul>
<li class=""><code>Content-Type: application/json</code>&nbsp;代表 request 內容是 JSON</li>
<li class=""><code>Content-Type: image/png</code>&nbsp;代表 request 內容是圖片檔</li>
</ul>
<p>其中，在上傳文件時，HTTP 支持一種叫做 <code>"multipart/form-data"</code> 的內容類型，這是一種專門用於優化二進位數據傳輸(如文件上傳)的編碼格式。<code>multipart/form-data</code> 會將表單數據分成多個部分（每部分可能包含不同類型的數據），每個部分都由一個 boundary 分隔符分隔開來，這種方法特別適用於傳送文本或二進位資料。</p>
<p>前端要傳送 Content-Type 為 <strong>multipart/form-data</strong> 的 request 有兩種方法：</p>
<ol>
<li class=""><strong>使用 &lt;form&gt; 元素</strong></li>
</ol>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">form</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">method</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">POST</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">action</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">/upload</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">method</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">POST</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">text</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">name</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">username</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">file</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f">  </span><span class="token tag attr-name" style="color:#00a4db">name</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">avatar</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Submit</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain-text"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">form</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p>瀏覽器會將&lt;form&gt; 元素內的 &lt;input&gt; 元素序列化，並 以 POST 方法送出&nbsp;<code>Content-Type: multipart/form-data</code>&nbsp;的 HTTP 請求</p>
<ol>
<li class=""><strong>使用 FormData 物件</strong></li>
</ol>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// 從 HTML 表單元素自動擷取資料</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> formElement </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">querySelector</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'form'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> formData </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">FormData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">formElement</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// 或者手動創建並添加數據</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> formData </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">FormData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">formData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'username'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'JohnDoe'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">formData</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'age'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">30</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>FormData</code> 是一個 JavaScript 的 Web API，用於構建表單資料的集合，它可以封裝資料並準備傳送到伺服器。這包括文本字段（如姓名、郵件等）和二進制文件（如圖片上傳）。使用 <code>FormData</code> 最大的優勢是可以讓我們不需要手動處理表單資料的編碼問題，且如果將 FormData 作為 payload，瀏覽器會自動幫我們以&nbsp;<code>multipart/form-data</code>&nbsp;的形式傳送。</p>
<blockquote>
<p>參閱 <strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" target="_blank" rel="noopener noreferrer" class="">@MDN-FormData</a></strong></p>
</blockquote>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="後端express接收與處理檔案資料機制"><strong>後端(Express)接收與處理檔案資料機制</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E5%BE%8C%E7%AB%AFexpress%E6%8E%A5%E6%94%B6%E8%88%87%E8%99%95%E7%90%86%E6%AA%94%E6%A1%88%E8%B3%87%E6%96%99%E6%A9%9F%E5%88%B6" class="hash-link" aria-label="Direct link to 後端express接收與處理檔案資料機制" title="Direct link to 後端express接收與處理檔案資料機制" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multer"><strong>multer</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#multer" class="hash-link" aria-label="Direct link to multer" title="Direct link to multer" translate="no">​</a></h3>
<p><strong><code>multer</code></strong> 是一個用於處理 <code>multipart/form-data</code> 類型資料的 middleware 套件，它通常與 Express.js 框架搭配使用，用於處理上傳檔案。以下是 <strong>multer</strong> 的一些主要功能：</p>
<ol>
<li class=""><strong>檔案處理</strong>: 開發者可以自訂檔案儲存的位置以及檔案名稱等設定。Multer 提供不同的儲存引擎(storage engines)，如 <strong>DiskStorage</strong> 用於儲存檔案於磁碟，以及 <strong>MemoryStorage</strong> 用於儲存檔案於記憶體中。</li>
<li class=""><strong>檔案過濾</strong>: 可以設定過濾器(filter),允許或拒絕特定類型的檔案上傳。</li>
<li class=""><strong>錯誤處理</strong>: Multer 提供了多個中介軟體來處理不同的錯誤,如檔案大小限制、MIME 類型錯誤等。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multer-設定檔案處理方法"><strong>multer 設定檔案處理方法</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#multer-%E8%A8%AD%E5%AE%9A%E6%AA%94%E6%A1%88%E8%99%95%E7%90%86%E6%96%B9%E6%B3%95" class="hash-link" aria-label="Direct link to multer-設定檔案處理方法" title="Direct link to multer-設定檔案處理方法" translate="no">​</a></h3>
<p>本範例中，我們直接將文件直接存儲到伺服器端特定的資料夾，可以使用 <strong><code>multer</code></strong> 的 <strong><code>diskStorage</code></strong> 選項來配置存儲方式。</p>
<ul>
<li class=""><code>destination</code> 設置了文件應該被存儲的目錄</li>
<li class=""><code>filename</code> 用於生成存儲時使用的文件名</li>
</ul>
<p>這兩者都是接收 (<code>req, file, cb</code>) 做為參數的函數。</p>
<ul>
<li class=""><code>req</code>: 代表了 HTTP 請求，包含了請求參數、內容、HTTP標頭等。<!-- -->
<ul>
<li class=""><strong>req.file</strong>：當使用 <strong>single()</strong> 中間件處理單個文件時，上傳的文件資訊會存儲在 req.file。</li>
<li class=""><strong>req.files</strong>：當使用 <strong>array()、fields()</strong> 或 <strong>any()</strong>  middleware 處理多個文件時，上傳的文件資訊會存儲在 req.files。</li>
</ul>
</li>
<li class=""><code>file</code>: 包含了關於上傳文件的各種資訊<!-- -->
<ul>
<li class=""><strong>file.originalname</strong>：用戶上傳的文件的原始名稱。</li>
<li class=""><strong>file.mimetype</strong>：文件的 MIME 類型。</li>
<li class=""><strong>file.size</strong>：文件的大小（字節為單位）。</li>
<li class=""><strong>file.fieldname</strong>：表單中的字段名。</li>
<li class=""><strong>file.buffer</strong>：當使用 <strong>memoryStorage</strong> 存儲時，文件的數據會存儲在 file.buffer。</li>
</ul>
</li>
<li class=""><code>cb</code>: 回調函數，這個函數通常有兩個參數：<!-- -->
<ul>
<li class=""><strong>第一個參數</strong>：錯誤物件（如果有錯誤發生）。如果一切正常，應該傳遞 <strong><code>null</code></strong>。</li>
<li class=""><strong>第二個參數</strong>：要傳回給 <strong>multer</strong> 的結果。例如，在 <code>filename</code> 函數中，你需要傳遞計算出的文件名；在 <code>destination</code> 函數中，你需要傳遞目標存儲路徑。</li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="在-api-route-嵌入接收檔案方法"><strong>在 api route 嵌入接收檔案方法</strong><a href="https://notes.boshkuo.com/en/blog/upload_file#%E5%9C%A8-api-route-%E5%B5%8C%E5%85%A5%E6%8E%A5%E6%94%B6%E6%AA%94%E6%A1%88%E6%96%B9%E6%B3%95" class="hash-link" aria-label="Direct link to 在-api-route-嵌入接收檔案方法" title="Direct link to 在-api-route-嵌入接收檔案方法" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> upload </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">multer</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> storage </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">app</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/upload"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> upload</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">single</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"file"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">log</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Received file: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">req</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">file</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation property-access">path</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"檔案上傳成功"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">path</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> req</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">file</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">path</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">else</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    res</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">400</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">message</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"檔案上傳失敗"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>在這裡，<code>upload.single('file')</code> 是 <strong>multer</strong> 的一個 middleware，<strong>multer</strong> 將會處理其中的 <strong>multipart/form-data</strong>，尋找名為 <strong>file</strong> 的文件，並嘗試讀取這個文件的數據，將其儲存至配置的目標位置（磁盤或內存），然後將文件的信息放入 <strong>req.file</strong> 以供後續的中間件或路由處理器使用。</p>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="Web" term="Web"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[2023 菜鳥軟體工程師的 Mac 開發工具配置]]></title>
        <id>https://notes.boshkuo.com/en/blog/macbook-setup-2023</id>
        <link href="https://notes.boshkuo.com/en/blog/macbook-setup-2023"/>
        <updated>2023-05-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本篇文章記錄了我於 2023 入職 Web 軟體工程師工作後，在 Macbook 工作機上的開發環境配置。本篇文章包含了日常使用的軟體和一系列開發工具的介紹。包括開發者工具、Homebrew 套件管理器、iTerm2 終端機模擬器、Oh My Zsh 和 Powerlevel10k等等。]]></summary>
        <content type="html"><![CDATA[<p>最近拿到了公司配的 macbook pro 13(2022) 工作機，趁這個機會整理一下我常使用的軟題和工具，順便記錄一下一台新 mac 安裝各種工具的過程中遇到的小問題以及我做了哪些配置。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="日常軟體"><strong>日常軟體</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#%E6%97%A5%E5%B8%B8%E8%BB%9F%E9%AB%94" class="hash-link" aria-label="Direct link to 日常軟體" title="Direct link to 日常軟體" translate="no">​</a></h2>
<ul>
<li class=""><strong>桌面應用程式</strong>
<ul>
<li class=""><a href="https://www.google.com/intl/zh-TW/chrome/?brand=YTUH&amp;gclid=CjwKCAjwo7iiBhAEEiwAsIxQET9t-AtfzDWJHyrwCUty28fINWUtRR3CFUjxGWxbT7fZrqmqFx_yjRoC78AQAvD_BwE&amp;gclsrc=aw.ds" target="_blank" rel="noopener noreferrer" class="">Chrome</a></li>
<li class=""><a href="https://www.microsoft.com/zh-tw/edge?form=MA13FJ" target="_blank" rel="noopener noreferrer" class="">Microsoft Edge</a></li>
<li class=""><a href="https://www.notion.so/product?gspk=ZG9uZ3RyYW5xdWFuZzk1NTU&amp;gsxid=qncUEfz735Q0&amp;pscd=affiliate.notion.so&amp;utm_medium=dongtranquang9555&amp;utm_source=affl&amp;gclid=CjwKCAjwo7iiBhAEEiwAsIxQEUwyMGPtLTA3pouNeURWsLZFw75sEH6G2zyjs_7HZWFJtS7hemjOjxoCckEQAvD_BwE" target="_blank" rel="noopener noreferrer" class="">Notion</a></li>
<li class=""><a href="https://line.me/zh-hant/" target="_blank" rel="noopener noreferrer" class="">Line</a></li>
<li class=""><a href="https://open.spotify.com/" target="_blank" rel="noopener noreferrer" class="">Spotify</a></li>
<li class=""><a href="https://slack.com/intl/zh-tw" target="_blank" rel="noopener noreferrer" class="">Slack</a></li>
<li class=""><a href="https://macpaw.com/cleanmymac" target="_blank" rel="noopener noreferrer" class="">CleanMyMac X (Free)</a></li>
<li class=""><a href="https://rectangleapp.com/" target="_blank" rel="noopener noreferrer" class="">Rectangle</a></li>
<li class=""><a href="https://www.logitech.com/zh-tw/software/logi-options-plus.html" target="_blank" rel="noopener noreferrer" class="">Logi Options+</a></li>
<li class=""><a href="https://alt-tab-macos.netlify.app/" target="_blank" rel="noopener noreferrer" class="">AltTab</a></li>
</ul>
</li>
<li class=""><strong>Chrome extensions</strong>
<ul>
<li class=""><a href="https://chrome.google.com/webstore/detail/google-translate/aapbdbdomjkkjkaonfhkkikfgjllcleb?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Google 翻譯</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/grammarly-grammar-checker/kbfnbcaeplbcioakkpcpgfkobkghlhen?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Grammarly: Grammar Checker and Writing App</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/language-reactor/hoombieeljmmljlkjmnheibnpciblicm?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Language Reactor</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Lighthouse</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">React Developer Tools</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Redux DevTools</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/save-to-notion/ldmmifpegigmeammaeckplhnjbbpccmm?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Save to Notion</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Vue.js devtools</a></li>
<li class=""><a href="https://chrome.google.com/webstore/detail/wappalyzer-technology-pro/gppongmhjkpfnbhagpmjfkannfbllamg?hl=zh-TW" target="_blank" rel="noopener noreferrer" class="">Wappalyzer - Technology profiler</a></li>
</ul>
</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="開發工具與軟體"><strong>開發工具與軟體</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#%E9%96%8B%E7%99%BC%E5%B7%A5%E5%85%B7%E8%88%87%E8%BB%9F%E9%AB%94" class="hash-link" aria-label="Direct link to 開發工具與軟體" title="Direct link to 開發工具與軟體" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="developer-tool"><strong>Developer tool</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#developer-tool" class="hash-link" aria-label="Direct link to developer-tool" title="Direct link to developer-tool" translate="no">​</a></h3>
<p>在 terminal 輸入 <code>git</code>，由於尚未安裝 developer tool，系統會跳出安裝提示。安裝好後可參考這篇 <a href="https://git-scm.com/book/zh-tw/v2/%E9%96%8B%E5%A7%8B-%E5%88%9D%E6%AC%A1%E8%A8%AD%E5%AE%9A-Git" target="_blank" rel="noopener noreferrer" class="">1.6 開始 - 初次設定 Git</a> 設定本地 Git Global 使用者資訊</p>
<p><img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1683448878/Docusaurus%20Blog/%E9%96%8B%E7%99%BC%E5%B7%A5%E5%85%B7/Mac%20%E9%96%8B%E7%99%BC%E5%B7%A5%E5%85%B7%E9%85%8D%E7%BD%AE/developer_tool_x9e52t.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="homebrew"><strong>Homebrew</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#homebrew" class="hash-link" aria-label="Direct link to homebrew" title="Direct link to homebrew" translate="no">​</a></h3>
<p><a href="https://brew.sh/index_zh-tw" target="_blank" rel="noopener noreferrer" class="">Homebrew</a> 是一個針對 macOS 系統的套件管理器，讓使用者可以方便地透過終端機下載、安裝、更新和刪除各種軟體套件，而且可以自動解決依賴問題，使用起來非常方便。要使用 Homebrew 下載一個套件，首先需要先安裝 Homebrew，可以透過在終端機輸入以下指令來安裝：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"</span><br></div></code></pre></div></div>
<p>安裝完記得輸入下面兩行將Homebrew 加入 PATH</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 將 /opt/homebrew/bin/brew 的環境設定寫入到 /Users/boshkuo/.zprofile 檔案中以便在每次使用 Bash shell 時都能自動載入這些設定。</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">(echo; echo 'eval "$(/opt/homebrew/bin/brew shellenv)"') &gt;&gt; /Users/{Your user name}/.zprofile</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 將 /opt/homebrew/bin/brew 的環境設定指令執行，以設定相應的環境變數。這樣可以使得這些環境變數在當前的 Bash shell 環境中生效</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">eval "$(/opt/homebrew/bin/brew shellenv)"</span><br></div></code></pre></div></div>
<p>完成後，就可以使用 brew 指令來下載各種套件了。例如，如果要下載 wget 這個套件，可以在終端機輸入以下指令：</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">brew install wget</span><br></div></code></pre></div></div>
<p>這個指令會告訴 Homebrew 下載並安裝 wget 套件，如果你之前已經下載過 wget，Homebrew 會自動檢查並更新至最新版本。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="iterm2"><strong>iTerm2</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#iterm2" class="hash-link" aria-label="Direct link to iterm2" title="Direct link to iterm2" translate="no">​</a></h3>
<p><a href="https://iterm2.com/" target="_blank" rel="noopener noreferrer" class="">iTerm2</a> 是一款在 macOS 上常用的終端應用程式，相較於 macOS 內建的 Terminal，iTerm2 提供了更多的自訂選項和功能，使得使用者能夠更方便地進行終端操作。iTerm2 提供了更多的外觀自訂選項。使用者可以自由調整字體、背景圖片、文字顏色等等，讓終端機的外觀更加符合個人喜好。此外，iTerm2 還支援主題和佈局的儲存和分享，讓使用者可以方便地分享和使用自己喜歡的風格。<br>
<!-- -->除了上面提的的功能外，iTerm2 還有一個吸引我的地方，就是他的分頁和視窗管理功能，iTerm2 可以實現 tmux 提供的分割視窗、同時開啟多個終端機等功能，可以參考我之前寫的 <a href="https://notes.boshkuo.com/docs/DevTools/Terminal/iterm-shortcut-key" target="_blank" rel="noopener noreferrer" class="">iTerm2 技巧大公開：提高效率的快捷鍵</a> 這篇文章。</p>
<p>以下是我的 iTerm2 各個配置的參考資料：</p>
<ul>
<li class=""><strong>Status bar:</strong>
<ul>
<li class=""><a href="https://luiyongsheng.medium.com/better-iterm-system-status-bar-421f8944dd5b" target="_blank" rel="noopener noreferrer" class="">Better iTerm: System status bar</a></li>
</ul>
</li>
<li class=""><strong>Color Schemes:</strong>
<ul>
<li class=""><a href="https://iterm2colorschemes.com/" target="_blank" rel="noopener noreferrer" class="">Iterm2-color-schemes</a></li>
<li class=""><a href="https://github.com/josean-dev/dev-environment-files/blob/main/coolnight.itermcolors" target="_blank" rel="noopener noreferrer" class="">coolnight.itermcolors</a></li>
<li class=""><a href="https://www.youtube.com/watch?v=CF1tMjvHDRA&amp;t=191s&amp;ab_channel=JoseanMartinez" target="_blank" rel="noopener noreferrer" class="">How To Make Your Boring Mac Terminal So Much Better</a></li>
</ul>
</li>
<li class=""><strong>快捷鍵:</strong>
<ul>
<li class=""><a href="https://blog.csdn.net/skyyws/article/details/78480132" target="_blank" rel="noopener noreferrer" class="">Mac下iTerm2光标按照单词快速移动设置</a></li>
</ul>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="oh-my-zsh-and-powerlevel10k"><strong>Oh My Zsh and Powerlevel10k</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#oh-my-zsh-and-powerlevel10k" class="hash-link" aria-label="Direct link to oh-my-zsh-and-powerlevel10k" title="Direct link to oh-my-zsh-and-powerlevel10k" translate="no">​</a></h3>
<p><a href="https://ohmyz.sh/" target="_blank" rel="noopener noreferrer" class="">Oh My Zsh</a> 是一個基於 Zsh shell 的強大框架，它為終端應用程式提供了豐富的特性和工具。使用 Oh My Zsh，我們可以輕鬆地自定義終端的外觀和行為，並且能夠更加高效地完成終端操作。Oh My Zsh 提供了多種主題供使用者選擇，其中最多人使用的就是 <a href="https://github.com/romkatv/powerlevel10k#manual" target="_blank" rel="noopener noreferrer" class="">Powerlevel10k</a>，它不僅外觀精美，而且還有許多實用的功能，如顯示當前 git 分支、提示錯誤等等。此外，Oh My Zsh 還提供了許多有用的插件，如自動完成、命令調用、快速切換目錄等等。使用這些插件，我們可以更加快速地完成許多終端操作，節省寶貴的時間。</p>
<ul>
<li class="">
<p><strong>Oh My Zsh + Powerlevel10k 完整安裝與配置教學資源</strong></p>
<ul>
<li class="">關於 Oh My Zsh 與 Powerlevel10k 的安裝與配置，我主要都看這篇文章：<a href="https://holychung.github.io/2020/12/24/%E3%80%90%E5%88%86%E4%BA%AB%E3%80%91Oh-My-Zsh-powerlevel10k-%E5%BF%AB%E9%80%9F%E6%89%93%E9%80%A0%E5%A5%BD%E7%9C%8B%E5%A5%BD%E7%94%A8%E7%9A%84-command-line-%E7%92%B0%E5%A2%83/" target="_blank" rel="noopener noreferrer" class="">【分享】Oh My Zsh + powerlevel10k 快速打造好看好用的 command line 環境</a>，看完這篇文章應該就會對如何安裝 Oh My Zsh 與 Powerlevel10k 以及要怎麼配置設定文件有個完整的概念。</li>
</ul>
</li>
<li class="">
<p><strong>Oh My Zsh plugin</strong></p>
<ul>
<li class=""><strong><a href="https://github.com/zsh-users/zsh-completions" target="_blank" rel="noopener noreferrer" class="">zsh-completions</a> :</strong> follow <a href="https://github.com/zsh-users/zsh-completions#oh-my-zsh" target="_blank" rel="noopener noreferrer" class="">oh-my-zsh</a> 安裝方法</li>
<li class=""><strong><a href="https://github.com/zsh-users/zsh-autosuggestions" target="_blank" rel="noopener noreferrer" class="">zsh-autosuggestions</a> :</strong> follow <a href="https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md#oh-my-zsh" target="_blank" rel="noopener noreferrer" class="">oh-my-zsh</a> 安裝方法</li>
</ul>
</li>
<li class="">
<p><strong>安裝紀錄</strong></p>
<ul>
<li class="">
<p>安裝 Oh My Zsh:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>安裝 Powerlevel10k (官方建議搭配 Oh My Zsh 的安裝方式)</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>修改 zshell 的 .zshrc 設定檔，在空白處加入以下這行，把主題換成剛剛安裝的 powerlevel10k。</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ZSH_THEME="powerlevel10k/powerlevel10k"</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>重新載入 zsh 配置文件</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">source ~/.zshrc</span><br></div></code></pre></div></div>
</li>
</ul>
</li>
<li class="">
<p><strong>Powerlevel10k 配置紀錄</strong></p>
<ul>
<li class="">
<p>輸入下列指令來配置喜歡的 theme</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">p10k configure</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>由於我的 Node.js 版本號以及 icon 沒有顯示出來，因此我修改了 .p10k.zsh 檔案，手動調整了一些 powerlevel10k 的設定，手動開啟顯示 node_version，調整 Node.js icon 與顏色(icon 可以去 <a href="https://www.nerdfonts.com/cheat-sheet" target="_blank" rel="noopener noreferrer" class="">Cheat Sheet</a> 找)</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 顯示 node_version()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    node_version            # node.js version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">)</span><br></div></code></pre></div></div>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">##############################[ node_version: node.js version ]###############################</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Node version color.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=240</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">typeset -g POWERLEVEL9K_NODE_VERSION_BACKGROUND=230</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Show node version only when in a directory tree containing package.json.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Custom icon.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># typeset -g POWERLEVEL9K_NODE_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">typeset -g POWERLEVEL9K_NODE_ICON='\uF898'</span><br></div></code></pre></div></div>
</li>
</ul>
</li>
<li class="">
<p><strong>Issue</strong></p>
<ul>
<li class="">剛配置完成時，我打開 VSCode 的 terminal，發現 icon 沒有辦法正確顯示還有出現亂碼字等問題。這主要是因為 VSCode 終端機使用的字體與 Powerlevel10k 不相容，可以 follow 這篇教學文章 <a href="https://sasacode.wordpress.com/2021/06/18/%E5%9C%A8vscode-%E8%A3%9D%E5%80%8B%E6%BC%82%E4%BA%AE%E7%9A%84-terminal-%E4%BB%8B%E9%9D%A2-zsh-powerlevel10k/" target="_blank" rel="noopener noreferrer" class="">在VSCode 裝個漂亮的 Terminal 介面- zsh + powerlevel10k</a> 把 VSCode terminal 的字體改成 <code>MesloLGS NF</code> 就可以解決亂碼問題了～</li>
</ul>
</li>
</ul>
<p>我的 terminal 設置完成後的大概長這樣 ～
<img decoding="async" loading="lazy" src="https://res.cloudinary.com/djtoo8orh/image/upload/v1683454022/Docusaurus%20Blog/%E9%96%8B%E7%99%BC%E5%B7%A5%E5%85%B7/Mac%20%E9%96%8B%E7%99%BC%E5%B7%A5%E5%85%B7%E9%85%8D%E7%BD%AE/terminal_eyarwl.png" alt="" class="ui-theme-mdxcomponents-img-styles-image cursor-zoom-in focus-visible:[outline:2px_solid_var(--ifm-color-primary)] focus-visible:outline-offset-4 img_ev3q" role="button" tabindex="0" aria-label="Open image preview"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="others"><strong>Others</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#others" class="hash-link" aria-label="Direct link to others" title="Direct link to others" translate="no">​</a></h3>
<ul>
<li class="">
<p><a href="https://www.docker.com/" target="_blank" rel="noopener noreferrer" class="">Docker</a></p>
</li>
<li class="">
<p><a href="https://www.postman.com/" target="_blank" rel="noopener noreferrer" class="">PostMan</a></p>
</li>
<li class="">
<p><a href="https://github.com/nvm-sh/nvm" target="_blank" rel="noopener noreferrer" class="">NVM</a></p>
<ul>
<li class=""><a href="https://titangene.github.io/article/nvm.html" target="_blank" rel="noopener noreferrer" class="">nvm：安裝、切換不同 Node.js 版本的管理器</a> (看這篇就足夠了)</li>
</ul>
</li>
<li class="">
<p><a href="https://docs.conda.io/en/latest/miniconda.html" target="_blank" rel="noopener noreferrer" class="">miniConda</a></p>
<ul>
<li class="">設置取消自動激活 conda base 環境</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">conda config --set auto_activate_base false</span><br></div></code></pre></div></div>
<p>有些教學寫可以在 ~/.condarc 加入這行：<code>auto_activate_base: false</code>，若 <del>/.condarc 檔案不存在則需要用上面的 conda config 指令，</del>/.condarc 檔案就會被建立出來了</p>
</li>
</ul>
<br>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="visual-studio-code"><strong>Visual Studio Code</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#visual-studio-code" class="hash-link" aria-label="Direct link to visual-studio-code" title="Direct link to visual-studio-code" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="推薦安裝套件"><strong>推薦安裝套件</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#%E6%8E%A8%E8%96%A6%E5%AE%89%E8%A3%9D%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="Direct link to 推薦安裝套件" title="Direct link to 推薦安裝套件" translate="no">​</a></h3>
<ul>
<li class=""><a href="https://vscodethemes.com/" target="_blank" rel="noopener noreferrer" class="">VS Code Themes</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens" target="_blank" rel="noopener noreferrer" class="">GitLens</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph" target="_blank" rel="noopener noreferrer" class="">Git Graph</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint" target="_blank" rel="noopener noreferrer" class="">ESLint</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" target="_blank" rel="noopener noreferrer" class="">Prettier</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments" target="_blank" rel="noopener noreferrer" class="">Better Comments</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag" target="_blank" rel="noopener noreferrer" class="">Auto Rename Tag</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-close-tag" target="_blank" rel="noopener noreferrer" class="">Auto Close Tag</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=steoates.autoimport" target="_blank" rel="noopener noreferrer" class="">Auto Import</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight" target="_blank" rel="noopener noreferrer" class="">Color Highlight</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow" target="_blank" rel="noopener noreferrer" class="">indent-rainbow</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components" target="_blank" rel="noopener noreferrer" class="">vscode-styled-components</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=vincaslt.highlight-matching-tag" target="_blank" rel="noopener noreferrer" class="">Highlight Matching Tag</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker" target="_blank" rel="noopener noreferrer" class="">Code Spell Checker</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one" target="_blank" rel="noopener noreferrer" class="">Markdown All in One</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode" target="_blank" rel="noopener noreferrer" class="">IntelliCode</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python" target="_blank" rel="noopener noreferrer" class="">Python</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker" target="_blank" rel="noopener noreferrer" class="">Docker</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=alefragnani.project-manager" target="_blank" rel="noopener noreferrer" class="">Project Manager</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight" target="_blank" rel="noopener noreferrer" class="">TODO Highlight</a></li>
<li class=""><a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack" target="_blank" rel="noopener noreferrer" class="">Remote Development</a></li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="我的-vscode-設定檔"><strong>我的 VSCode 設定檔</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#%E6%88%91%E7%9A%84-vscode-%E8%A8%AD%E5%AE%9A%E6%AA%94" class="hash-link" aria-label="Direct link to 我的-vscode-設定檔" title="Direct link to 我的-vscode-設定檔" translate="no">​</a></h3>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"editor.fontSize"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">14</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"files.autoSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"onFocusChange"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"terminal.integrated.fontFamily"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"MesloLGS NF"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"terminal.integrated.defaultProfile.osx"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"zsh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"editor.formatOnSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"workbench.iconTheme"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"material-icon-theme"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"workbench.colorTheme"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"GitHub Dark Dimmed"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// For which languages indent-rainbow should be activated (if empty it means all).</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"indentRainbow.includedLanguages"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// for example ["nim", "nims", "python"]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// Using the light mode</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"indentRainbow.indicatorStyle"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"light"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// we use a simple 1 pixel wide line</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"indentRainbow.lightIndicatorStyleLineWidth"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// the same colors as above but more visible</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"indentRainbow.colors"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"rgba(255,255,64,0.1)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"rgba(127,255,127,0.1)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"rgba(255,127,255,0.1)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"rgba(79,236,236,0.1)"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// The indent color if the number of spaces is not a multiple of "tabSize".</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"indentRainbow.errorColor"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rgba(128,32,32,0.1)"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"[javascript]"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.formatOnSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.defaultFormatter"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"esbenp.prettier-vscode"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"[javascriptreact]"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.formatOnSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.defaultFormatter"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"esbenp.prettier-vscode"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"[typescript]"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.formatOnSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.defaultFormatter"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"esbenp.prettier-vscode"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"[typescriptreact]"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.formatOnSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.defaultFormatter"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"esbenp.prettier-vscode"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"[css]"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.formatOnSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.defaultFormatter"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"esbenp.prettier-vscode"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"[json]"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.formatOnSave"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"editor.defaultFormatter"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"esbenp.prettier-vscode"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"editor.guides.bracketPairs"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"editor.guides.bracketPairsHorizontal"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reference"><strong>Reference</strong><a href="https://notes.boshkuo.com/en/blog/macbook-setup-2023#reference" class="hash-link" aria-label="Direct link to reference" title="Direct link to reference" translate="no">​</a></h2>
<ul>
<li class=""><strong><a href="https://www.youtube.com/watch?v=l9n-5OdwJuQ&amp;t=1&amp;ab_channel=AndresVidoza" target="_blank" rel="noopener noreferrer" class="">Setting up M2 MacBook Air For Programming</a></strong></li>
<li class=""><strong><a href="https://www.robinwieruch.de/mac-setup-web-development/" target="_blank" rel="noopener noreferrer" class="">Mac Setup for Web Development [2023]</a></strong></li>
</ul>]]></content>
        <author>
            <name>Bosh</name>
            <uri>https://github.com/Bosh-Kuo</uri>
        </author>
        <category label="工作設備" term="工作設備"/>
        <category label="開發工具" term="開發工具"/>
    </entry>
</feed>