embryo

エンジニアの備忘録

技術書典5にサークル参加しました その2

前回はサークル参加の反省をメインにまとめましたが、今回はイベント自体の感想を一般参加者目線のまとめになります。

戦利品

一人サークル参加してしまったため、半ば諦めていたのですが隣のサークルの方のご好意で回ることができました。ありがとうございました!

時間は15:00頃で本は売り切れているところも多かったですが、DLカードをメインで狙っていたので全く問題なかったです。以下戦利品です(あまり買えなかった)。

f:id:sue71:20181014215435j:plain

一周だけ回って気になるものを買うという感じだったのであまり量は買えませんでした...。 あとで買っておけばと後悔したものも沢山あるのですがBooth版などを期待したいと思います。

f:id:sue71:20181014215441j:plain

ちなみにこちらは販売している際に、とあるお客様が「UIに携わるものとしてこういった活動をしてくれる方に感謝している」とくださったものです。こちらこそとても励みになりました。ありがとうございます。

知らない技術に出会える場

今回特に参加してよかったなと思ったのは知らない技術に出会えるところですね。本当に色々な技術領域の方が参加しているので、まったく触れることのなかった技術に触れるきっかけになります。

「水晶雫世界」さんが頒布されていたCrystalDiskInfoの開発秘話本ですが、僕が不勉強なのもあり名前も知りませんでした。WindowsSSD/HDDモニタリングといえばコレというほどデファクトOSSだそうです。知らなかったものの普段Webばかりの自分にとってはSSD/HDDのモニタリングツールというワードには惹かれるものがあり、購入しました。受け身で知らない技術に出会えるのは嬉しいですね。

「技術季報」は去年売る側だったのですが、今回は買う側でした。正直パンフレットかなくらいの認識だったのですが「サークルの自動配置」などの技術記事が書いてあったりと技術書として楽しめました。

暗号通貨の分野にも興味があったので「NEO」に関する書籍を購入したのですがこちらはDLカードを無くしてしまいました...。しかし、購入した際、月一で勉強会を開いているという情報をサークルの方から頂いたので参加してみようかなと考えています。

まとめ

今回はサークルとしても一般としても参加できたのですごく楽しかったです。こういったイベントを支えてくれるスタッフ、サークルの皆さんには感謝しかないです。次回開催時も参加するつもりなので皆さんよろしくおねがいします。

技術書典5にサークル参加しました その1

頒布したもの

techbookfest.org

「本 + PDFダウンロードカード セット」で500円で頒布しました。

用意したのは以下。

  • 本は100部
  • PDFダウンロードカード100枚 (+予備94枚)

※ ダウンロードカード6枚は手違いでダメにしてしまいました

結果

詳しく時間を見ていないのですが12:30頃に本は完売してホッとしていました。 その後もダウンロードカードのみで購入していただき最終的には残り8枚でした。

  • 本+PDFダウンロードカード: 100セット完売
  • PDFダウンロードカード88枚

でした。予備のカードまで割と買っていただけてかなり嬉しかったです。 とにかく安心しました。

なぜデザインシステムをテーマにしたか

Web、iOSのフロントエンドエンジニアとして働いていくなかで、かねてからUIの共通化コンポーネント化は開発効率だけを目的にするものではないし、UIコンポーネント=正義というどちらかというとエンジニア目線の押し付けはデザイナーにとってもユーザーにとっても嬉しくない、と思っていたのでデザインシステムはそのあたりの話をするのにちょうどよい、ということでこのテーマでの執筆を決めました。端的にいうと次のようなことを話したかったのです。

  • 感覚に訴えかけるための要素がデザインには存在する
  • 無機質なUIのセットですべてが構成されるわけではない
  • きれいなコンポーネント化はユーザビリティに貢献できる

執筆の反省

計画もなく取り敢えず書く。といった感じだったのでギリギリになって知らないことが増えてきて焦りだすわけです。印刷所について調べることもないので当然デッドラインなども知らないわけです。なんとかなるだろうと思って書いていました。当たり前ですが締切を事前に調べておき、マストで必要なものを洗い出し、それからじっくり書き始めるのが良かったかと思います。

以下大きなしくじりです。

  • 印刷は一部カラーにする予定が締切に間に合わず、モノクロに
  • PDFダウンロードカードもギリギリ。前日にQRの不備に気づき貼り直す
  • 事前提出について確認しておらず当日頒布停止

準備の反省

ほとんどを前日に用意しました。100均素晴らしいです。

