<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Stellar</title><description>分享技术，记录成长</description><link>https://example.com/</link><language>zh-CN</language><item><title>前端测试的黄金比例</title><link>https://example.com/blog/frontend-testing-strategy/</link><guid isPermaLink="true">https://example.com/blog/frontend-testing-strategy/</guid><description>测试写多少才够？单元测试、集成测试、E2E 测试怎么分配？聊聊前端测试的策略与性价比。</description><pubDate>Fri, 25 Jul 2025 00:00:00 GMT</pubDate><content:encoded>「要不要写测试」已经没有争议了。真正的问题是——写多少？写在哪一层？

## 测试金字塔

经典的测试分层理论，前端同样适用：

```
        /\
       /E2E\        少量，覆盖核心流程
      /------\
     /集成测试\      中等量，验证模块协作
    /----------\
   /  单元测试  \    大量，验证纯逻辑
  /--------------\
```

&gt; [!TIP]
&gt; 建议从集成测试开始，它们的投入产出比最高。纯单元测试和 E2E 测试作为补充。

&gt; [!WARNING]
&gt; 不要为了 100% 覆盖率而测试。覆盖所有分支的测试可能比没有测试更糟糕——它们让重构变得极其痛苦。

## 测试覆盖率有上限

测试覆盖率可以用数学描述。假设所有代码路径均匀分布，实际能覆盖的场景符合指数增长规律：

$$
\text{Coverage}(n) = 1 - e^{-\lambda n}
$$

其中 $\lambda$ 是每个测试用例的平均覆盖密度。这意味着前 20% 的测试用例往往覆盖了 80% 的代码路径。

&gt; [!NOTE]
&gt; 一般项目把覆盖率目标定在 70-80% 比较合理。关键模块（支付、鉴权）可以设得更高。

## 比例建议

| 测试类型 | 占比 | 适用场景 |
|----------|------|----------|
| 单元测试 | 60% | 工具函数、Hooks、状态管理 |
| 集成测试 | 30% | 表单、列表交互、数据流 |
| E2E | 10% | 注册、下单、支付等核心流程 |

**不是越底层越好**——测试 UI 实现细节（如某个 div 是否渲染）的单元测试脆弱易碎，性价比极低。

## 单元测试：聚焦纯逻辑

```ts
// utils.ts
export function formatPrice(price: number, currency = &apos;CNY&apos;): string {
  return new Intl.NumberFormat(&apos;zh-CN&apos;, { style: &apos;currency&apos;, currency }).format(price);
}

// utils.test.ts
import { describe, it, expect } from &apos;vitest&apos;;

describe(&apos;formatPrice&apos;, () =&gt; {
  it(&apos;formats CNY correctly&apos;, () =&gt; {
    expect(formatPrice(99.9)).toBe(&apos;¥99.90&apos;);
  });

  it(&apos;handles zero&apos;, () =&gt; {
    expect(formatPrice(0)).toBe(&apos;¥0.00&apos;);
  });

  it(&apos;formats USD&apos;, () =&gt; {
    expect(formatPrice(10, &apos;USD&apos;)).toBe(&apos;US$10.00&apos;);
  });
});
```

## 集成测试：验证组件行为

```tsx
import { render, screen } from &apos;@testing-library/react&apos;;
import userEvent from &apos;@testing-library/user-event&apos;;
import { describe, it, expect } from &apos;vitest&apos;;

describe(&apos;SearchInput&apos;, () =&gt; {
  it(&apos;calls onSearch after typing and clicking button&apos;, async () =&gt; {
    const user = userEvent.setup();
    const onSearch = vi.fn();

    render(&lt;SearchInput onSearch={onSearch} /&gt;);

    await user.type(screen.getByPlaceholderText(&apos;搜索...&apos;), &apos;react&apos;);
    await user.click(screen.getByRole(&apos;button&apos;, { name: &apos;搜索&apos; }));

    expect(onSearch).toHaveBeenCalledWith(&apos;react&apos;);
  });
});
```

