【テクニカル・上級編】SketchのData機能完全活用術!JSON連携とプラグインでダミーデータを自動生成する方法 – UI/UX・デザインツール活用バイブル

Sketch Data Engineの深淵:JSONパイプラインとPlugin APIによる動的データ注入の完全自動化

プロダクト開発において、無機質な「Lorem Ipsum」や使い回されたアバター画像で埋め尽くされたUIモックアップほど、デザインレビューを形骸化させ、エッジケースの発見を遅らせるものはない。本番環境のリアルなデータ構造――極端に長いユーザー名、マルチバイト言語の改行問題、ネットワーク遅延で崩れるレイアウト――をデザインの初期段階からデザインキャンバスに結合することは、堅牢なDesignOpsにおける絶対条件である。

本稿では、Sketch標準の「Data機能(DataSupplier API)」の内部アーキテクチャを解剖し、ローカルJSON連携からリモートAPIフェッチ、さらにはCI/CDパイプライン(`sketchtool` CLI)と連携した自動データ注入基盤の構築までを徹底解説する。

—

1. Sketch DataSupplier APIのアーキテクチャ

SketchのData機能は、単なるテキスト・画像の置換マクロではない。内部的にはSketch Plugin API(JavaScript/CocoaScriptランタイム)上に構築されたイベント駆動型のデータプロバイダ機構である。

[ Sketch UI / Symbol Instance ]
│
▼ (User triggers Data Supply / Action)
[ Sketch Plugin Engine ]
│
├── DataSupplier.registerDataSupplier()
│ │
│ ▼
├── Worker (Fetch JSON / File I/O / Process Image)
│ │
│ ▼
└── DataSupplier.supplyData(key, dataArray)
│
▼
[ Canvas Layer Native Rendering (Text / Layer Fill) ]

コアメカニズム

1. `DataSupplier.registerDataSupplier`: プラグイン初期化時、Sketchコアに「データ供給機能」を登録する。
2. Context Key: ユーザーがデータ注入を要求したレイヤー群に対して一意の`context.data.key`が発行される。
3. データバッチ解決: 要求されたレイヤー数(`context.data.requestedItems`)に応じた配列を生成し、`supplyData`へ返却する。テキストは`string[]`、画像はローカルファイルパス(`string[]`)または`NSImage`バイナリとして供給される。

—

2. 実践:Production-Ready Data Pluginの構築

最小限の構成で、ローカルJSONおよびリモートAPIから動的にデータをフェッチし、テキストと画像をバッチ流し込みするカスタムプラグインを構築する。

ディレクトリ構成

DynamicData.sketchplugin/
├── Contents/
│ └── Sketch/
│ ├── manifest.json
│ ├── data-provider.js
│ └── mock-dataset.json

`manifest.json` の設計

`data-supplier` スキーマを宣言し、Sketchのコンテキストメニューに登録する。

{
“name”: “ProductionDataBridge”,
“identifier”: “com.architecture.sketch.data-bridge”,
“version”: “2.0.0”,
“description”: “High-throughput JSON and API Data Provider for DesignOps”,
“author”: “Chief Design Systems Architect”,
“commands”: [
{
“name”: “Supply User Profiles (Remote API)”,
“identifier”: “supply-users”,
“script”: “data-provider.js”,
“handler”: “onSupplyUserProfiles”
},
{
“name”: “Supply Product Inventory (Local JSON)”,
“identifier”: “supply-products”,
“script”: “data-provider.js”,
“handler”: “onSupplyProducts”
}
],
“menu”: {
“isRoot”: false,
“items”: [“supply-users”, “supply-products”]
},
“data-supplier”: {
“commands”: [
{
“dataIdentifier”: “users-text-supplier”,
“name”: “Users: Full Name & Email”,
“type”: “public.text”,
“action”: “supply-users”
},
{
“dataIdentifier”: “products-image-supplier”,
“name”: “Products: Thumbnails”,
“type”: “public.image”,
“action”: “supply-products”
}
]
}
}

`data-provider.js` の実装

非同期HTTPフェッチ、ローカルキャッシュ、画像パイプラインのメモリ制御を含む堅牢な実装を行う。

import DataSupplier from ‘sketch/data-supplier’;
import UI from ‘sketch/ui’;
import AppKit from ‘AppKit’; // macOS Native Bridge

/

  • リモートAPIからユーザーデータを取得して供給するハンドラ

