for Startups Tech blog

このブログのデザインを刷新しました。(2023/12/26)

Figma APIを使用し、svg形式のアイコンを /figma/images 配下にインポートするrake taskを実装してみる

こんにちは、2022年4月にフォースタートアップスにジョインしたエンジニアの八巻(@hachimaki37)です。入社から早半年間が経ちました。引き続き、社内向けプロダクト「タレントエージェンシー支援システム(SFA/CRM)」のシステム開発を担当しております。

はじめに

百聞は一見にしかず!ということで、まずはどんなモノか動画をご覧ください(34秒)

ご視聴ありがとうございました。今回のTech Blogは 、「Figma APIを使用し、svg形式のアイコンを/figma/images配下にインポートするrake taskを実装してみる」です。開発の背景や目的、苦労話などを交えながら執筆していきたいと思います。

事前に様々な記事を拝見させて頂きましたが、アイコンをFigmaで管理する上で、どこも同じような課題感があるんだなと所感を持ちました。

  • アイコンが大量に増えて、管理がめんどくさい
  • 毎度更新されたアイコンをsvgに書き出すのが手間
  • わざわざFigmaを見に行くのがめんどくさい
  • デザイナーがfigma上でアイコンを編集していたことが開発者に伝わらずに、アウトプットが予想と違うものになった
  • アイコンの更新漏れが発生した
  • などなど・・

開発の背景

所属チームでは、2022年7月に新しくUI/UXデザイナーの方を迎え(2022年4月からUI/UXデザイナーが不在)、タレントエージェンシー支援システム(SFA/CRM)のDesign System構築が本格的にキックオフされました。

以下、デザイナーの@Minmi303より得た内容です。

Design System構築に至った経緯

  • 既存のDesign Systemが使われていない(使いづらい)状態だった
  • サービスの詳細、機能、画面全てを網羅している人がいない(網羅するのが難しいほどの画面数)
  • デザインのテイストが2〜3種類ほど混在していた (例:アイコンがデザイナーが作ったものとFont Awesomeが混在していた)
  • 機能追加や削除など、開発のペースが早い
  • デザイナーの効率化を図る仕組みが必要だった(デザイナーが1人のため、作業時間の短縮を図りたい)
  • エンジニアサイドの仕組み化も必要だった(同じパーツでもコードがまとまっていない)
  • などなど・・

Design System構築前に抱えていたUI/UX周りの課題

  • UXを検討するフローが抜けていた
  • デザインレビューのフローがなかった(デザインを担保できない)
  • デザインのトンマナが整っていない
  • 多くの画面がUIを深く考慮されておらず、機能だけがある形の仕様になっていた
  • などなど・・

上記のような背景からDesign System構築が進むにつれ、Colors, Text Rule, Iconsなどが刷新され、開発者用にFixデータがFigmaに格納されるようになりました。

開発の目的

そこで課題に上がったのが、アイコンの格納方法です。

▼議論されたイメージ図

当初、「Figma -> Notion -> タレントエージェンシー支援システム(SFA/CRM)」という格納フローを検討しておりましたが、デザイナー/エンジニア間、双方に課題感を持っておりました。

  • デザイナー: svg書き出しの手間、更新されたアイコンの適応状況 など
  • エンジニア: svgの配置、適用ディレクトリの検討、アイコンの更新漏れ など

結果、Notion管理を不要とし、Figma APIを活用する方針となりました。よって、「Figma -> タレントエージェンシー支援システム(SFA/CRM)」という格納フロー構築を目指し、開発スタートに至ります。

Figma APIとは?

What can I do with the Figma API?

The Figma API supports read access and interactions with Figma files. This gives you the ability to view and extract any objects or layers, and their properties, so you can render them as images outside of Figma. You can then present your designs, connect them to other applications, or use them to expand on your vision. Future versions of the API will unlock even greater functionality around Files.

How does it work?

The Figma API is based on the REST structure. We support authentication via access tokens and OAuth2. Requests are made via HTTP endpoints with clear functions and appropriate response codes. Endpoints allow you to request files, images, file versions, users, comments, team projects and project files.

引用元 公式ドキュメント:https://www.figma.com/developers/api

端的に言うと、Figma API を使用することで、Figma上の様々なデータ取得が可能になります。こちらを用いて、格納フロー構築を行いました。

やったこと

概要

rakeコマンドを実行することで、Figmaからsvg形式のアイコンを/figma/imagesディレクトリにインポートする

大まかな実装イメージ

  • rake taskに以下処理を実装
    • Figma API コールの定義
    • svgダウンロードURLを取得する
    • svgをダウンロードする
    • 生成したファイルにsvgを書き込む

コードの紹介

※アイコンは以下ページから取得することとします

