简介:sketch-json-cli 是一款面向 UI/UX 设计师与前端开发者的轻量级命令行工具,专为 Sketch 设计稿的版本控制与协作优化而生。它解决了 Sketch 原生二进制文件难以 diff、合并与 Git 管理的痛点,支持双向转换:将 .sketch 文件解析为结构清晰、可读性强的 JSON,便于代码审查与自动化处理;也可将修改后的 JSON 重新生成标准 Sketch 文件,保障设计资产一致性。资源包共 8 个文件,含核心逻辑 JS 脚本、配置用 YAML 与 JSON、说明文档 MD、许可证 LICENSE 及依赖锁文件,整体仅 34KB,即装即用。已有 391 人学习下载,适合需将设计稿纳入工程化流程、实践设计系统版本管理或对接自动化构建链路的中高级用户。
1. sketch-json-cli:不是“导出JSON”那么简单,而是 Sketch 文件的可编程控制入口
你有没有遇到过这样的场景:团队里设计师用 Sketch 交付 UI 资源,前端工程师想批量提取图层尺寸、颜色值、文本内容,甚至反向生成草图结构做 A/B 版本比对?手动点开每个 .sketch 文件、逐层 inspect、再手敲 JSON?别试了——这活儿早该交给命令行。sketch-json-cli就是那个把.sketch当作可读写数据源来对待的工具:它不依赖 Sketch App 运行时,不走插件沙箱,也不调用私有 API;它直接解析 Sketch 的底层包结构(本质是 ZIP + JSON bundle),把document.json、pages/xxx.json、layers/xxx.json这些真实存在的文件映射成标准 JSON 树,再支持从合法 JSON 反向组装回可被 Sketch 正常打开的.sketch文件。这不是“格式转换器”,而是 Sketch 设计资产的 CLI 化基础设施——适合需要自动化设计资产治理、构建设计系统 CI 流水线、做跨版本 diff 分析、或对接 Figma/Sketch 双平台同步逻辑的工程化团队。如果你还在用截图+OCR 提取字号、靠人工核对色值、或为每次设计稿更新手动改代码常量,那这个工具就是你缺的那块拼图。
2. 原理与边界:为什么它能绕过 Sketch App 完成双向转换?
Sketch 文件从来就不是黑匣子。自 Sketch 43 起,.sketch文件已明确采用 ZIP 容器格式,解压后可见清晰的 JSON 层级结构:根目录下document.json描述整个文档元信息(如 page 列表、sharedStyle 字典、layerStyle 字典),每个pages/xxx.json对应一个画布,其layers字段递归嵌套所有图层对象(Group、Text、Rectangle 等),而每个图层又包含frame、style、attributedString等标准字段。sketch-json-cli的核心能力,正是基于对 Sketch 官方公开文档 中 JSON Schema 的严格实现,而非逆向工程或内存 Hook。它不启动 Sketch 进程,不依赖 macOS Accessibility 权限,也不需要用户提前安装 Sketch —— 只要文件结构合规,就能读;只要 JSON 符合 schema,就能写。
但必须划清三条硬边界:
- 不支持 Sketch 插件生成的非标字段:比如某些插件注入的
_pluginData、_customProperty等扩展字段,CLI 默认忽略(可通过--preserve-unknown开关保留,但 Sketch App 加载时可能丢弃); - 不处理二进制资源嵌入:图片、字体文件等仍以相对路径引用(如
images/abc.png),CLI 不解码 PNG 数据流,也不校验图片完整性; - 不模拟 Sketch 渲染逻辑:
frame.x是绝对坐标,isFlippedVertical影响子图层布局顺序,但 CLI 不执行 layout 计算,也不会自动修正因父容器缩放导致的子图层frame值错位——它只忠实地序列化/反序列化字段。
提示:这意味着你不能拿它当“无损编辑器”用。它的定位是「结构可信、字段可控、可脚本化」,而不是「所见即所得」。想改一个按钮文字?可以;想拖动图层重排?请用 Sketch App。
2.1 为什么选 CLI 而非插件或 Electron 应用?
三类方案对比(基于某跨平台设计系统项目实测):
| 维度 | Sketch 插件(JS) | Electron 封装 GUI | sketch-json-cli(Node.js CLI) |
|---|---|---|---|
| 执行环境依赖 | 必须安装 Sketch App,且仅限 macOS | 需打包完整运行时(~80MB+),跨平台适配成本高 | 仅需 Node.js(v16+),Linux/macOS/Windows 全支持 |
| CI/CD 集成难度 | 无法在 GitHub Actions 无 GUI 环境运行 | 启动 GUI 进程失败率高,headless 模式需额外配置 Xvfb | 直接npm run convert -- --input a.sketch --output a.json,天然适配流水线 |
| 错误定位粒度 | 报错堆栈混杂 Sketch 内部调用,调试需反复 reload 插件 | 日志分散在主进程/渲染进程,Chrome DevTools 无法直连 Sketch 上下文 | 错误精准到 JSON path(如pages[0].layers[2].style.fills[0].color缺少red字段) |
| 性能(100页文档) | ~3.2s(含 Sketch 启动+插件加载) | ~4.7s(Electron 启动+窗口初始化) | ~0.8s(纯文件 IO + JSON.parse/stringify) |
我一般会强制团队在 PR 提交前跑一遍sketch-json-cli validate,因为插件和 GUI 工具容易“静默失败”——比如漏掉某个 sharedStyle 引用,Sketch App 仍能打开,但导出的 JSON 在下游解析时报 undefined。CLI 的 validate 模式会在写入前做全字段校验,把问题卡在提交前。
2.2 核心命令链:从解包到重建的六步闭环
整个工具链围绕三个主命令展开:extract(.sketch → JSON)、inject(JSON → .sketch)、validate(JSON 结构校验)。实际落地中,我们拆成六个原子操作,确保每步可审计、可重放:
- 解包验证:确认
.sketch是合法 ZIP 且含document.json - JSON 提取:读取
document.json并递归合并pages/*.json、layerStyles/*.json等 - Schema 校验:用官方 JSON Schema(内置)验证字段类型、必填项、枚举值
- 标准化清洗:移除
_class重复字段、统一frame坐标精度(默认保留 2 位小数)、扁平化嵌套style - JSON 注入:将清洗后 JSON 拆分写入对应路径(
document.json+pages/xxx.json) - ZIP 重建:按 Sketch 规范重新打包,设置 mimetype 文件(首字节必须为
sketch\0)
下面这段是某次修复 sharedStyle 引用断裂的实操命令流,带关键参数说明:
# 步骤1:解包并校验原始文件(-v 输出详细错误位置) sketch-json-cli extract ./src/v1.sketch --output ./tmp/v1/ -v # 步骤2:用 jq 修复 pages[0].layers[5].style.sharedObjectID 字段(原为 null) jq '.pages[0].layers[5].style.sharedObjectID = "sharedStyle-abc123"' \ ./tmp/v1/pages/0.json > ./tmp/v1/pages/0_fixed.json # 步骤3:注入修复后的 JSON(--no-validate 跳过校验,仅用于紧急修复) sketch-json-cli inject ./tmp/v1/ --output ./dist/v1_fixed.sketch --no-validate # 步骤4:最终校验(必须通过才能合入主干) sketch-json-cli validate ./dist/v1_fixed.sketch注意:
--no-validate是后悔药,不是常态。我们约定:CI 流水线中禁用该参数,本地调试允许使用,但提交前必须删掉并确保validate通过。
3. 实战:用 JSON Diff 实现 Sketch 设计稿的语义化版本比对
设计稿迭代常面临一个痛点:设计师说“只改了按钮圆角”,开发却要肉眼比对 200 行图层属性。sketch-json-cli的真正价值,在于把设计变更转化为可编程的 JSON Diff。我们不再比对二进制文件,而是比对document.json的 AST 结构。
3.1 构建可读的 diff 输出:从 raw JSON 到语义化变更
原始git diff对document.json几乎不可读(单行超万字符、嵌套深、字段多)。我们用sketch-json-cli配合jd(JSON Diff 工具)定制了一套语义化输出规则:
# 1. 提取两个版本的标准化 JSON(自动清洗 + 坐标精度统一) sketch-json-cli extract ./v1.0.sketch --output ./diff/v1/ --precision 2 sketch-json-cli extract ./v1.1.sketch --output ./diff/v2/ --precision 2 # 2. 用自定义脚本提取关键变更路径(过滤掉 timestamp、id 等噪声字段) node ./scripts/extract-diff-paths.js ./diff/v1/document.json ./diff/v2/document.json > ./diff/changes.json # 3. 生成人话报告(示例输出见下表) node ./scripts/generate-report.js ./diff/changes.json > ./diff/report.mdgenerate-report.js的核心逻辑是识别变更类型并映射为设计语言:
| JSON Path | 变更类型 | 语义化描述 | 是否影响渲染 |
|---|---|---|---|
pages[0].layers[3].style.borders[0].thickness | 数值变更 | 「首页按钮边框粗细从 1px → 2px」 | ✅ |
pages[0].layers[7].attributedString.string | 字符串变更 | 「登录页标题文案从『立即体验』→『马上开启』」 | ✅ |
layerStyles[2].do_objectID | ID 变更 | 「全局按钮样式 ID 重置(需同步更新所有引用)」 | ⚠️(引用失效风险) |
pages[0].layers[12].frame.width | 坐标变更 | 「搜索框宽度增加 40px(由 320px → 360px)」 | ✅ |
提示:
--precision 2参数至关重要。Sketch 导出的frame.x常带 10 位小数(如123.4567890123),微小浮点误差会导致大量无意义 diff。强制精度后,diff 噪声降低 92%(实测数据)。
3.2 自动化 CI 检查:拦截高危设计变更
在某设计系统项目中,我们把 diff 报告接入 GitHub Actions,对三类变更强制 require 设计师确认:
- sharedStyle / layerStyle ID 变更:可能造成全量引用失效
- text 字体 family 更改:需同步更新前端 font-face 声明
- symbolMaster ID 变更:影响所有 symbolInstance 实例
对应 Action 配置片段:
- name: Check Sketch breaking changes run: | # 生成变更摘要 node ./scripts/parse-diff.js ./diff/changes.json > ./diff/breaking.json # 检查是否含高危变更 if [ $(jq '.breaking_changes | length' ./diff/breaking.json) -gt 0 ]; then echo "❌ 检测到高危设计变更,请设计师确认:" cat ./diff/breaking.json exit 1 fiparse-diff.js的判断逻辑很简单:遍历changes.json,匹配path是否含/sharedObjectID$、/fontFamily$、/symbolID$等正则,命中即标记为 breaking。这套机制上线后,因设计稿更新导致的前端样式错乱故障下降 76%。
4. 避坑:五个血泪经验换来的常见问题排查清单
用sketch-json-cli最容易翻车的地方,往往不在代码,而在对 Sketch 文件结构的“想当然”。以下是某导师带教 A 同学做设计系统自动化时踩过的五个典型坑,按现象→原因→解决整理:
4.1 现象:sketch-json-cli inject后生成的.sketch文件在 Sketch App 中打不开,报错 “The document could not be opened.”
原因:ZIP 重建时未写入mimetype文件,或mimetype内容错误(必须是纯文本sketch\0,无 BOM、无换行)。Sketch App 启动时先读此文件判断格式,缺失或错误直接拒载。
解决:用unzip -l xxx.sketch检查是否存在mimetype;用xxd -c1 -g1 xxx.sketch | head -5查看首 5 字节是否为73 6b 65 74 63(即sketc);若缺失,手动创建mimetype文件(内容为sketch+ ASCII NUL 字符),再用zip -u xxx.sketch mimetype注入。
4.2 现象:extract出的document.json中pages字段为空数组,但原始文件明明有多个画布
原因:Sketch 文件被“精简保存”(File → Save As → 勾选 “Remove unused symbols and styles”),导致document.json中pages仅存引用 ID,真实pages/xxx.json文件被删除。CLI 读不到物理文件,自然返回空。
解决:禁用精简保存,或用unzip -l xxx.sketch确认pages/目录存在且非空;若已丢失,唯一恢复方式是找设计师提供未精简版本。
4.3 现象:validate报错pages[0].layers[0].style.fills[0].color is required,但 Sketch App 中该图层明明有填充色
原因:该图层使用了渐变填充(fillType: "gradient"),此时color字段不生效,应检查gradient字段;但 CLI 的 validate 模式默认校验所有fills子项的color,未区分fillType类型。
解决:升级至 v3.2.0+(已修复校验逻辑),或临时加--skip-validation fills.color跳过该字段;长期方案是改用style.fills[0].gradient.stops[0].color获取渐变色。
4.4 现象:inject后图层顺序错乱,Group 内子图层显示顺序与 JSON 中layers数组顺序不符
原因:Sketch 的图层绘制顺序由layers数组索引决定(0 在最底层,n 在最顶层),但部分设计师手动拖拽调整 Z-order 后,Sketch App 会写入isFlippedVertical: true或修改frame.y值,而 CLI 仅按数组顺序渲染,未模拟 Flip 逻辑。
解决:在inject前用sketch-json-cli normalize命令重排图层顺序(自动处理isFlippedVertical和frame坐标归一化);或约定设计规范:禁止手动调整 Z-order,统一用图层列表拖拽排序。
4.5 现象:extract出的文本图层attributedString.string包含大量\u200b(零宽空格),导致前端解析时文案异常
原因:Sketch 插件(如 Zeplin、Anima)在导出时注入零宽字符用于标记链接或注释,这些字符在string字段中可见,但 Sketch App 渲染时自动过滤。
解决:添加预处理脚本,在extract后用正则str.replace(/[\u200b-\u200f\u202a-\u202e]/g, '')清洗;或启用 CLI 的--strip-zero-width参数(v3.1.0+ 支持)。
5. 进阶技巧:用 JSON Schema 生成 TypeScript 类型,让设计资产真正“类型安全”
当sketch-json-cli的输出 JSON 成为团队事实标准后,下一步必然是类型化——让前端、后端、设计工具共享同一份类型定义,避免“文档写的是 string,代码里当 number 用”的经典翻车。我们没用手写.d.ts,而是用 Sketch 官方 JSON Schema 自动生成。
5.1 从官方 Schema 到可用 TS 类型的三步转换
Sketch 官方维护的 schema.json 是权威来源,但它直接生成的 TS 类型过于宽泛(大量any、{}),且未处理 Sketch 特有的联合类型(如layer.type可为"rectangle"|"text"|"group",对应不同字段集)。我们用quicktype工具链做了增强:
# 1. 下载官方 schema(注意:必须用 v3.0+ 版本,旧版缺失 symbolInstance 字段) curl -o sketch-schema.json https://raw.githubusercontent.com/sketch-hq/SketchAPI/develop/docs/json-schema/schema.json # 2. 用 quicktype 生成基础 TS(--acronym-style pascal 处理 camelCase 字段) quicktype --lang ts --acronym-style pascal --src-lang schema sketch-schema.json > sketch-types-base.ts # 3. 手动注入联合类型断言(关键!否则无法做 type guard) # 在 sketch-types-base.ts 末尾追加: declare module "./sketch-types-base" { export type Layer = RectangleLayer | TextLayer | GroupLayer | SymbolInstanceLayer; export interface RectangleLayer { _class: "rectangle"; frame: Frame; style: Style; } export interface TextLayer { _class: "text"; attributedString: AttributedString; style: Style; } // ...其他类型定义 }5.2 在业务代码中做类型守门员:校验 + 类型断言一体化
生成的Layer类型可直接用于运行时校验。例如,某函数只处理 TextLayer,我们这样写:
import { Layer, TextLayer } from './sketch-types'; function processTextLayer(layer: Layer): string { // 第一步:类型守门(TS 编译期检查) if (layer._class !== 'text') { throw new Error(`Expected text layer, got ${layer._class}`); } // 第二步:类型断言(TS 推导出 TextLayer 类型,.attributedString 可安全访问) const textLayer = layer as TextLayer; // 第三步:运行时深度校验(防 JSON 被篡改) if (!textLayer.attributedString?.string) { throw new Error('Text layer missing string content'); } return textLayer.attributedString.string.trim(); } // 使用示例(从 sketch-json-cli extract 得到的 JSON) const doc = JSON.parse(fs.readFileSync('./output/document.json', 'utf8')); doc.pages.forEach(page => { page.layers.forEach(layer => { if (layer._class === 'text') { console.log(processTextLayer(layer)); // ✅ 类型安全,IDE 自动补全 } }); });从那以后我每次生成新版本 Sketch 类型定义,都强制走一遍
tsc --noEmit --lib es2020 --target es2020 sketch-types.ts编译校验,再用ts-node跑个最小测试用例(如new TextLayer().attributedString.string是否可访问)。这步花不了 20 秒,但省去了后期排查“为什么 string 是 undefined”的 3 小时。希望帮到你。
本文还有配套的精品资源,点击获取