プロジェクト推進室の服部 (@yhatt) です。
弊社では今年 2 月より、Slack のメッセージをよりメンテナブルにするオープンソースライブラリ jsx-slack を公開しています(@speee-js/jsx-slack
)。
公開から半年が経過しましたが、継続的に更新が行われておりますので、本記事では、現在までに新たに jsx-slack に追加された機能や、各種改善点についてお伝えしつつ、Slack 公式フレームワーク Bolt によるサンプルプログラムを交えた、jsx-slack のより実践的な利用方法をご紹介できればと思います。
Slack アプリケーション開発のエコシステムも拡充されてきており、快適な開発ができる環境が整っていますので、Slack アプリ開発の参考にしていただければ幸いです。
目次
jsx-slack のおさらい
jsx-slack は、Slack API でリッチなメッセージを利用するための UI フレームワーク Block Kit を JSX で構築できる JavaScript ライブラリです。
Slack アプリケーションの開発において、冗長になりやすい Block Kit JSON の定義を React や Vue などでお馴染みの JSX に置き換えることで、Slack アプリの User experience のみならず、開発における Developer experience を向上させることを目的としています。JSX の定義は HTML 要素の使用感に寄せており、メッセージのフォーマット(太字、斜体、引用など)にも一般的な HTML 要素を使用できるため、メッセージの構造を理解するのが容易になります。
Web 上でも JSX → JSON への変換を試せるほか、その出力を Slack の Block Kit Builder に渡して、実際の表示を確認することもできます。
基本機能の概説などについては、 4 月に公開した前記事 をご覧ください。本稿では、これ以降の jsx-slack のアップデートで追加・改善された点について説明していきます。
また、7月に Slack Dev Meetup Tokyo #1 にて "jsx-slack: React 風 Block Kit" と題した LT を行いましたので、そちらでの発表資料も参考にしていただければ幸いです。
半年間での追加・改善点
この半年間で、多くの追加・改善が行われました。要点は以下の 3 点です。
モーダル(ダイアログ)のサポート
今年の 9 月下旬に Block Kit の機能が拡充され、新しく モーダル (Modals) で Block Kit による UI を構築できるようになりました。これにより、従来から存在していた Slack のダイアログを置き換え、より表現力の高い画面を作成できるようになりました。
今まで提供されていた 旧仕様のダイアログ は、すでに an outmoded approach
(時代遅れの方法) として非推奨となっているようです。
jsx-slack では、10 月にリリースされた v0.10 でモーダルのサポートを追加しました。 1
<Modal>
コンテナコンポーネント
モーダル用の JSON を生成する際は、コンテナとして <Modal>
コンポーネントを使用します。モーダルにはタイトルが必須なので、title
属性を忘れずに指定しましょう(最大 24 文字)。
コンテンツには、従来から提供されている Block Kit の各種ブロックを使用することができます。
<Modal title="My first modal"> <Section>Hello, my modal!</Section> </Modal>
必要最低限のモーダルはこれだけです!この JSX で生成された JSON を、Slack API の views.open
に渡してあげれば、以下のようなダイアログを表示することができます。
Slack が従前提供していたダイアログは、入力フォームを必ず用意しないといけませんでしたが、モーダルではその必要はなく、シンプルなメッセージを表示するだけでも OK です。
chat.postEphemeral
を使って一時的なメッセージを表示していた場合、モーダルに置き換えることで UX が改善するケースもあることでしょう。
<Input>
ブロック
モーダルの登場に合わせ、自由入力や項目選択などの入力欄をサポートする Input ブロック が Block Kit に追加されました。jsx-slack では、モーダル専用のブロックコンポーネント <Input>
として使用できます。
ただし、jsx-slack では通常、以下で述べるインプットコンポーネントの使用を推奨しているため、Input ブロックを直接使うことはあまりないはずです。
インプットコンポーネント
jsx-slack における インプットコンポーネント は、HTML フォームと同じ感覚でモーダルを定義できるようにするための、モーダル内で直接使用できるコンポーネント群です。
これに関しては、理屈を説明するより、実際のコードを見たほうが早いと思います。
<Modal title="ユーザー登録" close="キャンセル"> <Input name="user" label="名前" title="名前は全ユーザーに表示されます。" required /> <Select name="sex" label="性別"> <Option value="male">男性</Option> <Option value="female">女性</Option> <Option value="other">その他</Option> </Select> <Textarea name="bio" label="自己紹介" placeholder="簡単な自己紹介をしましょう" maxLength={300} /> <Input type="hidden" name="user_id" value={userId} /> <Input type="submit" value="登録" /> </Modal>
その見た目は HTML (ないしは React) フォームとほとんど変わりません。実際に Slack で表示するとこうなります。
HTML フォームとの主な違いは、 "ラベルを設定するための label
属性が必須" であることぐらいです。HTML フォームの基本的な知識があれば、直感的にモーダル向けの入力フォームを構築することができます。
jsx-slack は、単に冗長な JSON を JSX で書けるようにした類のものではなく、 『テンプレートの定義に既存の知識を使うことで、出力結果を容易に予測できる』 ことに重点を置いています。そのため、jsx-slack を使うことで、メッセージやモーダルのテンプレート管理の障壁を大きく下げることができます。
カスタムコンポーネントにおける <Fragment>
jsx-slack が強力なのは、生成するメッセージにコンポーネント指向の考え方を適用できる点にあります。React の関数コンポーネントと同じように、jsx-slack でも関数コンポーネントをユーザーが定義することができるので、複雑なアプリケーションにおけるメッセージ(ブロック)の再利用が容易になります。
v0.6 で、既存の複数のブロックを組み合わせたカスタムコンポーネント(Higher-order Component)を提供できるようにすることを目的として、React と同様の <Fragment>
が導入されました。
たとえば、<Section>
と <Divider>
を組み合わせて、コンテンツのヘッダーのような見た目を作る <Header>
コンポーネントを定義してみましょう。
/** @jsx JSXSlack.h */ import JSXSlack, { Fragment, Section, Divider } from '@speee-js/jsx-slack' const Header = props => ( <Fragment> <Section> <b>{props.children}</b> </Section> <Divider /> </Fragment> )
定義したコンポーネントは、以下のように、あたかも Block Kit のブロックの一部であるかのように使うことができます。
<Blocks> <Header> <i>jsx-slack custom block</i> :sunglasses: </Header> <Section>Let's build your block.</Section> </Blocks>
Block Kit サポートの継続的な改善
<File>
ブロック
v0.7 で基本コンポーネントに追加された <File>
は、7 月の Remote Files API の登場に伴い、新たに使えるようになったブロックです。Slack に登録したリモートファイルを、Slack にアップロードしたファイルと同じ見た目で表示することができるようになります。
比較的ユースケースが限定されたブロックなので、実際に使用する機会はそう多くないと思いますが、jsx-slack でも不自由なく全ての Block Kit の機能を使ってもらえるよう、最新のアップデートへの追従を心がけています。
選択系コンポーネントの複数選択
9 月の Slack API のアップデートでも Block Kit に新しいタイプが追加され、選択系コンポーネントのバリエーションとして、複数選択がサポートされました。
jsx-slack では、最新の v0.10 でこれに追従しています。特に難しいことはなく、すでに提供されている選択系のコンポーネントに multiple
属性をつけるだけで OK です。
<Blocks> <Section> ユーザーを選択: <UsersSelect multiple /> </Section> </Blocks>
ただし、multiple
属性は <Actions>
ブロック内では使えないので注意してください(Slack 側がサポートしていないため、実行時エラーになります)。2
テンプレートリテラルタグの改善
jsxslack
テンプレートリテラルタグ を使うと、JSX を使用するための面倒な準備(Babel / TypeScript のインストールや設定)をすっ飛ばし、素の JavaScript で jsx-slack コンポーネントを使ったメッセージを構築することができます(エンジンとして HTM (Hyperscript Tagged Markup) を採用しています)。
import { jsxslack } from '@speee-js/jsx-slack' console.log(jsxslack` <Blocks> <Section> Hello, <b>world</b>! </Section> </Blocks> `)
v0.9 で、テンプレートリテラルにおける文字列の扱いが、JSX の挙動に準じるように改善されました。具体的には ♥
などの HTML エンティティのエンコードが正しく処理されるようになり、Slack 上でも期待通りに表示されるようになります。
また、先述したカスタムコンポーネントも、テンプレートリテラルタグのみで記述できます。関数コンポーネントを定義する際は、 jsxslack
タグの代わりに jsxslack.fragment
タグを使用してください。
import { jsxslack } from '@speee-js/jsx-slack' const Header = ({ children }) => jsxslack.fragment` <Section> <b>${children}</b> </Section> <Divider /> `
実践編: jsx-slack + ⚡️Bolt でアプリを作る
では、実際に jsx-slack を組み込んだアプリを開発してみましょう。
Slack は 5 月に、Slack アプリ開発のための Node.js 向け公式フレームワーク Bolt を発表・公開しました。3 日本語マニュアルも用意されており、Slack に特化したアプリの開発においては、他の Bot フレームワークに比べてとっつきやすいものになっています。
jsx-slack とも相性が良く、実際に社内でもこの組み合わせによる Slack アプリが開発されていたりします。
伝書鳩アプリ 🕊
ここでは、メッセージを指定した時刻に他のユーザーに届ける 『伝書鳩アプリ』 を作ってみたいと思います。このアプリは、モーダルを含め、今年 Slack API に追加された機能をフルに活かしたアプリになっています。
Slack 公式のチュートリアルに倣い、Glitch 上に伝書鳩アプリをホスティングしています。ご自身でこのアプリを動かしたり改造したりしたい場合は、プロジェクトを Remix (フォーク) して、色々といじってみてください。
なお、JSX のセットアップを省略するため、このアプリでは jsxslack
テンプレートリテラルタグ を使用して Block Kit JSON を記述していきます。もし JSX を使用する場合でも、テンプレートの書き方はほとんど同じです。
開発の準備
Slack API の Apps ページにある "Create New App" ボタン から、新しい Slack アプリを作成し、権限を設定しましょう。このアプリで必要な権限は以下の3つです。
- [Interactive Components] を有効にする
- [Request URL] に
https://[Glitch プロジェクト名].glitch.me/slack/events
を指定 (例:https://jsx-slack-bolt-example.glitch.me/slack/events
)
- [Request URL] に
- [Event Subscriptions] を有効にする
- [Request URL] は Interactive Components と同じ
- Glitch プロジェクトの環境変数を設定した後でないとセットできないので注意
- [Subscribe to Bot Events] で "message.im" を許可
- [Subscribe to Workspace Events] で "app_home_opened" を許可
- [Request URL] は Interactive Components と同じ
- [Bot Users] で Bot を作成(名前はお好みで)
アプリをインストールできたら、Glitch プロジェクトの .env
を編集し、Slack アプリに合わせて環境変数を設定します(この値は、プロジェクトに参加している人以外は見えません)。
SLACK_BOT_TOKEN=xoxb-xxxxxxxx-xxxxxxxx-xxxxxxxxxxxxxx SLACK_SIGNING_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
各トークンは以下の場所で確認できます。
- SLACK_BOT_TOKEN: [OAuth & Permissions] → [Bot User OAuth Access Token]
- SLACK_SIGNING_SECRET: [Basic Information] → [App Credentials] → [Signing Secret]
もしアプリがうまく動かない場合、アプリに与えられている権限や、環境変数の値をもう一度見直してみてください。
アプリの初期メッセージ
ここから Bolt と jsx-slack を使った具体的なコーディングに入ります。ここでは Bolt の使い方の解説は省きますので、 詳しい使い方については公式ドキュメントを参照してください 。
まず、アプリを開いた時に、伝言を送るためのボタンを表示するようにしましょう。4 月に新しく追加された app_home_opened
イベントを使うと、Bot を開いた時に処理を引っ掛けることができます。
app.event('app_home_opened', async ({ context, event, say }) => { const history = await app.client.im.history({ token: context.botToken, channel: event.channel, count: 1, }) if (history.messages.length === 0) { say({ blocks: jsxslack` <Blocks> <Section> <p><b>伝書鳩アプリへようこそ!</b></p> <p>指定した日時に、ユーザーへの伝言を送信します。🕊️</p> </Section> <Actions> <Button name="post" style="primary">伝言を送る...</Button> </Actions> </Blocks> `, }) } })
最初に im.history
を使っているのは、メッセージの 2 重投稿を防止するためです。もしアプリに何の投稿もなければ、アプリがモーダルを開くためのメッセージを投稿します。
もし開発中に、このイベントで送るメッセージの内容を変えた場合は、このメッセージを削除して、再度 Bot を開き直せば再投稿されます。
モーダルを開く
次に、『伝言を送る...』のボタンでモーダルを開くようにしましょう。
const modal = (props = {}) => jsxslack` <Modal title="伝言を送る" callbackId="post"> <Section> 私にお任せください! <Image src="https://source.unsplash.com/ic-13C3QhAI/256x256" alt="鳩" /> </Section> </Modal> ` app.action('post', ({ ack, body, context }) => { ack() app.client.views.open({ token: context.botToken, trigger_id: body.trigger_id, view: modal(), }) })
モーダルを開くには views.open
API を使い、view
にモーダル仕様の Block Kit JSON を渡すのですが、ここで jsx-slack の <Modal>
の出番です。モーダルの挙動を検証するため、一旦モーダルの内容はシンプルにしました。
『伝言を送る...』ボタンを押したら、上のようなモーダルが開くはずです。
基本のフォームを追加
では続いて、<Section>
ブロックの下にフォームを追加していきましょう。
const modal = props => jsxslack` <Modal title="伝言を送る" callbackId="post"> <Section> 私にお任せください! <Image src="https://source.unsplash.com/ic-13C3QhAI/256x256" alt="鳩" /> </Section> <Textarea id="message" name="message" label="伝言" placeholder="伝言をどうぞ..." required /> <UsersSelect id="users" name="users" label="送付先" multiple required /> <DatePicker id="date" name="date" label="日付" required /> <Input type="hidden" name="userId" value=${props.userId} /> </Modal> ` app.action('post', ({ ack, body, context }) => { ack() app.client.views.open({ token: context.botToken, trigger_id: body.trigger_id, view: modal({ userId: body.user.id }), }) })
ここでは、<Textarea>
、<UsersSelect>
、<DatePicker>
の 3 つのインプットコンポーネントを使用しています。送付先のユーザーは、multiple
属性を使って複数選択ができるようにしてみました。
また、モーダルから戻ってくる API は、モーダルを開いたユーザーの情報を持っていないので、<Input type="hidden">
を使ったプライベートメタデータとして、誰がモーダルを開いたか分かるように、ユーザー ID (body.user.id
) を保存しておきます。
従来のダイアログでは、このような『リッチコンテンツ+フォーム』は実現できません。Block Kit を使った、モーダルならではの表現力と言えましょう。
<TimePicker>
コンポーネントを作る
さて、日付は入力できますが、時刻は入力できません。残念ながら、Slack にはタイムピッカーは用意されていません。ここでは、自分でカスタムコンポーネント <TimePicker>
を作ってしまいましょう。
テンプレートタグを使ってコンポーネントを定義する際は、jsxslack.fragment
を使います。
const options = (count, start, suffix) => [...Array(count)].map((_, i) => { const s = (i + start).toString() return jsxslack.fragment` <Option value=${s}>${s.padStart(2, '0')}${suffix}</Option> ` }) const TimePicker = props => jsxslack.fragment` <Section> <b>${props.label}</b> </Section> <Actions id=${props.id}> <Select name="hour" value=${props.hour} placeholder="時"> <Optgroup label="午前">${options(12, 0, '時')}</Optgroup> <Optgroup label="午後">${options(12, 12, '時')}</Optgroup> </Select> <Select name="minute" value=${props.minute} placeholder="分"> ${options(60, 0, '分')} </Select> </Actions> <!-- エラーメッセージ (後述) --> ${props.error & jsxslack.fragment`<Context>:warning: <b>${props.error}</b></Context>`} <!-- 選択値をプライベートメタデータに保存 --> <Input type="hidden" name="hour" value=${props.hour} /> <Input type="hidden" name="minute" value=${props.minute} /> `
jsx-slack は、React と同じように、map
の結果をレンダリングに反映させることができるので、大量の <Option>
を動的に生成することができます。ここではヘルパー関数を用意して、連番の <Option>
を生成するようにしました。
選択した値は、<Input type="hidden">
でプライベートメタデータとしても保持するようにしています。これは必須ではありませんが、あとで値の取り回しがよくなります。
では、先の modal
関数の中に <TimePicker>
を置きましょう。
const modal = props => jsxslack` <Modal title="伝言を送る" callbackId="post"> <Section> 私にお任せください! <Image src="https://source.unsplash.com/ic-13C3QhAI/256x256" alt="Pigeon" /> </Section> <Textarea id="message" name="message" label="伝言" placeholder="伝言をどうぞ..." required /> <UsersSelect id="users" name="users" label="送付先" multiple required /> <DatePicker id="date" name="date" label="日付" required /> <${TimePicker} id="time" label="時刻" hour=${props.hour} minute=${props.minute} error=${props.timePickerError} /> <Input type="hidden" name="userId" value=${props.userId} /> </Modal> `
見た目はバッチリですね!
ですが、これだけでは正しく動作しません。<Actions>
ブロックの選択値を正しく反映させるためには、あともうひと手間必要です。
タイムピッカーで選択した値を永続化させる
モーダルで定義されたインタラクティブコンポーネント(<Actions>
内の <Select>
)を選択すると、Slack アプリに「どの値を選択したか」を通知するイベントが送られます。このイベントを捉えた上で、選択された項目をモーダルに反映する必要があります。4
更新には views.update
API を使います。すでに開いているビュー(モーダル)の ID を、新しいモーダル JSON と一緒に渡すことで、モーダルの内容を更新することができます。このモーダルを生成する際に、新しい値を反映することで、選択した値を永続化させます。
app.action(/^(hour|minute)$/, ({ ack, body, context, payload }) => { ack() app.client.views.update({ token: context.botToken, view_id: body.view.id, view: modal({ ...JSON.parse(body.view.private_metadata), [payload.action_id]: payload.selected_option.value, }), }) })
モーダルのバリデーション
これでモーダルのレイアウトは完了しました。続いては、入力値のバリデーションです。
app.view('post', ({ ack, context, next, view }) => { // バリデーション処理 const values = { ...JSON.parse(view.private_metadata), message: view.state.values.message.message.value, users: view.state.values.users.users.selected_users, date: view.state.values.date.date.selected_date, } if (values.hour && values.minute) { ack() // 次のミドルウェアに値を渡す context.values = values next() } else { ack({ response_action: 'update', view: modal({ ...JSON.parse(view.private_metadata), timePickerError: '時刻を入力してください。', }), }) } })
インプットコンポーネント 3 種の入力値は、結構深いところ (view.state.values.[id].[name].<各コンポーネント別の入力値表現...>
) にあります。id
(= block_id
) や name
(= action_id
) を設定しない場合、各モーダルごとにキーが自動生成され、予測不可能な値になるので注意が必要です。
また、インプットコンポーネントは required
属性による入力保証や、response_action: 'error'
で定義できるエラーメッセージなどを利用できますが、既存のブロックの寄せ集めで実現される <TimePicker>
はそうはいかず、自前でバリデーションやエラーの表示をしなければなりません。
ここでは、現在のモーダルを更新するため response_action: 'update'
を使用し、時刻が入力されていない場合に <TimePicker>
にエラーメッセージを表示するようにします。挙動は views.update
API と同様です。
実際の処理(指定した時間にメッセージを投稿)
実際の処理は、バリデーションと分けて書く(ミドルウェアを分割する)と良いでしょう。
app.view( 'post', ({ ack, context, next, view }) => { // バリデーション処理... }, async ({ context }) => { // 実際の処理 const { values } = context const { date, hour, minute } = values // サンプルのため、タイムゾーンは JST 固定 const postAt = new Date( `${date}T${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:00+0900` ) / 1000 for (const user of values.users) { let scheduledMessageId try { scheduledMessageId = (await app.client.chat.scheduleMessage({ token: context.botToken, channel: user, post_at: postAt, blocks: jsxslack` <Blocks> <Section> <a href="@${values.userId}" /> さんからの伝書をお届けします 🕊️ </Section> <Divider /> <Section> <Escape>${values.message}</Escape> </Section> </Blocks> `, })).scheduled_message_id } catch (e) { await app.client.chat.postMessage({ token: context.botToken, channel: values.userId, blocks: jsxslack` <Blocks> <Section> おっと! <a href="@${user}" /> さんへの伝書をお届けできないようです :sob: </Section> <Context> <b>エラー内容:</b><span>${e.message}</span> </Context> </Blocks> `, }) continue } await app.client.chat.postMessage({ token: context.botToken, channel: values.userId, blocks: jsxslack` <Blocks> <Section> <time datetime=${postAt}>{date} {time}</time> に <a href="@${user}" /> さんへ伝書をお届けします 🕊️ </Section> <Context> <b>ID:</b><span>${scheduledMessageId}</span> </Context> </Blocks> `, }) } } )
ここで使用している API chat.scheduleMessage
は、5 月に新しく追加された、指定した時刻にメッセージを送信するための機能です。以前は CRON などを使う必要がありましたが、この機能のおかげで予約投稿がかなり楽になりました。伝書鳩アプリは、この API を使って、指定した時刻にメッセージの投稿をセットします。
過去の日付を指定された場合など、値によってはエラーが発生する可能性があるため、その場合は失敗した旨をユーザーに伝えます。
さらなる改善
以上で、一通り動作する伝書鳩アプリが完成しました。このアプリには、まだまだ以下に挙げるような改善の余地がありますが、あくまでサンプルですので、今回は省略しました。興味があれば、ぜひこれらの機能を実装してみてください。
- スケジュールされたメッセージをキャンセルする機能
- ユーザーのタイムゾーンの考慮
- 通知などに用いられるプレーンテキストメッセージ (
text
) を追加する - etc...
また、モーダルの内容をいじってみるのも良いかもしれません。jsx-slack のデモサイトは、メッセージのみならず モーダルの JSX → JSON 変換 にも対応しています。Block Kit Builder に転送し、実際のモーダルをプレビューすることもできるため、モーダルのプロトタイピングに最適です。
JSX コンポーネントの詳細については、jsx-slack のリポジトリに一新されたリファレンスを用意しています ので、jsx-slack で本格的にアプリを開発する際は、ぜひご一読ください。
おわりに
弊社で開発しているオープンソースライブラリ jsx-slack の新機能・改善箇所のご紹介、および Bolt フレームワークを組み合わせた最新の Slack API による実践的なアプリ開発例をご紹介しました。
来週 (現地時間 10月22日〜23日) には、Slack の開発者向けイベント Spec が控えており、Slack API や Block Kit に関連するさらなる新機能や、かねてからリリースが予告されていた Workflow Builder (速報:本日リリースされました) など、Slack アプリに関わる多くの発表が行われることが予想されます。5
Richer, more interactive apps are coming to Slack. Come to Spec to find out more. https://t.co/NJTrxSDQdZ pic.twitter.com/IUxeB8doMY
— Slack (@SlackHQ) October 9, 2019
jsx-slack は、Spec にて発表されるであろう Slack API の更新に引き続き追従し、Block Kit で提供される機能をあまねく使えるように維持していく予定です。
Slack アプリの開発のお共に、是非 jsx-slack を使ってみてください。もし使ってみて気になった点や改善点などがありましたら、GitHub (https://github.com/speee/jsx-slack) にて Issue や Pull Request を引き続きお待ちしております。
-
旧仕様のダイアログも 8 月の v0.8 でサポートされましたが、細かい仕様が Block Kit と異なるため、独立した機能として提供していました。Block Kit に統合されたモーダルの登場後は Deprecated になりましたが、インターフェースはあまり変えていないので、テンプレートの記述は自然に移行できるはずです。↩
-
multiple
属性は、<Section>
ブロック内に 1 つだけ置けるアクセサリ、もしくはインプットコンポーネントでのみ使用できます。↩ -
Bolt はもともと、Bot 開発のホスティングサービスを運営していた Beep Boop というスタートアップが提供していた、Slapp というオープンソースの Slack アプリ用フレームワークでした。やがて会社はワークフローの自動化を支援する Missions に合流し、さらに Missions が Slack に買収された ことで、晴れて Slack 公式のフレームワークになったサクセスストーリーを持ちます。↩
-
React に慣れている方であれば、 制御コンポーネント (Controlled component) をイメージしてもらうとわかりやすいでしょう。「
onChange
を受けて、setState
で値を更新する」のと同じ流れです。また、インプットコンポーネントは、新しいvalue
属性を渡しても、入力された値は変化しません(React の非制御コンポーネントにおけるdefaultValue
に似た挙動)。↩ -
実はモーダルや追加された Block などに関しては、Slack 公式ブログでの発表がまだ行われていません。おそらく、この Spec で大々的に発表されるものと思われます。↩