この記事は Goodpatch Advent Calendar 2020 の6日目です.

モバイルアプリの UI デザインツールとして Sketch はデファクトと言っても良いくらい広く使われるようになりました. それに伴って Sketch の利用を前提としたデザインコラボレーションの手段を提供する Abstract のようなサービスも現れました.

Sketch や Abstract は基本的には GUI での利用を前提としたツールですが,それぞれ CLI ツールや Web API も提供しています. 今回はこれらを利用した UI デザインのポストプロセス自動化について考えます.

…考えます.そうです「考える」だけなのです.ブラウザの戻るは Cmd + [ です.

概要

  • プログラミングの観点で UI デザインワークフローの効率化を考える
  • CI による自動化を考える
  • Abstract を中心とした CI の構成を考える

なお,文中で補足しますが,ここでいう「UI デザイン」はかなり狭義な範囲を指しています.

プログラミングの観点で UI デザインのワークフローの効率化を考える

プログラミングのワークフロー

近年のモバイルアプリ開発において,プログラミングのワークフローはおおよそベストプラクティスと呼べるものが定まっていると思います. もちろん,各々のプロジェクトの意向によって構成は様々だと思いますが,例えば以下のようなワークフローを組んでいるのプロジェクトは多いのではないでしょうか.

  1. Git-flow に従い,作業ブランチを作って実装を進める
  2. 実装が完了したら GitHub で Pull Request を提出する
  3. Pull Request でコードレビューを受ける
  4. CI による自動テストを通過する
  5. レビュアーからの承認を得る
  6. Pull Request をマージする
  7. CI によって自動でビルドが作成される

プログラミングのワークフロー

特に,コードの品質を保つためのレビューや作業効率化のための自動化は,アジャイルな開発スタイルでは欠かせない要素となっています.

さらに,ワークフローの中でも「実装」部分に着目すると,ここでも作業効率化のために様々な仕組みを使うことが当たり前となっていると思います.

  • テキストエディタの機能を使ってコードの補完や自動編集を行うこと
  • Linter などの静的解析ツールを使って機械的に検出可能なミスを減らすこと
  • ユニットテストを用意して回帰テストを行うこと
  • さらには TDD のようにテストによって製品コードの設計の質を上げること

ワークフローを定めたり様々なレイヤーで効率化を行うことで,エンジニアはエンジニアが本当に注力すべきクリエイティブな仕事に集中できます.

UI デザインのワークフロー

それでは,プログラミングのワークフローと比較して UI デザインのワークフローはどうなっているでしょうか. ただし,ひとまとめに「UI デザイン」と言ってしまうととても話をまとめきれないほど大きな概念なので,ここでは上記のプログラミングのワークフローに相当する部分についてのみ考え,それを狭義の「UI デザイン」と呼ぶことにします.

プログラミングのワークフローで挙げた項目それぞれに相当するものを考えると,例えば以下のようなフローが考えられます. Abstract のようなツールの登場により,プログラミングと同じようなフローが組めるようになっています.

プログラミング UI デザイン
作業ブランチ Abstract でブランチを作ることができる
Pull Request を提出する ブランチ単位で Review に出すことができる
コードレビュー Review の上でコメントをすることができる
CI による自動テスト ???
レビュアーからの承認 Review の上で承認を得ることができる
Pull Request のマージ ブランチを Master にマージする
CI による自動ビルド ???

UI デザインのワークフロー

こうして比較すると,Abstract や Sketch を As-Is の範疇で使うと考えた場合,自動化の観点が抜けているのではないか?ということに気付きます.

わざわざ「As-Is で」と書いたのは,これらの点についてツールの開発者は既に考慮していて,Abstract には Web API が,Sketch には sketchtool という CLI ツールが提供されているためです. プロジェクトに合わせて CI を組んだりコードを書いたりする必要がありますが,自動化するために必要なツールは揃っている状況です.

続いて,ワークフローの中で「実装」に相当するような,Sketch ファイルの編集作業について比較してみます.

プログラミング UI デザイン
コードの補完や自動編集 サードパーティ製の Sketch プラグインで検索や置換などが可能
静的解析ツール ???(Assistants を利用可能)
回帰テスト ???
テストによって製品コードの設計の質を上げる ???

プログラミングの場合と比較すると,UI デザインの効率化はかなり遅れているように感じます. ただし,これはあくまでプログラミングのフローを元に一方的な視点で比較しているので,恣意的でかなりアンフェアな比較です. それでも,現状多くのプロジェクトにおいて Sketch ファイルを「きれいに」保つのは,UI デザイナの注意力に頼っている状況であると感じている人も多いのではないでしょうか.

なお,2020年8月から Sketch では Assistants という機能を使えるようになりました. Assistants については今回検証しませんが,静的解析による効率化が期待できる機能です.

CI による自動化

それではプログラムの力を使って Sketch ファイルに対する単純作業を自動化できないでしょうか? 例えば「CI による自動ビルド」に相当するような作業を Sketch に対して行えるでしょうか?

実は CI に sketchtool を組み込むというアイデア自体は,既に数年前から各所で事例が共有されています. 例えば iOSDC Japan 2017 ではアイコンや画像の配置をCIで自動化するという発表があり,CI と sketchtool を使って iOS の Asset Catalog を書き出して GitHub に Pull Request を出すまでを自動化したとあります(より詳しくは Konifar’s WIP を参照).

ただし,これらの事例は Git を中心としたエンジニア寄りのワークフローになっています. Git を使うこと自体は CI の組みやすさという点でメリットもあり,また,デザイナーが Git を使う障壁も GUI ツールを使えばそこまで高くない(個人の経験)ですが,Abstract を使っているフローの中で「Git リポジトリにも push してください」というのは明らかに面倒です.

よって Abstract API を使えば Git の場合と同様に CI を組めるのではないか?というのが今回の検証内容となります. ようやく少し技術ブログらしい感じになってきました.

Abstract を中心とした CI の構成

Abstract を中心とした CI の構成を考えます. 基本的には Git を中心とした構成と変わらず,Sketch ファイル更新の検知と Sketch ファイルの取得を Abstract 経由に置き換える形です.

  1. デザイナーが Abstract の Master ブランチを更新する
  2. Master ブランチの更新を検知する
  3. Master ブランチから Sketch ファイルを取得する
  4. Sketch ファイルからアセットを書き出す
  5. Google Drive にデプロイする

CI の構成

上記は自分のプロジェクト環境に合わせて組んでみた構成ですが,もちろんアセットの出力内容や出力先を変えることは自由にできます. また,それらステップ 4-5 については既存の事例を Web で見つけることができるので,今回は説明しません.

Abstract API

今回の検証の主な対象である Abstract 公式の API です. API の仕様については Abstract SDK API Reference にまとまっています. SDK が提供されているので,それを使って API を叩く形になります.

SDK は npm パッケージとして提供されていて TypeScript で使うことができます. API トークンはプロジェクトに対する Viewer 権限があれば発行できるようなので,比較的簡単に使い始めることができます.

API でできること

詳細については API Reference を参照するとして,例えば以下のようなことができます.

API できること
client.branches.list(...) ブランチの一覧を取得する.
client.files.raw(...) 任意の Sketch ファイルを取得する.
client.previews.raw(...) 任意の Layer を png として書き出す.なお svg や pdf には非対応.

少なくともこれだけできれば,ブランチに更新があるか調べて,更新があった場合には Sketch ファイルを書き出すといったことが可能です.

Webhooks について

更新を push 型で受け取る仕組みとして Abstract は Webhooks を提供しているようです. ただし Webhooks の機能は2020年11月時点では beta となっています. 実は今回の検証のために Request beta access now から何度かリクエストを送ったのですが,残念ながら返答がありませんでした.

Webhooks が使えないので,検証では Crontab による定期トリガーを設定しました. トリガーさえしてしまえば pull 型の差分チェックはできるのでヨシ!とします.(嘘です.Webhooks 使いたいです.よろしくお願いします…)

Sketch ファイルを書き出す

Abstract のファイル構造としては,組織(Organization)> プロジェクト(Projects) > Sketch ファイル(Files)という形になっています. また,ブランチについては,プロジェクト > ブランチ(Branches)という形でプロジェクトに紐づきます. したがって,最初に API トークンと Organization ID さえ分かっていれば,あとはトップダウンでクエリしていくことができます.

以下のコードは実際のコードではなく説明用に簡略化したコードですが,Sketch ファイルを書き出すまでの流れはおおよそ把握できるかと思います.

import * as abstract from 'abstract-sdk';

async function main() {
  const client = new abstract.Client({ accessToken: process.env.ABSTRACT_API_TOKEN });
  const organization = { organizationId: process.env.ABSTRACT_ORGANIZATION_ID };

  const projects = await client.projects.list(organization);
  const project = projects.find(x => x.name === 'GR@DATE WING') || process.exit(1);

  // Master ブランチの ID は 'master' と決まっている.
  const branches = await client.branches.list({ projectId: project.id });
  const master = branches.find(x => x.id === 'master') || process.exit(1);

  const headCache = /* ... */ ;
  if (master.head === headCache) process.exit(0);

  // 最新コミットの SHA は 'latest' でクエリできる.
  const baseQuery = { projectId: project.id, branchId: master.id, sha: 'latest' };
  const files = await client.files.list(baseQuery);

  for (const file of files) {
    const fileQuery = { ...baseQuery, fileId: file.id };
    await client.files.raw(fileQuery, { filename: `${file.name}.sketch` });
  }
}

実際のコードでは head の差分比較を別スクリプトとして分離したり,書き出す Sketch ファイルの条件を設定ファイルで指定できるようにしたり,といった考慮がされています. 実際のプロジェクトだと「アイデア置き場」としての Sketch ファイルなど,書き出す必要のない中間成果物もあるためです(まさにスケッチ!).

sketchtool のインストール

Sketch ファイルが取得できたので,あとは sketchtool をインストールすればアセットを書き出すことができます.

インストールからアセット書き出しまでの手順は前述の iOSDC トークに関連する GitHub リポジトリなどに詳しく紹介があります. ただし,Sketch の ZIP ファイルのダウンロード URL が古くなっているようで,新しい URL は次のとおりでした.

https://download.sketch.com/sketch.zip

ちなみに,ファイルのダウンロードや Unzip などはシェルのコマンドで実行する方が楽なので,Node.js から child_process.exec() を呼びまくるという,やや強引な方法で実装しました. いっそのことシェルスクリプトの方が良いかもしれません.

import { promisify } from 'util';
import * as child_process from 'child_process';

async function main() {
  const exec = promisify(child_process.exec);
  await exec(`curl -L -o sketch.zip https://download.sketch.com/sketch.zip`);
  // ...
}

その他の技術的要素

その他の技術的要素について,ひとことふたこと説明しておきます.

Google Drive へのアップロードは Google Drive API Reference を参照すれば,少々面倒なことはあっても難しいことはないと思います. なお2020年11月時点で最新版の V3 を使いました. 共有ドライブ(Shared Drive)を使っている場合は,オプションの指定方法を変えるなどちょっとした対応が必要です.

CI ツールは GitHub Actions を使いました. 定期トリガーは on.schedule で設定できます. Head 情報はファイルに書いて actions/cache@v2 でキャッシュしました.

最後に Slack に通知して完了です.

通知

まとめ

  • Sketch ファイルからアセットを自動で書き出す CI を組みました
  • 先行事例を参考にしつつ,Git 中心ではなく Abstract を中心とした構成にしました
  • Abstract API を使って差分があるかどうかを検知したり,最新の Sketch ファイルを取得したりできました

One More Thing

静的解析の可能性について,本文中では Sketch Assistants について少しだけ言及しました. Assistants は Sketch アプリの中で動く拡張機能で,こちらも Node.js と TypeScript で開発できます.

試しに文言のバリデーションルールを書いてみたところ,不可視のレイヤーに古い文言が残っているなど,目視では発見しにくいミスを見つけることができました. Sketch を使ったワークフローにおいて,早い段階でミスを検知するための有力な選択肢になりそうです.

もしかすると Goodpatch Advent Calendar 2020 で誰かが何か書いてくれるかもしれません.

:bulb: 追記:Advent Calendar 15日目で @mnkd が Assistants について書いてくれました. 日本語で丁寧に説明された文章は貴重だと思いますので,気になった方々はチェックしてみてください.

Sketch Assistants で DesignOps をアシスト - Qiita

References