/
export function onSupplyUserProfiles(context) {
const dataKey = context.data.key;
const items = context.data.items;
const count = items.length;

if (count === 0) return;

UI.message(`Fetching ${count} records from upstream API…`);

// macOSネイティブのNSURLSessionを用いて同期/非同期通信を制御
const url = [NSURL URLWithString:`https://api.internal.network/v1/mock/users?limit=${count}`];
const request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@”GET”];
[request setValue:@”application/json” forHTTPHeaderField:@”Accept”];

const responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

if (!responseData) {
UI.message(“Error: Upstream network unreachable.”);
return;
}

const jsonString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
const parsed = JSON.parse(jsonString.toString());

// 供給データの配列を生成
const data = items.map((item, index) => {
const record = parsed.data[index % parsed.data.length];
return `${record.firstName} ${record.lastName} <${record.email}>`;
});

// Sketch Coreにデータを流し込む
DataSupplier.supplyData(dataKey, data);
}

/

  • ローカルJSONおよび画像キャッシュを利用した高速アセット注入

/
export function onSupplyProducts(context) {
const dataKey = context.data.key;
const items = context.data.items;
const count = items.length;

// プラグインバンドル内のJSONリソースを取得
const pluginRoot = context.plugin.url().path();
const jsonPath = `${pluginRoot}/Contents/Sketch/mock-dataset.json`;

const rawData = [NSString stringWithContentsOfFile:jsonPath encoding:NSUTF8StringEncoding error:nil];
const dataset = JSON.parse(rawData.toString());

// 一時ディレクトリにキャッシュされた画像パスを供給
const tmpDir = NSTemporaryDirectory();
const suppliedImages = items.map((item, index) => {
const product = dataset.products[index % dataset.products.length];
const imagePath = `${tmpDir}/${product.id}_thumb.png`;

// キャッシュが存在しない場合はBase64またはURLからデコードしてローカル保存
if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
const imageData = [[NSData alloc] initWithBase64EncodedString:product.base64Image options:0];
[imageData writeToFile:imagePath atomically:YES];
}
return imagePath;
});

DataSupplier.supplyData(dataKey, suppliedImages);
}

—

3. ヘッドレスCI/CD連携:`sketchtool` による自動データ注入

DesignOpsの究極形は、エンジニアリング側のスキーマ変更(OpenAPI / GraphQL)に追従して、デザイナーがSketchを開くことなく最新データ入りのアートボードが自動生成・エクスポートされるパイプラインである。

以下は、`sketchtool` と Node.js を組み合わせ、生の `.sketch` ファイル内部(SQLite / JSONアーカイブ)を展開・走査・データパッチする自動化スクリプトである。

/

  • ci-data-injector.js
  • .sketchファイル(ZIP形式のJSONアーカイブ)を解凍し、
  • Symbol overridesのテキスト/画像をヘッドレスに更新する。

/

const fs = require(‘fs’);
const path = require(‘path’);
const AdmZip = require(‘adm-zip’);
const { execSync } = require(‘child_process’);

const SKETCH_FILE = path.resolve(__dirname, ‘./DesignSystem_Master.sketch’);
const OUTPUT_FILE = path.resolve(__dirname, ‘./DesignSystem_ProductionData.sketch’);
const EXPORT_DIR = path.resolve(__dirname, ‘./dist_renders’);

function patchSketchData() {
console.log(‘⚡ Extracting Sketch Archive…’);
const zip = new AdmZip(SKETCH_FILE);
const zipEntries = zip.getEntries();

// 最新のマスターデータを読み込み
const masterData = JSON.parse(fs.readFileSync(‘./live-db-dump.json’, ‘utf8’));

zipEntries.forEach(entry => {
// ページ定義JSONを探索
if (entry.entryName.startsWith(‘pages/’)) {
const pageData = JSON.parse(entry.getData().toString(‘utf8’));
let patched = false;

// レイヤー走査ユーティリティ(再帰的Symbol Overrideパッチ)
function traverseLayers(layers) {
layers.forEach(layer => {
if (layer._class === ‘symbolInstance’ && layer.overrideValues) {
layer.overrideValues.forEach(override => {
// 特定のデータキー命名規則を持つOverrideを置換
if (override.overrideName.includes(‘USER_NAME_TEXT’)) {
override.value = masterData.users[0].name;
patched = true;
}
});
}
if (layer.layers) {
traverseLayers(layer.layers);
}
});
}

traverseLayers(pageData.layers);

if (patched) {
zip.updateFile(entry.entryName, Buffer.from(JSON.stringify(pageData), ‘utf8’));
console.log(`✔ Patched: ${entry.entryName}`);
}
}
});

zip.writeZip(OUTPUT_FILE);
console.log(`📦 Saved: ${OUTPUT_FILE}`);
}