そしてfigma.rakeはこのようになりました

namespace :figma do
  desc 'import svg icons of Figma API'
  task :import_icons do
    # Figma API コールの定義
    file_key = Settings.FIGMA_FILE_KEY
    x_figma_token = Rails.application.secrets.x_figma_token
    figma_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/files/#{file_key}"
    header = { 'X-Figma-Token' => x_figma_token }

    # svgダウンロードURLを取得
    body = get(figma_url, header)
    body['document']['children'].each do |children|
      next unless children['name'] == 'New Icons'

      children['children'][0]['children'].each do |child|
        id   = child['id']
        name = child['name'].gsub('/', '-')
        next unless name.include?('icon-')

        # svgをダウンロード
        svg_download_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/images/#{file_key}?ids=#{id}&format=svg"
        child_body = get(svg_download_url, header)
        child_res = res(child_body['images'][id], header)

        # 生成したファイルにsvgを書き込む
        file = "app/src/javascripts/spa/assets/figma/images/#{name}.svg"
        File.open(file, 'wb') do |f|
          f.write(child_res.body)
        rescue SystemCallError => e
          puts "class: [#{e.class}] message: [#{e.message}]"
        rescue IOError => e
          puts "class: [#{e.class}] message: [#{e.message}]"
        end
      end
    end
  end

  private

  def get(url, header)
    res = res(url, header)
    JSON.parse(res.body)
  end

  def res(url, header)
    url = URI.parse(url)
    Net::HTTP.get_response(url, header)
  end
end

ソースコードの説明を加えていきたいと思います。

Figma API コールの定義

file_key = Settings.FIGMA_FILE_KEY # ※1
x_figma_token = Rails.application.secrets.x_figma_token # ※2
figma_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/files/#{file_key}" # ※3
header = { 'X-Figma-Token' => x_figma_token } # ※4

Figma APIを使用するにあたり、「File Key」と「Access Token」が必要になります。

svgダウンロードURLを取得

body = get(figma_url, header)
body['document']['children'].each do |children|
next unless children['name'] == 'New Icons'

    children['children'][0]['children'].each do |child|
       id   = child['id']
       name = child['name'].gsub('/', '-')

Figma APIをコールすると、

{"document"=>
  {"id"=>"0:0",
   "name"=>"Document",
   "type"=>"DOCUMENT",
   "children"=>
    [{"id"=>"0:1",
      "name"=>"Page 1",
      "type"=>"CANVAS",
      "children"=>
       ・
  ・
  ・
    }]
  }
}

のようなJSON形式で指定したファイルの値が返ってきます。今回Figmaから「New Icons」というページ(上の画像を参照)からアイコンを取得したいため、'next unless'で'New Icons'の値を取得、eachで返ってきた値を変数idとnameに格納します。今回、アイコン名に階層('icon/icon-hoge'のような)があったため、gsubメソッドで置換した値を変数nameに格納する形にしました。

svgをダウンロードする

svg_download_url = "#{Settings.HTTPS_PROTOCOL}://#{Settings.FIGMA_HOST_URL}/images/#{file_key}?ids=#{id}&format=svg" # ※1
child_body = get(svg_download_url, header)
child_res = res(child_body['images'][id], header)

※1 本来は、'https://api.figma.com/v1/images/#{file_key}?ids=#{id}&format=svg"'のようなHTTP Endpointになります

HTTP Endpointには、一つ前で格納したそれぞれのidを、そしてsvg形式で値を取得したい場合、’ format=svg ’を末尾に加え、APIコールをします。すると、以下のような値がレスポンスとして返ってきます。

=> "<svg width=\"108\" height=\"62\" viewBox=\"0 0 108 62\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"M53.9998 61.5632C52.0642 61.5632 50.1289 60.8241 48.6532 59.3491L2.21553 12.911C-0.73851 9.95695 -0.73851 5.16748 2.21553 2.21464C5.16838 -0.738212 9.95689 -0.738212 12.9112 2.21464L53.9998 43.3057L95.0887 2.21607C98.0427 -0.736777 102.831 -0.736777 105.783 2.21607C108.739 5.16892 108.739 9.95839 105.783 12.9124L59.3464 59.3506C57.87 60.8258 55.9347 61.5632 53.9998 61.5632Z\" fill=\"black\"/>\n</svg>\n"

これで、svg形式のアイコンを取得することができました。そして、この値を変数に格納し、次のステップへいきます。

生成したファイルにsvgを書き込む

file = "app/src/javascripts/spa/assets/figma/images/#{name}.svg"
File.open(file, 'wb') do |f|
  f.write(child_res.body)
rescue SystemCallError => e
  puts "class: [#{e.class}] message: [#{e.message}]"
rescue IOError => e
  puts "class: [#{e.class}] message: [#{e.message}]"
