embryo

エンジニアの備忘録

SketchPlugin開発の効率化

この記事はSketch Adevent Calendar の 17日目の記事になります。

今日はデザインツールSketchのPlugin開発を効率的に行う手法についてまとめていきます。開発者向けの記事となりますのでご留意ください。

SketchPlugin開発の基本

SketchPluginは以下のディレクトリ構造で構成されています。下記の場合、pluginame.sketchpluginをダブルクリックすることでSketchにインストールされます。各要素の意味は下記のとおりです。

  • Sketch: SketchPluginの本体が配置されます
  • Resources: 画像などの静的リソースが配置されます。こちらに配置されたリソースはスクリプトからアクセスすることが出来ます
  • manifest.json: 名前などの情報や、スクリプトのパスなどが記述されます
pluginname.sketchplugin
└── Contents
    ├── Resources
    │   └── logo.png
    └── Sketch
        ├── command.js
        └── manifest.json

詳細は公式のドキュメントを参照するのが一番分かりやすいです。最近更新されたようで更にわかりやすくなっています。

developer.sketchapp.com

SketchPlugin開発のつらみ

一度でもSketchPluginを開発したことがある方なら以下のようなつらみに悩まされたことがあるかと思います。

  • cocoascriptによる開発
  • UIの実装が高コスト
  • 貧弱なデバッグ機能
  • UndocumentedなAPI

今回はこれらの問題点をできるだけ解消して開発を効率的に行う方法についてまとめたいと思います。

ES6で開発するための環境を整える

cocoascriptによる開発

cocoascriptはJavascriptの記法でネイティブの機能を操作できるというもので、基本的にSketchPluginはcocoascriptで記述されます。 文法はJavascriptCoreに準拠するため、新しいバージョンのSketchであればES6文法もサポートされていますが、import/exportは当然ES6のように書くことはできないため、以下のような特殊な文法で記述する必要があります。

@import "someUtils.js";

someUtils();

webpackでバンドルする

文法自体はES6に対応しているため、webpackなどでBundleしたファイルをSketchフォルダ以下に移してあげれば良さそうです。これでimport/exportの問題は解消され、npmモジュールなどの使用も簡単に行えるようになります。また後述しますが、WebView上のJavascriptとcocoascriptでコードを共有することも出来るようになります。

skpmを使う

実際にwebpackを使用するには、いくつか特殊な設定が必要になります。例えばエントリポイントとなる関数をグローバルに定義する必要があるのですが、それらのSkechPluginに必要なwebpackの設定を内包したビルドツールがskpm-buildです。

他にも本来SketchPlugin上では使用できないconsoleなどを使用できるようにするpolyfillなども自動で追加されるのでSketchPluginの開発においてはこちらのツールを使用するのが良いかと思います。公式でも紹介されていますがドキュメントがまだ整備されていないので近々別の記事で紹介できればと思います。

UIをWebViewで実装する

AppKitを用いた場合

基本的にUIはAppKitで実装することになるので下記のようにコードで直接矩形情報を指定する必要があります。iOS開発者やFlash開発者には馴染みがあるかもしれませんが、SketchPluginの開発ではUI確認のためのツールなどが提供されているわけではないので、複雑なUIを実装するのには不向きです。

let view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, 200, 180));

WebViewを利用する

WebViewを用いることでHTMLでUIを作成することができます。cocoascript とWebView Scriptのイベントのやり取りは下図のように行うことができます。

f:id:sue71:20171218022839p:plain

cocoascript -> webView

WebViewのAPI経由でWebView上のJavascriptを実行することができます。予めWebView上にSketchから呼ばれる関数をグローバルに登録しておくことで特定の関数を呼び出すことが出来ます。

webView -> cocoascript

WebViewはURLの変更イベントをハンドリングすることが出来るので、WebView内のJavascriptでURLにハッシュを付与することでイベントを通知することができます。

sketch-module-web-view

sketch-module-web-viewは前項のやり取りを抽象化してくれるライブラリです。こちらを利用した場合下記のように処理をやり取りすることが出来ます。

  • cocoascriptからwebviewの処理を実行
const webUI = new WebUI(context, "index.html", options);
webUI.eval(`callJavascript()`);
  • webviewからcocoascriptの処理を実行
import { pluginCall } from "sketch-module-web-view/client";
pluginCall("callNative", "args")

デバッグ環境を整える

下記ログファイルにSketchPlugin内でlogを用いてロギングされた内容や、エラー内容が出力されています。

tail -f ~/Library/Logs/com.bohemiancoding.sketch3/Plugin\ Output.log 

上記のようにtailで参照することも可能ですが、sketch-dev-toolsを利用することでSketch上でログを確認することができます。またsketch-dev-tool上でドキュメントツリーの状態やSketchのアクションも確認できるので大変便利になりました。(下図)

  • Output Log f:id:sue71:20171218030808p:plain

  • Tree f:id:sue71:20171218030810p:plain

  • Action Log f:id:sue71:20171218030804p:plain

また、manifest.jsonに不備がある場合、エラー出力は下記のようにして確認できます。実行時のログと出力箇所が違うことに注意してください。

tail -f ~/Library/Logs/com.bohemiancoding.sketch3/Plugin\ Developer.log 

class-dumpでクラスヘッダを確認する

公式サイトにていくつかのAPIドキュメントが公開されていますが、これらはほんの一部です。実際にレイヤーなどがどういった構造を持っているかは実行時にログで確認するしかありません。そこでclass-dumpを用いてヘッダファイルを出力しインターフェースを確認します。

class-dumpはこちらからダウンロードしてください。下記のように実行することで使用しているクラスのヘッダファイルを取得できます。

$ class-dump /Applications/Sketch.app

例えばSketchのレイヤーを司るクラスのインターフェースは下記のように出力されます。

@interface _MSLayer : MSModelObject
{
    BOOL _isFlippedHorizontal;
    BOOL _isFlippedVertical;
    BOOL _isLocked;
    BOOL _isVisible;
    long long _layerListExpandedType;
    NSString *_name;
    BOOL _nameIsFixed;
    NSString *_originalObjectID;
    unsigned long long _resizingType;
    double _rotation;
    BOOL _shouldBreakMaskChain;
    NSDictionary *_userInfo;
    MSExportOptions *_exportOptions;
    MSRect *_frame;
}

まとめ

SketchPuginを開発する上で困るポイントとその問題に対する対処法を説明しました。本記事を書き始めた頃からもplugin開発事情に変化が起きているようです。(例えばsketch-dev-toolは10月末頃リリースされました。) 情報が古い可能性もあるため、もっと良い方法などありましたらコメント頂けるとうれしいです。

最後に今回説明した実装のサンプルです。

github.com