用意したもの

  • テーブルクロス(布)
  • 見本誌を立てかけるためのやつ (当日忘れる)
  • 値段+ポップを印刷したもの
  • スケッチブック
  • 小物入れ(ペン+DLカード他入れるためのもの)
  • 小銭入れ
  • 小銭(500円✕5枚、100円✕50枚)
  • カッターとか諸々

必要だったもの

  • A4くらいのポスターを立てるやつ
  • 机の前にはる本の説明書き
  • 太いペン
  • サークルの名刺

後述するのですがお隣のサークルさんに大変親切にしていただいたのでなんとかなりましたが、 結構色々足りなかったです。特に本が完売してからは何売ってるか全然わからない状態になってしまったので 大きめのポップは必須でした。

サークルの名刺は欲しがる方が結構いらっしゃって、DLカードとよく間違われていました(これは本当に申し訳なかったです)。次回は必ず用意しようと思いました。

当日の反省

取り敢えず一人で参加するのは本を買いに行けないし、トイレも飲み物もままならないし何より寂しいです。なんとかなるかなと思ったましたが当然なんとかなりませんでした。

お隣のサークルの「水晶雫世界」さんには大変親切にしていただきました。お店番を代わっていただいたり、ペンを貸していただいたり、話し相手になっていただいたりと最初から最後まで本当にお世話になりました。こういった方達に出会えただけでも参加して良かったと思います。

techbookfest.org

まとめ

色々と段取り悪くバタバタしていましたが、本当に楽しかったです。多分次回もまた何か書きます。 ちょっと長くなりそうだったので、戦利品やイベント自体の感想は別の記事にてまとめようと思います。

技術書典スタッフの皆さん、サークル参加の皆さん、当日足を運んでくださった皆さん、お疲れ様でした。 ありがとうございました。

言語ファイルの命名規則と管理方法

今日は言語ファイルの管理方法及び命名規則に関してです。技術書典5でデザインシステムの本を書く予定なんですが、その中でも言語は重要な要素なので一度整理しておこうと思います。

なぜ言語ファイルを管理するのか

実際のところ本当に多言語対応するつもりなのであれば言語ファイルの管理だけでは厳しいところが多いです。言語以外の差異も数多くある上に、UI上で日本語と同じように再現することも簡単ではないからです。それでも言語ファイルを管理したい理由は2点あって

  • 用語の更新を簡単にしたいから
  • 表記揺れを無くしたいから

です。つまるところUIを再利用可能なコンポーネントの組み合わせで作りたいという欲求と変わりません。

どのように管理すべきか

色々なアプローチがあるのですが、現状私が確認してるもので大きい区分で分けると

  • コード中にある文字列を抽出して言語ファイルとして出力する
  • 別途言語用のファイルを定義してコード中から呼び出す

の2点があります。前者は何も考えずに渡されたデザインに記述された文字列を記述できるが、表記ゆれは起こりやすい。後者は文字列を別途定義する分コストが高いが設計次第ではサービス内で使用している用語を整理するソースになり得る。といったメリット・デメリットがありそうです。

ちなみに再利用性を考えない設計にした場合には、前者のようなアプローチが良いかなと思います。 どちらの方針を取るかは言語管理の目的を用語の整理とするか、多言語化のための単なる定数とするかで異なります。

どのように設計するか

キーに関してルールがない場合、再現なくネストしていくため、現状私の開発時は <コンテキスト>.<書式>.<適当なキー> としています。

コンテキスト

コンテキストはスコープを表します。今開発しているプロダクトでは 共通部分のcommon, ドメイン知識を持つdomain, 表示領域を表すmodule で分けています。

  • common : ドメインに依存しないような用語、例えば「メールアドレス」などもそれに当たります。
  • domain : ドメインに依存するような用語、例えば「ユーザー」、「ユーザーの氏名」などです。
  • module : 表示領域、Viewに依存します。例えば「ユーザー一覧」などがこれにあたります。

コンテキストを用意している理由は適用の範囲を表すことはもちろん、文脈を意識するようにするためです。同じ単語でも文脈が異なれば別の意味を持っている可能性があります。

書式(タイプ)

以下のようなタイプを定義しています。

  • label
  • button
  • message
  • placeholder

書式をなぜ用意しているかというと、言語の検索をする際に必要な情報になるからです。また書式のバリデーションの用途も兼ねています。 例えば button の場合、あとで見返したときに一律して「〜する」という書式になっているのかなどを確認するためです。

コンテキスト間の依存関係

f:id:sue71:20180924204601p:plain

言語間の依存関係は図のようになるはずで、例えばユーザーデータが次のように表される場合

interface User {
    id: string;
    name: string;
    email: string;
}

このメールアドレスのキー管理は