end

最後に、一つ前で取得したsvg形式のアイコンをファイルに書き込みます。1行目(file = xx)には、変数nameを加えたファイルを定義し、書き込みにはFileクラスのopenメソッドを使用しました。あとは、取得したsvg形式のアイコンをwriteメソッドを使用して、指定したファイルに書き込めば完了です。

はまった点

チケット完了のゴールは定まっていたものの、そもそも実装イメージが皆無だった

  • Figma APIとはなんぞや...
  • どのようなTODOで進めて行けば良いのだろうか..
  • Figma自体さほど触っておらず、理解が浅い...

そんな状況からスタートしました。やったことは、開発着手前に「Figma APIの公式ドキュメントを一読する」、「現時点で思いつくTODOを洗い出す」ことから始めました。今までの経験から、プログラミングで右も左もわからなくなるケースは、大体何をすべきか理解できていないことが多かったため、着手前に思いつくTODOを洗い出し、都度TODOを追加していくようにしました。一例ですが、「Figmaにテストデザインを作成し、そのデザインをFigma API をコールして取得する」ことをTODOに含め実施したことで、「Figma APIの理解が進んだこと」、「やるべきことが都度明確になっていったこと」で、実装イメージが膨らみ、進めることができました。

svg形式のアイコンを取得したかったが、HTTP Endpointを叩くとレスポンスの値がnilになる

以下のようなHTTP Endpointをコールすることで、svgに置換された値がレスポンスされるはずでしたが、なぜかnilになっていました。

'https://api.figma.com/v1/images/#{file_key}?ids=#{id}&format=svg

pry(main)> child_res.body
=> nil

調査を進めていくと、’ ids=#{id} ’に格納した id の値が誤りであることがわかりました。APIのレスポンス値である’ components id ‘を格納していたことがnilの原因であったため、各アイコンに紐づく id に変更することで解消されました。

工夫したこと

今後の運用を考慮したこと(なるべくシンプルに保つ)

運用についてデザイナーと議論し、以下のことを決めました。

  • アイコンの命名規則
    • 基本的に「icon-」の形式とする
  • アイコンのツリー構造
    • 基本的にノード同士は親子関係にはせず、並列とする
  • アートボードの運用
    • 基本的に1アートボードにアイコンを集約する。もしアートボードを追加する際は、エンジニアに一報を入れる。(複数のアートボードからアイコンを取得する場合、データ構造を見直す必要の可能性があるため)
  • exportされるページへの注意喚起
    • 後任者などが見ても理解できるようFigmaのページ上に注意喚起を追記する

ソースコードの汎用性や可読性を考慮したこと

  • 汎用的に使用できるよう、Figma APIコールの定義をsettingsにまとめました。また、X_FIGMA_TOKENは作業者で異なるキーを運用するため、「gitの管理対象ファイルにして、間違えてコミットしてしまう可能性」を考慮し、.gitignoreにあるファイルで管理するようにしました

  • 処理のメソッド化、直感的に理解が進むよう必要最低限のコメントを追記、またソースコードを処理ごとにまとめ実装しました

改善余地があること

Access Tokenの運用方法

APIコールに使用するAccess Tokenに関して、チームで1つのTokenを運用するか、もしくは個人個人のTokenで運用するか、別途議論の余地がありました。所属チームの現時点での運用は、個人個人での運用とし、必要に応じて議論する形にしました。

image optimizerの検討

画像圧縮についてです。理想は、APIコールからsvgをダウンロードした際、プログラム上で画像圧縮が実行できればベストでしたが、優先度の兼ね合いから今回のチケットではスコープ外とし、対応可否を別途検討する形にしました。

Github Actionsでの定期実行

手動でコマンドを叩くこと自体が課題になるかもしれません。こちらも今回の対象チケットには含めなかったため、定期実行をScheduleに組込み、自動実行できる仕組み構築が今後必要になってくるかもしれません。

まとめ

実装から無事リリースに至り、これから運用へとプロセスを踏んでいきます。運用しながら課題が新たに発生するかもしれませんが、ひとまず元々感じていた双方の課題を解消できる開発になったのではないかと思います。

あとがき

私自身、今回初めてFigma APIに触れましたが、思いのほか手軽に実装することができました。というか、初めてでもわかりやすい仕様だったということでしょう。デザイナー、エンジニア双方の課題感を知り、実装を進められたこと、非常に楽しくワクワクしながら開発できました。

最後に採用情報です。 当社では、まだまだ採用募集中です。ぜひ一緒に成長していきませんか。 ご興味ありましたらぜひ一度カジュアルにお話できたらと思います。

採用ページはこちら。(冒頭のTwitterにDM頂いてもOKです!)

参考資料