function exportHeadlessRenders() {
console.log(‘🖼 Running sketchtool export…’);
execSync(`sketchtool export artboards “${OUTPUT_FILE}” –output=”${EXPORT_DIR}” –formats=png –scales=2`, {
stdio: ‘inherit’
});
console.log(‘🚀 Visual Regression Check Assets Generated.’);
}

// 実行パイプライン
patchSketchData();
exportHeadlessRenders();

—

4. 大規模モックアップにおけるパフォーマンスとメモリ最適化

数百のアートボードと数千のシンボルインスタンスが存在するデザインシステムマスターファイルでData APIを無邪気に叩くと、JavaScriptCoreランタイムとSketchのUndo Managerがメモリを圧迫し、UIフリーズやクラッシュを引き起こす。

以下の3つの最適化テクニックを必ず組み込むこと。

① Undoマネージャの抑制

DataSupplier APIで大量のレイヤーにデータを連続代入する際、ネイティブのUndoスタックが指数関数的に増大する。

// 大量注入時はUndoスタックの追跡を一時無効化
const doc = context.document || NSDocumentController.sharedDocumentController().currentDocument();
const undoManager = [doc undoManager];

[undoManager disableUndoRegistration];
try {
DataSupplier.supplyData(dataKey, massiveDataset);
} finally {
[undoManager enableUndoRegistration];
}

② 画像リサイズとデコードのオフロード

リモートから取得した4K解像度の写真をそのままDataSupplierに渡してはならない。Sketchキャンバスのレンダリングパイプライン(Metalバックエンド)がテクスチャ展開で枯渇する。供給前にネイティブレベルでダウンサンプリングする。

function createOptimizedThumbnail(sourcePath, maxDimension = 512) {
const image = [[NSImage alloc] initWithContentsOfFile:sourcePath];
const currentSize = [image size];

const scale = Math.min(maxDimension / currentSize.width, maxDimension / currentSize.height);
const newSize = NSMakeSize(currentSize.width scale, currentSize.height scale);

const newImage = [[NSImage alloc] initWithSize:newSize];
[newImage lockFocus];
[image drawInRect:NSMakeRect(0, 0, newSize.width, newSize.height)
fromRect:NSZeroRect
operation:NSCompositingOperationCopy
fraction:1.0];
[newImage unlockFocus];

const tiffData = [newImage TIFFRepresentation];
const bitmap = [[NSBitmapImageRep alloc] initWithData:tiffData];
const pngData = [bitmap representationUsingType:NSPNGFileType properties:@{}];

const optimizedPath = `${NSTemporaryDirectory()}/opt_${NSUUID.UUID().UUIDString()}.png`;
[pngData writeToFile:optimizedPath atomically:NO];

return optimizedPath;
}

③ GC(ガベージコレクション)プレッシャーの管理

CocoaScriptランタイムでは、Objective-Cブリッジのオブジェクト参照が自動で即座に解放されない場合がある。大量のループ処理内では`@autoreleasepool`相当のメモリ解放スコープを明示的に形成する。

// CocoaScript環境での明示的Autorelease Pool
const pool = [[NSAutoreleasePool alloc] init];
try {
// 大量のNSData/NSString処理
} finally {
[pool drain];
}

—

5. 設計思想:Data-Driven Designの極致

UIコンポーネントは、静的なベクターデータの集積ではなく、「データを受け取って描画を行う純粋関数(Pure Function)」として捉えるべきである。

UI = f(State, Data)

SketchのDataSupplier機構を正しく拡張し、バックエンドのモックジェネレータ(Prism、MSW等)やCI/CDパイプラインと結びつけることで、デザイナーは「想定外のテキスト長によるレイアウト崩れ」や「ダークモード時における特定画像のコントラスト不足」を、コードを1行も書く前にキャンバス上で検知・修正できる。

本稿で解説した内部アーキテクチャとパイプラインを活用し、プロトタイピングを「お絵描き」から「実データ駆動のエンジニアリング」へと昇華させてほしい。

タイトルとURLをコピーしました