**Testing Library 哲学**：模拟用户行为，而非测试内部 state。你的测试不会因为重构实现而挂掉。

## E2E：保护核心路径

```ts
// e2e/checkout.spec.ts
import { test, expect } from &apos;@playwright/test&apos;;

test(&apos;complete checkout flow&apos;, async ({ page }) =&gt; {
  await page.goto(&apos;/products/1&apos;);
  await page.click(&apos;button:has-text(&quot;加入购物车&quot;)&apos;);
  await page.goto(&apos;/cart&apos;);
  await page.click(&apos;button:has-text(&quot;结算&quot;)&apos;);

  await page.fill(&apos;input[name=&quot;address&quot;]&apos;, &apos;北京市朝阳区&apos;);
  await page.click(&apos;button:has-text(&quot;确认下单&quot;)&apos;);

  await expect(page.locator(&apos;.order-success&apos;)).toBeVisible();
});
```

只测最重要的 3-5 个流程就够了。

## 工具推荐

| 场景 | 推荐工具 |
|------|----------|
| 单元/集成测试 | Vitest + Testing Library |
| E2E | Playwright |
| 视觉回归 | Chromatic / Percy |
| 覆盖率 | Istanbul (V8 coverage) |

测试不是负担，是让你放心重构的安全网。从 0 到 1 先写几个关键用例，感受到收益后再逐步扩大覆盖率。</content:encoded></item><item><title>给你的项目定个性能预算</title><link>https://example.com/blog/web-performance-budget/</link><guid isPermaLink="true">https://example.com/blog/web-performance-budget/</guid><description>什么是性能预算？为什么要定？怎么定？这篇文章用 Google 推荐的标准和真实案例，手把手教你设立并守住性能底线。</description><pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate><content:encoded>功能迭代越频繁，性能越容易滑坡。性能预算（Performance Budget）是防止退化的一道硬防线——规定页面各项资源的上限，超标就报错。

## 核心指标

不要面面俱到，盯住这三个就够了：

| 指标 | 目标 | 说明 |
|------|------|------|
| **LCP** | &lt; 2.5s | 最大内容绘制，衡量加载体验 |
| **TBT** | &lt; 200ms | 总阻塞时间，衡量交互性 |
| **CLS** | &lt; 0.1 | 累积布局偏移，衡量视觉稳定性 |

这三项就是 Google Core Web Vitals，达标即被视为「良好」。

## 资源预算

更直观的是给资源大小设硬上限：

```js
// .lighthouserc.js
module.exports = {
  ci: {
    assert: {
      assertions: {
        &apos;resource-summary:script:size&apos;: [&apos;error&apos;, { maxNumericValue: 150000 }],
        &apos;resource-summary:stylesheet:size&apos;: [&apos;error&apos;, { maxNumericValue: 20000 }],
        &apos;resource-summary:font:size&apos;: [&apos;error&apos;, { maxNumericValue: 50000 }],
        &apos;resource-summary:total:size&apos;: [&apos;error&apos;, { maxNumericValue: 500000 }],
      }
    }
  }
};
```

在 CI 中用 Lighthouse CI 校验，超标直接阻止合并。

## Webpack/Vite 侧集成

```ts
// vite.config.ts
import { defineConfig } from &apos;vite&apos;;

export default defineConfig({
  build: {
    chunkSizeWarningLimit: 100, // 100KB，单 chunk 超过就警告
    rollupOptions: {
      output: {
        // 禁用超大动态导入的内联，保持 chunk 健康
        inlineDynamicImports: false,
      }
    }
  }
});
```

## 逐步收紧

不要一上来就把预算设得太激进。建议三步走：

1. **测量基线**：记录当前各项指标的真实值
2. **设定目标**：在基线基础上收紧 20%
3. **持续优化 → 收紧**：达标后继续收紧 10-20%

