<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://halfism.github.io/labtab/feed.xml" rel="self" type="application/atom+xml" /><link href="https://halfism.github.io/labtab/" rel="alternate" type="text/html" hreflang="zh-CN" /><updated>2026-05-18T09:07:08+00:00</updated><id>https://halfism.github.io/labtab/feed.xml</id><title type="html">labtab</title><subtitle>A personal tech blog by huxiaomin — exploring code, design, and ideas.</subtitle><author><name>huxiaomin</name></author><entry><title type="html">Welcome to labtab</title><link href="https://halfism.github.io/labtab/2026/05/17/welcome-to-labtab/" rel="alternate" type="text/html" title="Welcome to labtab" /><published>2026-05-17T02:00:00+00:00</published><updated>2026-05-17T02:00:00+00:00</updated><id>https://halfism.github.io/labtab/2026/05/17/welcome-to-labtab</id><content type="html" xml:base="https://halfism.github.io/labtab/2026/05/17/welcome-to-labtab/"><![CDATA[<h2 id="hello-world">Hello World</h2>

<p>Welcome to <strong>labtab</strong> — my new personal blog built with Jekyll and deployed on GitHub Pages.</p>

<p>This blog features a custom dark theme with <strong>neumorphism</strong> and <strong>glassmorphism</strong> design elements, inspired by the Orion UI Kit aesthetic.</p>

<h2 id="what-youll-find-here">What You’ll Find Here</h2>

<p>I’ll be writing about:</p>

<ul>
  <li>Web development techniques and best practices</li>
  <li>Software engineering insights</li>
  <li>Design systems and UI/UX explorations</li>
  <li>Open source contributions and discoveries</li>
</ul>

<h2 id="how-this-blog-works">How This Blog Works</h2>

<p>This blog is powered by <a href="https://jekyllrb.com/">Jekyll</a>, a static site generator that transforms Markdown files into a beautiful website.</p>

<h3 id="adding-a-new-post">Adding a New Post</h3>

<p>To create a new post, simply add a Markdown file to the <code class="language-plaintext highlighter-rouge">_posts</code> directory with the following naming convention:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>YYYY-MM-DD-your-post-title.md
</code></pre></div></div>

<p>Each post starts with front matter like this:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Your</span><span class="nv"> </span><span class="s">Post</span><span class="nv"> </span><span class="s">Title"</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2026-05-17 10:00:00 +0800</span>
<span class="na">categories</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">Category</span><span class="pi">]</span>
<span class="na">tags</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">tag1</span><span class="pi">,</span> <span class="nv">tag2</span><span class="pi">]</span>
<span class="na">toc</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">comments</span><span class="pi">:</span> <span class="kc">true</span>
<span class="nn">---</span>
</code></pre></div></div>

<h3 id="code-highlighting">Code Highlighting</h3>

