Speee DEVELOPER BLOG

Speee開発陣による技術情報発信ブログです。 メディア開発・運用、スマートフォンアプリ開発、Webマーケティング、アドテクなどで培った技術ノウハウを発信していきます!

Slack Block Kit のメッセージをよりメンテナブルにする jsx-slack のご紹介

こんにちは、プロジェクト推進室 (PJD) の服部 (@yhatt) です。おそらく、平成最後の DEVELOPER BLOG です。 1

先月より、弊社によるオープンソースライブラリとして、Slack Block Kit のメッセージをよりメンテナブルにする jsx-slack を公開しています。この記事では、その内容について簡単にご紹介させていただきます。

jsx-slack

Slack Block Kit とは?

SlackBlock Kit は、今年 2 月にリリースされた Slack メッセージのための新しい UI フレームワークです。「ブロック」と呼ばれる複数のコンポーネントを組み合わせて、よりリッチなメッセージを表現できるようになります。

medium.com api.slack.com

これまでは Attachments を使ってリッチなメッセージを構築できましたが、API に渡す JSON の内容が少し複雑で、レイアウトも一定のものしか扱えませんでした。Block Kit は JSON の構造がシンプルなものになり、レイアウトの自由度もグンと高まっています。

また、同時にリリースされた Block Kit Builder を使用することで、リッチなメッセージのプロトタイピングを簡単に行えるようになりました。使いたいブロックをポチポチ選ぶだけで、複雑なレイアウトを持ったメッセージを構築できる上、Slack アプリケーションで使うための JSON も出力されます。

実際の使い方は、Slack によるアプリ作成のチュートリアルを参考にしてください。最近は日本向けの記事が強化されていて嬉しいですね。

api.slack.com (Qiita にも同内容の記事があります)

社内での利用状況

旧仕様 (Attachments) が すでに Legacy となっていることもあり、最近社内で新しく作られた bot では Block Kit が採用されています。

今年始めに、弊社の福利厚生制度である Speee Library のシステムをご紹介 しましたが、Speee Library は特に Slack を活用しており、多くの操作が Slack メッセージや Bot 上で完結できます。現時点ではまだ移行が完了していませんが、Block Kit を活用することで、さらに生産性の高い UX を提供できるだろうと考えています。

jsx-slack のご紹介

今回公開した jsx-slack (@speee-js/jsx-slack) は、 JSX を使用して Block Kit JSON を構築する JavaScript ライブラリです。React のような書き心地で、Slack のメッセージを構築できます。

以下は、 リポジトリ より抜粋した JSX コードのサンプルです。

/** @jsx JSXSlack.h */
import JSXSlack, { Blocks, Section } from '@speee-js/jsx-slack'

export default function exampleBlock({ name }) {
  return JSXSlack(
    <Blocks>
      <Section>
        Hello, <b>{name}</b>!
      </Section>
    </Blocks>
  )
}

exampleBlock({ name: 'Yuki Hattori' }) を呼び出すと、以下のような JSON が出力されます。

[
  {
    "type": "section",
    "text": {
      "text": "Hello, *Yuki Hattori*!",
      "type": "mrkdwn",
      "verbatim": true
    }
  }
]

そして、この JSON を Slack に渡せば、以下のようなメッセージが表示されます。

ちょっと例がシンプルすぎるので、Block Kit の持つ機能を活かした例をもう 1 つ。

<Blocks>
  <Section>
    <p>
      Enjoy building blocks!
    </p>
    <blockquote>
      <b>
        <a href="https://github.com/speee/jsx-slack">@speee-js/jsx-slack</a>
      </b>
      <br />
      <i>Build JSON for Slack Block Kit from JSX</i>
    </blockquote>
    <Image src="https://github.com/speee.png" alt="Speee, Inc." />
  </Section>
  <Context>
    Maintained by <a href="https://github.com/yhatt">Yuki Hattori</a>
    <img src="https://github.com/yhatt.png" alt="yhatt" />
  </Context>
  <Divider />
  <Actions>
    <Button url="https://github.com/speee/jsx-slack">GitHub</Button>
    <Button url="https://www.npmjs.com/package/@speee-js/jsx-slack">npm</Button>
  </Actions>
</Blocks>

様々な要素が詰まったメッセージですが、 JSX のおかげで構造が掴みやすく、HTML 要素などを含めた自然な形でメッセージの内容を記述することができます。

jsx-slack のデモサイト 上で、実際に JSX 2 を編集することで、リアルタイムで JSON への変換を試すことができます。また、右下のボタンから、その結果をそのまま Block Kit Builder に渡すこともできます。