const user = {
    "domain.user.label.email": "ユーザーのメールアドレス"
};

としたいのですが、「メールアドレス」の表記がコンテキストによって「Eメール」、「メール」と表記が異なるのはサービスとしてまずいので

const common = {
    "common.label.email": "メールアドレス"
};

const user = {
    "domain.user.label.email": "ユーザーの{common.label.email}"
};

の関係であるべきです(参照の形式は適当です)。同様にユーザー一覧用の言語ファイルは

const userList = {
    "module.userList.label.user_email": "{domain.user.label.email}",
    "module.userList.button.ok": "{common.label.ok}", 
    "module.userList.message.hint": "ユーザー一覧でユーザーを追加できます"
};

となり、commonmoduleから参照可能なものは参照し、固有のものは個別に定義します。

これはモジュールなのかドメインなのか

最早言語ファイルに限った話ではないのですが抽象的なレイヤーが挟まるとどれに入れるべきか迷うことがあるはずです。 例えば「ユーザーを追加する」リンクやボタンの文言はモジュール、ドメインどちらに入れるのか、などです。

正直なところドメインオブジェクトがきちんと分析されているか左右されるのですが、ドメインオブジェクトの振る舞いとしてドメイン配下に定義するのがいいと思っています。 しかしそれをチーム全体に浸透させるのは難易度が高いのも事実なので迷うくらいなら機能の用語としてしまってモジュールに定義するのが良いかと思います。

まとめ

現状実践している言語ファイル管理の方法について簡単にまとめました。言語ファイルを管理する目的によって管理方法は様々なので、まずは目的を整理する必要がありそうです。

AtomicDesignが解決してくれないこと

今日はAtomicDesignについてです。

概要

弊社のとあるチームでAtomicDesignの導入を試みていたのですがあまり上手くいっていないようでした。そこでなぜ上手く行っていなかったのか考えて整理したので、その内容をまとめました。

AtomicDesignとは

Design Language System (DLS) を作る上でのパターンの一つです。詳しくは下記を参照してください。

atomic design

大まかに説明するとデザインパーツの粒度を定義し、それらの組み合わせでデザインパーツを構成していこうというものです。 粒度は下記のように定義されています。

Organisms > Molecules > Atoms

フロントエンド界隈には大分普及してきたようで最近では活用事例を耳にする機会も多くなりました。

AtomicDesignのメリット

まずはAtomicDesignのメリットについてですが以下の2点がよく挙げられています。

共通言語が作られる

粒度を共通言語として定義することにより、開発者間でのコミュニケーションが円滑に進みます。

分解粒度が属人化しづらい

再利用性の高いUIコンポーネントを作っていく際、どうしても開発者のセンスによるところが大きかったように思えますが、とにかく細かく分解して足し合わせるというメソッドなので属人化しにくいのもメリットの一つと言えるでしょう。

※ しづらい、としたのは限られた階層しか無い以上必ず属人化はするからです。

AtomicDesignで起こりがちな問題

AtomicDesignを採用する時によく起きている問題をまとめました。

分解されている == 良いUIコンポーネントではない

Atomic Designの粒度に分解して配置することを目的にしてしまい、1度しか使わないUIコンポーネントが量産されるケースを現場でよく見かけます。目的は再利用することなので、分解していく過程でどこが再利用出来るかを考えるべきです。

グループ化やパターン化は解決してくれない

良いUIコンポーネントを作るためには適切なグループ化とパターン化が必要です。例えばボタンの場合は下記のようなグループ化、パターン化を行うことで再利用性が高まりますが、AtomicDesignをただ実践するだけではそうはなりません。

Group Pattern
TextButton type, size
IconButton type, size, icon

ユーザビリティが良くなるわけではない

UIコンポーネントの分解粒度が最適化されることがユーザーにとって嬉しいわけではありません。あるUIコンポーネントのルールがアプリケーション内で一貫していることで初めてユーザーにとって嬉しいものになります。

そもそも良いUIコンポーネントとは

AtomicDesignをきっかけにDLSを作り始める事自体は良いことです。ただAtomicDesignを実践する前に良いUIコンポーネントとはどういうものなのかを整理し、チェックすると良いかと思います。

再利用性が高いこと

主に開発効率化のためになりますが、UIコンポーネントに関して言えば最も重要な要素です。新たにページや機能を制作する際に、今まで分解したパーツを使用できないか、またそれらをどのようにグループ化されていれば矛盾なくパーツにはめ込めるのかを考えましょう。

最終的には下記のように文字で指示書が書けるのが理想です。

f:id:sue71:20180502232454p:plain

