简介:面向 Sketch 设计师和前端开发者的数据填充插件,基于 Faker.js 构建,可在设计稿中快速生成姓名、邮箱、电话、段落等一百三十多种智能占位符内容。它支持在符号内部以及导入的 Sketch Libraries 中反复刷新出同一类型的不同随机数据,适合为列表、表格或表单提供具有真实感的模拟信息,解决设计阶段 Mock 数据重复枯燥的问题。资源包为 zip 压缩包,大小约 919KB,共 17 个文件,以 JavaScript 脚本、JSON 配置、PNG 图标、XML 及 Markdown 文档为主,其中脚本与配置分别承担插件逻辑和数据类型定义,结构紧凑,便于阅读或二次开发。目前已有 159 人学习浏览,适合对 Sketch 插件机制、Faker.js 应用或前端 Mock 方案感兴趣的读者。借助这份资源,可以完整了解插件清单的编写、随机内容的生成调用、图层插入与刷新机制,并能参考其源码扩展自定义占位类型,相关实现思路还可迁移到其他设计工具的数据模拟流程中。
1. 给设计稿批量喂假数据:sketch-data-faker 解决的是交付前最后一公里
做了几年设计稿对接,你会发现最耗时间的不是画界面,而是给界面填内容。列表要 20 条用户信息、卡片要 8 个商品、设置页要 12 组账号数据,手打既不真实又慢,复制粘贴又容易穿帮。sketch-data-faker 这个 Sketch 插件,核心就是做这件事:它把 Faker.js 的 130 多种数据类型搬进 Sketch,你在文本图层里写一个占位符,它就能帮你生成对应类型的假数据,再批量写回图层。适合三类人:独立做作品集的设计师、需要给开发同学提供真实感样式的 UI 设计师、以及频繁做数据可视化 Demo 的开发者。它的学习成本几乎为零,不需要写代码也能用,但理解它的数据映射原理后,你能把它改造成团队自己的数据生成工具。
2. 130 种占位符的幕后组织:数据映射、Faker.js 模板与文本识别原理
2.1 占位符语法为什么是双花括号加命名空间
用这个插件之前,先理解它的核心约定:你在文本图层里写的内容,一旦包含{{name.firstName}}这种双花括号片段,就会在运行填充命令时被替换成具体数据。双花括号不是随手定的,它有两个原因。其一,设计稿正文里单花括号太常见,比如{价格}、{数量},双花括号能显著降低误触发的概率。其二,name.firstName这种命名空间写法,直接对应 Faker.js 内部的调用路径,name是分类,firstName是该分类下的具体方法,两者用点连接,语义直观到不需要查文档。
Faker.js 本身是一个 Node 生态里非常成熟的假数据生成库,它内部维护了一张巨大的模板表。{{name.firstName}}会被解析成一次faker.name.firstName()的函数调用,{{internet.email}}则对应faker.internet.email()。sketch-data-faker 并没有重新发明一套数据生成逻辑,它做的事情是把这张模板表搬运到了 Sketch 插件能访问到的 JavaScript 环境里,同时补了一批 Faker.js 里没有的数据类型,比如车牌号、颜色名、企业部门名称。所以标题里说"来自 Faker.js 和其他来源",其他来源指的就是这批额外注册的类型。
2.2 数据表结构和命中过程
整个插件的核心数据表,拆开看就是一个普通的 JavaScript 对象。我用简化代码说明它的组织方式:
// data.js —— 类型注册表的简化结构示意 const registry = { person: [ { label: "First Name", template: "{{name.firstName}}" }, { label: "Full Name", template: "{{name.findName}}" } ], internet: [ { label: "Email", template: "{{internet.email}}" }, { label: "Avatar", template: "{{internet.avatar}}" } ], custom: [ { label: "License Plate", template: "{{vehicle.vin}}" } ] }; function matchTemplate(text) { // 提取所有双花括号片段 const pattern = /\{\{([^{}]+)\}\}/g; const matches = []; let m; while ((m = pattern.exec(text)) !== null) { matches.push(m[1]); } return matches; }这段代码里,registry按分类组织,每个分类下是一组label和template的配对。matchTemplate是识别逻辑:遍历文本图层里的字符串,用正则把所有双花括号内的内容提取出来。注意我用了[^{}]+而不是.*?,这是有意的,前者不允许嵌套花括号,能避免把{{a}} {{b}}这种连续占位符错误吞并。
当你在 Sketch 里选中一个写着{{name.findName}} 的邮件是 {{internet.email}}的文本图层并运行填充时,插件先把这段文字拆成模板片段,逐段交给 Faker.js 生成,再拼回完整字符串。这个过程的顺序敏感点在于:必须在一次生成周期内完成所有替换,否则两次调用产生的随机数据会互相矛盾——比如姓名和邮箱对不上。实际代码中会用一次性faker.fake(template)调用而不是逐个替换。
2.3 文本图层的读取与判定边界
占位符写在哪、怎么被读出来,决定了这个插件好不好用。Sketch 的文本图层在插件 API 里是一个Text对象,它有text属性可以拿到字符串内容。但有一个隐蔽的坑:当文本带着富文本样式时,text属性返回的可能是去掉了样式的纯文本,也可能带一些控制字符。我通常建议在填充前先对文本做一次String(layer.text)强转,再 trim 掉首尾空白,否则正则匹配很容易因为换行符而漏掉最后一个占位符。
还有一个边界条件需要明确:这个插件只处理文本图层,不处理形状里的文本路径、不处理符号实例内部的文本。如果你选中的是一个 Group,插件不会自动钻进去,这一点到第 4 章避坑部分会展开讲。识别命中后,插件会把原始文本保存到内存变量里,等所有图层都生成完毕后统一写回,这样保证一个画板内多次填充的数据风格一致。
3. 跑通一次批量填充:SketchJavaScript 调用链、菜单声明与图层写入
3.1 插件包结构与 manifest 声明
Sketch 插件本质上是一个带固定目录结构的文件夹,最核心的是manifest.json和脚本文件。你下载的 zip 解压后,目录里一定有Contents/Sketch/manifest.json,这个文件决定了插件在 Sketch 菜单里长什么样、快捷键是什么、命令入口在哪里。一个最小可用的 manifest 是这样:
{ "identifier": "com.example.data-faker", "name": "Data Faker", "description": "Generate mock data from Faker.js", "author": "your-name", "version": "1.0.0", "commands": [ { "name": "Fill Selected Layers", "identifier": "data-faker.fill", "script": "plugin.js", "handler": "onFill", "shortcut": "ctrl cmd f" } ], "menu": { "title": "Data Faker", "items": ["data-faker.fill"] } }这里的identifier是全局唯一标识,建议用反向域名风格避免冲突。commands数组里每一项对应一个菜单动作,script指向插件目录下的脚本文件,handler是脚本文件里导出的函数名,shortcut是快捷键声明。需要注意快捷键是字符串形式,ctrl cmd f表示同时按 Control、Command、F 三个键,如果声明格式写错,Sketch 会静默忽略,不会报错,这也是后面避坑章节要提到的点。
安装插件时,Sketch 会把整个插件文件夹复制到~/Library/Application Support/com.bohemiansketch.bohemiancoding.plugins或新版本的 Plugins 目录下。检测安装是否成功的标志,是 Sketch 顶部菜单栏出现 "Plugins → Data Faker" 子菜单。如果没出现,优先检查 manifest 的 JSON 格式,用系统自带的文本编辑器打开看一眼括号配平即可,这个文件对格式极其敏感。
3.2 从选中图层到写回文本的完整调用链
第一次跑通批量填充,重点看两段逻辑:拿选中图层,以及写回内容。Sketch 的 JavaScript API 提供了sketch.getSelectedLayers()函数返回当前选中图层数组,但这个函数有兼容性问题,老插件更常用的方式是直接从context.selection读取原生选中对象。我一般两者结合,写一个通用的遍历函数:
// plugin.js —— 批量填充主流程 import sketch from 'sketch'; export function onFill(context) { const doc = sketch.getSelectedDocument(); const selection = doc.selectedLayers; // 收集选中的文本图层 const textLayers = []; selection.layers.forEach(layer => { if (layer.type === 'Text') { textLayers.push(layer); } else if (layer.type === 'Group') { // 递归查找组内文本图层 collectTextLayers(layer, textLayers); } }); if (textLayers.length === 0) { sketch.UI.message('No text layers selected.'); return; } // 逐个图层替换占位符 textLayers.forEach(layer => { const raw = String(layer.text || ''); const filled = replacePlaceholders(raw); layer.text = filled; }); sketch.UI.message(`Filled ${textLayers.length} layers.`); } function collectTextLayers(group, result) { group.layers.forEach(layer => { if (layer.type === 'Text') { result.push(layer); } else if (layer.type === 'Group') { collectTextLayers(layer, result); } }); }doc.selectedLayers.layers是 Sketch API 提供的高层对象集合,每个元素都有type属性和text属性,读和写都走同一个属性,这比原生 API 直观。sketch.UI.message会在画布底部弹一条淡入淡出的提示,用于反馈填充数量,干设计活的人不希望在填充完后再弹干扰性模态框,所以这里特意选了 message 而不是 alert。
replacePlaceholders函数内部做的事情就是第 2 章的 matchTemplate 加 faker 调用,这里不再重复。写回时有一个参数细节:直接给layer.text赋值,Sketch 会保留图层原有的字号、颜色、对齐方式,只替换字符串部分。这是很多人忽略的舒适点——它不会重置你的样式。
3.3 数据源类型如何弹出给用户选择
上面代码默认对占位符做全量替换,但实际使用中,你往往希望先选一个类型,比如"这次只填人名"或"这次只填邮箱"。插件运行时应该弹一个下拉面板。常见做法是调用 Sketch 的 UI 框架:
import sketch from 'sketch'; export function onFillWithPicker(context) { const selection = sketch.getSelectedDocument().selectedLayers; const templates = buildTemplateOptions(); // 从 registry 里拍平所有候选 const options = templates.map(t => t.label); const chosenIndex = sketch.UI.getSelectionFromData( 'Select data type', options ); if (chosenIndex === -1) return; const chosen = templates[chosenIndex]; selection.layers.forEach(layer => { if (layer.type === 'Text') { layer.text = chosen.template; // 直接把整个文本替换成一种数据 } }); }sketch.UI.getSelectionFromData是原生面板函数,第一个参数是标题,第二个是字符串数组,返回用户选中的索引。这里有个分叉设计:如果是带占位符的既有文本,走 replace 逻辑;如果是空文本图层,就可以用chosen.template直接整体生成内容。实际插件两者的处理都有,我在本地复现时更常用后者——先画一排文本图层,全选,然后按类型统一填充,速度快得多。参数chosenIndex === -1表示用户点了取消,这个判断必须写,否则undefined会触发后续数组越界。
4. 高频问题排查与避坑:中文数据、整组替换失灵和快捷键失效
4.1 填充后富文本样式丢失,只剩纯文本
现象:文本图层原本是局部标红、局部加粗的富文本,运行填充后样式全部丢失,变成纯黑色。 原因:写回时用了非属性字符串赋值。部分封装在调用layer.setText()时传入的是普通字符串,Sketch 会用默认样式重建整个 attributed string,原生文本样式就散了。 解决:写回前先读取原图层样式对象。合理做法是取出layer.sketchObject.attributedStringValue(),只替换其中的可变部分,或者干脆先备份layer.style,赋值后再 restore。
4.2 快捷键没反应,菜单里也找不到
现象:manifest 里声明了shortcut: "ctrl cmd f",重启 Sketch 后按三键组合没有响应,但是通过菜单手动点击可以填充。 原因:快捷键格式不合规或与系统快捷键冲突。Sketch 对快捷键大小写敏感,ctrl cmd F和ctrl cmd f是不同键;另外如果系统里已经占用这个组合,Sketch 不会弹冲突提示,而是悄悄放弃自己那份。 解决:把快捷键改成少用的组合,比如shift cmd d,然后完全退出 Sketch 重新打开。注意修改 manifest 后必须重启 Sketch,插件系统不会热加载 manifest 变更。如果还不行,打开系统设置里的键盘快捷键面板,搜索相同组合排查冲突源。
4.3 选中一个 Group 后填充无效果
现象:在画布上选中一个包含多个文本图层的组,运行填充,提示 "Filled 0 layers"。 原因:doc.selectedLayers.layers返回的是第一层子对象,如果一个组被整体选中,这个 API 返回的可能是 Group 本身而不是组内的文本图层,需要手动展开递归。 解决:回到第 3 章那段代码,collectTextLayers递归进入每个 Group。更好的习惯是先用 Sketch 的 "Ungroup" 快捷键把画板内的组拆平,或者自动遍历group.layers深度优先。我在实际项目里把这段递归封装成了工具函数,每次运行数据填充前都强制跑一遍,杜绝漏填。
4.4 生成的假数据全是英文,想要中文人名和中文地址
现象:跑完填充后文本是 "John Smith"、"123 Main Street",设计稿是中文界面,数据风格完全对不上。 原因:Faker.js 的默认 locale 是英文,sketch-data-faker 保留了这一默认值,没有自动切换语言环境。 解决:在插件初始化时设置faker.locale = 'zh_CN',这会切换人名、地址、电话等大部分分类。但注意 Faker.js 的中文语言包覆盖不全,部分分类如vehicle和commerce仍会回退到英文。遇到这种情况,不要硬改源码,而是用第 5 章的自定义数据源方案补充。填完后人工抽检几条,中文界面就不要混入英文产品名,这一条我踩过不止一次。
4.5 占位符没有被识别,双花括号原样留在画布里
现象:文本图层写的{{name.firstName}},填充后画布上还是一模一样的字符串,没有任何变化。 原因:文本里可能带了不可见控制字符,或者layer.text读取时返回的是NSAttributedString,强转 String 后混入了\u{2028}之类的换行分隔符,正则\s默认匹配不上这些分隔符。 解决:匹配正则从/\{\{([^{}]+)\}\}/g改成/\{\{([\s\S]+?)\}\}/g,让任意字符包括换行都能被捕获。同时填充前用.replace(/\u2028/g, ' ')把特殊分隔符统一替换成普通空格再执行替换逻辑。
5. 把插件扩展成自己的数据源:JSON 注入与自定义类型注册
5.1 为什么 130 种类型还不够
第 2 章的 registry 表是通用数据,覆盖人名、地址、公司、互联网、金融、日期等通用场景,但设计稿里最真实的假数据往往来自业务本身:某个内容的标题风格、内部测试账号格式、行业术语组合。Faker.js 再强大,也生成不了"你们产品特有的分类名"。所以合格的做法不是改源码,而是给插件预留一个可加载的 JSON 数据文件,让团队每个人都能往里加内容而不用碰核心逻辑。
我一般会在插件目录下建一个custom-data.json,结构是分类名到模板数组的映射。加载逻辑放在插件启动时,用 Node 的文件读取接口把它并进主 registry。这样既保留了 Faker.js 的随机性,又塞进了业务数据源。
5.2 JSON 数据文件怎么写
{ "business": [ { "label": "Project Name", "template": "{{custom.projectName}}" }, { "label": "Ticket Number", "template": "{{custom.ticketNo}}" } ], "test": [ { "label": "Test Account", "template": "{{custom.testAccount}}" } ] }label是弹窗里显示给用户看的友好名称,template是替换占位符。注意我把命名空间统一写成了custom.xxx,这是刻意避开了person、internet等原生分类,避免团队同学误以为这些是 Faker.js 内置函数。JSON 文件里不能写注释,所以字段含义要在外层说明文档里讲清楚,或者用label字段写足够自解释的中文名。
5.3 自定义数据如何注册进 faker.js
拿到 JSON 里的 template 字符串后,要让它真正参与生成,需要把这批自定义数据注册到 Faker.js 的 fake 调用链里。faker.js 是黑匣子,但它的扩展机制是公开的:可以往faker.custom命名空间挂函数。我推荐的做法是直接从 JSON 生成一组函数注入:
// custom-loader.js —— 从 JSON 文件注入自定义数据 const fs = require('@skpm/fs'); const faker = require('faker'); function loadCustomData(filePath) { const raw = fs.readFileSync(filePath, 'utf8'); const data = JSON.parse(raw); // 为每个模板注册一个生成函数 faker.custom = faker.custom || {}; Object.keys(data).forEach(category => { data[category].forEach(item => { const key = item.template.match(/custom\.(\w+)/)[1]; faker.custom[key] = () => generateFromTemplate(item.template); }); }); } function generateFromTemplate(template) { // 逐个处理 template 里的字段 return template.replace(/\{\{(\w+\.\w+)\}\}/g, (match, path) => { const [namespace, method] = path.split('.'); if (namespace === 'custom' && faker.custom[method]) { return faker.custom[method](); } return faker.fake(match); }); }fs.readFileSync是 SKPM 提供的增强版文件接口,可以访问插件包内相对路径。Openfaker.custom对象的过程就是在运行时注册函数。generateFromTemplate是关键:它遍历模板里的每个占位符,命中custom.前缀就调用注册函数,否则回退给faker.fake原生处理。这种兜底设计让自定义模板里可以混用{{custom.projectName}}和{{name.firstName}},生成"某项目负责人张三"这类混合句子。
加载完 JSON 后,要同步更新弹窗候选列表。常见做法是把custom-data.json的分类数组 push 进 registry,然后重新 build 一次模板选项数组。这个操作在插件每次运行时执行即可,数据量通常很小,内存代价可以忽略。注意 JSON 文件一旦格式错误,JSON.parse会直接 throw,插件运行会静默失败,所以加载代码要包一层 try-catch,失败时降级为纯 Faker.js 模式并提示用户检查 JSON 格式。
5.4 一个内存缓存的小优化
每次都readFileSync读取 JSON 不划算,尤其是大团队维护的 data 文件动辄几百条模板。我习惯在插件模块顶层缓存数据对象,只在文件变更时重新加载:
let customDataCache = null; function getCustomData() { if (customDataCache) return customDataCache; customDataCache = loadCustomData('custom-data.json'); return customDataCache; }缓存必须放在模块作用域而不是函数内部,否则每次调用都失效。缓解记忆压力的是:插件每次运行是独立进程,缓存生命周期等于一次填充命令的完整生命周期,够用且不过度。如果团队数据文件更新频繁,可以在弹窗里加一个 "Reload Custom Data" 菜单项,手动清掉缓存重读,这样既保证性能又给了后悔药。
6. 验证一次假数据是否真的到位:双跑对比与图层标记法
填充完数据后最怕的就是"看起来填了,实际没填对"。我吃过亏:某次交付前没验证,结果画布里一半文本框还是双花括号原样,开发同学拿到的设计稿根本无法用于切图。从那以后我每次填充数据都强制走一遍双跑对比。
双跑对比做法很简单:第一次填充前,先用 Faker.js 的随机种子机制固定随机流,faker.seed(20240101),填完所有图层;第二次把同样的占位符复制到另一组文本图层,再次以同一 seed 填充。两次生成的文本应该完全一致。如果第二次出现了不同内容,说明填充链里有非确定性逻辑,比如某些类型走了系统随机而没走 faker 的随机流,这就要回到第 2 章的匹配逻辑排查。
图层标记法更直观。我习惯在填充命令执行前,把占位符同时写进文本内容和一个独立图层名里。例如图层名叫>function verifyNoPlaceholders(layers) { const remaining = layers.filter(layer => { return /\{\{[\s\S]+\}\}/.test(String(layer.text || '')); }); if (remaining.length > 0) { sketch.UI.message(`Warning: ${remaining.length} layers still contain placeholders.`); } return remaining.length === 0; }
这段代码在填充命令尾部调用一次,把仍有占位符的图层数弹出来。别小看这一行校验,它把"填充是否成功"从目测变成了可断言的状态。从那以后我每次批量填充都把这个校验挂在命令末尾,就像写代码跑单测一样,先让机器告诉我哪里没填上,再谈视觉检查。希望帮到你。
本文还有配套的精品资源,点击获取