技術探し

JavaScriptを中心に記事を書いていきます :;(∩´﹏`∩);:

botたちの家を作っている

今日が入社日です。

abouthiroppy.hatenablog.jp

この記事は第2のドワンゴ Advent Calendar 2017の18日目です。


12/11に旅行行っててなんとなく現地で作りたいと思ってそこから書いていますので、まだ下地の段階です。今後に期待してください:)

とりあえず最低限のベースの機能実装を行いました。

github.com

これはなに?

Slack Appです!
名前の通り、botたちを収容する施設です。
BotがJSのコードを持っていて、親がそれを動かします。

以下を見てもらえるとわかりやすいかも。

招待されたのは親botで実行されたのは子供botとなります。

完全にPC用で作っているので、レスポンシブデザインとかないです。

目的

botを書く -> どこかにデプロイする -> slackでbotの設定をする

このフローを毎回やるの辞めたい!(めんどくさい)

ブラウザ上でJSの実行させたいコードを書くだけで後はやってくれる感じにしてほしいのでそれが目的です。

現在の進捗

  • botの登録、実行、編集、削除が行える
  • botからのコマンド

今後やりたいこと

機能面

  • 今現在、botに対する呼び出しの単語が一つなので正規表現を使えるようにする
  • 一つのbotに対して複数のチャンネルを登録できるようにする
  • ストレージ機能の実装(例: TODOアプリ)
  • private機能と権限周り
  • SlackのRTMを使っているため様々なイベントフックができるので、それにも反応できる
  • アイコンの画像対応
    • 表示はできるがアップロード画面作るのめんどくさくてまだやってない

コード面

  • botのeval実行(Functionでも可)に対して、キャンセリングを実装する
  • セキュリティやばい
    • 今はslackチームという身内で使うので優先度が低い
  • 例外処理が雑魚
  • デザインの変更
  • テストを書く
  • NodeのコードもBabelを通すか迷う
    • フロントエンドのコードがFlowで型定義されているため、APIの型を使いたい
  • dockerを真面目な設定に書き換える
  • 連打とかローディングとか

仕組み

親のbot(Slack app)がいて、それが子供のscriptを実行することより、擬似的に子供のbotがいるように見える。
簡単にいうと、postMessageという関数にiconやnameを付けれるのでそれを変更して別botが動いている仕組みです。

なので、web上でJSのコードを書いたらそれが応答で動くbotになります。

ドワンゴ社内slackでも動いているAlfaさんのbot天国のJS版実装です。
github.com

今現在、子供botができること

fetch, dom操作, メッセージを送るができます。(attachmentはまだ)
async/awaitに対応しているので、ある程度のことはできますが、まだキャンセリングの実装してないのでそれも落ち着いたらやろうと思っています。

Cancel Promise pattern (no cancellable promises)

技術スタック

書く量が多くなるので、ピックアップしていきます。
トランスパイラがBabelでバンドラーがwebpackです。

  • react
  • react-loadable
    • JSチャンクを読み込むルーティング時にローディングする
  • react-alert
  • react-ace
    • JSのエディタで補完とかもできる
  • redux
  • redux-saga
  • express
  • sequelize
    • 今回はpostgresqlとsequelize-cliでmigrate等を行う
    • 今後、bookshelfに戻すかも
  • emoji-mart
  • brace
  • riek
    • inlineフォームの実装
  • classnames
  • workbox
    • googleのService Workerライブラリ
  • postcss-cssnext
  • node-slack-sdk

あとはpackage.jsonを見てください。

DB

ORMはsequelizeでDBはpostgresqlを使いました。

github.com

書くまでに実装する時間があまり無かったため、bookshelfよりも慣れているsequelize, sequelize-cli, postgresqlを使います。

基本的には、sequelize-cli経由でマイグレーションを行ったり、シードを差し込んだりします。

docker-compose

当初、docker-compose内で開発を行っていたのですが、どうもnodemonが上手く動かなかったので一旦ローカルでの開発に戻しました。
もちろん、プロダクションではdocker-composeで動きます。

ちなみにdockerでのwebpack-dev-serverのHMR

github.com

.envを使って環境変数を操作しているのですが、プロダクションやディベロップメントとかの.envファイルを読み込ませるのはどうするのが最適解なのだろうか気になります。。。

またherokuへのデプロイが本当に楽でした。

$ heroku container:push web --app bot-house

これでbuildしてpushしてくれて動くので最高です。

node-slack-sdk

github.com

slack社公式のNode.jsクライアント。
基本ドキュメントが無いので、コード読んだほうが明らかに速いです。
コードに書かれたコメントやdocはスゴイ丁寧なのでそれを一番最初に見るべきだと感じました。
RTMはまだ未実装な部分(例えばRTMでattachmentのメッセージを送れない等)が多いのでそういうときはWEBというnode-slack-sdkのメソッドを使うといいです。

また、Slackの認証そのものがよくわからなかったので以下の記事をよみました。

qiita.com

fastify

github.com

速いことで有名なwebフレームワーク。
初めて今回使ってみましたが使いやすかったです。
ただ、やはりexpressと比べるとエコシステムが充実していなくて、アプリケーションを作っていくと大変でした。
view周りや認証(passport対応)が弱いという印象があります。
例えば、webpack-hot-middlewareが使えなかったりします。
github.com

まだまだ始まったばかりのプロジェクトなので、使ってみて使いやすいという印象が強かったので、コミットしていきたいと思った反面、構築ですごい時間がかかったのでやはりプロダクションのコアな部分はエコシステムが充実しているライブラリを使うべきだと思います。
今後周りのエコシステムの作成やコアにコミットしてみたいと思っています。
ググっても情報は基本ないので、最初にissueとコードを読んだほうが速いです。

今回は2日間ぐらいハマった部分があり、結局expressへ乗り換えました。

ちなみにチートシート

devhints.io

クライアント側ルーティング及びチャンク

react-routerを使っています。
また、今回はdynamic importのためreact-loadableを使っています。

github.com

react-loadableはチャンクで切られたパーツを読み込むまでに表示させるローダーの設置などができます。
もちろん、Preloadingもできますが今回はまだやってません。

                                                 Asset       Size  Chunks                    Chunk Names
StratumNo1 Medium-06a6ac1709970857f419dc78aca05353.ttf    62.3 kB          [emitted]
                           BotsShow.bundle.54f65aa8.js     304 kB       0  [emitted]  [big]  BotsShow
                              Index.bundle.54f65aa8.js    10.3 kB       1  [emitted]         Index
                       ChannelsShow.bundle.54f65aa8.js     8.3 kB       2  [emitted]         ChannelsShow
                         UsersIndex.bundle.54f65aa8.js    4.39 kB       3  [emitted]         UsersIndex
                                    bundle.54f65aa8.js    31.2 kB       4  [emitted]         bundle
                                    vendor.54f65aa8.js    1.46 MB       5  [emitted]  [big]  vendor
                  1f42da94f3ff91d479713521dee90778.css     1.5 kB       4  [emitted]         bundle
                                            index.html  326 bytes          [emitted]
import Loadable from 'react-loadable';

export const BotsShow = Loadable({
  loader : () => import(/* webpackChunkName: 'BotsShow' */ '../containers/pages/Bots/Show'),
  loading: () => (<div>Loading...</div>)
});

const Router = () => (
  <App>
    <Switch>
      <Route
        path="/bots/:id"
        component={() => (<BotsShow />)}
      />
    </Switch>
  </App>
);

フロントエンド用のライブラリはvendor.jsへすべてまとめ、ルーティングごとにファイルを切ります。

react-universal-componentとどちらを使うか迷いましたが、react-loadableにしました。

github.com

history

github.com

今回は、browserHistoryではなく一旦hashHistoryにしています。
理由としては、watch時にhtml-webpack-pluginとexpressがルーティング周りで競合するため開発効率が落ちるからです。
普段、SSRの時にhtml-webpack-pluginを使っていなかったため気付かずハマってしまってました。。

SSR

今回は、とくにやる必要がないのでしません。
SSRをやるメリットとしては、SEO対策とFirst Viewに関する速度がありますが2つとも今回は必要の無いことだからです。

そのかわりに表示パフォーマンスの面では、dynamic importとService Workerで補います。

PWAのためのライブラリであるWorkboxを使い、今回はService Workerを扱います。

github.com

今はhtml-webpack-pluginでhtmlファイルを吐き出してますが、今後SSR同様にinlineへと変えたいと思います。

感想

意外と大規模なものを作っていて時間がありませんでした。
特にSlackのデバッグが本当に大変なのとSlack Appというのがなんなのかみたいな部分から知らないといけなくて少しSlackに詳しくなった気がする。

まだまだやることが多いのでもう少しこの開発に楽しもうと思っています。


簡単に導入できるので是非使ってみてください!!