简介:sketch-json-cli 是一款面向设计师、前端开发者及需要版本管理设计稿的团队的命令行小工具,可将 Sketch 草图文件转换成 JSON,也能从 JSON 反向生成草图文件,适用于设计稿纳入 Git 管理、自动化生成组件数据等场景。资源包共包含 8 个文件,压缩后体积约 34KB,主要文件类型包括核心 JavaScript 脚本、示例 JSON 数据、YAML 配置文件、Markdown 说明文档以及 license 许可文件等,体量小巧、结构清晰,便于直接阅读源码。目前已有 390 人学习,适合希望理解 Sketch 文件结构并尝试自动化流程的读者。通过查看 index.js 与相关配置,可以掌握命令行工具的参数解析与文件转换机制;安装后在终端执行 sketch-json 命令即可完成双向转换,方便将设计流程接入脚本化与 CI 构建,减少手动重复操作,提升团队协作效率。
1. 草图文件与 JSON 互转:为什么我会把设计稿塞进纯文本里
很多刚接触设计稿自动化的同学会问:Sketch 文件明明是一份带图形、图片、样式的设计稿,为什么非要把它转成 JSON?转成 JSON 又图什么?先说结论:Sketch 文件本身就不是一个二进制黑匣子,它内部就是一份有规则的 JSON 数据,外面套了个 zip 壳。sketch-json-cli 干的事情就是把壳剥开,把 JSON 拿出来给你改、给你 diff、给你版本管理,改完再重新装上壳变成可打开的草图文件。适合的人是设计师、前端工程师、自动化测试和搞 CI 构建的同事——但凡你被“设计稿改了但没人知道改了哪里”坑过,这个方向就值得投入。至于“json 用什么打开”这类基础疑问,读完这篇文章你会有完全不同的答案。
2. Sketch 文件剥掉外壳:zip 包里的 JSON 世界
2.1 先看清 Sketch 文件的结构:一个 zip 包里三层内容
我最早接触 Sketch 文件转 JSON,是因为项目里设计稿版本管理全靠人肉对比,每次对接设计师就像在猜谜。后来我用 zip 工具直接解压过一份.sketch文件,才彻底明白这东西的内部逻辑。
Sketch 文件本质上是一个 zip 压缩包,解压后通常能看到这几层内容:
document.json:整个文档的核心数据,图层树、样式、布局、文本内容全在这里;meta.json:文档版本信息、格式版本号、兼容性标记;user.json:用户视角的状态数据,比如当前打开了哪个画板、视图缩放比例;pages/或/pages目录:旧版本里每个页面是独立的 JSON 文件,新版本则合入了document.json的pages数组;images/目录:引用的位图资源,PNG、JPG 都在这里;previews/目录:Sketch 在 Finder 或图库里显示的预览缩略图。
所以“把草图文件转换为 json”这句话,拆开来说就是把 zip 解包、把document.json提取出来、或者把整个包的内容展开成可读的 JSON 文件集合。反向操作就是把这些 JSON 内容重新打包成 zip,再命名成.sketch后缀。
理解这一层结构是所有操作的前提。因为当你只修改document.json试图加一个图层时,如果忘记同步meta.json里的版本信息,或者丢掉了images/里的图片资源,打包出来的文件要么打不开,要么打开后图片全部裂开。sketch-json-cli 这类工具的核心职责是帮你把这几层的依赖关系处理好,而不是简单做一次“文件格式转换”。
2.2 安装 sketch-json-cli 与转换的最小配置
安装这一步需要区分两件事:你是在本地手工操作,还是要在自动化流水线里使用。我自己的经验是,本地用 npm 全局安装即可,CI 里则建议用npx直接调用,避免污染构建环境。
常见做法是先把 Node.js 环境备好,然后执行:
npm install -g sketch-json-cli sketch-json-cli --help先别急着转文件。--help输出会列出这个 CLI 支持的子命令,不同维护者发布的版本子命令命名有差异。常见的有convert、restore、export、extract等。我一般会用sketch-json-cli --help确认当前版本的实际命令再动手,避免记错子命令名导致“命令不存在”的尴尬。
配置文件方面,多数实现支持在项目根目录放一个.sketchjsonrc或使用命令行参数指定行为。比如指定输出目录、是否保留分页、是否解包图片资源。一个最小可用的调用方式通常是:
sketch-json-cli convert design.sketch --output ./json-output这条命令把design.sketch解包并转换成 JSON 输出到./json-output目录。注意这里的关键点:convert转换出来的不只是一个document.json文件,而是整个文件集合——meta.json、user.json和图片资源都在输出目录里。如果你只想拿纯 JSON 数据做 diff,可以把images/和previews/排除掉,只保留 document、meta、user 三个 JSON 文件。
参数上我会关注三个:
--output指定输出路径,目录不存在时工具会自动创建;--pretty或--format控制缩进,默认是紧凑型输出,人眼查看时很难受,建议开启格式化方便 diff;--skip-resources跳过图片资源导出,只导出 JSON 部分。
为什么默认不跳过资源?因为反向转换时,图片资源如果缺失,Sketch 打开文件后会提示资源丢失。你当然可以在还原时让工具忽略缺失资源,但结果是画板里的位图全部变成灰色占位块。所以除非你确认 JSON 里没有位图引用,否则别跳得过猛。
2.3 转换前的环境检查:版本、class 映射与资源引用
很多人在第一步就翻车,不是命令不对,而是环境不兼容。Sketch 文件格式有过多次变更,尤其是 43 版本、50 版本、70 版本这几个节点,文件内部结构差异很大。sketch-json-cli 对老版本格式的支持往往依赖第三方解析库,而这些库对“新版 document.json 里某些字段从pages独立文件迁移到 pages 数组”这类变化处理得并不完全一致。
所以转换之前,我建议先做三件事。第一,确认 Sketch 文件本身的版本。魔数检查或直接解压看meta.json里的appVersion字段最直观:
unzip -p design.sketch meta.json | head -n 20如果appVersion是 70 以上,但你的 sketch-json-cli 依赖的解析库还是旧逻辑,转换出来的pages字段可能会是空数组或者结构错位。第二,确认命令行工具的版本号,别拿一年前装好的全局包对付最新的设计稿。第三,确认图片资源没有外部引用。有些设计稿里的图片是“链接到文件”模式,JSON 里保存的只是文件路径,并没有把图片打进 zip。这类引用在你转 JSON 到另一台机器时百分百会断。
另外要注意 class 映射问题。Sketch 文档里每个图层对象都有一个_class字段,比如MSTextLayer表示文本图层,MSShapeGroup表示形状组,MSArtboardGroup表示画板。sketch-json-cli 的解析能力就体现能不能正确识别这些 class 并还原成可编辑对象。转换前看一眼前面--help的输出,有没有提到支持图层类型清单,比猜要靠谱得多。
提示:不要用“只要命令能跑通就代表转换成功”来判断结果。Sketch 打开还原后的文件如果不报错,只能代表格式合法,不代表图层树、样式、文本内容都完好无损。验证必须靠打开文件人工检查,或者用
sketchtool导出预览图做像素级对比。
3. 把草图文件转成 JSON:逐页导出与字段是做什么的
3.1 sketch→json 的转换脚本与分页处理
在讲具体脚本前,先说清楚一个概念:Sketch 文件里的“页面”和很多人理解的“画板”是两回事。页面对应的是 Sketch 左侧栏顶部的页面切换,一个文件可以包含多个页面,每个页面内再放多个画板。转换 JSON 时,页面结构直接对应 JSON 数组。如果你在 CI 里要做页面级别的对比,必须把这一层结构保留住。
从 CLI 执行转换是这样:
sketch-json-cli convert ./prototype.sketch --output ./converted --pretty这段命令的重点是--pretty。紧凑的 JSON 对程序友好,但对人完全不友好。开了格式化之后,你才能用肉眼去读document.json找图层。执行完转换后的产物,document.json里一般是一个巨大的对象,pages数组在根部。这个数组里每个元素就是一个页面,页面对象的layers数组里才是画板、文本、形状这些实际图层。
如果你需要按页面拆分文件,节省后续处理的资源消耗,可以在命令后加一个分页参数,具体参数名以你装的工具的--help为准。有些实现是--split-pages,有些是--page-output,本质上都是告诉工具不要把所有页面写进一个document.json,而是每个页面单独生成一个 JSON 文件。
从原理上讲,CLI 内部做的事无非三步:
unzip design.sketch -d temp_dir python3 split_pages.py temp_dir/document.json temp_dir/pages/ python3 merge_meta.py temp_dir拆解这一步的话,split_pages.py干的就是把document.json里的pages数组一个个拎出来,每个数组元素写成一个独立 JSON 文件,同时保留它们在document.json中的索引位置,方便后续还原时按顺序合并回去。这个脚本完全可以自己写,我有时候嫌 CLI 输出的结构不满足需求,就直接在它基础上做二次处理。
整个过程有一个关键的逻辑点:页面拆分不是简单地把数组切片。Sketch 的pages里每个页面元素包含do_objectID(对象唯一标识)、name(页面名称)、layers(图层数组)。拆分后必须把do_objectID记录下来,因为meta.json和user.json里可能有对特定页面 ID 的引用,比如当前选中了哪个页面。拆完不管这个过程,还原时页面选中状态就可能丢失。
3.2 解读转换结果:从 pages 数组找到你的图层
转换完成后,打开document.json,你会面对一棵非常深的 JSON 树。这一节帮你拆解你最常见的几个字段。先看一段经过格式化的 JSON 片段,这是仿照常见结构写的示例,不是某个真实文件:
{ "_class": "document", "do_objectID": "A1B2C3D4-1234-5678-9ABC-DEF012345678", "pages": [ { "_class": "page", "do_objectID": "PAGE-ID-001", "name": "首页", "layers": [ { "_class": "artboard", "do_objectID": "ART-ID-001", "name": "登录", "frame": { "x": 0, "y": 0, "width": 375, "height": 812 }, "layers": [] } ] } ] }这段 JSON 的本质是一个文档对象,根部是pages数组,每个页面有自己的layers数组。artboard是画板对象,frame定义它的位置和尺寸。这个过程如果你熟悉 DOM 树,会感到很亲切:document 是根节点,page 是 section,artboard 是 container,再往下就是具体的文本节点和形状节点。
在转 JSON 之后,我要做的最常见的操作是“找文本”。Sketch 里文本图层是MSTextLayer,它有一个attributedString字段,里面嵌套了string值,存的就是实际显示的文字。我写脚本扫描所有页面、所有图层,凡是_class等于MSTextLayer的,就把attributedString.string打印出来。这个操作在“设计师临时改了文案但没同步给开发”的场景中极其常用,效果类似数据库查询:
import json with open('document.json', 'r', encoding='utf-8') as f: doc = json.load(f) def walk_layers(layers, found): for layer in layers: if layer.get('_class') == 'MSTextLayer': text = layer.get('attributedString', {}).get('string', '') found.append({'id': layer.get('do_objectID'), 'name': layer.get('name'), 'text': text}) walk_layers(layer.get('layers', []), found) all_texts = [] for page in doc.get('pages', []): walk_layers(page.get('layers', []), all_texts) for item in all_texts: print(item['name'], ':', item['text'])这段代码做了三件事:加载 JSON 到内存,递归遍历每一层级,筛选出MSTextLayer记录文本内容和对象 ID。walk_layers函数里先处理当前图层,再递归进入它的layers,所以不管嵌套多深都能遍历到。输出的do_objectID可以用来在反向转换时精确指认要改哪个图层。
要注意的是,Sketch 文本图层的attributedString结构在不同版本里并不一致。旧版本直接有string,有些版本则把它包在attributedString.attributes里。这也是为什么我强调要先把转换结果打开看一遍,用自己的文件确认字段路径,再写自动化脚本。
4. 把 JSON 转回草图文件:逆向映射与打包复原
4.1 json→sketch 的还原脚本与必须保留的资源文件
正向转换是把 zip 拆开读 JSON,逆向就是把 JSON 重新组装成 zip。这个过程的坑比正向多得多,因为你不光要把document.json写回原位,还要保证meta.json、user.json、images/、previews/都回到正确的位置。缺少任何一个关键文件,Sketch 都可能拒绝打开。
CLI 的还原调用一般是这样的:
sketch-json-cli restore ./converted --output ./restored.sketch如果转换时用了--split-pages把页面拆成了多个文件,还原命令通常需要额外指定页面目录的位置,或者它通过配置文件状态自动找回来。我在实际使用中发现,不指定分页合并参数的时候,一些实现会把所有页面写回一个document.json,但页面的排序可能依赖文件名前缀,如果你的文件名上没用001_、002_这种序号,排序就会乱。
还原时最重要的原则是:资源文件与 JSON 必须来自同一个转换结果。所以我一般会把converted目录原封不动保留,整个images/目录一起交给还原工具。如果只把document.json拿到另一个目录里还原,工具会提示图片缺失,最终产物是一个没有位图的空壳文件。
为了弄清楚还原到底做了什么,我建议你在临时目录里手工模拟一遍核心流程:
mkdir -p temp_restore cp converted/*.json temp_restore/ cp -r converted/images temp_restore/ cd temp_restore zip -r restored.sketch .这段命令把 JSON 和图片拷贝到同一目录,然后整体压缩成 zip,把后缀改成.sketch。手工执行一次你就会明白:还原的实质是把转换结果的目录树重新压缩。CLI 里的restore命令只是把这个过程包装得更友好,自动处理了previews/缩略图的重新生成、zip 内部文件顺序等细节。
但为什么我不建议长期用手工 zip 方式?因为 zip 压缩的文件顺序、压缩算法的选择、meta.json里的信息完整性,这些都会影响 Sketch 是否能正确识别。CLI 的restore命令会用与 Sketch 兼容的打包方式重新生成文件,如果你用系统自带 zip 命令,winzip 或 mac 的归档工具压缩出来的 zip 里有额外隐藏文件,Sketch 就可能读不了。
4.2 图层 ID 与类名的核对清单:改什么字段才会生效
从 JSON 改回 Sketch 文件,最容易踩的坑是我改了 JSON 但打开后没变化。这张对照表来自我自己的血泪经验,告诉你哪些字段改动后会生效,哪些是冗余的:
| 字段 | 作用 | 改完是否生效 |
|---|---|---|
do_objectID | 图层唯一标识 | 建议不要改,很多引用依赖它 |
name | 图层名称 | 即时生效 |
frame.x/y/width/height | 位置和尺寸 | 即时生效 |
attributedString.string | 文本内容 | 即时生效,但注意文字长度变化后可能溢出 |
style.borders/fills | 样式属性 | 生效,但颜色类型需与_class匹配 |
layerStyle/sharedStyleID | 共享样式引用 | 部分生效,依赖meta.json中的共享样式表 |
这份清单的关键是第四行:改文本内容。文本是设计稿改版中最常见的修改点,把“立即购买”改成“马上抢”这种需求,用 CLI 处理就是找到MSTextLayer节点的attributedString.string,替换字符串,再还原。不过改文字长度后,文本框不会自动重算宽度,如果原来固定了框宽,新文字可能显示不全或被截断。改之前最好先查一下该图层是否设置了自动高度,或者改完在 Sketch 里手动确认。
另外要重点检查do_objectID。这些 ID 分布在document.json的图层节点、meta.json的样式引用、user.json的选中状态里。如果你用脚本修改 JSON 时不小心全局替换了某个 ID,导致多处引用失配,Sketch 打开后会显示“找不到对象”或直接崩溃。我做过一次教训是替换文本时顺手把do_objectID一起替换成了 UUID 新值,结果页面之间的跳转关系全部断掉。
执行还原后我几乎固定会跑一遍验证脚本,确认还原后的文件能通过最基本的完整性检查:
import zipfile with zipfile.ZipFile('restored.sketch', 'r') as zf: names = zf.namelist() required = ['document.json', 'meta.json', 'user.json'] for f in required: if f not in names: raise SystemExit(f'缺少必要文件: {f}') img_count = len([n for n in names if n.startswith('images/')]) print('文件结构正常,图片资源数:', img_count)这段代码用zipfile打开还原后的 sketch 文件,检查三个必要 JSON 文件是否存在,并统计图片资源数量。如果你转出的 JSON 里图层引用了图片,但images/目录为空,这里就能提前发现,不用等设计师反馈“图裂了”。
5. 那些让转换失败的坑:排查与避坑记录
5.1 现象:还原后用 Sketch 打开直接报“文档已损坏”
这个是最早遇到的翻车现场,原因通常不是 JSON 格式不对,而是整个 zip 包的结构不完整。Sketch 对 zip 包内部的文件顺序和某些隐藏字段有要求,直接用 mac 上“压缩”功能打出来的 zip 包,往往会多出__MACOSX隐藏目录,Sketch 读这种包就容易报错。
解决方法是不要用系统图形界面压缩,而是用命令行zip -r压缩,并在压缩前确认没有多余文件。CLI 里的restore命令一般会自动过滤这类污染文件,但如果你是自己写脚本还原,务必先做一次“还原后直接 zip 校验”。我习惯把校验加进 CI 流程里,只要还原产物不能通过zipfile读取并找到三个关键 JSON,就直接 fail 掉。
5.2 现象:还原后所有图片都变灰色占位块
图片丢失几乎是每个用过 JSON 转 Sketch 的人必踩的坑。原因有两种:一是转换时跳过了images/目录导出,二是 JSON 里图片引用是绝对路径或外部链接,根本没有打包进 zip。
解决方法是转换时就明确不要跳过资源,必要时把--skip-resources参数去掉。如果 JSON 里images/的引用是外部路径,需要手工把这些图片也复制进输出目录,或者把路径改成相对路径。图片资源的问题没有后悔药,丢弃前一定要检查document.json里是否有image类型的引用字段。还有一个细节:Sketch 的位图图层会有一个image字段指向images/目录下的文件名,这个文件名的 hash 值必须与 JSON 里一致。如果你替换了图片文件但没改引用名,Sketch 打开后会用旧缓存。
5.3 现象:多个设计师改了同一份 JSON,合并时冲突炸了
既然转 JSON 是为了版本管理,那多人协作就绕不开“json merge conflict”。JSON 文件的结构让 git 的自动合并几乎不可用:p andles数组里每个页面对象很长,两个分支各自改了不同页面,git diff 的冲突块却是整段整段地标红,无法自动合并。
如果你真的需要多人并行改 JSON,我建议先把页面拆分到独立文件,这样 git 至少能在文件级别处理冲突。合并时再用脚本按do_objectID做映射归并,而不是让 git 硬合。这是我走过的弯路:一度让两个设计师直接在一个巨型document.json改,结果每次 merge 都要花一小时手工解决冲突,最后还是回到按页拆分的老路。
5.4 现象:文本改完后文字溢出或消失
前面提到,改attributedString.string后文本框大小不会自动调整。这个问题的严重性远超想象——尤其是中英文混排时,中文字符变多后自动换行逻辑可能失去作用,画板打开后文字被截断。
解决方法是修改前先定位该层级的style.textStyle段落样式,看是否有自动行高或自适应设置。实在不行,改完还原后打开 Sketch 人工过一遍。这不是技术问题,是工作流的必要步骤。我在 CI 里加了“文本长度校验”:每个MSTextLayer的字符串长度超出画板宽度的,直接打印警告,通知设计师复核。
5.5 现象:转换后 JSON 体积爆炸、提交到 git 后仓库变大几十倍
JSON 的可读性是以体积为代价的。格式化 + 带分页拆分的 JSON 目录,体积往往比原.sketch文件大 5 倍以上。图片资源本来就在 zip 里,但如果你把images/目录也放进 git,仓库会迅速膨胀到无法接受。
解决方法是区分“数据”和“资源”。document.json、meta.json、user.json这三个文件值得入库,因为它们承载了可 diff 的设计数据;images/和previews/应该走资源管理方案,或者干脆不入库,需要时再整体导出。这个决策应该在项目启动时定好,不然后面改会造成仓库历史无法清理。
6. 进阶:在 CI 里用 JSON 做版本对比和自动更新
最后聊一个我目前每天都在用的落地技巧:把 sketch-json-cli 转换出来的 JSON 放进 CI,配合自动脚本做设计稿变更提示。这里有一个可以抄作业的实践。
在 CI 里,用git diff对比两次提交的document.json,配合sketch-json-cli转换出的页面拆分文件,可以实现“昨天和今天的设计稿到底改了什么”。我的做法是写一个小脚本:检测到document.json有变更时,加载新旧两份 JSON,递归对比每个图层的name和关键属性,输出一段可读的变更摘要:
#!/bin/bash # 提取当前版本的 JSON sketch-json-cli convert current.sketch --output ./current_json --pretty # 与上一次构建产出的 JSON 对比 diff -r ./previous_json/document.json ./current_json/document.json | head -n 80这段命令天然适用于 CI,直接用diff -r比对整个 JSON 目录就能定位变化,不用写复杂的 API。配合前文的 Python 遍历脚本,可以进一步筛选出「图层名以btn_开头」的节点,重点关注它的文本和样式变化。我还会加一步:把变更摘要写入构建产物,让设计师在 MR 描述里看到自动生成的“UI 变更说明”,省掉了人工沟通成本。
如果再往前走一步,是 JSON 转 Sketch 的自动化修稿。比如批量替换字体颜色:
import json with open('document.json', 'r', encoding='utf-8') as f: doc = json.load(f) def replace_color(layers, old_hex, new_hex): for layer in layers: style = layer.get('style', {}) fills = style.get('fills', []) for fill in fills: color = fill.get('color', {}) if color.get('red') == old_hex[0]: color['red'] = new_hex[0] replace_color(layer.get('layers', []), old_hex, new_hex) replace_color(doc['pages'][0]['layers'], (0.5, 0.5, 0.5), (1.0, 0.2, 0.2)) with open('document.json', 'w', encoding='utf-8') as f: json.dump(doc, f, ensure_ascii=False, indent=2)这段代码递归遍历图层,找到style.fills里的color字段做替换。注意color里的red、green、blue用的是 0 到 1 的浮点数,不是 0 到 255 的整数,这个转换经常让第一次写脚本的人翻车。改完后再跑一次sketch-json-cli restore就能得到一个颜色已更新的新设计稿。
验证环节我从不省。还原后的文件先跑一次 zip 完整性校验,再用 Sketch 打开确认改动生效。我在一个项目里用这套流程把设计稿版本对比时间从半小时减到两分钟,真正解决了“改没改、改了哪”的疑问。希望这套思路帮你也省下那些被无效沟通消耗的时间——但记住,任何自动化改造都要先做好完整备份,毕竟设计稿没了,比脚本报错可怕得多。
本文还有配套的精品资源,点击获取