第 9 章:发布后迭代

这是教程的最后一章。

前面 8 章讲的是"怎么从 0 到 1"。这一章讲"1 之后怎么继续"——加新图标的 checklist、容易踩的坑、还有 sag-icons 的路线图。

读完这一章你会知道:

  • 加一个新图标的完整流程
  • sag-icons 当前已知的小坑
  • 怎么避免"发版事故"
  • sag-icons 接下来往哪走

9.1 加一个新图标的 checklist

sag-icons 加图标的步骤是固定的。下面以"加一个 user-circle 图标"为例。

步骤 1:Figma 画图

按第 2 章规范画 24×24 多色图标。命名约定:icon-user-circle(kebab-case)。

步骤 2:清理 SVG + 标 data-tone

按第 3 章流程:

  • 内联样式 → fill="currentColor" / stroke="currentColor"
  • 语义色块加 data-tone="blue|cyan|green|amber|white"
  • 复制 innerHTML(不要外层 <svg>)

步骤 3:新建 TS 文件

src/data/icons/ui/user-circle.ts
export const userCircle = `<circle cx="12" cy="12" data-tone="blue" fill="currentColor" r="9" />
<circle cx="12" cy="9" data-tone="white" fill="currentColor" r="3.5" />
<path data-tone="cyan" d="M5.5 19c.8-3.4 3.3-5.5 6.5-5.5s5.7 2.1 6.5 5.5c.2 1-.5 1.9-1.5 1.9H7c-1 0-1.7-.9-1.5-1.9Z" fill="currentColor" />`;

对应目录:src/data/icons/<category>/

步骤 4:在ICON_PATHS 注册

src/data/index.ts
import { userCircle } from "./icons/ui/user-circle";

// ...其他 imports

export const ICON_PATHS = {
  // ...其他图标
  "user-circle": userCircle,
} as const;

注意:键名用 kebab-case("user-circle"),变量名用 camelCase(userCircle)。这样 IconName 联合类型自动包含 "user-circle"。

步骤 5:在ICON_CATEGORIES 注册

src/data/categories.ts
ui: [
  "user",
  "users",
  "user-circle",  // ← 新加的
  "mail",
  // ...
]

步骤 6:验证

pnpm typecheck
# 检查 IconName 类型是否正确

pnpm build:lib
# 检查产物能编译

打开文档站确认:

pnpm dev
# → http://localhost:3000/icons/ui

应该看到新图标出现在"基础界面"分类下。

步骤 7:发版

# 1. 改版本号
# package.json: "version": "0.0.1" → "0.0.2"

# 2. 跑构建
pnpm build:lib

# 3. 发版
npm publish

9.2 容易踩的坑

坑 1:ICON_PATHS 漏注册

// ❌ 漏了
export const ICON_PATHS = {
  home,
  // 漏了 brand
} as const;

类型 IconName 仍然正常("brand" 不会出现在 union 里),但 <sag-icon name="brand" /> 渲染为空。

修复:在 src/data/index.ts 顶部加 import 和 ICON_PATHS 注册。

坑 2:data-tone 拼错

// ❌
<path data-tone="Blue" ...>  // 大写 B

// ✅
<path data-tone="blue" ...>

CSS 选择器是 [data-tone="blue"](小写)。如果 data-tone 拼错,CSS 主题切换不生效。图标看起来永远是默认灰色。

修复:统一小写。可以加个 lint 规则检查:

.eslintrc.json (假设)
{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "JSXAttribute[name.name='data-tone'] > Literal[value=/[A-Z]/]",
        "message": "data-tone must be lowercase"
      }
    ]
  }
}

坑 3:fill 没改成 currentColor

// ❌ 颜色写死
<path data-tone="blue" fill="#3b82f6" ...>

// ✅ 让 CSS 接管
<path data-tone="blue" fill="currentColor" ...>

如果忘改 currentColor,CSS 主题切换无效——图标永远是你写的那个颜色。

坑 4:多词图标命名

// ❌
import { userCircle } from "./icons/ui/userCircle";
ICON_PATHS = { userCircle, ... };  // TS key 是 "userCircle"

// ❌
ICON_PATHS = { "userCircle": ..., ... };  // name 属性也得是 "userCircle"

// ✅
import { userCircle } from "./icons/ui/user-circle";
ICON_PATHS = { "user-circle": userCircle, ... };

保持键名 = 文件名(去后缀),变量名 = camelCase。这是 sag-icons 的强约定。

坑 5:自定义元素重复注册

如果用户手动 register() 又 import 主入口,主入口的自动注册会静默跳过:

let registered = false;
export function register(tagName: string = "sag-icon"): void {
  if (registered) return;  // ← 这里
  // ...
}

但用户改 tagName 时(如 register("my-icon")),自动注册会冲突:

customElements.define("sag-icon", SagIcon);   // 主入口自动跑
register("my-icon");                            // 用户手动跑
// 结果:用户拿到了 <my-icon>,但 <sag-icon> 也注册了

修复:要么只用 import "@jujiuyey/sag-icons",要么只手动 register("custom-name")。

坑 6:CSS 变量名冲突

用户应用可能也用了 --sag-icon-blue 变量做其他事。冲突时:

/* 用户也定义了 */
:root {
  --sag-icon-blue: #ff0000;  /* 红色,但被他们用于其他组件 */
}

/* sag-icons 想用蓝色,但被覆盖 */
sag-icon.is-active [data-tone="blue"] {
  color: var(--sag-icon-blue);  /* 实际是红色 */
}

修复:用户应该用不同前缀(--my-app-blue),或者 sag-icons 改成更难冲突的变量名。