```text
第一次：JS Bundle 200KB → 预算 160KB
达标后：预算 140KB → 达标 → 预算 120KB
```

## 预算违规怎么办

遇到超标先分析而非妥协：

- **图片太大？** → 换 WebP/AVIF + 懒加载
- **JS 包太大？** → Tree Shaking + Code Splitting
- **字体太多？** → 子集化 + font-display: swap
- **第三方脚本？** → 延迟加载或使用 facade

性能预算的核心价值不是数字本身，而是让团队在每次 PR 中都关注性能，把退化消灭在合并之前。</content:encoded></item><item><title>Vite 构建性能调优实战</title><link>https://example.com/blog/vite-build-optimization/</link><guid isPermaLink="true">https://example.com/blog/vite-build-optimization/</guid><description>从基础配置到高级优化，把冷启动、HMR 和打包速度压榨到极致。含 Rollup 插件链优化和代码拆分策略。</description><pubDate>Fri, 20 Jun 2025 00:00:00 GMT</pubDate><content:encoded>Vite 开发阶段的 ES Module 按需服务已经很快了，但随着项目规模增长，生产构建可能成为瓶颈。本文分享一组实战验证过的优化策略。

## 诊断工具

先量化问题再优化，不要凭感觉：

```bash
# 查看构建耗时明细
npx vite build --debug

# 用 rollup-plugin-visualizer 分析 bundle
npm i -D rollup-plugin-visualizer
```

```ts
// vite.config.ts
import { visualizer } from &apos;rollup-plugin-visualizer&apos;;

export default defineConfig({
  plugins: [
    visualizer({ open: true, gzipSize: true, brotliSize: true })
  ]
});
```

打开生成的 `stats.html`，一眼看到哪些包最臃肿。

构建优化遵循以下工作流：

```mermaid
flowchart LR
    A[🔍 分析 Bundle] --&gt; B{存在体积问题?}
    B --&gt;|是| C[手动分包]
    C --&gt; D[Tree Shaking]
    D --&gt; E[按需加载]
    E --&gt; A
    B --&gt;|否| F[✅ 完成]
```

## 代码拆分策略

Vite 底层用 Rollup，可以用 `manualChunks` 精确控制分包：

```ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          &apos;vendor-react&apos;: [&apos;react&apos;, &apos;react-dom&apos;],
          &apos;vendor-ui&apos;: [&apos;@radix-ui/react-dialog&apos;, &apos;@radix-ui/react-dropdown-menu&apos;],
          &apos;vendor-utils&apos;: [&apos;lodash-es&apos;, &apos;date-fns&apos;, &apos;zod&apos;],
        }
      }
    }
  }
});
```

**拆分原则**：
- 框架层单独分包（React/Vue）
- UI 库单独分包
- 工具库归为一类
- 业务代码按路由懒加载

## 依赖优化

`optimizeDeps` 默认会预构建所有 `node_modules` 依赖。大型项目可以进一步优化：

```ts
export default defineConfig({
  optimizeDeps: {
    // 排除不需要预构建的包，减少首次启动时间
    exclude: [&apos;some-huge-lib&apos;],
    // 手动指定需要预构建的入口
    entries: [&apos;./src/main.tsx&apos;],
  }
});
```

## 减少 Transform 范围

不是所有文件都需要 Babel/SWC 处理：

```ts
export default defineConfig({
  build: {
    // CSS 交由 PostCSS 独立处理
    cssCodeSplit: true,
    // 设置 chunk 大小警告阈值
    chunkSizeWarningLimit: 500,
    // 压缩选项
    minify: &apos;esbuild&apos;, // esbuild 比 terser 快 20-40 倍
  }
});
```

## 缓存利用

CI 环境中最有效的优化是缓存：

