SlackのBlock Kitをより便利にするモジュールを作った話

本記事は、[株式会社Works Human Intelligence](https://www.works-hi.co.jp/)のアドベントカレンダー、[「Develop fun!」を体現する Works Human Intelligence #2](https://qiita.com/advent-calendar/2020/whi-2)の10日目の記事です。

## はじめに
今年は、Slackアプリを作りまくった1年でした。
Slackアプリ開発の知識がない状態から、2つのアプリを作り、現在、さらに1つのアプリを実装中です。

そこで、これらのアプリ開発の効率化のために独自に作成した、SlackのUIフレームワーク（Block Kit）を扱うためのモジュールの紹介をしたいと思います。

## Block Kitとは
[Block Kit](https://api.slack.com/block-kit)とは、SlackアプリのUIフレームワークです。

Slackでは、ボタンやセレクトメニュー、日付セレクターなどのコンポーネントを組み合わせてUIを構成しますが、これらのコンポーネントはBlock Kitによって提供されています。

また、ボタンなどのコンポーネントをユーザーが操作した際に、UIの表示を変更したり、アプリ（Bot）からメッセージを送信したりするなど、ユーザーとアプリ間で対話的なコミュニケーションを行うことができます。

さらに、[Block Kit Builder](https://api.slack.com/tools/block-kit-builder)というツールを利用することによって、コードを記述することなく、Slackのメッセージやモーダルのデザインを簡単に作成することができます。
これは、具体的なアプリのイメージを作成する際に、大変役に立ちます！

詳しい説明は、[Block Kit](https://api.slack.com/block-kit)のページに記載してあるので、参照してください！

## Block Kitの欠点
しかし、こんな素晴らしいBlock Kitでも、（多分）1つだけ、欠点があります。

それは、Block KitのUI定義はJSONで記述されるため、ソースコードの中で扱いにくいということです。

## 作ったもの
このBlock Kitの唯一の欠点を補うべく、作ったものが「[block-kit-handler](https://www.npmjs.com/package/block-kit-handler)」です。
block-kit-handlerは、npmで公開しているため、Node.jsでSlackアプリのバックエンドを実装する場合に利用することができます！

block-kit-handlerでは、SlackのBlock Kitで提供されている各コンポーネント（テキストやボタン、セレクトメニューなど）を返す関数を実装しています。
そのため、これらのコンポーネントを（JSONとしてではなく、）オブジェクトとして扱うことができます。

## block-kit-handlerのおすすめの使い方
ここでは、block-kit-handlerを使った、効率的なSlackアプリのUI実装の方法をご紹介します。

block-kit-handlerの使い方は、下記のページや公開プロジェクトでも説明をしています。
とにかく、実装例を見たいという方は、下記ページをご参照ください！

- [block-kit-handler npmページ](https://www.npmjs.com/package/block-kit-handler)
- [block-kit-handlerを利用した実装例](https://github.com/saito-ya/block-kit-handler-examples/blob/master/src/index.ts)

### とりあえずインストールする
まずはインストール方法です。
Node.jsのプロジェクトでblock-kit-handlerを利用するためには、下記コマンドを実行します。

```shell
npm install block-kit-handler
```

### UIを作成してみる
次に、頭に思い描いているUIを実際に作ってみます。
こんなときには、前述の[Block Kit Builder](https://api.slack.com/tools/block-kit-builder)がとても便利です。
今回は、モーダルで下記のようなUIを作成してみることにします。

![modal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614712/bc56860a-d713-9953-9b32-6e77e4122241.png)

ちなみに、このUIをJSONで記述すると、こんな感じになります。
意外と行数が多く感じるのではないでしょうか？

<details>
<summary>JSONで記述した場合</summary>
<div>

```json
{
	"title": {
		"type": "plain_text",
		"text": "App menu",
		"emoji": true
	},
	"submit": {
		"type": "plain_text",
		"text": "Submit",
		"emoji": true
	},
	"type": "modal",
	"close": {
		"type": "plain_text",
		"text": "Cancel",
		"emoji": true
	},
	"blocks": [
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "*Hi <fakelink.toUser.com|@David>!* Here's how I can help you:"
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": ":calendar: *Create event*\nCreate a new event"
			},
			"accessory": {
				"type": "button",
				"text": {
					"type": "plain_text",
					"text": "Create event",
					"emoji": true
				},
				"style": "primary",
				"value": "click_me_123"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": ":clipboard: *List of events*\nChoose from different event lists"
			},
			"accessory": {
				"type": "static_select",
				"placeholder": {
					"type": "plain_text",
					"text": "Choose list",
					"emoji": true
				},
				"options": [
					{
						"text": {
							"type": "plain_text",
							"text": "My events",
							"emoji": true
						},
						"value": "value-0"
					},
					{
						"text": {
							"type": "plain_text",
							"text": "All events",
							"emoji": true
						},
						"value": "value-1"
					},
					{
						"text": {
							"type": "plain_text",
							"text": "Event invites",
							"emoji": true
						},
						"value": "value-1"
					}
				]
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": ":gear: *Settings*\nManage your notifications and team settings"
			},
			"accessory": {
				"type": "static_select",
				"placeholder": {
					"type": "plain_text",
					"text": "Edit settings",
					"emoji": true
				},
				"options": [
					{
						"text": {
							"type": "plain_text",
							"text": "Notifications",
							"emoji": true
						},
						"value": "value-0"
					},
					{
						"text": {
							"type": "plain_text",
							"text": "Team settings",
							"emoji": true
						},
						"value": "value-1"
					}
				]
			}
		},
		{
			"type": "actions",
			"elements": [
				{
					"type": "button",
					"text": {
						"type": "plain_text",
						"text": "Send feedback",
						"emoji": true
					},
					"value": "click_me_123"
				},
				{
					"type": "button",
					"text": {
						"type": "plain_text",
						"text": "FAQs",
						"emoji": true
					},
					"value": "click_me_123"
				}
			]
		}
	]
}
```
</div>
</details>

### block-kit-handlerでUIを実装する
最後に、Block Kit BuilderでデザインしたUIを、block-kit-handlerで実装します。
今回は、モーダルに描画するUIを作成しますが、ここではモーダルのことは一旦忘れて、とりあえず、UIに含めたいコンポーネント達をリストに詰め込んでいきます。

```typescript
import { Block } from '@slack/types';
import { Modal, actions, section, button, divider, staticSelect, option, mrkdwnText, plainText } from 'block-kit-handler';

const blocks: Block[] = [
        section({ text: mrkdwnText('*Hi <fakelink.toUser.com|@David>!* Here\'s how I can help you:')}),
        divider(),
        section({
            text: mrkdwnText(':calendar: *Create event*\nCreate a new event'),
            accessory: button(plainText('Create event'), 'create', { value: 'click_me_123', style: 'primary' })
        }),
        section({
            text: mrkdwnText(':clipboard: *List of events*\nChoose from different event lists'),
            accessory: staticSelect('chose-list', plainText('Choose list'), [
                option(plainText('My events'), 'value-0'),
                option(plainText('All events'), 'value-1'),
                option(plainText('Event invites'), 'value-2')
            ])
        }),
        section({
            text: mrkdwnText(':gear: *Settings*\nManage your notifications and team settings'),
            accessory: staticSelect('edit-settings', plainText('Edit settings'), [
                option(plainText('Notifications'), 'value-0'),
                option(plainText('Team settings'), 'value-1')
            ])
        }),
        actions([
            button(plainText('Send feedback'), 'send-feedback', { value: 'click_me_123' }),
            button(plainText('FAQs'), 'faqs', { value: 'click_me_123' })
        ])
];
```

次に、作成したUIをモーダルに組み込みます。
block-kit-handlerでは、モーダルとホームタブのクラスを提供しているため、これらもオブジェクトとして扱うことができます。

```typescript
const modal = new Modal(
        plainText('App menu'), 
        blocks, 
        { close: plainText('Cancel'), submit: plainText('Submit') }
);
```
モーダルでは、作成したUIの他に、タイトルやキャンセルボタンのテキスト、送信ボタンのテキストを自由に設定することができます。

メッセージやホームタブでの実装例は、[block-kit-handlerのnpmページ](https://www.npmjs.com/package/block-kit-handler)に記述しています。
是非、ご参照ください。

### モーダルを表示する
Slackでは、メッセージを送信したり、モーダルを表示したり、その他様々なことを行うためのAPIが提供されています。
今回は、モーダルを表示するための[views.open API](https://api.slack.com/methods/views.open)を実行します。

```typescript
await app.client.views.open({
        token: botToken,
        trigger_id: trigger_id,
        view: modal.getView()
});
```

`token`や`trigger_id`は、`views.open`APIを実行するために必要なパラメータです。
詳しくは、[views.open API](https://api.slack.com/methods/views.open)をご確認ください。

`view`パラメータには、モーダルのJSON（をstring化したもの）を設定する必要があります。
これは、既に作成したblock-kit-handlerのModalクラスのオブジェクトの`getView`メソッドを実行することで取得することができます。

これでモーダルの表示ができました！

※ 今回は、Slackのアプリ開発のフレームワーク [Bolt for JavaScript](https://slack.dev/bolt-js/ja-jp/tutorial/getting-started) を利用しています。

### 補足
今回の例では、「Block Kit Builderで作成したJSONをそのまま使ったほうが楽じゃん」と思われた方も多いかと思います。
しかし、もし下記のようなUIを実装したい場合、

- セレクトメニューに1~10のオプションを設定したい場合  
- 条件によって表示を変更したいとき

block-kit-builderを利用した上で、for文やif文を用いたコードの記述を行うことで、より効率的に、また動的にUIの実装を行うことができます。

## まとめ
SlackアプリのUIを実装を行うためのモジュール「block-kit-handler」を作成しました。
block-kit-handlerを利用することで、以下のようなメリットがあります。

- JSONで記述するより、少ないコード量でUIの実装ができる
- UIのコンポーネントをオブジェクトとして扱うので、for文やif文などを利用して、効率的に、また動的にUIを作成することができる

是非、使ってみてください！

## おまけ
最後に、この一年、Slackアプリ開発を1から学んだ際に、参考になったサイトや、普段のSlackアプリ開発でよく参照するサイトをご紹介します！
これからSlackアプリ開発を始める方の役に立てば嬉しいです。

- Bolt
  Boltは、簡単にSlackアプリ開発を行うことができるフレームワークです。
  具体的には、Boltを使うことで、Slackアプリからサーバーサイドへのリクエストの認証をうまくやってくれたり、メソッドを実行する感覚でSlackのAPIを簡単に実行することができたりします。
  JavaScriptの他、PythonやJavaで公開されています。（2020年12月10日現在）

    - [Boltを使ったアプリ開発を紹介している記事](https://qiita.com/tomomi_slack/items/93538f9a69eb4015f951)  
      とりあえず、手を動かしてみたい人にオススメです！  
      基本的なことは、この記事で全て分かると思います。
    - [Boltの概要ページ](https://api.slack.com/tools/bolt)
    - [Bolt for JavaScriptの概要ページ](https://api.slack.com/start/building/bolt-js)
    - [Bolt 入門ガイド（日本語）](https://slack.dev/bolt-js/ja-jp/tutorial/getting-started)

- Block Kit
    - [UIコンポーネントのリファレンス](https://api.slack.com/reference/block-kit)
    - [Block Kit Builder](https://api.slack.com/tools/block-kit-builder)

- Slack API
    - [APIの利用ガイド](https://api.slack.com/web)
    - [API一覧](https://api.slack.com/methods)