这是 CSS 变量方案天生的局限。如果你需要更严格的隔离,考虑:

  • 给 sag-icon 加 Shadow DOM(放弃 CSS 主题穿透)
  • 用 CSS-in-JS(性能差、不支持 SSR)

9.3 怎么测试

sag-icons 当前没有自动化测试。建议至少做这几项手动测试:

视觉测试

每个图标渲染出来要和 Figma 里的基本一致。1px 偏差可以接受,2px 以上需要改。

pnpm dev
# 浏览 http://localhost:3000/icons/<category>

主题切换测试

每个图标在 is-active 状态应该正确显示彩色:

// 测试页
<>
  <sag-icon name="home" />
  <sag-icon name="home" class="is-active" />
</>

跨框架测试

sag-icons 主打跨框架,至少在 React、Vue、原生 HTML 各跑一次:

// React
<sag-icon name="workbench" />

// Vue
<sag-icon name="workbench" />

// 原生 HTML
<sag-icon name="workbench"></sag-icon>

SSR 测试

在 Next.js / Nuxt 项目里安装,确认 import "@jujiuyey/sag-icons" 不抛错(SSR 阶段)。


9.4 sag-icons 的路线图

当前 sag-icons 是 0.0.1——还没到 1.0。Roadmap:

短期

  • 补 exports 子路径:让 @jujiuyey/sag-icons/data/... 和 @jujiuyey/sag-icons/css/... 真正可访问(第 8 章讲过)
  • 更多图标:目标 200+
  • 测试套件:用 Vitest + jsdom 跑基本测试

中期

  • Figma 源文件:把图标库 Figma 文件公开
  • 图标搜索 API:在文档站加搜索
  • tree-shaking 优化:确保用户用 <sag-icon name="x" /> 不会带全包进 bundle

长期

  • Vue/React 包装组件:虽然不需要,但有些团队习惯用 <SagIcon name="x" /> 而不是 <sag-icon>
  • 图标编辑器:让用户能在文档站自定义 data-tone 映射
  • Figma 插件:自动把图标标 data-tone

不做的事

  • ❌ 不做 1500+ 图标的通用库(专注多色 + 状态场景)
  • ❌ 不做按框架分发的多包(Web Component 一套走天下)
  • ❌ 不做主题切换系统(只做 1 套主题内的状态切换)
  • ❌ 不做付费版 / Pro 版(保持 MIT)

9.5 教训:sag-icons 走过的弯路

如果你要做一个图标库,下面这些是 sag-icons 已经验证过的不要做的事:

❌ 不要按框架分发

最早 sag-icons 想做 react-sag-icons / vue-sag-icons / svelte-sag-icons 三个包。

结果:维护成本 * 3。Web Component 一个包搞定所有。

❌ 不要支持 props 传色

最早想给 sag-icon 加 color / primary / secondary props。

结果:用户传 5 个颜色,参数列表爆炸;还要做 props → CSS 变量同步。

CSS 变量穿透 + is-active 一招鲜。

❌ 不要做主题切换

最早想支持 light / dark / brand 4 套主题。

结果:每个图标要为每个主题各做一套配色,复杂度 × 4。

只做"1 套主题下的状态切换",把简单留给 CSS。

❌ 不要用 Shadow DOM

最早想给 sag-icon 加 Shadow DOM 做样式隔离。

结果:CSS 变量穿透失败,主题系统崩盘。

外部 CSS 变量穿透是 sag-icons 的命脉,Shadow DOM 不能上。

❌ 不要做巨型图标库

最早想对标 Lucide / IconPark 做 1500+ 图标。

结果:风格一致性崩盘,每个图标都有点违和。

聚焦 200+ 高质量 + 风格统一,远比 1500+ 但风格杂好。


9.6 如果你来重新设计 sag-icons

一些可能的方向:

加一个size 属性

<sag-icon name="home" size="lg" />

支持 xs / sm / md / lg / xl 五档预设。但 CSS 覆盖 width / height 也行,看用户偏好。

加color 直接传色

<sag-icon name="home" color="purple" />

绕过 data-tone 直接覆盖。代价:和 is-active 系统冲突,需要二选一。

加outline /filled 切换

<sag-icon name="bell" variant="outline" />
<sag-icon name="bell" variant="filled" />

每个图标做两套 SVG 字符串。包体积 × 2。不推荐。

加图标分组

<sag-icon name="home" group="navigation" />

让用户按场景分组查找图标。但 sag-icons 已经按目录分类了,价值不大。


9.7 教程总结

到这里,sag-icons 的 9 章教程讲完了。我们走过的路:

章主题关键设计
02Figma 画图24×24 + 2px stroke + 5 色板
03图标数据层data-tone 标记 + 字符串存储 + ICON_PATHS 派生
04包工程exports 子路径 + tsconfig 双层 + Vite 库模式
05Web ComponentobservedAttributes + 幂等 register + SSR 安全
06CSS 多色调CSS 变量 + data-tone + is-active 状态切换
07类型系统as const + IconName + JSX 增强 .d.ts
08构建发布tsc + vite 双步 + pnpm pack 验证
09迭代路线checklist + 避坑 + 路线图

sag-icons 的几个核心 takeaway

  1. Web Component 跨框架:<sag-icon> 一套走天下,省 N 个包
  2. CSS 变量 + data-tone:纯 CSS 主题切换,零 JS 状态机
  3. 图标当字符串存:物理可拆分,tree-shaking 友好
  4. 类型派生:as const + keyof typeof 让 IconName 自动维护
  5. 运行时 CSS 注入:幂等 + SSR 安全 + 用户可旁路

这些设计可以搬到任何图标库,不是 sag-icons 专属。

下一步读什么


教程结束。如果有问题,欢迎到 sag-icons GitHub Issues 讨论。