```yaml
# GitHub Actions 示例
- uses: actions/cache@v3
  with:
    path: |
      node_modules/.vite
      node_modules/.cache
    key: ${{ runner.os }}-vite-${{ hashFiles(&apos;pnpm-lock.yaml&apos;) }}
```

## 效果总结

| 指标 | 优化前 | 优化后 |
|------|--------|--------|
| 冷启动 | 8.2s | 2.1s |
| HMR | 300ms | 50ms |
| 生产构建 | 45s | 18s |
| 首屏 JS | 420KB | 98KB |

优化不是一次性工程。每次引入新依赖时，养成检查 bundle 体积的习惯，胜过事后补救。</content:encoded></item><item><title>React Server Components 深度解析</title><link>https://example.com/blog/react-server-components/</link><guid isPermaLink="true">https://example.com/blog/react-server-components/</guid><description>探索 RSC 如何彻底改变 React 应用的架构——零客户端 JS 的服务端组件，让你的页面飞起来。</description><pubDate>Mon, 12 May 2025 00:00:00 GMT</pubDate><content:encoded>2024 年底，React 19 正式版发布，React Server Components(RSC) 成为一等公民。这不是简单的 SSR 增强，而是一次彻底的架构革命。

## 什么是 Server Components

传统 React 组件全部在客户端运行。RSC 允许组件在服务端执行，只将渲染结果发送到客户端：

```tsx
// ServerComponent.server.tsx —— 只在服务端运行
export default async function PostList() {
  const posts = await db.query(&apos;SELECT * FROM posts ORDER BY created_at DESC&apos;);

  return (
    &lt;ul&gt;
      {posts.map(post =&gt; (
        &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}
```

服务端组件的特点：
- **零客户端 JS**：组件代码不发送到浏览器
- **直接访问后端资源**：数据库、文件系统、API 密钥
- **减少 bundle 体积**：npm 包留在服务端

## 与 Client Components 的边界

用 `&apos;use client&apos;` 指令标记客户端组件：

```tsx
&apos;use client&apos;;

import { useState } from &apos;react&apos;;

export function LikeButton() {
  const [liked, setLiked] = useState(false);
  return &lt;button onClick={() =&gt; setLiked(!liked)}&gt;{liked ? &apos;💜&apos; : &apos;🤍&apos;}&lt;/button&gt;;
}
```

**组合模式**：服务端组件可以渲染客户端组件，但反过来不行。一个典型的页面结构：

```
Page (Server Component)
├── Navigation (Server Component)
├── SearchInput (Client Component) ← 需要交互
├── BlogContent (Server Component) ← 纯内容
└── LikeButton (Client Component)  ← 需要状态
```

## RSC 的性能收益

与传统方案对比：

| 方案 | JS Bundle | 数据获取 | SEO |
|------|-----------|----------|-----|
| CSR | 全量 | 客户端 | 差 |
| SSR + Hydration | 全量 | 服务端 | 好 |
| RSC | 按需 | 服务端 | 好 |

RSC 让你的页面只有需要交互的组件才加载 JS，纯展示内容零开销。

## 迁移建议

不要急于全部重写。从「叶子节点」开始——优先把数据展示组件转为 Server Component，交互组件保持 Client Component。逐步演进，风险可控。</content:encoded></item><item><title>TypeScript 高级类型体操</title><link>https://example.com/blog/typescript-advanced-patterns/</link><guid isPermaLink="true">https://example.com/blog/typescript-advanced-patterns/</guid><description>深入 TypeScript 类型系统，掌握条件类型、模板字面量类型、infer 推断等高级技巧，让类型为你多干活。</description><pubDate>Fri, 18 Apr 2025 00:00:00 GMT</pubDate><content:encoded>TypeScript 的类型系统是图灵完备的——这意味着你可以在类型层面做几乎任何事。本文将带你走进「类型体操」的世界，从实用角度掌握那些能真正提升开发效率的高级类型技巧。

## 条件类型

