Symbol Kit 用于把 Figma 导出的 SVG 源文件和 Design Token JSON 编译为多字重、多样式的 SVG 图标集。Normal / outline 是必需样式,Fill 和 Duotone 根据配置可选生成。
- Node.js 22 或更高版本
- pnpm
pnpm install
pnpm build当前公开版本为 0.1.0:
| 包 | 用途 |
|---|---|
@claralight-design/symbol-kit-core |
Symbol IR、字重定义、SVG 渲染与已编译图标数据 |
@claralight-design/symbol-kit-compiler |
SVG 解析、语义分类、几何处理与图标生成 |
@claralight-design/symbol-kit-cli |
可安装的 SVG 与 Design Token 构建命令 |
pnpm add @claralight-design/symbol-kit-core @claralight-design/symbol-kit-compiler
pnpm add -D @claralight-design/symbol-kit-cliCompiler 和 CLI 固定依赖同版本的 Core。根包、Demo 和独立框架子仓不发布到 npm。
CLI 默认使用英文输出;在 build 后加入 zh 参数可切换为简体中文。安装 npm 包后使用 pnpm clsk-cli build;clsymbol-kit-cli 和 symbol-kit 保留为命令别名,根仓另保留 pnpm clsymbol-kit:cli build 脚本。交互终端中会显示读取输入、编译 SVG、写入产物三个阶段的渐变进度动画;CI 和管道环境会自动使用稳定的逐行日志。
编译单个 SVG:
pnpm clsk-cli build \
--input test/CreditCard.svg \
--width-tokens test/Width \
--style-tokens test/Style \
--config symbol-kit.config.json \
--out-dir output/CreditCard \
--name CreditCard批量编译目录下的 SVG:
pnpm clsk-cli build \
--input test \
--width-tokens test/Width \
--style-tokens test/Style \
--config symbol-kit.config.json \
--out-dir output--input 可以是单个 .svg 文件,也可以是目录。目录模式只读取该目录第一层的 .svg 文件,不会递归扫描。--name 仅用于单文件模式;不传时使用文件名作为图标名。
| 参数 | 是否必填 | 说明 |
|---|---|---|
--input |
是 | SVG 文件或 SVG 目录 |
--width-tokens |
是 | 字重 Token 目录 |
--style-tokens |
是 | 样式 Token 目录 |
--out-dir |
是 | 生成目录;已有的同名图标目录会先被清理 |
--config |
否 | 项目编译配置 JSON 文件 |
--name |
否 | 单文件模式下覆盖图标名 |
项目级生成规则写在 symbol-kit.config.json 中,并通过 --config 传给 CLI。Token 提供颜色、样式透明度和字重线宽,项目配置在其后合并,可配置生成行为和黑名单。
{
"blacklist": {
"combinations": [{ "weight": "ultralight", "mode": "fill" }],
"icons": {
"CreditCard": [{ "weight": "medium", "mode": "duotone" }]
}
},
"rendering": {
"duotoneFillOpacity": 0.2,
"fillFillOpacity": 1
}
}blacklist.combinations 对所有图标生效,blacklist.icons 按最终图标名精确匹配。全局规则和单图标规则会合并;每条规则使用编译器字重名以及 outline、fill、duotone 模式名。某个图标的全部组合都被排除时,CLI 会将其作为正常跳过处理并清理已有产物。
CLI 从两个目录读取以 .tokens.json 结尾的 JSON 文件。文件名可以自定义,模式名以 $extensions.com.figma.modeName 为准。
每个字重一个文件,必须包含 Width.Stroke Width.$value:
{
"Width": {
"Stroke Width": {
"$type": "number",
"$value": 2.2
}
},
"$extensions": {
"com.figma.modeName": "Regular"
}
}CLI 可识别的模式名为 UltraLight、Thin、Light、Regular 和 Medium。$value 必须是大于 0 的数字,并与源 SVG 中的 stroke-width 对应。
必须提供 Normal 模式。Duotone 和 Fill 都可选;CLI 只有在读到对应模式时才会生成该样式。每个模式一个文件,结构如下:
{
"Fill": {
"Color": { "$value": { "hex": "#000000" } },
"Reverse": { "$value": { "hex": "#FFFFFF" } },
"Line Opacity": { "$value": 0 },
"Duotone Line Opacity": { "$value": 60 },
"BG Opacity": { "$value": 80 },
"NoFill BG Opacity": { "$value": 0 },
"NoDuo BG Opacity": { "$value": 90 }
},
"$extensions": {
"com.figma.modeName": "Fill"
}
}| 字段 | 含义 |
|---|---|
Color |
前景色,读取 $value.hex |
Reverse |
反色或挖空颜色,读取 $value.hex |
Line Opacity |
普通线条透明度 |
Duotone Line Opacity |
双色线条透明度 |
NoFill Line Opacity |
不进入 Fill 样式的线条透明度 |
NoDuo Line Opacity |
不进入 Duotone 样式的线条透明度 |
OnlyFill Line Opacity |
仅进入 Fill 样式的线条透明度 |
OnlyDuo Line Opacity |
仅进入 Duotone 样式的线条透明度 |
BG Opacity |
背景层透明度 |
NoFill BG Opacity |
不进入 Fill 样式的背景层透明度 |
NoDuo BG Opacity |
不进入 Duotone 样式的背景层透明度 |
透明度 Token 使用 0 到 100 的百分数。CLI 会转换为编译器使用的 0 到 1。当前测试配置 test/Style 同时包含 Normal、Duotone 和 Fill,因此会生成三种样式;字重配置可参考 test/Width。
- 根节点必须是
<svg>,并提供有效的viewBox。 - 图形使用
<path>;线条宽度要能匹配 Width Tokens 中的一个字重。 - 颜色应与 Style Tokens 中的
Color或Reverse一致。 - 透明度用来区分普通线条、双色线条和不同背景层,应与 Style Tokens 中的对应值一致。
- 未配置的颜色、透明度或线宽会产生错误诊断,CLI 不会写入该批产物。
需要显式标注语义时,可以给 path 设置以 sk- 开头的 id:
| ID 段 | 语义 |
|---|---|
line |
普通线条 |
duotone-line |
双色线条 |
line-no-fill |
不进入 Fill 的线条 |
line-no-duo |
不进入 Duotone 的线条 |
line-only-fill |
仅进入 Fill 的线条 |
line-only-duo |
仅进入 Duotone 的线条 |
bg |
背景层 |
bg-no-fill |
不进入 Fill 的背景层 |
bg-no-duo |
不进入 Duotone 的背景层 |
reverse |
反色修饰符,可用 -- 与其他段组合 |
例如:sk-line、sk-bg--reverse。
单个图标的目录结构如下:
output/CreditCard/
├── UltraLight/
│ ├── Normal.svg
│ ├── Fill.svg
│ └── Duotone.svg
├── Regular/
│ └── ...
└── manifest.json
上面是当前完整测试配置的产物。如果未提供 Fill 或 Duotone Style Token,对应的 SVG 不会生成。
批量模式会再按图标名创建一层目录,并在输出根目录生成总 manifest.json。每个图标目录也有自己的 manifest.json。
需要在 TypeScript 中直接生成 SVG 集时,可使用 generateFigmaSvgSet:
import { readFile } from "node:fs/promises";
import {
generateFigmaSvgSet,
type CompilerConfigInput
} from "@claralight-design/symbol-kit-compiler";
const config: CompilerConfigInput = {
colors: {
foreground: ["#000000"],
background: ["#FFFFFF"]
},
styles: {
normal: {
color: "#000000",
reverse: "#000000",
lineOpacity: 1,
duotoneLineOpacity: 0,
noFillLineOpacity: 1,
noDuotoneLineOpacity: 1,
onlyFillLineOpacity: 0,
onlyDuotoneLineOpacity: 0,
backgroundOpacity: 0,
noFillBackgroundOpacity: 0,
noDuotoneBackgroundOpacity: 0
},
duotone: {
color: "#000000",
reverse: "#000000",
lineOpacity: 0,
duotoneLineOpacity: 0.7,
noFillLineOpacity: 1,
noDuotoneLineOpacity: 0,
onlyFillLineOpacity: 0,
onlyDuotoneLineOpacity: 1,
backgroundOpacity: 0.2,
noFillBackgroundOpacity: 0.3,
noDuotoneBackgroundOpacity: 0
},
fill: {
color: "#000000",
reverse: "#FFFFFF",
lineOpacity: 0,
duotoneLineOpacity: 0.6,
noFillLineOpacity: 0,
noDuotoneLineOpacity: 1,
onlyFillLineOpacity: 1,
onlyDuotoneLineOpacity: 0,
backgroundOpacity: 0.8,
noFillBackgroundOpacity: 0,
noDuotoneBackgroundOpacity: 0.9
}
},
weights: {
ultralight: { strokeWidth: 0.6 },
regular: { strokeWidth: 2.2 }
},
modes: ["outline", "fill", "duotone"]
};
const svg = await readFile("icons/CreditCard.svg", "utf8");
const result = generateFigmaSvgSet({ name: "CreditCard", svg, config });
if (result.diagnostics.some(({ severity }) => severity === "error")) {
throw new Error(JSON.stringify(result.diagnostics, null, 2));
}
for (const file of result.files) {
console.log(file.fileName, file.svg);
}需要直接生成 Symbol IR 时,weight 表示源 SVG 的实际字重,targetWeights 表示需要从该源展开的目标字重。编译器只解析和分类一次源文件,再分别物化各目标字重:
import { SymbolWeight } from "@claralight-design/symbol-kit-core";
import {
compileSymbol,
createPaperGeometryMaterializer
} from "@claralight-design/symbol-kit-compiler";
const symbolResult = compileSymbol({
name: "CreditCard",
sources: [
{
weight: SymbolWeight.Ultralight,
targetWeights: [SymbolWeight.Ultralight, SymbolWeight.Regular],
fileName: "CreditCard.svg",
svg
}
],
config,
geometryMaterializer: createPaperGeometryMaterializer({
weights: config.weights ?? {}
})
});编程接口的所有透明度都使用 0 到 1。配置合并顺序为 global → project → cli,后一层覆盖前一层:
import { resolveCompilerConfig } from "@claralight-design/symbol-kit-compiler";
const config = resolveCompilerConfig({
global: { rendering: { duotoneFillOpacity: 0.2 } },
project: { stroke: { strokeLinecap: "round", strokeLinejoin: "round" } },
cli: { modes: ["outline", "duotone"] }
});完整配置项:
| 配置 | 可选值或默认值 | 用途 |
|---|---|---|
blacklist.combinations |
[] |
所有图标都不生成的字重与模式组合 |
blacklist.icons |
{} |
按图标名配置不生成的字重与模式组合 |
colors.foreground |
[] |
可识别的前景颜色列表;空列表表示不限制前景色 |
colors.background |
[] |
可识别的背景或反色列表 |
opacity.full |
1 |
主要图层的完全不透明值 |
opacity.tolerance |
0.001 |
判定透明度相等时允许的误差 |
opacity.secondaryThreshold |
1 |
未配置样式档位时,识别次要图层的阈值 |
outline.foreground |
drop / convert-to-background |
Outline 模式如何处理前景填充 |
styles |
{} |
按样式名配置颜色与各语义透明度 |
weights |
{} |
按字重名配置 strokeWidth 和可选 tolerance |
rendering.duotoneFillOpacity |
0.2 |
Duotone 输出的辅助色填充透明度 |
rendering.fillFillOpacity |
1 |
Fill 输出的填充透明度 |
semanticIds |
sk- 前缀、-- 分隔符 |
自定义 SVG path ID 的语义解析规则 |
stroke.strokeLinecap |
butt / round / square |
统一覆盖源 SVG 的线帽 |
stroke.strokeLinejoin |
miter / round / bevel |
统一覆盖源 SVG 的连接样式 |
modes |
outline / fill / duotone |
必须包含 outline;另两项可选,默认仅生成 outline |
| 目录 | 用途 |
|---|---|
packages/core |
平台无关的 Symbol IR 和字重定义 |
packages/compiler |
SVG 解析、语义分类、几何处理与渲染 |
packages/cli |
命令行批量生成工具 |
packages/unplugin |
构建工具集成 |
apps/demo |
Astro 壳层与 React 交互预览应用 |
React、Vue、Unplugin、Flutter 和 Figma Plugin 适配层都是独立 Git 仓库,不由主仓库追踪,也不参与主工作区的 pnpm workspace 和 TypeScript project references。子仓库由 modules.config.json 和 scripts/modules.mjs 统一管理。
配置格式:
{
"version": 1,
"modules": [
{
"name": "React",
"path": "packages/react",
"branch": "main"
}
]
}| 字段 | 是否必填 | 说明 |
|---|---|---|
name |
是 | 模块名称,同时可用于 --module 筛选 |
path |
是 | 相对于配置文件的子仓库路径,必须位于工作区内 |
branch |
否 | 默认分支,默认为 main |
url |
否 | Git SSH 或 HTTPS 地址;未填时只初始化本地仓库 |
常用命令:
pnpm modules
pnpm modules init
pnpm modules status
pnpm modules sync
pnpm modules status --module Reactpnpm modules 在交互终端中会打开全屏 TUI,可以用 ↑、↓ 或 j、k 选择模块,直接执行单个/全部初始化、同步、刷新和查看详情。在非交互环境中会自动回退为普通状态输出。
init 会自动更新主仓库 .gitignore、对已被主仓追踪的模块执行 git rm --cached,然后初始化本地子仓库或从 url 克隆。sync 只处理配置了 url 的模块,有未提交修改时会跳过更新,并且只允许 fast-forward。
主仓库克隆时不会自动带上这些目录;它们不是 Git submodule,需要运行 pnpm modules init 配置。
pnpm test
pnpm lint
pnpm build
pnpm demopnpm build:credit-card 可用于生成单个示例,pnpm build:icons 可批量生成 test 目录中的全部图标。
发布 @claralight-design/symbol-kit-core、@claralight-design/symbol-kit-compiler 和 @claralight-design/symbol-kit-cli,三个包版本保持一致。
npm login
pnpm release:check
pnpm --filter @claralight-design/symbol-kit-core publish
pnpm --filter @claralight-design/symbol-kit-compiler publish
pnpm --filter @claralight-design/symbol-kit-cli publish必须先发布 Core,再发布 Compiler 和 CLI。三个包都通过 prepack 重新生成正式构建产物,并以 public scoped package 发布。
可以在仓库同级的非 Git 目录中安装公开包后验证批量生成:
mkdir ../symbol-kit-npm-cli-test
cd ../symbol-kit-npm-cli-test
pnpm add -D @claralight-design/symbol-kit-cli@0.1.0
pnpm clsk-cli build \
--input icons \
--width-tokens tokens/Width \
--style-tokens tokens/Style \
--out-dir output使用 ArrowLeft、ArrowRight、CaretDown,以及 UltraLight、Regular 两个字重和 Normal、Fill、Duotone 三种样式验证时,会生成 18 个 SVG 和 3 个 Symbol JSON。