jsx-slack デモサイト
jsx-slack デモサイト (https://speee-jsx-slack.netlify.com)

開発動機

Devloper experience

jsx-slack は、Block Kit を用いた Slack アプリケーションの Developer experience を改善するために作成されました。

一般的な Slack アプリケーションであれば、Block Kit Builder で出力された JSON をそのまま使うことは希で、普通は表示するブロックやメッセージをアプリケーションの状況に応じて変更したいと思うでしょう。

規模の小さなアプリなら、JSON のままでも大丈夫ですが、規模が大きくなると、管理・運用が難しくなってきます。Slack API のドキュメントとにらめっこしながら、アプリのロジックを含んだ JSON と向き合うのは、なかなか骨が折れる作業です(実際、この問題を Speee Library で経験したのが特に大きい)。

Block Kit の JSON は、それまでの JSON よりも構造が明確で分かりやすくなった反面、定義が冗長になりやすく、コードの中で迷子になりがちという欠点もあります。

今回のライブラリは、 『Slack のメッセージをもっとメンテナブルにして開発体験を改善したい!』 という強い思いで、Block Kit の公開から 2 週間ほどで書き上げました。

主な機能

Block Kit as component

Slack は現在、Block Kit を構成する Block として、以下の 5 種類を提供しています。

  • Section: テキストメッセージ+α
  • Context: 付加情報
  • Image: 画像
  • Divider: 区切り線
  • Action: インタラクティブコンポーネント

それぞれの内容の概説は、 Slack 公式ブログ に委ねますが、jsx-slack では、これらのブロックを JSX コンポーネントとして扱い、 <Blocks> の中で組み立てる形でメッセージを構築することができます。

また、ブロック内で使用されるインタラクティブな要素(<Button>, <Select>, <Overflow>, <DatePicker> など)に対応するコンポーネントも提供するので、まさに React のような感覚でリッチなメッセージを構築することができます。

HTML-like formatting

Slack のメッセージには、Markdown 風のフォーマット (mrkdwn) による装飾や、チャンネルへのリンク、ユーザーメンションなどが利用できます。jsx-slack でも mrkdwn はそのまま使えます 3 が、せっかく JSX で書いているなら、React や Vue と同じように HTML をそのまま書きたいですよね。

実際、Slack アプリの管理を行っていると、_*${foo}* ${bar}_ のようなテキストによるマークアップは埋もれやすく、何を意図した記号なのかが時々分からなくなることがあります(体験談)。Developer experience の観点でも、<b><i> といった既知の HTML タグで『どんな装飾が、どこでされているのか』が一目で分かる状態が理想的です。

jsx-slack では、主要な HTML タグを認識し、Slack の対応する記法に変換 することで、 HTML 風にメッセージをフォーマットすることができます。

jsx-slack Slack mrkdwn
<i>Italic</i> ➡️ _Italic_
<b>Bold</b> ➡️ *Bold*
<s>Strike</s> ➡️ ~Strike~
Line<br />break ➡️ Line\nbreak
<p>foo</p><p>bar</p> ➡️ foo\n\nbar
<blockquote>quote</blockquote> ➡️ &gt; quote
<code>code</code> ➡️ `code`
<pre>{'code\nblock'}</pre> ➡️ ```\ncode\nblock\n```
<ul><li>List</li></ul> ➡️ • List
<a href="https://example.com/">Link</a> ➡️ <https://example.com/|Link>
<a href="#C024BE7LR" /> ➡️ <#C024BE7LR>
<a href="@U024BE7LH" /> ➡️ <@U024BE7LH>
<a href="@SAZ94GDB8" /> ➡️ <!subteam^SAZ94GDB8>
<a href="@channel" /> ➡️ <!channel|channel>

mrkdwn 形式で再現できる装飾は一通り揃っており、各種タグの考えうる組み合わせもある程度網羅しているつもりです。ユニークなところでは、<ul> <ol> のリストの対応などでしょうか(Unicode の記号を使って、テキストでリストを再現します)。

使い方

Node.js >= 8 に対応しています。

npm install --save @speee-js/jsx-slack
yarn add @speee-js/jsx-slack

クイックスタート (テンプレートリテラル)

JSX は普通トランスパイラが必要なので、セットアップが面倒で敬遠されがちです。そこで jsx-slack では、設定なしでいきなり書き始められる、 jsxslack テンプレートリテラルタグ を用意しました。4

先ほどのシンプルなメッセージのサンプルを、テンプレートリテラルで書くとこんな感じです。

import { jsxslack } from '@speee-js/jsx-slack'

export default function exampleBlock({ name }) {
  return jsxslack`
    <Blocks>
      <Section>
        Hello, <b>${name}</b>!
      </Section>
    </Blocks>
  `
}

IDE による補完は効きませんが、面倒な設定の変更なしで jsx-slack を使用できます。それほど大規模でないアプリであれば、jsxslack だけでも十分戦えるでしょう。

余談ですが、当初はテンプレートリテラルの対応は予定していませんでした。この機能については、Slack のエンジニアの方からの直接の提案があり、それに応える形で実現したものです。

github.com

JSX をトランスパイルして使う

もちろん、JSX を Babel または TypeScript を使ってトランスパイルして使うこともできます。この場合、JSX のトランスパイル先が jsx-slack になるように設定する必要があります。

前述のサンプルのように/* @jsx JSXSlack.h */ コメントプラグマを使用するのが良いでしょう。React などですでに JSX を使用していても、共存が可能です。

TypeScript を使用している場合、IDE で props の補完や型チェックが効くため、大規模な Slack アプリ開発時は特に重宝します。 5

https://user-images.githubusercontent.com/3993388/55933678-28f55900-5c69-11e9-9696-fd1bb17a41a1.png

メッセージを送信する

jsx-slack が担うのは、Block Kit に関連する JSON の変換処理のみです。なので、実際に Slack API を扱う部分は、Slack が公式に提供する Node SDKBolt (a.k.a. Slapp)、サードパーティー製のクライアント (e.g. BotKit) など、お好みのライブラリを使用できます。

例えば、サンプル example.jsx で構築したメッセージSlack 公式の Node SDK を使って特定のチャンネルに送る場合、以下のようなコードになります。(リポジトリより抜粋+α)

import { WebClient } from '@slack/client'
import exampleBlock from './example'

// SLACK_TOKEN 環境変数に API のトークンを渡しておく
const web = new WebClient(process.env.SLACK_TOKEN)

web.chat
  .postMessage({
    // 送信先のチャンネル ID
    channel: 'C1232456',

    // jsx-slack による出力結果 (JSON) を blocks に渡す
    blocks: exampleBlock({ name: 'Yuki Hattori' }),
  })
  .then(res => console.log('Message sent: ', res.ts))
  .catch(console.error)

各種 JSX コンポーネント・HTML タグの詳しい使い方やサンプルなど、リポジトリの README に詳細が記載されています。

Real-World Use Cases

すでに jsx-slack を使用した Bot や Slack App もあり、ユーザーによる利用例が各種ブログにて紹介されています。より具体的なユースケースについては、以下の記事を参考にしてください。

おわりに

メンテナブルな Slack Block Kit のメッセージを JSX で構築できる JavaScript ライブラリ jsx-slack をご紹介しました 。jsx-slack は MIT ライセンスによるオープンソースプロジェクトです。

jsx-slack 自体は 3 月に初版を公開 したのですが、当時 Slack 社内のエンジニアの中でも話題になっていたようです。個人 Twitter (@y_hatt) や Issue などに、様々なフィードバックを頂けて、感謝の限りです 😄

Block Kit は引き続きアップデートが行われる予定で、新たな Block type の追加も予定されています(参考:Slack、BotやSlackアプリなどのメッセージ表示をリッチ化する「Block Kit Builder」発表 - Publickey)。今月も、ボタンの色(スタイル)が指定できるようになるアップデートがありました。

jsx-slack では v0.4.2 で対応済みですが、今後もメンテナとして、できる限り迅速に Slack API の変更に追従していく予定です。もちろん OSS なので、Issue や Pull Request も歓迎しています。よろしくお願いします 😉

github.com

追記: 2019-19-16

その後の更新(モーダル、追加されたブロック、<Fragment> など)に関する内容や、Slack 公式のフレームワーク Bolt を組み合わせたアプリのサンプルを交えた解説を含む、実践篇もぜひご覧ください。👇

tech.speee.jp


  1. [2019/04/26 追記] 真の平成最後の記事が公開されました 👉 「たのしい開発がたくさん生まれるKaigi」を作ったRubyKaigi 2019 - Speee DEVELOPER BLOG

  2. セキュリティ上の理由で、デモサイト上では JSX の JavaScript expression interpolation ({123} など) は使えません。

  3. 本当は HTML に統一したかったのですが、既存の mrkdwn 装飾文字には真っ当なエスケープ手段が用意されていない(似た字形に変換する=内容が変わる可能性がある)ため、止むを得ず両対応の形になっています。

  4. HTM (Hyperscript Tagged Markup) を使用しているため、厳密には JSX ではありませんが、互換性の高い記法で jsx-slack の提供する要素を使えます。

  5. 子要素 (children) のコンポーネントに対する型補完も試行錯誤しましたが、残念ながら現在の TypeScript では不可能のようです。(デモサイトでは適切な子要素補完が効きますが、これは個別にスキーマを定義して実現しています)