- サクッと Slack App 書いてみたい
- Block Kit でリッチなUIを作りたいけど、JSON 書きたくない
- Slack App くらいの規模なら...という気持ちもあるが、補完や予期しない型ではじいてくれるのは嬉しい
となるため、今後はこんな構成になりがちなのではないでしょうか
がまだそんなに事例を見ていないのでちょっとやってみて記事に起こしてみました。
(まとめが追いついておらずだいぶ半端になっている)
私自身まだ色々試行錯誤中なため、色々ご意見頂戴できると嬉しいです
やらない
- Slack App の作成とインストール
- これは各所で説明があると思うので省きます
- Slack Appの作り方について - Qiita
- デプロイ
- いろんな環境へのデプロイが考えられるしどうしようか考え中
- そもそもまだデプロイまでやってない
Bolt、jsx-slackについて
Bolt
Bolt は Slack App をスッと開発できるように作られたフレームワークです。
イベントをリスニングするエンドポイントを作ったり、APIのリクエストをしたりすることが簡単にできます。
のコードですぐに立ち上がります。
slackapi/bolt: A framework for building Slack apps
本来であれば、Webアプリケーションサーバをたててイベントを受け付けるエンドポイントをつくり、リクエストのボディを処理して、何らかのロジックを組み込む必要がありますが、Boltを使えば Slack App のロジック部分を作ることに集中できます。
jsx-slack
まず、jsx-slack の前にBlock Kit についてです。
Block Kit
Block Kit は Slack App のための UI フレームワークです。
メッセージやモーダル、最近では Slack App の Home タブで UI コンポーネントを組み込み、インタラクティブな操作が可能になっています。
- 画像
- フォーム
- テキスト
などをかなり自由にレイアウトすることができます。
本来であれば、 Block Kit Builderを使って GUI でつくって生成される JSON をコードに組み込むことになりますが、一度コードに組み込んだあとのメンテナンスが辛くなります。
それを解決してくれるのが jsx-slack です。
名前のとおり、JSX で Block Kit で作る UI の記述ができます。
下記サンプルのように JSX だとコードをみて直感的に UI のイメージがつきやすくなります。
speee/jsx-slack: Build JSON object for Slack Block Kit surfaces from readable JSX
とりあえず動かすところまで
ボイラープレート + Sample Code
Bolt + TypeScript + jsx-slack でボイラープレート作ったのでこちらを使って動かすとこまで行くことを想定しています。見ながら参考にしてください。
phigasui/slack_app_boilerplate
これでいつでもサクッと Slack App がつくれるぞ
TypeScriptをトランスパイルせずそのまま実行できるように ts-node をつかいます。
また、開発中はホットリロードしてほしいので nodemon をつかいます。
Slack App 作って Token を設定
PORT 番号はよしなに
SLACK_SIGNING_SECRET=****************************
SLACK_BOT_TOKEN=xoxb-**********************************
PORT=3000
ローカルで動かす
Bolt のチュートリアルでも使っている ngrok をつかってローカルで Slack App を使えるようにします。
各々の環境で ngrok をインストールし、
$ ngrok http {PORT}
再実行すると、Slack App に設定する URL を変えないとならないため、かなりしんどい
Slack App に エンドポイントのURLを設定
Bolt で作るアプリケーションは /slack/events
をリスニングするため、ngrok
が発行した URL を使って下記のように設定します。
スラッシュコマンド
モーダルなどでのアクション
Slack App を立ち上げる
$ yarn start
yarn run v1.19.1
$ ./node_modules/.bin/nodemon
[nodemon] 2.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): src/**/*
[nodemon] watching extensions: ts,tsx
[nodemon] starting `./node_modules/.bin/ts-node ./src/App.ts`
⚡️ Bolt app is running!
実装について
ディレクトリ構成
JSX のコードはメインではなく、ロジックのコードが .tsx
になってしまうのは嫌だったので Views.ts
で各種 .tsx
をimport して一括でexport して切り離すようにしました。
$ tree src
src
├── App.ts
├── Views.ts
└── views
└── SampleFormModal.tsx
スラッシュコマンドの定義とBoltの実行
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET
})
app.command('/sample_form_modal', async ({ ack, body, context, payload }) => {
ack()
const user_name: string = body.user_name
try {
app.client.views.open({
token: context.botToken,
trigger_id: payload.trigger_id,
view: SampleFormModal({ name: user_name })
})
} catch (error) {
console.log(error)
}
})
const run = async () => {
await app.start(process.env.PORT || 3000)
console.log('⚡️ Bolt app is running!')
}
run()
View を作る
JSX で Block Kit の UI をつくってそれを Block Kit Builder で表示できるツールを用意してくれていたため、それを利用しました。
@speee-js/jsx-slack: Build JSON for Slack Block Kit from JSX
/** @jsx JSXSlack.h */
import JSXSlack, { Input, Modal, Section, Textarea } from '@speee-js/jsx-slack'
export default ({ name }: { name: string }) => {
return JSXSlack(
<Modal title="Article" close="Cancel" callbackId="send_form">
<Section>
Hello {name}!
<p>記事を作成します。</p>
</Section>
<Input label="タイトル" type="text" blockId="title" actionId="title" required />
<Input label="タグ" type="text" blockId="tags" actionId="tags" placeholder="スペース区切りで複数登録 ex.'Ruby Rails'" />
<Textarea label="本文" blockId="body" actionId="body" required />
<Input type="submit" value="Save" />
</Modal>
)
}
サンプルを見ながら作ってみるとそれっぽくできるため JSX のありがたみを感じます。
サンプルのフォームを見るとなんとなくわかるのですが、もともと実装していたものでは Qiita、Qitta Team の Personal Access Token の保存のための DB アクセスや API リクエストを組み込んでいますが、今回は割愛
モーダルからのアクションの受け取り
上記 view で作ったフォームを送信するとイベントが送信されるためそれをリスニングする app.view
を定義します。
interface FormViewStateValues {
values: {
title: { title: { value: string } }
tags: { tags: { value: string } }
body: { body: { value: string } }
}
}
app.view('send_form', async ({ ack, body, context, view }) => {
ack()
const user_id: string = body.user.id
const form_view_state_values = (view.state as FormViewStateValues).values
const form_title = form_view_state_values.title.title.value
const form_tags = form_view_state_values.tags.tags.value
const form_body = form_view_state_values.body.body.value
app.client.chat.postMessage({
token: context.botToken,
channel: user_id,
text: `Title: ${form_title}\nTags: ${form_tags}\nBody: ${form_body}`
})
})
ViewOutput.state
は作ったUIで変わるため Bolt では抽象度の高い Object になってしまいます。
そのため、TSで扱うには自分でどんな Interface になるか定義する必要があります。
bolt/index.ts at 5ed8c89f3442448f7b0c1c2c8581d46b93533e7b · slackapi/bolt
ここはしんどいですが、ViewOutput
や SlackViewAction
など、コールバックで受け取る引数のオブジェクトがどんなプロパティを持っているのか補完が効くところで TypeScript で実装している恩恵が受けれて良いかなと思います。
もうおしまい
Bolt や jsx-slack のおかげで Slack App を作る敷居がかなり下がっていて大変ありがたさを感じます
今後開発していく上で手放せない存在になりそうです。