ルールと拡張性のバランス

そのUIパーツが本質的にどういう性質なのかを考えましょう。画面だけ見ていると見えていない性質が見えてくるはずです。 例えば下記のようなナビゲーションバーが提供されている場合

f:id:sue71:20180502231513p:plain

- 左部はページの遷移をコントロールするものである
- 中央はページを説明するものである
- 右部はページに対するアクションを提供するものである

というルールが考えられます。

ある程度ルールが定まっているとその拡張性の方向も限られてくるため実装も最適化されます。 ルールが定まっていない場合そのUIパーツ内で拡張性の方向が定まらないため、パターン化がしづらくなります。これが1度しか使われないコンポーネントが量産されること、もしくはUIコンポーネントの分岐が多くなる原因になります。

また、必ずしも汎用的なルールに落とし込めないことに注意してください。時には似ている形でも全く別の欲求によってデザインされている場合があります。 こういった場合は無理にパターンに当てはめるのではなく、特殊なケースとして考えるのが良いかと思います。

ちなみに自分はどうしているか

基本的には汎用的なUIコンポーネントと、より詳細度の高いUIコンポーネントの2つを定義しています。(名前は適当です

またユーザービリティを意識した場合、必ずしも汎用性の高いパーツのみで完結するとは限らないため、各ページ内でしか使用しないものも定義することを許容していますが、依存を分かりやすくするためにページのディレクトリ配下に置くようにしています。

詳しくは別の記事でまとめたいと思いますがOOUI(UX)などが近い考え方になると思います。

まとめ

AtomicDesignの実践時によく見かける課題について説明しました。

「設計とはフレームワークではなく良いコードを書こうとする意思である」という言葉も記憶に新しいですが、DLSやUIコンポーネント設計にも同じことが言えます。

手法に名前をつけることは大変意義があることであるのには間違いは無いのですが、ベストプラクティスを考えることが最も重要です。つまり、あるパターンを実践するのを目的とするのではなく、良いUIコンポーネントを作ることを目的としましょうということです。

Xcode9のビルドを高速化する

Xcode9においてビルド高速化するための手法についてまとめました。

設定ファイルの最適化

ビルドの速度に関係する主な項目が下記になります。それぞれの設定の意味と推奨される設定値を解説していきます。

  • Build Active Architecture
  • Debug Information Format
  • WholeModuleOptimization
  • XFrontendOptions
  • New Build System

Build Active Architecture

  • Build Settings > Build Active ArchitectureDebug 時に限りYESに設定する

実行する対象のArchitectureのみを出力するオプションです。 DebugビルドはデフォルトでYESになっているはずですが一応確認しておくと良いかと思います。

Debug Information Format

  • Build Settings > Debug InformationDWARF with dSYMからDWARFにする

DebugビルドはデフォルトでDWARFになっていますが、実機ビルドなどでdSYMが必要になるのであれば下記のように設定することができます。

f:id:sue71:20180226121812p:plain

Whole Module Optimization

このオプションを有効にすることでフルビルドにかかる時間が少なくとも1/2以下になるようです。(とあるプロジェクトで実施したところ 274s -> 98sという結果でした。) 下記2つの設定を変更することでwhole module optimizationを有効にしつつ、コードの最適化は実行しないようにします。

  • Optimization LevelFast, Whole Module Optimizationに設定する
  • Other Swift Flags-Ononeを設定する

whole module optimizationの仕組みについては Swift.org - Whole-Module Optimization in Swift 3 で説明されています。並列処理は減少することになりますが、重複する処理は減少することにより結果的にビルド速度が向上しているのではないかと思われます。随所で推奨する記事を見かけますが差分コンパイルが劇的に遅くなるので何も考えず入れて良い設定ではないことに注意しましょう。

XFrontend Options

コンパイラの解析部分に対するオプションになります。こちらにいくつかオプションを指定することでコンパイルに時間がかかっているメソッドや式を解析することができます。

-XFrontend -warn-long-function-bodies=limit

Xcode9以前からもあったオプションですが特定のメソッドの解析にlimit以上の時間がかかっている場合、該当の箇所に警告を出力します。

-XFrontend -warn-long-expression-type-checking=limit

こちらはXcode9から提供されているオプションです。式の解析にlimit以上の時間がかかっている場合、該当の箇所に警告を出してくれます。前述のオプションではメソッド単位のワーニングしか判別できませんでしたが、このオプションによってより細かく判別することが可能になります。

New Build System

Xcode9から新たに提供されているビルドシステムで、公式では下記のように説明されています。

- New in Xcode 9 – Preview of a new build system written in Swift. Currently, This system is optional but it will become the default in a future version of Xcode
- Added a preview of a new build system written in Swift.
- Provides higher reliability.
- Catches many project configuration problems.
- Improves overall build-system performance.

Note, build system performance does not include the compilers, linkers, and other tools used by the build system.
  • 将来的にはデフォルトで提供されるビルドシステムだがpreviewとしてオプションで使用できる
  • 現在より高い信頼性を提供する
  • プロジェクトの設定による問題をキャッチする
  • 全体的なパフォーマンスを向上させる
  • コンパイラやリンカなどのパフォーマンスを向上させるわけではない

とのことで基本的にはメリットしかなさそうです。

下記記事によると28%速度が向上したとのこと。 https://www.linkedin.com/pulse/get-faster-more-stable-xcode-your-swift-projects-dan-zinngrabe/

Framework分割

Frameworkを適切な粒度に分割して差分ビルドしやすい構成にします。Webフロントエンドでもリソース取得の効率化のためにバンドルするファイルを分割することがありますが構成は似たような形になるかと思います。現状XcodeはDynamic Frameworkしかサポートしていないため、起動時間が遅くなってしまうことが懸念されますがStatic Framework化する方法が下記ページにて紹介されています。

Carthage/StaticFrameworks.md at master · Carthage/Carthage · GitHub

また副次的な効果ではありますが依存や設計が整理されるのも大きなメリットかと思います。

まとめ

Xcode開発者の悩みの種であるビルド速度についてまとめました。もしあまり考慮していない場合は参考にして頂ければと思います。ちなみに自分の場合はフルビルドが走った際は、Webフロントエンドの作業に移行するようにしています。

再帰下降構文解析で電卓プログラムを実装する

パーサ入門として式解析の一つである再帰下降構文解析をTypescriptで実装してみたのでまとめました。

構文解析の基礎

構文解析入門にあたって基本的な語句を整理します。

構文図

構文の規則を図式化したもの。構文の構成要素とその関係性を図式で表します。 例えばある言語のルールとして「英字で始まりで英字と数字で構成されるもの」が識別子とされている場合、その識別子は下記のような構文図で表されます。下記の例だとab0, b01aなどが該当します。

f:id:sue71:20180201023554p:plain

BNF記法

構文図を文字列で表現したもの。例えば前述の例を表す場合

<識別子> ::= <英字> (<数字> | <英字>)
<英字> ::= a | b | c| ... | z 
<数字> ::= 0 | 1 | 2 | ... | 9

のようになります。<>で囲まれているものは非終端記号、それ以外を終端記号といいます。

  • 非終端記号: 他の文字列で代替されるもの
  • 終端記号: 他の文字列で代替されないもの

字句解析

構文解析する前に、文字列を構文規則に従って意味を持つ集合(トークン)に分割する必要があります。トークンは例えば以下のようなものが該当します。

  • if
  • else
  • (
  • )

ここで取得されたトークンを元に構文解析を行います。構文解析の手法は色々ありますが今回はよく使用されている再帰下降構文解析を実装します。

再帰下降構文解析

BNFに出現した非終端記号に対応する関数を用意し、末端から値を決定していく方法です。

再帰下降構文で四則演算

四則演算を実装する場合、演算子の優先度を考慮する必要がありますが再帰下降構文を用いた場合は構文規則そのものに演算子優先度が含まれます。 式をexpression, 項をterm, 因子をfactorとした場合、四則演算の構文規則は

<expression> ::= <term> [ ('+'|'-') <term> ]*
<term>   ::= <factor> [ ('*'|'/') <factor> ]*
<factor> ::= <number> | '(' <expression> ')'

のようになります。expressionを根としたツリー構造を考えるとexpressionから下向きに処理が実行されてていき、factorは必要に応じてexpressionを呼び出すので再帰的な性質も持っています。

Node.jsによる実装

下記仕様で簡易電卓プログラムを実装しました。

  • 変数には[a-z]が使用可能
  • 四則演算が使用可能
  • ?はPrint文として扱う

gist.github.com

  1. 入力を受け付け
  2. statement関数で1文(1行)ずつ処理
  3. expression, term, factorで式を解析、stackに値を詰める
  4. Print文が呼び出された場合はstackから値を取り出して出力する

といった流れで実装しています。

識別子の扱いについて

今回は代入される値が数値であることが決まっているのでシンプルに配列を定義していますが実際は型情報などを保持する必要があります。これを記号表と呼ぶのですがこちらに関しては別記事で紹介します。

実行結果

a = 10 + 5
b = a + 10
? b
25

参考

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