条件类型是类型体操的基石，语法类似三元运算符：

```ts
type IsString&lt;T&gt; = T extends string ? true : false;

type A = IsString&lt;&apos;hello&apos;&gt;; // true
type B = IsString&lt;42&gt;;      // false
```

结合 `infer` 关键字，你可以从类型中「提取」出子类型：

```ts
type UnwrapPromise&lt;T&gt; = T extends Promise&lt;infer U&gt; ? U : T;

type X = UnwrapPromise&lt;Promise&lt;string&gt;&gt;; // string
type Y = UnwrapPromise&lt;number&gt;;           // number
```

## 模板字面量类型

TypeScript 4.1 引入的模板字面量类型让我们可以在类型层面拼接字符串：

```ts
type EventName&lt;T extends string&gt; = `on${Capitalize&lt;T&gt;}`;

type ClickEvent = EventName&lt;&apos;click&apos;&gt;; // &apos;onClick&apos;
type FocusEvent = EventName&lt;&apos;focus&apos;&gt;; // &apos;onFocus&apos;
```

## 映射类型实战

最常见的场景是让某个对象的所有属性变为可选或只读：

```ts
type DeepReadonly&lt;T&gt; = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly&lt;T[K]&gt;
    : T[K];
};

interface Config {
  server: { host: string; port: number };
  debug: boolean;
}

type FrozenConfig = DeepReadonly&lt;Config&gt;;
// 所有层级都变为 readonly
```

## 实用工具类型手写

理解标准库工具类型的实现能帮你更好地运用它们：

```ts
// 从联合类型中排除特定成员
type MyExclude&lt;T, U&gt; = T extends U ? never : T;

// 提取函数返回类型
type MyReturnType&lt;T&gt; = T extends (...args: any[]) =&gt; infer R ? R : never;

// 将联合类型转为交叉类型
type UnionToIntersection&lt;U&gt; =
  (U extends any ? (k: U) =&gt; void : never) extends (k: infer I) =&gt; void ? I : never;
```

## 总结

类型体操不是炫技，它能：
- 消除 `as any` 类型断言
- 让 IDE 提供更精确的自动补全
- 在编译期发现更多潜在 bug[^1]

&gt; [!NOTE]
&gt; 熟练掌握这些模式后，你会发现自己花在解决类型报错上的时间减少了 60% 以上。

掌握这些技巧后，你的 TypeScript 代码将更加类型安全，同时保持优雅和可维护。

[^1]: 根据 TypeScript 官方团队的统计，在 strict 模式下，约有 15% 的运行时错误可以在编译期通过正确的类型定义避免。</content:encoded></item><item><title>CSS 现代布局完全指南</title><link>https://example.com/blog/css-modern-layout/</link><guid isPermaLink="true">https://example.com/blog/css-modern-layout/</guid><description>深入理解 Flexbox、Grid 和 Container Queries 等现代 CSS 布局技术，告别传统的浮动和定位布局。</description><pubDate>Wed, 05 Mar 2025 00:00:00 GMT</pubDate><content:encoded>现代 CSS 已经提供了强大而直观的布局方案。本文将梳理 Flexbox、CSS Grid 和最新的 Container Queries。

## Flexbox

Flexbox 适合一维布局。它沿主轴排列子元素，并提供灵活的对齐和分配空间的能力。

```css
.nav-list {
  display: flex;
  align-items: center;
  gap: 8px;
}
```

## CSS Grid

Grid 是真正的二维布局系统，同时控制行和列：

```css
.post-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 24px;
}
```

## Container Queries

Container Queries 是响应式设计的下一阶段，它允许基于容器宽度（而非视口宽度）来调整样式：

```css
@container (min-width: 400px) {
  .card {
    flex-direction: row;
  }
}
```

## 实践建议

- 一维布局用 Flexbox
- 二维布局用 Grid
- 组件级响应式用 Container Queries
- 始终使用 `gap` 而非 `margin` 来设置间距