<p>The blog supports syntax highlighting for various languages:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">greet</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Hello, </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">!`</span><span class="p">);</span>
  <span class="k">return</span> <span class="p">{</span> <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Welcome to labtab</span><span class="dl">'</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Generate fibonacci sequence.</span><span class="sh">"""</span>
    <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">yield</span> <span class="n">a</span>
        <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
</code></pre></div></div>

<h2 id="features">Features</h2>

<p>Here’s what’s included in this blog:</p>

<ol>
  <li><strong>Search</strong> — Press <code class="language-plaintext highlighter-rouge">Ctrl+K</code> to search through all posts</li>
  <li><strong>Categories &amp; Tags</strong> — Organized content navigation</li>
  <li><strong>Comments</strong> — Powered by Giscus (GitHub Discussions)</li>
  <li><strong>Responsive</strong> — Looks great on all devices</li>
  <li><strong>Dark Theme</strong> — Easy on the eyes with purple-blue accents</li>
</ol>

<blockquote>
  <p>“The best way to learn is to build and share.” — A wise developer</p>
</blockquote>

<h2 id="whats-next">What’s Next</h2>

<p>Stay tuned for upcoming posts. I’m excited to share my journey and learnings with you.</p>

<hr />

<p><em>Thanks for reading! Feel free to leave a comment below.</em></p>]]></content><author><name>huxiaomin</name></author><category term="Blog" /><category term="Jekyll" /><category term="GitHub Pages" /><category term="Introduction" /><summary type="html"><![CDATA[Welcome to my personal blog! Here I share thoughts on code, design, and technology.]]></summary></entry><entry><title type="html">Setting Up a Modern Development Environment</title><link href="https://halfism.github.io/labtab/2026/05/16/modern-dev-environment/" rel="alternate" type="text/html" title="Setting Up a Modern Development Environment" /><published>2026-05-16T06:00:00+00:00</published><updated>2026-05-16T06:00:00+00:00</updated><id>https://halfism.github.io/labtab/2026/05/16/modern-dev-environment</id><content type="html" xml:base="https://halfism.github.io/labtab/2026/05/16/modern-dev-environment/"><![CDATA[<h2 id="why-your-dev-environment-matters">Why Your Dev Environment Matters</h2>

<p>A well-configured development environment can significantly boost your productivity. In this post, I’ll share my current setup and the tools I find essential.</p>

<h2 id="essential-tools">Essential Tools</h2>

<h3 id="terminal">Terminal</h3>

<p>A good terminal is the foundation of any dev setup. Here are my recommendations:</p>

<ul>
  <li><strong>Windows Terminal</strong> — Great for Windows users, supports tabs and panes</li>
  <li><strong>iTerm2</strong> — The gold standard for macOS</li>
  <li><strong>Alacritty</strong> — Cross-platform, GPU-accelerated</li>
</ul>

<h3 id="code-editor">Code Editor</h3>

<p>Visual Studio Code remains my editor of choice for its extensibility and performance:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"editor.fontSize"</span><span class="p">:</span><span class="w"> </span><span class="mi">14</span><span class="p">,</span><span class="w">
  </span><span class="nl">"editor.fontFamily"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JetBrains Mono"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"editor.fontLigatures"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"editor.minimap.enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
  </span><span class="nl">"workbench.colorTheme"</span><span class="p">:</span><span class="w"> </span><span class="s2">"One Dark Pro"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="version-control">Version Control</h3>

<p>Git is non-negotiable. Here are some useful aliases:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--global</span> alias.co checkout
git config <span class="nt">--global</span> alias.br branch
git config <span class="nt">--global</span> alias.st status
git config <span class="nt">--global</span> alias.lg <span class="s2">"log --oneline --graph --all"</span>
</code></pre></div></div>

<h2 id="package-managers">Package Managers</h2>

<p>Different ecosystems require different package managers:</p>

<table>
  <thead>
    <tr>
      <th>Language</th>
      <th>Manager</th>
      <th>Lock File</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Node.js</td>
      <td>pnpm</td>
      <td>pnpm-lock.yaml</td>
    </tr>
    <tr>
      <td>Python</td>
      <td>uv</td>
      <td>uv.lock</td>
    </tr>
    <tr>
      <td>Ruby</td>
      <td>Bundler</td>
      <td>Gemfile.lock</td>
    </tr>
    <tr>
      <td>Rust</td>
      <td>Cargo</td>
      <td>Cargo.lock</td>
    </tr>
  </tbody>
</table>

<h2 id="conclusion">Conclusion</h2>

<p>The key is finding tools that work for you and investing time in configuring them properly. The upfront cost pays dividends in daily productivity.</p>

<blockquote>
  <p>“First, solve the problem. Then, write the code.” — John Johnson</p>
</blockquote>]]></content><author><name>huxiaomin</name></author><category term="Tech" /><category term="Development" /><category term="Tools" /><category term="Productivity" /><summary type="html"><![CDATA[A guide to setting up a productive development environment with modern tools and workflows.]]></summary></entry><entry><title type="html">JavaScript 异步编程完全指南：从回调到 async/await</title><link href="https://halfism.github.io/labtab/2026/05/15/javascript-async-guide/" rel="alternate" type="text/html" title="JavaScript 异步编程完全指南：从回调到 async/await" /><published>2026-05-15T01:00:00+00:00</published><updated>2026-05-15T01:00:00+00:00</updated><id>https://halfism.github.io/labtab/2026/05/15/javascript-async-guide</id><content type="html" xml:base="https://halfism.github.io/labtab/2026/05/15/javascript-async-guide/"><![CDATA[<h2 id="为什么需要异步编程">为什么需要异步编程</h2>

<p>JavaScript 是单线程语言，但 Web 开发中存在大量耗时操作——网络请求、文件读写、定时器……如果这些操作同步执行，页面会直接”卡死”。</p>

<p>异步编程正是为了解决这个问题：<strong>让耗时操作在后台运行，主线程继续响应用户交互</strong>。</p>

<h2 id="第一代回调函数">第一代：回调函数</h2>

<p>最早的异步方案，简单直接，但嵌套多层后形成”回调地狱”：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">getUserData</span><span class="p">(</span><span class="nx">userId</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span>
  <span class="nf">getOrders</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">orders</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">getOrderDetail</span><span class="p">(</span><span class="nx">orders</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">id</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">detail</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// 嵌套三层，已经开始混乱...</span>
      <span class="nf">renderPage</span><span class="p">(</span><span class="nx">detail</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 嵌套四层，维护噩梦 😱</span>
        <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">done</span><span class="dl">'</span><span class="p">);</span>
      <span class="p">});</span>
    <span class="p">});</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>

<p><strong>问题：</strong></p>
<ul>
  <li>代码横向增长，难以阅读</li>
  <li>错误处理复杂，每层都要 <code class="language-plaintext highlighter-rouge">if (err) return cb(err)</code></li>
  <li>无法使用 <code class="language-plaintext highlighter-rouge">try/catch</code></li>
</ul>

<h2 id="第二代promise">第二代：Promise</h2>

<p>ES6 引入 Promise，将异步操作包装成对象，支持链式调用：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">getUserData</span><span class="p">(</span><span class="nx">userId</span><span class="p">)</span>
  <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">user</span> <span class="o">=&gt;</span> <span class="nf">getOrders</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">))</span>
  <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">orders</span> <span class="o">=&gt;</span> <span class="nf">getOrderDetail</span><span class="p">(</span><span class="nx">orders</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">id</span><span class="p">))</span>
  <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">detail</span> <span class="o">=&gt;</span> <span class="nf">renderPage</span><span class="p">(</span><span class="nx">detail</span><span class="p">))</span>
  <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">result</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">done</span><span class="dl">'</span><span class="p">))</span>
  <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">err</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">出错了:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">));</span>
</code></pre></div></div>

<p><strong>核心状态机：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pending → fulfilled (成功，触发 .then)
pending → rejected  (失败，触发 .catch)
</code></pre></div></div>

<p><strong>手写一个简单的 Promise：</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyPromise</span> <span class="p">{</span>
  <span class="nf">constructor</span><span class="p">(</span><span class="nx">executor</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">pending</span><span class="dl">'</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">handlers</span> <span class="o">=</span> <span class="p">[];</span>

    <span class="kd">const</span> <span class="nx">resolve</span> <span class="o">=</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">pending</span><span class="dl">'</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">fulfilled</span><span class="dl">'</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">handlers</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">.</span><span class="nf">onFulfilled</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span>
    <span class="p">};</span>

    <span class="kd">const</span> <span class="nx">reject</span> <span class="o">=</span> <span class="p">(</span><span class="nx">reason</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">pending</span><span class="dl">'</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">rejected</span><span class="dl">'</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">reason</span><span class="p">;</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">handlers</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">.</span><span class="nf">onRejected</span><span class="p">(</span><span class="nx">reason</span><span class="p">));</span>
    <span class="p">};</span>

    <span class="k">try</span> <span class="p">{</span>
      <span class="nf">executor</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
      <span class="nf">reject</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nf">then</span><span class="p">(</span><span class="nx">onFulfilled</span><span class="p">,</span> <span class="nx">onRejected</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nc">MyPromise</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">handlers</span><span class="p">.</span><span class="nf">push</span><span class="p">({</span>
        <span class="na">onFulfilled</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">try</span> <span class="p">{</span> <span class="nf">resolve</span><span class="p">(</span><span class="nf">onFulfilled</span><span class="p">(</span><span class="nx">value</span><span class="p">));</span> <span class="p">}</span> <span class="k">catch</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nf">reject</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span> <span class="p">}</span>
        <span class="p">},</span>
        <span class="na">onRejected</span><span class="p">:</span> <span class="p">(</span><span class="nx">reason</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">try</span> <span class="p">{</span> <span class="nf">resolve</span><span class="p">(</span><span class="nx">onRejected</span> <span class="p">?</span> <span class="nf">onRejected</span><span class="p">(</span><span class="nx">reason</span><span class="p">)</span> <span class="p">:</span> <span class="nb">Promise</span><span class="p">.</span><span class="nf">reject</span><span class="p">(</span><span class="nx">reason</span><span class="p">));</span> <span class="p">}</span> <span class="k">catch</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="nf">reject</span><span class="p">(</span><span class="nx">e</span><span class="p">);</span> <span class="p">}</span>
        <span class="p">}</span>
      <span class="p">});</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="第三代asyncawait">第三代：async/await</h2>

<p>ES2017 的语法糖，让异步代码写起来像同步代码：</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nf">fetchUserPage</span><span class="p">(</span><span class="nx">userId</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getUserData</span><span class="p">(</span><span class="nx">userId</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">orders</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getOrders</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">id</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">detail</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getOrderDetail</span><span class="p">(</span><span class="nx">orders</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">id</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">renderPage</span><span class="p">(</span><span class="nx">detail</span><span class="p">);</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">done</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">出错了:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
    <span class="k">throw</span> <span class="nx">err</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>并行执行多个请求：</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 顺序执行（慢）</span>
<span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getUser</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">settings</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getSettings</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span> <span class="c1">// 等 user 完成后才执行</span>

<span class="c1">// 并行执行（快）</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">user</span><span class="p">,</span> <span class="nx">settings</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nf">all</span><span class="p">([</span>
  <span class="nf">getUser</span><span class="p">(</span><span class="nx">id</span><span class="p">),</span>
  <span class="nf">getSettings</span><span class="p">(</span><span class="nx">id</span><span class="p">)</span>  <span class="c1">// 同时发起</span>
<span class="p">]);</span>
</code></pre></div></div>

<h2 id="常用-promise-工具方法">常用 Promise 工具方法</h2>

<table>
  <thead>
    <tr>
      <th>方法</th>
      <th>说明</th>
      <th>用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Promise.all()</code></td>
      <td>全部成功才 resolve</td>
      <td>并行请求多个接口</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Promise.allSettled()</code></td>
      <td>等所有完成（无论成败）</td>
      <td>批量操作，统计结果</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Promise.race()</code></td>
      <td>第一个完成即 resolve</td>
      <td>超时控制</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Promise.any()</code></td>
      <td>第一个成功即 resolve</td>
      <td>多个备用接口</td>
    </tr>
  </tbody>
</table>

<p><strong>超时控制示例：</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">withTimeout</span><span class="p">(</span><span class="nx">promise</span><span class="p">,</span> <span class="nx">ms</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">timeout</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Promise</span><span class="p">((</span><span class="nx">_</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span>
    <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nf">reject</span><span class="p">(</span><span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">请求超时</span><span class="dl">'</span><span class="p">)),</span> <span class="nx">ms</span><span class="p">)</span>
  <span class="p">);</span>
  <span class="k">return</span> <span class="nb">Promise</span><span class="p">.</span><span class="nf">race</span><span class="p">([</span><span class="nx">promise</span><span class="p">,</span> <span class="nx">timeout</span><span class="p">]);</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">withTimeout</span><span class="p">(</span><span class="nf">fetchData</span><span class="p">(),</span> <span class="mi">5000</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="最佳实践">最佳实践</h2>

<ol>
  <li><strong>避免在 async 函数中使用 <code class="language-plaintext highlighter-rouge">forEach</code></strong>，改用 <code class="language-plaintext highlighter-rouge">for...of</code> 或 <code class="language-plaintext highlighter-rouge">Promise.all</code></li>
  <li><strong>总是处理 rejected 状态</strong>，否则会产生未捕获的 Promise 异常</li>
  <li><strong>适当使用并行</strong>，<code class="language-plaintext highlighter-rouge">await</code> 是顺序执行，多个独立请求应该并行</li>
</ol>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ 错误：串行执行，慢</span>
<span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">id</span> <span class="k">of</span> <span class="nx">userIds</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">await</span> <span class="nf">processUser</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// ✅ 正确：并行执行，快</span>
<span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span><span class="nx">userIds</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">id</span> <span class="o">=&gt;</span> <span class="nf">processUser</span><span class="p">(</span><span class="nx">id</span><span class="p">)));</span>
</code></pre></div></div>

<hr />

<p>掌握异步编程是 JavaScript 进阶的必经之路。从回调到 async/await，每一代方案都是对前一代痛点的改进。</p>]]></content><author><name>huxiaomin</name></author><category term="技术" /><category term="JavaScript" /><category term="异步" /><category term="Promise" /><category term="async/await" /><summary type="html"><![CDATA[深入理解 JavaScript 异步编程的演进历程，从回调地狱到 Promise 链，再到优雅的 async/await 语法。]]></summary></entry><entry><title type="html">CSS 设计系统实战：从零搭建你的组件库</title><link href="https://halfism.github.io/labtab/2026/05/13/css-design-system/" rel="alternate" type="text/html" title="CSS 设计系统实战：从零搭建你的组件库" /><published>2026-05-13T02:30:00+00:00</published><updated>2026-05-13T02:30:00+00:00</updated><id>https://halfism.github.io/labtab/2026/05/13/css-design-system</id><content type="html" xml:base="https://halfism.github.io/labtab/2026/05/13/css-design-system/"><![CDATA[<h2 id="什么是设计系统">什么是设计系统</h2>

<p>设计系统不只是 UI 组件库，它是<strong>团队共同遵守的设计语言</strong>——包括颜色规范、字体比例、间距系统、交互规则。</p>

<p>一个好的设计系统能做到：</p>
<ul>
  <li>保证 UI 视觉一致性</li>
  <li>加速开发（复用而非重复）</li>
  <li>降低沟通成本（设计师与开发者说同一种语言）</li>
</ul>

<h2 id="第一步design-tokens设计令牌">第一步：Design Tokens（设计令牌）</h2>

<p>Design Tokens 是设计系统的原子，将抽象的设计决策存储为具名变量。</p>

<h3 id="颜色系统">颜色系统</h3>

<p>不要直接使用颜色值，要赋予语义：</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ 反例：硬编码颜色</span>
<span class="nc">.button</span> <span class="p">{</span> <span class="nl">background</span><span class="p">:</span> <span class="mh">#6c63ff</span><span class="p">;</span> <span class="p">}</span>

<span class="c1">// ✅ 正例：语义化命名</span>
<span class="nd">:root</span> <span class="p">{</span>
  <span class="cm">/* Primitive tokens（基础颜色） */</span>
  <span class="na">--color-purple-500</span><span class="p">:</span> <span class="mh">#6c63ff</span><span class="p">;</span>
  <span class="na">--color-purple-600</span><span class="p">:</span> <span class="mh">#5a52e0</span><span class="p">;</span>
  <span class="na">--color-blue-500</span><span class="p">:</span>   <span class="mh">#3b82f6</span><span class="p">;</span>

  <span class="cm">/* Semantic tokens（语义颜色） */</span>
  <span class="na">--color-primary</span><span class="p">:</span>     <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-purple-500</span><span class="p">);</span>
  <span class="na">--color-primary-hover</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-purple-600</span><span class="p">);</span>
  <span class="na">--color-bg-base</span><span class="p">:</span>     <span class="mh">#0f0a1e</span><span class="p">;</span>
  <span class="na">--color-text-primary</span><span class="p">:</span> <span class="mh">#e8e6f0</span><span class="p">;</span>
<span class="p">}</span>

<span class="nc">.button</span> <span class="p">{</span> <span class="nl">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-primary</span><span class="p">);</span> <span class="p">}</span>
</code></pre></div></div>

<h3 id="间距系统">间距系统</h3>

<p>使用 4px 基础单位（<code class="language-plaintext highlighter-rouge">base-4</code> 系统）：</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="na">--space-1</span><span class="p">:</span> <span class="m">0</span><span class="mi">.25rem</span><span class="p">;</span>   <span class="cm">/* 4px */</span>
  <span class="na">--space-2</span><span class="p">:</span> <span class="m">0</span><span class="mi">.5rem</span><span class="p">;</span>    <span class="cm">/* 8px */</span>
  <span class="na">--space-3</span><span class="p">:</span> <span class="m">0</span><span class="mi">.75rem</span><span class="p">;</span>   <span class="cm">/* 12px */</span>
  <span class="na">--space-4</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>      <span class="cm">/* 16px */</span>
  <span class="na">--space-6</span><span class="p">:</span> <span class="m">1</span><span class="mi">.5rem</span><span class="p">;</span>    <span class="cm">/* 24px */</span>
  <span class="na">--space-8</span><span class="p">:</span> <span class="m">2rem</span><span class="p">;</span>      <span class="cm">/* 32px */</span>
  <span class="na">--space-12</span><span class="p">:</span> <span class="m">3rem</span><span class="p">;</span>     <span class="cm">/* 48px */</span>
  <span class="na">--space-16</span><span class="p">:</span> <span class="m">4rem</span><span class="p">;</span>     <span class="cm">/* 64px */</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="字体比例">字体比例</h3>

<p>使用模块化比例（Modular Scale），本例采用 <code class="language-plaintext highlighter-rouge">1.25</code> 比例（Major Third）：</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="na">--text-xs</span><span class="p">:</span>   <span class="m">0</span><span class="mi">.64rem</span><span class="p">;</span>   <span class="cm">/* 10.2px */</span>
  <span class="na">--text-sm</span><span class="p">:</span>   <span class="m">0</span><span class="mi">.8rem</span><span class="p">;</span>    <span class="cm">/* 12.8px */</span>
  <span class="na">--text-base</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>      <span class="cm">/* 16px   */</span>
  <span class="na">--text-lg</span><span class="p">:</span>   <span class="m">1</span><span class="mi">.25rem</span><span class="p">;</span>   <span class="cm">/* 20px   */</span>
  <span class="na">--text-xl</span><span class="p">:</span>   <span class="m">1</span><span class="mi">.563rem</span><span class="p">;</span>  <span class="cm">/* 25px   */</span>
  <span class="na">--text-2xl</span><span class="p">:</span>  <span class="m">1</span><span class="mi">.953rem</span><span class="p">;</span>  <span class="cm">/* 31px   */</span>
  <span class="na">--text-3xl</span><span class="p">:</span>  <span class="m">2</span><span class="mi">.441rem</span><span class="p">;</span>  <span class="cm">/* 39px   */</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="第二步组件设计">第二步：组件设计</h2>

<h3 id="button-组件">Button 组件</h3>

<p>一个完整的 Button 应该覆盖所有状态（default, hover, focus, active, disabled）和变体（primary, secondary, ghost）：</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.btn</span> <span class="p">{</span>
  <span class="cm">/* 基础样式 */</span>
  <span class="nl">display</span><span class="p">:</span> <span class="nb">inline-flex</span><span class="p">;</span>
  <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
  <span class="nl">gap</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-2</span><span class="p">);</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-2</span><span class="p">)</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-4</span><span class="p">);</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">0</span><span class="mi">.5rem</span><span class="p">;</span>
  <span class="nl">font-weight</span><span class="p">:</span> <span class="m">500</span><span class="p">;</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">text-sm</span><span class="p">);</span>
  <span class="nl">cursor</span><span class="p">:</span> <span class="nb">pointer</span><span class="p">;</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="nb">transparent</span><span class="p">;</span>
  <span class="nl">transition</span><span class="p">:</span> <span class="nb">all</span> <span class="m">0</span><span class="mi">.2s</span> <span class="nb">ease</span><span class="p">;</span>

  <span class="cm">/* Focus 可访问性 */</span>
  <span class="k">&amp;</span><span class="nd">:focus-visible</span> <span class="p">{</span>
    <span class="nl">outline</span><span class="p">:</span> <span class="m">2px</span> <span class="nb">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-primary</span><span class="p">);</span>
    <span class="nl">outline-offset</span><span class="p">:</span> <span class="m">2px</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="cm">/* Disabled 状态 */</span>
  <span class="k">&amp;</span><span class="nd">:disabled</span> <span class="p">{</span>
    <span class="nl">opacity</span><span class="p">:</span> <span class="m">0</span><span class="mi">.5</span><span class="p">;</span>
    <span class="nl">pointer-events</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="cm">/* Primary 变体 */</span>
  <span class="k">&amp;</span><span class="nt">--primary</span> <span class="p">{</span>
    <span class="nl">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-primary</span><span class="p">);</span>
    <span class="nl">color</span><span class="p">:</span> <span class="n">white</span><span class="p">;</span>

    <span class="k">&amp;</span><span class="nd">:hover</span> <span class="p">{</span> <span class="nl">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-primary-hover</span><span class="p">);</span> <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/* Ghost 变体 */</span>
  <span class="k">&amp;</span><span class="nt">--ghost</span> <span class="p">{</span>
    <span class="nl">background</span><span class="p">:</span> <span class="nb">transparent</span><span class="p">;</span>
    <span class="nl">border-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-primary</span><span class="p">);</span>
    <span class="nl">color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-primary</span><span class="p">);</span>

    <span class="k">&amp;</span><span class="nd">:hover</span> <span class="p">{</span> <span class="nl">background</span><span class="p">:</span> <span class="nf">rgba</span><span class="p">(</span><span class="m">108</span><span class="o">,</span> <span class="m">99</span><span class="o">,</span> <span class="m">255</span><span class="o">,</span> <span class="m">0</span><span class="mi">.1</span><span class="p">);</span> <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/* Size 变体 */</span>
  <span class="k">&amp;</span><span class="nt">--sm</span> <span class="p">{</span> <span class="nl">padding</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-1</span><span class="p">)</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-3</span><span class="p">);</span> <span class="nl">font-size</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">text-xs</span><span class="p">);</span> <span class="p">}</span>
  <span class="k">&amp;</span><span class="nt">--lg</span> <span class="p">{</span> <span class="nl">padding</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-3</span><span class="p">)</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-6</span><span class="p">);</span> <span class="nl">font-size</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">text-lg</span><span class="p">);</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="card-组件复合组件">Card 组件（复合组件）</h3>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.card</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-surface</span><span class="p">);</span>
  <span class="nl">border-radius</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>
  <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-border</span><span class="p">);</span>
  <span class="nl">overflow</span><span class="p">:</span> <span class="nb">hidden</span><span class="p">;</span>

  <span class="k">&amp;</span><span class="nt">__header</span> <span class="p">{</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-4</span><span class="p">)</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-6</span><span class="p">);</span>
    <span class="nl">border-bottom</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-border</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">&amp;</span><span class="nt">__body</span> <span class="p">{</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-6</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">&amp;</span><span class="nt">__footer</span> <span class="p">{</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-4</span><span class="p">)</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">space-6</span><span class="p">);</span>
    <span class="nl">border-top</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">color-border</span><span class="p">);</span>
    <span class="nl">background</span><span class="p">:</span> <span class="nf">rgba</span><span class="p">(</span><span class="m">255</span><span class="o">,</span> <span class="m">255</span><span class="o">,</span> <span class="m">255</span><span class="o">,</span> <span class="m">0</span><span class="mi">.02</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="第三步文档化">第三步：文档化</h2>

<p>每个组件都需要文档，包括：</p>
<ul>
  <li>使用场景</li>
  <li>Props/变体说明</li>
  <li>Do &amp; Don’t 示例</li>
  <li>无障碍注意事项</li>
</ul>

<blockquote>
  <p>没有文档的组件库，等于没有路标的城市。</p>
</blockquote>

<h2 id="深色模式支持">深色模式支持</h2>

<p>使用 CSS 自定义属性，深色模式切换只需重新定义根变量：</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* 浅色模式（默认） */</span>
<span class="nd">:root</span> <span class="p">{</span>
  <span class="na">--color-bg</span><span class="p">:</span> <span class="mh">#ffffff</span><span class="p">;</span>
  <span class="na">--color-text</span><span class="p">:</span> <span class="mh">#1a1a1a</span><span class="p">;</span>
  <span class="na">--color-border</span><span class="p">:</span> <span class="mh">#e5e7eb</span><span class="p">;</span>
<span class="p">}</span>

<span class="cm">/* 深色模式（系统自动或手动切换） */</span>
<span class="k">@media</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="o">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root</span> <span class="p">{</span>
    <span class="na">--color-bg</span><span class="p">:</span> <span class="mh">#0f0a1e</span><span class="p">;</span>
    <span class="na">--color-text</span><span class="p">:</span> <span class="mh">#e8e6f0</span><span class="p">;</span>
    <span class="na">--color-border</span><span class="p">:</span> <span class="nf">rgba</span><span class="p">(</span><span class="m">168</span><span class="o">,</span> <span class="m">139</span><span class="o">,</span> <span class="m">250</span><span class="o">,</span> <span class="m">0</span><span class="mi">.15</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="cm">/* 手动切换 */</span>
<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s2">"dark"</span><span class="o">]</span> <span class="p">{</span>
  <span class="na">--color-bg</span><span class="p">:</span> <span class="mh">#0f0a1e</span><span class="p">;</span>
  <span class="na">--color-text</span><span class="p">:</span> <span class="mh">#e8e6f0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<p>设计系统是长期投资，初期会增加一些工作量，但随着项目规模增长，它带来的一致性和开发效率提升是巨大的。</p>]]></content><author><name>huxiaomin</name></author><category term="设计" /><category term="CSS" /><category term="设计系统" /><category term="组件库" /><category term="Sass" /><summary type="html"><![CDATA[一套好的设计系统是团队协作的基石。本文介绍如何用 CSS 自定义属性和 Sass 从零构建一个可扩展的设计系统。]]></summary></entry><entry><title type="html">2026 年我的工具栈：效率工具全整理</title><link href="https://halfism.github.io/labtab/2026/05/12/my-toolstack-2026/" rel="alternate" type="text/html" title="2026 年我的工具栈：效率工具全整理" /><published>2026-05-12T00:00:00+00:00</published><updated>2026-05-12T00:00:00+00:00</updated><id>https://halfism.github.io/labtab/2026/05/12/my-toolstack-2026</id><content type="html" xml:base="https://halfism.github.io/labtab/2026/05/12/my-toolstack-2026/"><![CDATA[<h2 id="前言">前言</h2>

<p>工具是手段，不是目的。但合适的工具能让你<strong>专注于真正重要的事</strong>。这篇文章整理了我 2026 年实际在用的工具栈，每个工具我都说明为什么选它、它解决了什么问题。</p>

<hr />

<h2 id="编辑器与-ide">编辑器与 IDE</h2>

<h3 id="cursor主力编辑器">Cursor（主力编辑器）</h3>

<p>基于 VSCode，深度集成 AI 辅助编程。我迁移到 Cursor 的核心理由：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Tab</code> 自动补全整段代码（比 Copilot 更智能）</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl+K</code> 在文件内直接对话修改代码</li>
  <li><code class="language-plaintext highlighter-rouge">@codebase</code> 可以引用整个项目上下文</li>
</ul>

<p><strong>我的关键配置：</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"editor.fontFamily"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JetBrains Mono"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"editor.fontSize"</span><span class="p">:</span><span class="w"> </span><span class="mi">14</span><span class="p">,</span><span class="w">
  </span><span class="nl">"editor.fontLigatures"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"editor.formatOnSave"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"editor.wordWrap"</span><span class="p">:</span><span class="w"> </span><span class="s2">"on"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"workbench.colorTheme"</span><span class="p">:</span><span class="w"> </span><span class="s2">"One Dark Pro Monokai Darker"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="jetbrains-ide特定场景">JetBrains IDE（特定场景）</h3>

<p>Java/Kotlin/Go 项目我仍然用 IntelliJ/GoLand，它们的重构支持和调试器远超 VSCode。</p>

<hr />

<h2 id="终端与命令行">终端与命令行</h2>

<h3 id="wezterm">Wezterm</h3>

<p>用 Lua 配置的现代终端，替换了我用了三年的 Windows Terminal：</p>

<ul>
  <li>原生 GPU 渲染，滚动流畅</li>
  <li>多路复用（类 tmux，内置）</li>
  <li>跨平台（Windows/macOS/Linux 同一份配置）</li>
</ul>

<h3 id="fish-shell">Fish Shell</h3>

<p>比 zsh 更友好的 Shell，开箱即用的自动补全：</p>

<pre><code class="language-fish"># config.fish 关键配置
set -gx EDITOR "cursor"
set -gx PATH $HOME/.local/bin $PATH

# 常用 abbreviation（输入即展开）
abbr -a g git
abbr -a gs git status
abbr -a gc git commit -m
abbr -a gp git push
</code></pre>

<h3 id="常用-cli-工具">常用 CLI 工具</h3>

<table>
  <thead>
    <tr>
      <th>工具</th>
      <th>替代</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">eza</code></td>
      <td><code class="language-plaintext highlighter-rouge">ls</code></td>
      <td>彩色输出，支持 git 状态</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">bat</code></td>
      <td><code class="language-plaintext highlighter-rouge">cat</code></td>
      <td>带语法高亮的文件预览</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">fd</code></td>
      <td><code class="language-plaintext highlighter-rouge">find</code></td>
      <td>更快更友好的文件搜索</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ripgrep</code></td>
      <td><code class="language-plaintext highlighter-rouge">grep</code></td>
      <td>极速代码搜索</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">zoxide</code></td>
      <td><code class="language-plaintext highlighter-rouge">cd</code></td>
      <td>智能跳转目录（记忆你去过的地方）</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">delta</code></td>
      <td>git diff pager</td>
      <td>漂亮的 diff 输出</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="版本控制">版本控制</h2>

<h3 id="git--lazygit">Git + Lazygit</h3>

<p><code class="language-plaintext highlighter-rouge">lazygit</code> 是终端里的 Git TUI，让我不再需要 GUI 客户端：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 安装</span>
winget <span class="nb">install </span>jesseduffield.lazygit

<span class="c"># 启动（在任意 git 仓库目录）</span>
lazygit
</code></pre></div></div>

<p><strong>关键 Git 配置：</strong></p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[core]</span><span class="w">
  </span><span class="py">editor</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">cursor --wait</span>
<span class="w">  </span><span class="py">autocrlf</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">input</span>
<span class="w">
</span><span class="nn">[alias]</span><span class="w">
  </span><span class="py">lg</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">log --oneline --graph --all --decorate</span>
<span class="w">  </span><span class="py">undo</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">reset HEAD~1 --soft</span>
<span class="w">  </span><span class="py">wip</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">commit -am "wip: work in progress"</span>
<span class="w">
</span><span class="nn">[pull]</span><span class="w">
  </span><span class="py">rebase</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">true</span>
</code></pre></div></div>

<hr />

<h2 id="知识管理">知识管理</h2>

<h3 id="obsidian">Obsidian</h3>

<p>本地 Markdown + 双向链接，数据自己掌控：</p>

<ul>
  <li>所有笔记存在本地 <code class="language-plaintext highlighter-rouge">.md</code> 文件</li>
  <li>通过 Git 同步（不依赖官方云服务）</li>
  <li>Dataview 插件：把笔记当数据库查询</li>
</ul>

<p><strong>我的笔记结构：</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vault/
├── inbox/        # 快速捕捉，每日清理
├── projects/     # 进行中的项目
├── areas/        # 持续关注的领域
├── resources/    # 参考资料
└── archive/      # 归档
</code></pre></div></div>

<hr />

<h2 id="浏览器">浏览器</h2>

<h3 id="arc主力-chrome测试">Arc（主力）+ Chrome（测试）</h3>

<p>Arc 的 Space 和 Folder 功能改变了我的多任务工作方式：</p>
<ul>
  <li>不同项目放不同 Space，切换不串标签</li>
  <li>Command Bar（<code class="language-plaintext highlighter-rouge">Cmd+T</code>）直接搜索历史</li>
</ul>

<p><strong>必装扩展：</strong></p>
<ul>
  <li><strong>uBlock Origin</strong> — 广告拦截</li>
  <li><strong>Vimium C</strong> — 键盘控制浏览器</li>
  <li><strong>SteadyFocus</strong> — 专注模式，屏蔽干扰网站</li>
</ul>

<hr />

<h2 id="其他">其他</h2>

<ul>
  <li><strong>Rectangle Pro</strong>（macOS）/ <strong>FancyZones</strong>（Windows）— 窗口管理</li>
  <li><strong>Raycast</strong> — Alfred 的现代替代，快速启动+脚本</li>
  <li><strong>Shottr</strong> — 截图标注（macOS）</li>
  <li><strong>Bruno</strong> — Postman 替代，接口调试，文件存本地</li>
</ul>

<hr />

<h2 id="总结">总结</h2>

<p>我的选工具原则：</p>

<ol>
  <li><strong>数据本地化</strong>：尽量避免数据锁定在某家云服务</li>
  <li><strong>可配置</strong>：能适应自己的工作流，而不是被工具牵着走</li>
  <li><strong>键盘友好</strong>：减少鼠标依赖，提高速度</li>
</ol>

<p>工具会变，但这三条原则让我在工具选择上少走弯路。</p>]]></content><author><name>huxiaomin</name></author><category term="工具" /><category term="效率工具" /><category term="开发环境" /><category term="推荐" /><summary type="html"><![CDATA[整理我日常使用的开发工具、效率应用和配置方案，希望能帮你找到适合自己的组合。]]></summary></entry><entry><title type="html">读《人月神话》：40年后依然正确的软件工程真相</title><link href="https://halfism.github.io/labtab/2026/05/10/mythical-man-month/" rel="alternate" type="text/html" title="读《人月神话》：40年后依然正确的软件工程真相" /><published>2026-05-10T12:00:00+00:00</published><updated>2026-05-10T12:00:00+00:00</updated><id>https://halfism.github.io/labtab/2026/05/10/mythical-man-month</id><content type="html" xml:base="https://halfism.github.io/labtab/2026/05/10/mythical-man-month/"><![CDATA[<h2 id="一本写于-1975-年的书">一本写于 1975 年的书</h2>

<p>《人月神话》（<em>The Mythical Man-Month</em>）是 Fred Brooks 根据他主导 IBM OS/360 开发的经历写成的。那是 1964 年，离 GitHub 的诞生还有 44 年。</p>

<p>然而这本书里的观点放在今天，仍然刺痛人心。</p>

<hr />

<h2 id="核心论点人月不可互换">核心论点：人月不可互换</h2>

<p>书名来自软件行业最根深蒂固的谬误：<strong>“这个项目延期了，加人就能追回来”</strong>。</p>

<p>Brooks 的反驳简洁有力：</p>

<blockquote>
  <p>“向一个已经落后的软件项目增加人手，只会使它更加落后。”</p>
</blockquote>

<p>这就是著名的 <strong>Brooks 定律</strong>。</p>

<p>为什么？因为软件开发不像搬砖——新人需要培训时间，而且增加人手会增加<strong>沟通成本</strong>。</p>

<p>如果一个团队有 n 个人，沟通路径的数量是 <strong>n(n-1)/2</strong>：</p>
<ul>
  <li>3 人团队 → 3 条沟通路径</li>
  <li>5 人团队 → 10 条沟通路径</li>
  <li>10 人团队 → 45 条沟通路径</li>
</ul>

<p>人数翻倍，沟通路径增加 4 倍。这些沟通本身就会消耗大量时间。</p>

<hr />

<h2 id="没有银弹">没有银弹</h2>

<p>Brooks 后来补写的章节”没有银弹”（No Silver Bullet）同样振聋发聩：</p>

<blockquote>
  <p>“在未来十年，没有任何单一的软件工程进展，能够使生产率提高一个数量级。”</p>
</blockquote>

<p>他区分了软件复杂度的两种来源：</p>

<ul>
  <li><strong>本质复杂度</strong>（Essential Complexity）：问题本身固有的难度，无法消除</li>
  <li><strong>偶然复杂度</strong>（Accidental Complexity）：我们引入的工具、语言、流程带来的难度，可以优化</li>
</ul>

<p>大多数技术进步——高级语言、IDE、框架——只是在减少偶然复杂度。但本质复杂度始终存在，这正是软件依然难做的根本原因。</p>

<hr />

<h2 id="外科手术式团队">外科手术式团队</h2>

<p>Brooks 提出了一个反直觉的团队结构：与其用 10 个平均水平的程序员，不如用 1 个顶尖程序员加上支持他的几个辅助角色。</p>

<p>这和我们今天说的”10x engineer”遥相呼应。但 Brooks 强调的不只是个人能力，而是<strong>减少接口、保持概念完整性</strong>：</p>

<blockquote>
  <p>“概念完整性是系统设计最重要的考量因素。”</p>
</blockquote>

<p>一个人脑子里的设计，比 10 个人委员会讨论出来的设计，往往更一致、更优雅。</p>

<hr />

<h2 id="第二个系统综合症">第二个系统综合症</h2>

<p>有趣的心理现象：程序员第一个系统往往因为经验不足，反而简洁克制。到了第二个系统，信心大增，把所有”第一次没敢加”的东西全塞进去，结果做成了大而复杂的怪物。</p>

<p>这不是 50 年前的问题。看看多少开源项目 v2 重写把 v1 的简洁全丢掉了？</p>

<hr />

<h2 id="今天还有什么意义">今天还有什么意义？</h2>

<p>读完这本书，我的体会：</p>

<ol>
  <li><strong>进度估计要诚实</strong>：软件估时的艰难不是因为程序员懒惰，而是不确定性本质上难以量化</li>
  <li><strong>小团队更高效</strong>：Spotify、Amazon 的”two-pizza team”原则，本质上就是 Brooks 几十年前说的</li>
  <li><strong>警惕复杂度积累</strong>：每加一个依赖、每多一个抽象层，都在增加偶然复杂度</li>
  <li><strong>文档是系统的一部分</strong>：Brooks 花大量篇幅讲文档，因为代码只解释”怎么做”，不解释”为什么”</li>
</ol>

<hr />

<blockquote>
  <p>“乐趣在于做出东西……痛苦在于，要做出别人真正需要的东西，而不只是自己觉得有趣的东西。”</p>
</blockquote>

<p>这句话值得每个工程师贴在显示器旁边。</p>

<hr />

<p><strong>书籍信息：</strong> 《人月神话》，Fred Brooks，1975 年初版，推荐读带”没有银弹”章节的 20 周年纪念版。</p>]]></content><author><name>huxiaomin</name></author><category term="读书" /><category term="读书笔记" /><category term="软件工程" /><category term="管理" /><summary type="html"><![CDATA[Fred Brooks 在 1975 年写下的软件工程经典，为什么 40 年后读来仍字字珠玑？这本书到底讲了什么？]]></summary></entry><entry><title type="html">用 GitHub Actions 实现全自动 CI/CD 流水线</title><link href="https://halfism.github.io/labtab/2026/05/08/github-actions-cicd/" rel="alternate" type="text/html" title="用 GitHub Actions 实现全自动 CI/CD 流水线" /><published>2026-05-08T03:00:00+00:00</published><updated>2026-05-08T03:00:00+00:00</updated><id>https://halfism.github.io/labtab/2026/05/08/github-actions-cicd</id><content type="html" xml:base="https://halfism.github.io/labtab/2026/05/08/github-actions-cicd/"><![CDATA[<h2 id="什么是-cicd">什么是 CI/CD</h2>

<ul>
  <li><strong>CI（持续集成）</strong>：每次代码提交后自动运行测试，快速发现问题</li>
  <li><strong>CD（持续部署）</strong>：测试通过后自动部署到生产环境</li>
</ul>

<p>GitHub Actions 将这两者整合在一起，配置文件直接存在仓库里，版本可追踪。</p>

<h2 id="基础概念">基础概念</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># .github/workflows/ci.yml</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">CI Pipeline</span>

<span class="na">on</span><span class="pi">:</span>                         <span class="c1"># 触发条件</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">main</span><span class="pi">]</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">main</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">test</span><span class="pi">:</span>                     <span class="c1"># 一个 Job</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>  <span class="c1"># 运行环境</span>
    <span class="na">steps</span><span class="pi">:</span>                  <span class="c1"># 步骤列表</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
      <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">npm test</span>
</code></pre></div></div>

<p><strong>核心概念对应关系：</strong></p>

<table>
  <thead>
    <tr>
      <th>概念</th>
      <th>类比</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Workflow</td>
      <td>整条流水线</td>
      <td>一个 <code class="language-plaintext highlighter-rouge">.yml</code> 文件</td>
    </tr>
    <tr>
      <td>Job</td>
      <td>一个工序</td>
      <td>独立的虚拟机，可并行</td>
    </tr>
    <tr>
      <td>Step</td>
      <td>一个操作</td>
      <td>顺序执行</td>
    </tr>
    <tr>
      <td>Action</td>
      <td>预制模块</td>
      <td><code class="language-plaintext highlighter-rouge">uses: xxx</code> 引用</td>
    </tr>
  </tbody>
</table>

<h2 id="实战nodejs-项目完整流水线">实战：Node.js 项目完整流水线</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Full CI/CD Pipeline</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">main</span><span class="pi">]</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">main</span><span class="pi">]</span>

<span class="na">env</span><span class="pi">:</span>
  <span class="na">REGISTRY</span><span class="pi">:</span> <span class="s">ghcr.io</span>
  <span class="na">IMAGE_NAME</span><span class="pi">:</span> <span class="s">$</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># =====================</span>
  <span class="c1"># Job 1: 代码质量检查</span>
  <span class="c1"># =====================</span>
  <span class="na">lint</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Lint &amp; Type Check</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup Node.js</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-node@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">node-version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">20'</span>
          <span class="na">cache</span><span class="pi">:</span> <span class="s1">'</span><span class="s">npm'</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install dependencies</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">npm ci</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run ESLint</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">npm run lint</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run TypeScript check</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">npm run typecheck</span>

  <span class="c1"># =====================</span>
  <span class="c1"># Job 2: 单元测试</span>
  <span class="c1"># =====================</span>
  <span class="na">test</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Unit Tests</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">lint</span>        <span class="c1"># 依赖 lint Job 先完成</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-node@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">node-version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">20'</span>
          <span class="na">cache</span><span class="pi">:</span> <span class="s1">'</span><span class="s">npm'</span>

      <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">npm ci</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run tests with coverage</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">npm test -- --coverage</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload coverage report</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">codecov/codecov-action@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">$</span>

  <span class="c1"># =====================</span>
  <span class="c1"># Job 3: 构建 Docker 镜像</span>
  <span class="c1"># =====================</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Build &amp; Push Image</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">test</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">github.ref == 'refs/heads/main'</span>   <span class="c1"># 只在 main 分支构建</span>
    <span class="na">permissions</span><span class="pi">:</span>
      <span class="na">contents</span><span class="pi">:</span> <span class="s">read</span>
      <span class="na">packages</span><span class="pi">:</span> <span class="s">write</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Login to GitHub Container Registry</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">docker/login-action@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">registry</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">username</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">password</span><span class="pi">:</span> <span class="s">$</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Extract metadata</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">meta</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">docker/metadata-action@v5</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">images</span><span class="pi">:</span> <span class="s">$/$</span>
          <span class="na">tags</span><span class="pi">:</span> <span class="pi">|</span>
            <span class="s">type=sha,prefix=git-</span>
            <span class="s">type=raw,value=latest</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build and push</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">docker/build-push-action@v5</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
          <span class="na">push</span><span class="pi">:</span> <span class="kc">true</span>
          <span class="na">tags</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">cache-from</span><span class="pi">:</span> <span class="s">type=gha</span>     <span class="c1"># GitHub Actions 缓存层</span>
          <span class="na">cache-to</span><span class="pi">:</span> <span class="s">type=gha,mode=max</span>

  <span class="c1"># =====================</span>
  <span class="c1"># Job 4: 自动部署</span>
  <span class="c1"># =====================</span>
  <span class="na">deploy</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Production</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build</span>
    <span class="na">environment</span><span class="pi">:</span> <span class="s">production</span>    <span class="c1"># 需要手动审批（可选）</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy via SSH</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">appleboy/ssh-action@v1</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">host</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">username</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">$</span>
          <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span>
            <span class="s">docker pull ghcr.io/$:latest</span>
            <span class="s">docker-compose up -d --no-deps app</span>
            <span class="s">docker system prune -f</span>
</code></pre></div></div>

<h2 id="使用-secrets-管理敏感信息">使用 Secrets 管理敏感信息</h2>

<p>绝对不要在代码里硬编码密码、API Key！在仓库 <strong>Settings → Secrets and variables → Actions</strong> 中添加：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 使用方式</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy</span>
  <span class="na">env</span><span class="pi">:</span>
    <span class="na">API_KEY</span><span class="pi">:</span> <span class="s">$</span>    <span class="c1"># 从 Secrets 读取</span>
    <span class="na">DATABASE_URL</span><span class="pi">:</span> <span class="s">$</span>       <span class="c1"># 从 Variables 读取（非敏感）</span>
</code></pre></div></div>

<h2 id="优化构建速度">优化构建速度</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 缓存依赖安装</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-node@v4</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">cache</span><span class="pi">:</span> <span class="s1">'</span><span class="s">npm'</span>          <span class="c1"># 自动缓存 node_modules</span>

<span class="c1"># 缓存自定义目录</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v4</span>
  <span class="na">with</span><span class="pi">:</span>
    <span class="na">path</span><span class="pi">:</span> <span class="s">~/.cache/pip</span>
    <span class="na">key</span><span class="pi">:</span> <span class="s">$-pip-$</span>
    <span class="na">restore-keys</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">$-pip-</span>
</code></pre></div></div>

<h2 id="pr-检查保护">PR 检查保护</h2>

<p>在 <strong>Settings → Branches → Branch protection rules</strong> 中：</p>
<ul>
  <li>启用 “Require status checks to pass before merging”</li>
  <li>选中 <code class="language-plaintext highlighter-rouge">lint</code> 和 <code class="language-plaintext highlighter-rouge">test</code> 这两个 Job</li>
  <li>这样有 bug 的 PR 就无法合并到 main</li>
</ul>

<hr />

<p>GitHub Actions 的魅力在于它完全是代码化的——流水线配置存在仓库里，和业务代码同版本管理，任何人都能看到和修改。这才是真正的 Infrastructure as Code。</p>]]></content><author><name>huxiaomin</name></author><category term="DevOps" /><category term="GitHub Actions" /><category term="CI/CD" /><category term="自动化" /><category term="Docker" /><summary type="html"><![CDATA[从零配置一条完整的 CI/CD 流水线：代码 lint → 单元测试 → 构建 Docker 镜像 → 自动部署，每次 push 全自动完成。]]></summary></entry></feed>