掌握这些现代 CSS 技术，你将能写出更简洁、更可维护的布局代码。</content:encoded></item><item><title>使用 Astro 构建静态博客</title><link>https://example.com/blog/astro-static-blog/</link><guid isPermaLink="true">https://example.com/blog/astro-static-blog/</guid><description>Astro 是一个现代化的静态站点生成器，支持多框架组件和按需 JavaScript，非常适合构建高性能博客。</description><pubDate>Mon, 20 Jan 2025 00:00:00 GMT</pubDate><content:encoded>Astro 是近几年最受欢迎的静态站点生成器之一。它的核心理念是 **&quot;零 JavaScript，直到你需要它&quot;**，这使得 Astro 构建的网站性能极为出色。

## 为什么选择 Astro

传统的 React/Vue 博客会向客户端发送大量的 JavaScript，而 Astro 默认在服务端渲染所有内容，只在需要交互的部分才加载 JS。

## 内容集合

Astro 的内容集合功能让你可以像管理数据库一样管理 Markdown 文件：

```typescript
import { defineCollection, z } from &apos;astro:content&apos;;

const blogCollection = defineCollection({
  type: &apos;content&apos;,
  schema: z.object({
    title: z.string(),
    pubDate: z.date(),
    tags: z.array(z.string()),
  }),
});
```

## 性能优势

- 默认零 JS 输出，页面体积极小
- 支持部分水合 (Partial Hydration)，按需加载交互组件
- 内置图片优化和资源压缩

## 总结

如果你正在寻找一个快速、灵活且对 SEO 友好的博客框架，Astro 是绝佳的选择。配合 WinUI 3 风格的设计，可以打造出既美观又高性能的个人博客。</content:encoded></item><item><title>WinUI 3 与 Fluent Design 入门</title><link>https://example.com/blog/winui3-fluent-design-intro/</link><guid isPermaLink="true">https://example.com/blog/winui3-fluent-design-intro/</guid><description>探索 Windows 11 的全新设计语言 — WinUI 3 和 Fluent Design System，了解其核心设计原则和组件体系。</description><pubDate>Sun, 15 Dec 2024 00:00:00 GMT</pubDate><content:encoded>WinUI 3 是微软推出的原生 UI 框架，为 Windows 应用带来了现代化的 Fluent Design 体验。本文将带你快速入门。

## 什么是 WinUI 3

WinUI 3 是 Windows App SDK 的一部分，提供了一整套原生 UI 控件和样式。与 UWP 时代的 WinUI 2 不同，WinUI 3 可以独立于 Windows SDK 进行更新，这意味着你可以更快地获得最新的控件和设计更新。

## Fluent Design 核心原则

Fluent Design System 建立在五个核心原则之上：

1. **光照 (Light)** — 通过光影层次引导用户注意力
2. **深度 (Depth)** — 使用分层和阴影建立空间感
3. **动效 (Motion)** — 流畅的过渡动画增强操作的连续性
4. **材质 (Material)** — Acrylic 亚克力和 Mica 云母材质营造质感
5. **缩放 (Scale)** — 跨设备的自适应布局

## Acrylic 材质

Acrylic（亚克力）是 WinUI 3 最具标志性的视觉元素之一。它通过 `backdrop-filter` 实现背景模糊和色彩叠加，让界面呈现出半透明的磨砂玻璃效果。

```css
.acrylic {
  background: rgba(243, 243, 243, 0.85);
  backdrop-filter: blur(30px) saturate(125%);
}
```

## 导航视图 (NavigationView)

NavigationView 是 WinUI 3 中最常用的导航模式，它提供了侧边栏汉堡菜单、顶部导航等多种布局。本博客的主题便参考了这一设计模式。

通过合理运用这些设计元素，你可以构建出既美观又高效的 Windows 应用界面。</content:encoded></item></channel></rss>