「使っているもの」を管理・共有するWEBサービスを作った

f:id:mugi1:20180830225636p:plain

前々からコツコツ作っていて、形になったので公開した。

ibuos.net

「使っているもの」の管理・共有を主軸においたサービスです。 一人でやってるので荒削りな部分もあるんだけど、コンテンツが揃ってこそ楽しくなってくるので、抵抗がなければ触ってみてもらえると嬉しい。

ちなみに ibuos = 装備(soubi) を逆にしたものです。ダサいね!

できること

  • ログイン(Google or GitHub)
  • 使ってるものを登録 / 一覧
  • ユーザの使ってるものを見る
  • フォローできる(フォロー一覧がまだないので、フォローする意味はまだない!!)
  • 自分の表示名の変更

とりあえず、現状ではまだ機能は少なめです。

作ろうと思った動機

もともと、人の使っているガジェット・ツール・サービスとかに興味があって、社内のesaとかにもそういうまとめがあると積極的に見に行くし、自分も書き込んでいた。

社内だけじゃなくていろんな人のをみたいな〜、と思ったときに、一箇所にまとまってくれてたらいいのに、と思ったのが発端。

あと、単純に物を買うときに、Amazonのレビューとかも見るけど、それ以上に、「あの人が使ってるなら自分も使ってみよう!」っていう動機が多かった。

そういう人は自分以外にも多いんじゃないかと思っていて、そうであれば、眺めてるだけでも楽しめるんじゃないかな〜と思ってる。

技術的な話

フロントエンド

  • React
  • MobX
  • ReactRouter
  • TypeScript

バックエンド

  • Rails(API)
  • firebase
  • Heroku
  • CloudFlare
  • S3

あたりを使ってる

時代はNuxt.jsなのは把握しているけど、TypeScript&MobXを使いたいという欲求だけで突き進んでたら、気がついたらリリースしていた。

最初はSSRを頑張ろうと思ってたけど、まあ個人サービスだし別にええやろ!って思って、途中であきらめた。

もしかしたらいずれNext(or Nuxt)あたりで書き直すかもしれない。

仕事以外で、全てゼロから自分で準備して公開するっていう経験が実は無かっのので、色々と勉強になる部分もあるし、楽しい。 (とはいえ、Herokuとか使ってるのでだいぶチートっぽい感じはある)

なお、PostgreSQLも無料プランなので、ほうっておくとレコード件数上限で死ぬ未来が待っている。幸いにも使ってくれるユーザがいるようであれば、課金したいと思う。

今後の展望

とりあえず、直近では以下を作りたいと思ってる。

  • フォロー一覧・フォロワー一覧
  • ジャンルごとの一覧
  • タグ付け
  • 検索機能

というわけで、ぜひ使ってみてもらえると嬉しい。

そして、

  • エラー出たぞ!
  • こういう機能がほしいぞ!

みたいなのがあれば、@mugi_unoまで教えてもらえると嬉しいです。

React+MobX+TypeScriptが個人的にちょうど良い

Vue使いやすい

以前はフロントエンドといえばReact+Reduxでしょ!というのが主流だったが、最近ではVue+Vuex、あるいはNuxt.jsが注目を集めてる。

私自身も、趣味・業務両方でVueを使っていて、とても使いやすいな〜と思ってる。

TypeScript使うとちょっとつらい

TypeScript自体はかなり強力で、例えばkeyofのような機能はとても魅力的だったりする。

ただ、Vueと組み合わせてTypeScriptを使った型付けをしたい場合に、2018年7月現在ではわりと厳しい感じがしている。私が調べた限りでは、Vue&VSCodeを使うことでthisからの補完などはある程度はできるが、Vuexを絡めた場合には頑張って型を書く必要がある。(Nuxtの場合はもっと大変)

このあたりは、ClassComponentを駆使する有志作成のライブラリを使うことである程度はカバーできる。

ただ、人によって好みもあると思うけど、いわゆる一般的に見られるVueの書き方とは大きく離れることになってしまうことが多く、軽い気持ちで導入するにはハードルが高いな〜、と感じてしまう。

Reactの場合

Reactの場合はTypeScriptとの相性が良く、VSCodeを組み合わせた場合もかなり使いやすい。例えば、JSXに記述するpropsも厳密にチェックすることができる。PropTypesを使わずとも事前に検証できるのも心強い。

状態管理は?

王道だとReduxを採用することになると思うが、私はつらい。(これには個人的なトラウマによる偏見もかなり含まれている。)

これはもちろん開発対象のプロダクトやチームとの相性に依る部分もあるけど、Reduxはかなり記述が増えて、重厚なイメージがある。

もっと軽い感じで使いたい。

このあたりはVuexはとても良くできていて、理解もしやすい。

MobX

mobx.js.org

というわけで、ここ最近MobXを使ってみているが、これはいいな〜という手応えを感じている。

クラスベースでの定義

たとえば、Reduxで一通りのフローを作る場合

  • ActionCreator
  • Reducer
  • Store

あたりを書くことになるが、ファイルがめっちゃ増えたり、Reducerがなんかとんでもない感じに成長してしまうみたいなことがあった。Reducerがとにかくやばい。

MobXの場合は、状態管理をしたいクラスからインスタンスを作成して利用する。こんな感じ。

import axios from 'axios';
import { action, observable, toJS } from 'mobx';

export class Item {
  @observable public name: string = '';
  @observable public image: string = '';

  @action.bound
  public async save(): Promise<void> {
    const res = await axios.post('/items', toJS(this));
    return res.data.id;
  }
}

これが非常にわかりやすく、

  • 状態は @observable 定義のインスタンス変数
  • 処理や状態変更は @action.bound のメソッドを実行

だけで、非常にシンプル。実質ただのインスタンス

そしてTypeScriptと組み合わせた場合に嬉しい効果として、クラス定義されているので、別途型定義を書く必要がない。

Reactと組み合わせる場合にはこれが非常に嬉しく、Propsにクラス定義を指定しておくだけで、そこに紐づくプロパティが全部型チェックできる。

これが出来るかどうかということより、「意識せずに簡単にできる」というのが重要で、書いてて幸せな気持ちになれた。

Reactと組み合わせる

Reactと組み合わせる場合は mobx-react を使う。
このあたりの初期化周りは react-redux を使った場合と似てる。

ただ、ストアにするインスタンスを作って突っ込んでるんだな〜っていう感じで、わりとわかりやすい。

import { Provider } from 'mobx-react';
import { Item } from './Item';

const store = {
  item: new Item()
};

const App = () => (
  <Provider {...store}>
    ...
  </Provider>
);

export default App;

参照する場合には@inject を使う。 react-reduxのときの connectに近い。

import { inject, observer } from 'mobx-react';
import * as React from 'react';
import { Item } from './Item';

interface Props {
  item: Item;
}

@inject('item') @observer
export default class ItemContainer extends React.Component<Props> {
  public render() {
    return (
      <React.Fragment>
        <div>{this.props.item.name}</div>
        <ImageComponent item={this.props.item} />
        <button onClick={() => this.props.item.save()}>save</button>
      </React.Fragment>
    );
  }
}

Actionの呼び出しは、直接インスタンス変数のメソッドを呼び出してる。

この部分。

<button onClick={() => this.props.item.save()}>save</button>

これについては賛否両論あるかもしれない。

Reduxを使っていた場合に、子コンポーネントのReduxへの直接依存を避けるためにメソッドもPropsリレーをしてコードを書いたことがあるが、往々にしてバケツリレー地獄になって、最後には辛いなという気持ちになってた。

結局、「ActionをDispatchするのだけは子コンポーネントからでもOK」みたいになることも多くて、それと同じことをやってる感じ。

ただ、TypeScriptを組み合わせることで、どのメソッドがどのコンポーネントで呼ばれているのかを静的チェックすることができるので、これが非常に強力で、Actionがどこで実行されてるのか把握でき、リファクタリングをする際もある程度ビビらずに直していける。

TypeScriptと使う

というわけで、多々TypeScriptと組み合わせた場合のことを書いた通り、TypeScriptとの相性がかなり良いっぽい。

React+MobXだけで書いた場合には、「簡単に状態管理が出来てうれしい!」くらいで終わりそうだが、TypeScriptを組み合わせることで、クラスベースであることが一気に強力な機能に変わるような印象を受けた。

まとめ

というわけで、React+MobX+TypeScriptがかなり良さそうですよっていうのをどこかに書きたくて書いてみた。

やりたいことを「意識せずに」「簡単に」できるのが大事で、そのあたりを今の所ちょうど良い具合で実現してくれるのがこの組み合わせだな〜と感じてる。

良いことばかり書いてるが、趣味で書いている程度なので、大規模プロダクトの場合や、パフォーマンスが要求される場合などはどうなの?とか色々考えれてない部分はある。

けど、書いてて楽しいので、細かいことはいいやってなってる。

同じことを感じている人がいることに期待している。

とはいえ、Vueはやっぱり使いやすい。pugとかに慣れてると、JSXでclassNameって書くたびにアアアアアア!!!!ってなるし、気をつけててもネストが深くなりがちなのはつらい。

そもそもこれを試したのは「Vue+Vuex (or Nuxt) でTypeScriptを使いたいと思ったけど辛かったから」というのが大きいので、逆に言えばそちらが快適に書けるようになれば、そちらを使うことが多くなりそうではある。

ともあれ、何が一番良いかとかではなく、都度状況を見ながら最適なものを選べるようになっていきたいと思った。

終わり。

Toyama.rbで「オフラインリアルタイムどう書く」をやってみたらとても良かった

Toyama.rb #30のイベントとして、「オフラインリアルタイムどう書く」というのをやった。

toyamarb.connpass.com

とても楽しかったし、参加者の方からも好評だった。

オフラインリアルタイムどう書く

横浜へなちょこプログラミング勉強会さんで実施されているものがオリジナルのイベントです。

趣旨としては、主催者の出した問題を皆で解きましょう!といったもの。

先日のRubyKaigi AfterPartyで@yanchaさんと同じテーブルになる機会があり、そのときに「勉強会のネタに困ってるんですよね〜」という話をしたところ、オリジナルのイベントについて教えていただいた。

Toyama.rbではもくもく会を中心に活動しているが、たまにはテーマを決めてコードを書きたいな〜とも思っていたので、内容的にもピッタリで、とても楽しそうなのでやってみることにした。

当日の流れ

基本的な流れは

  1. 問題を参加者に共有
  2. 同時に、1時間のタイマーを全員が見える形でスタート
  3. 1時間後、トランプを配る
  4. 数の小さい人から発表。解けてなくても、どう挑んだか?みたいなとこを発表する
  5. 「解けた!」という人は最後に発表してもらう

といった感じにした。

2問用意し、だいたい1問につき2時間半程度でやった。

オリジナルである横浜へなちょこプログラミング勉強会さんで開催されている際の流れでは、最後に主催者側が事前に用意していた解答例を最後に解説する形とのこと。

ただ、私自身も楽しみたい!!!という思いから、問題自体は過去問題を利用させていただき、そのかわりに、解けた人に最後に発表してもらう形としてみた。

1時間で誰も解けなかったら少し延長しようかな?と考えていたものの、2問用意していたが、2問ともに1時間できっちり解ける参加者が現れたので、とてもちょうどよかった。

1問目 / フォークじゃない

フォークじゃない 〜 横へな 2014.2.1 問題

1問目は

  • (ほぼ)全員が初挑戦
  • 勝手がよくわからないで戸惑いそう
  • 解ける、あるいは解けそうな感じが見える

のほうが良いかな〜と思っていた。

過去問題には、主にテキストのみで出題されているものと、イメージ・図形が絡む問題のざっくり2種類があった。テキストのみのほうが簡単なのかな?と思い、その中でもスーパーのレジを題材にしている問題がキャッチーな感じだったので利用させていただいた。

結果

1名のみ時間内に完成した。

私はあるバグにハマってしまい、ギリギリ間に合わなかった。その後の発表の最中で気づき、1行だけ追加したら全部通ったのでめっちゃ惜しかった、くやしい。

ちなみに、私が書いたのはこんな感じになった。

2018/7/14 Toyama.rb #30 オフラインリアルタイムどう書く · GitHub

1問目の所感

そもそも「文章の理解にめっちゃ時間が..!!」みたいになってて面白かった。

問題文自体を理解するまでがまず最初の課題だった。

また、コードを書く順番も人によって全然違っていて、一気に作ろうと思うとハマる要素が多かった。

この問題については、

  • 1-9 が来たらレジに並ぶ
  • . が来たらレジが処理をする
  • x が来たらそいつは後ろの客をせき止める

といった形で、段階的に機能追加が可能だったので、そこに気付いてTDD的に作っている参加者がわりと惜しいところ、あるいは正解まで辿り着いていたように見えた。

ただ、どんな言語でもレジに並んでいる人数を算出・計算する部分が少しややこしくなるようで、私も作ってて混乱した。

唯一解けた人は、レジの人数を数値で保持するのではなく、例えば5人なら nnnnn といった形でその分の文字を確保することで、単純に文字列長を見るだけでレジ人数が取れるようになっていた。

1時間という制約の中ではまったく思いつかなかったので、聞いてて素直に「ほ〜〜なるほど〜〜」となっていて、同じ問題でこうも違う解き方になるんだなぁと、聞いてて楽しかった。

あと、Rubyが強い。

Array周りの標準ライブラリの充実度が半端ではないので、配列操作が絡んだときのコードの簡潔さは圧倒的だった。

2問目 / 積み木の水槽

積み木の水槽 〜 横へな 2013.9.6

2問目は、画像が使われてる問題を利用させていただいた。

なんの根拠もないが、画像が絡んでるとそれだけで難しそうに見えたので、「最悪全員解けないのも面白いかな」ということで、この問題にした。

結果

当日は7名の参加者がいて、そのうち1名が中学生プログラマだったのだが、その方のみが鮮やかに解答し、その他普段お金をもらってコードを書いてる大人の皆さん全員が解けないという楽しい結果になった。私自身も1問目とは異なり、もう全然だめだった。

一応、私の書いた無残なコードを残しておく。

2018/7/14 Toyama.rb #30 オフラインリアルタイムどう書く · GitHub

2問目の所感

めっちゃ難しかった!

やってて難しいと思ったポイントは、

  • そもそもどうアプローチしていいかわからない
  • 配列のインデックスがわけわからなくなる

だった。

まず、何をどう考えたらいいのかがわからず、コードを書き始めるまでに15分くらい要してしまった。もうこの時点で私の勝敗は決していた感がある。

また、Webサービスのためにコードを書いている中では、実際のところ二次元配列をX/Y軸を意識しながらゴリゴリに回しながら操作するというのは、そこまで登場頻度は高くはないと思う。

コードを書きながら「あぁ、昔こんな感じにJavaテトリス作ったな..」と懐かしい気持ちになりつつ、完全に混乱していた。

なお、こちらの問題は、1問目よりもさらに他の人の発表を聞くのが楽しかった。

1問目よりも人によってアプローチの差異が大きく、

  • 水が入る場所を1列単位で考えて割り出す人(正解者)
  • 水が入らない死んでいる空間を割り出して、水が残る箇所を逆算しようとする人(私)
  • 全空間に水を入れてから、ライフゲーム的に水を流動させようとする人

と、多種多少だった。最後のライフゲーム的に動かすというのは微塵も思いついてなかったので、すげーなーと思いつつ、実際に完成したものがグラフィカルに描画されるのを見てみたかった。

感想

とても楽しいイベントで、かなり盛り上がった。

2問目で中学生の方が見事に正解コードを書いたあとに、コードの内容に興味津々な大人達が質問しまくって説明してもらって「おー、なるほど!!」となっていて、年齢とか仕事とか関係なくワイワイやれてる感じがあってとても良かった。

そして大人の自分としては「精進せねば...!!!」という気持ちになった。

参加者の方からも「たのしかった!」という声が聞けたので、ぜひともまた開催したいな〜と思う。

その時は、今度はオリジナルの問題を用意したいところだ。


最後に、イベントについてご紹介していただいた@yanchaさん、そして、当日の進め方等について教えていただいた鍋谷さん、ありがとうございました!

リモートワークで昼に冷凍食品食べ続けたのでオススメを紹介する

リモートワークで家で仕事をするようになってから1年以上が経過した。

リモートワーカー昼飯問題

リモートワークをしていると困るのがお昼ご飯。

選択肢としてはいろいろあって、聞いたことがあるものとしては

  • 夕飯の残りを食べる
  • 普通に自分で作る
  • 外で食べる
  • コンビニとかで買う

などがある。

冷凍食品

私は基本的に冷凍食品を食べることが多かった。

栄養が〜!という考えもあると思うけど、個人的には昼食にそもそも栄養バランスなんて期待してないので、他の部分を重要視している。

いろいろメリットがあって

  • 外に出なくていいので楽
  • ほぼ温めるだけでよく、温めてる間に洗濯物を畳んだりできる
  • 安い
  • 最近のものは美味しい

といった具合。

一年も続けると、それなりに種類も食べてきたので、個人的なオススメを紹介したい。


日清具多 辣椒担々麺

https://images-na.ssl-images-amazon.com/images/I/617l3OKqreL.jpg

出典:www.amazon.co.jp

www.nissin.com

これをオススメしたくてこの記事を書いてると言っても過言ではない。

具材のひき肉も結構たくさん入ってて、かなり美味しい。
山椒みたいのが入ってるが、もともとの状態で結構辛いので入れなくてもいいかも。

鍋を用意する必要もなく、電気ケトルとかで300ccのお湯を沸かすだけでいい。

辛いのがだめじゃない人は是非一度買って食べてみてほしい。

セブンイレブンのつけ麺

f:id:mugi1:20180623211549j:plain

チャーシュー盛つけ麺 - セブン-イレブン~近くて便利~

コンビニの冷凍食品もかなりレベルが高くて、最近のはとても美味しい。

特にこのつけ麺が美味しかった。

麺が太麺で結構食べごたえもあるし、つけだれも魚介ベースな感じが美味しい。

これも、つけだれ用のお湯を少し沸かすだけでいいので作るのが楽でいい。

炒飯の極み[えび五目XO醤]

f:id:mugi1:20180623212814p:plain

出典:www.amazon.co.jp

炒飯の極み[えび五目XO醤]|冷凍食品|商品情報|マルハニチロ株式会社

冷凍チャーハンは人によって好みが分かれそうだな〜とは思うけど、個人的にはこれをオススメしたい。

他の冷凍チャーハンはチャーシューだったり、ガツンとした味付けみたいなところで推してるのが多いが、これは珍しくエビがゴロゴロ入ってるタイプ。

(私がエビ好きだっていうのもある)

海鮮感が嫌いじゃない人は食べてもらうといいかも。

ただ、以前Toyama.rbの年末LTでソニックガーデンの木原さんも同じことを言っていた記憶があるが、基本的に冷凍のチャーハンはだいたいうまい。

とりあえずチャーハンがあれば安心感がある。

他のだとこのへんが安定して美味しい。

具材の大きさとか本格っぽさであればコレがかなりすごかった。

が、これは八角の香りが結構強く、人によっては好き嫌いがあるかも。

私は大丈夫だったのでたまに食べてる。

オーマイプレミアム 海の幸のペスカトーレ

f:id:mugi1:20180623220249p:plain

出典:www.amazon.co.jp

www.nippn.co.jp

パスタであればこれがオススメだった。

パッケージを見てもらうとわかるが、具材がすごい。ゴロゴロ入ってる。

そして、これはトレーに入った状態で冷凍されているので、お皿がいらないのもありがたい。洗い物が減る。

味も美味しい。

まとめ

というわけで、

  • 麺(汁あり)
  • つけ麺
  • チャーハン
  • パスタ

の4つを書いてみた。これらは本当によく食べているので、食べたことがなければ是非1回食べてみてほしい。

が、どれだけ美味しかろうとも同じものを食べていると飽きる。

なので、なんかオススメあったら教えてください。(これを言いたかった)

リモートワークしてて気になってきたのでデスク周りのケーブルをスッキリさせた

気がつけばリモートワークを始めてから1年経過していた。

最初にデスク周りは一旦整理したけど、1年もあると色々モノも増えたりしてきて、結構ゴチャゴチャとしてきていた。

特にケーブル周りがひどく、たとえば電源ケーブル等についてはケーブルボックスに収納して見えなくしていたけど、

  • ケーブルボックスから距離があるもの
  • ケーブル自体の長さが中途半端なもの
  • 定期的に抜き差しするためボックスにしまいたくないもの

については適当な状態で、長い間見てみぬフリをしてた。

(以前の状態) f:id:mugi1:20180408151130j:plain

この写真の通り、そのまま垂れ下がってるような感じ。

見えないからいいんじゃね?という考えもできそうだけど、

  • 掃除機をかけるときに邪魔
  • 仕事中に足にケーブルがあたって鬱陶しい
  • なんかイヤ

という問題があるので、気合を入れて整理することにしてみた。

最終的にどうなったか

こうなった

f:id:mugi1:20180408164808j:plain

めっちゃスッキリした。

ポリシー

  • がんばりたくない(コードを1本ずつ固定するとかはやりたくない)
  • ケーブルが増えたり減ったりしても楽に対応したい
  • 雑でいいので、いい感じになってほしい

使ったもの

ネオジム磁石

  • 100均で売ってる

f:id:mugi1:20180408135140j:plain

ステンレスシート

  • ホームセンターに売ってる
  • 1枚700円くらい
  • 買った磁石を持っていって、ちゃんとくっつくものを買う

f:id:mugi1:20180408135058j:plain

ワイヤーネット

  • 100均
  • こちらも磁石がくっつくものを買う
  • ⇣こんな感じのやつ

アイリスオーヤマ メッシュ パネル MPP-6090 ブラック

マスキングテープ

  • 100均
  • なんでもいいけど、磁石より幅があるといい

f:id:mugi1:20180408223200j:plain

普段外さないケーブル類の整理

ワイヤーネットを使って、ケーブルをすべて机の裏に抑えつける。
固定自体は磁石のパワーで雑にやれる。

ステンレスシートを机の裏に貼る

それっぽい場所に適当に貼る

f:id:mugi1:20180408222821j:plain

ワイヤーネットにネオジム磁石を貼り付ける

適当でいい。磁石が強いので勝手にくっつく

f:id:mugi1:20180408154502j:plain

ケーブルをガッと掴んでガッとワイヤーネットに乗せる

ガッとつかんで

f:id:mugi1:20180408222854j:plain

ガッとのせる

f:id:mugi1:20180408222913j:plain

ワイヤーネットごとステンレスシートにガッとくっつける

ネオジム磁石の力によって、適当にやってもいい感じにケーブルごと止まる

f:id:mugi1:20180408222933j:plain

ケーブルが増えたら、ワイヤーネットを引っ張れば簡単に外れるので、 そこにガッとケーブルをねじこんでガッと止めれば終わり。簡単。

取り外すことのあるケーブルの固定

これはお好みでやればいいと思う。

私は磁石が余ったのでやった。

マスキングテープで磁石を挟む

こんなのいくつか作る

f:id:mugi1:20180408223023j:plain

  • マスキングテープ2切れの粘着面で磁石を挟む
    (粘着面同士を貼る。ステンレスシートに磁力でくっつける。)
  • 両端に1個ずつ磁石をいれる
  • どうせ見えないし適当でいい

ステンレスシートにくっつけてケーブルを挟む

こんな感じ。

f:id:mugi1:20180408223047j:plain

簡単に取れるので、ケーブルの取り外しも簡単。

f:id:mugi1:20180408223032j:plain

壁にケーブル止めるやつを作る

これでもまだ磁石が余った。(買いすぎ)

なので、もう何も考えずに、椅子から手がとどく位置の壁にマスキングテープで貼った。

イヤホンとかマイクロUSBケーブルとかがくっついて便利。

f:id:mugi1:20180408231521j:plain

以上です

だいぶスッキリした。

簡単なのでオススメです。

SlackBotをGUIでポチポチしていい感じにするツールを作ってる

年末年始の休みぐらいから、SlackBotをGUIでポチポチするだけでいい感じに作れるようなツールを作ってる。

画面でポチポチ作ると

f:id:mugi1:20180128144304p:plain

こんな感じで動く

f:id:mugi1:20180128144705p:plain

とりあえずある程度動くようになったのでリポジトリ公開しといた

github.com

動機

SlackBotについて考えてみたときに、以下のようなことだけ簡単に実現できて自由に組み合わせることが出来れば、全部じゃなくとも80%くらいのやりたいことは実現できるんじゃね?とある日思った。

  • 実行のトリガー
    • 何らかのキーワードで実行(@bot 次の予定 みたいな)
    • スケジュールして一定時間ごとに実行
    • 外部からのWebRequestで処理を実行
  • トリガーで処理すること
    • どっかにWebRequestを投げて結果をもらう
  • 最後にやること
    • どっかにWebRequestを投げる(投げ捨てる)
    • 何か結果をチャンネルに返す

すでにBotを構築するツールはいっぱいあると思うけど、GUIでポチポチやって簡単に作れたら楽だな〜というのと、なんでもいいのでNuxt.jsを使ってなんか作りたかった、というのがあって、作ってみることにした。

使ったもの

  • Nuxt.js
  • express : APIサーバ兼、Nuxt.jsの動作用
  • mongo : データ保存用。雑にmongooseでアクセスしてる
  • element-ui : 見た目はこいつに任せてる
  • axios : 外部へのWebRequest発行用
  • node-schedule : スケジュール実行用

でだいたい動いてる。

深い意味や理由はまったくないが、全部jsで書いてみようと思ったのでexpressとかを使ってる。

概念的な話

以下のような名前をつけて取り扱ってる

  • Flow : 一連の処理の流れ
  • Trigger : Flowを起動するためのきっかけとなるもの。Flowにつき1つ。
  • Action : Triggerによって実行される一連の処理。Flowにつき任意数。
  • Finisher : Flowの最後にやりたい処理。Flowにつき1つ。

ActionやFinisherは1つまえに実行されたものから何らかのパラメータを受け取って自由に扱う。

たとえば上に貼った画像の例ではToyama.rbのイベント情報をチャンネルに返してるが、これはActionで行ったaxiosのレスポンスが次のFinisherに渡されてるので可能になってる。

(ちなみにFinisherではlodash#template形式で雑にテンプレートを書けるようにしてみた)

Slackへの依存をいかに切り出すか

Slack以外のツールにも対応させたいな〜みたいな思いがあったので、Slackに依存する部分とのやりとりはすべて events 経由で間接的に行ってる。

具体的には、Botの動作全体を管理するクラスでは

module.exports = class Bot {
  constructor (client) {
    this.client = client;
  }

  start (configuration) {
    this.client.start(configuration);
  }

  stop () {
    this.client.stop();
  }

  bindMessageTriggerFlow (flow) {
    this.client.on('messageReceived', (data) => {
      if (!data.actionName || data.actionName !== flow.trigger.data.word) return;

      this.executeAllActions(flow, data);
    });
  }


  ...

みたいにして、client がSlackかどうかは関係なく、特定のinterfaceを持ってるかどうかだけ気にするようにしてみた。

client自体は

const EventEmitter = require('events').EventEmitter;

module.exports = class BaseClient extends EventEmitter {
  start (state) {
    throw new Error('not implements error');
  }

  stop () {
    throw new Error('not implements error');
  }

  emitMessageReceived (message = {}) {
    this.emit('messageReceived', message);
  }

  sendMessage () {
    throw new Error('not implements error');
  }
};

みたいなBaseクラスを継承させていて、Chatサービスに依存する実装はサブクラスに丸投げしてる。

定期実行の方法

cron形式で定期実行できるようにしたかったんだけど、その実現に node-schedule を利用させてもらった。

github.com

さくっと動いて便利だった。

気をつけないといけなかったのが、当たり前だけど、Botを止めてもScheduleは止まらない。そのあたりの実装忘れてて、テスト用チャンネルが地獄みたいなことになってた。

f:id:mugi1:20180128152458p:plain

これから作ろうと思ってるもの

  • Herokuのデプロイボタン置く
  • Finisherの概念って実はいらないのでActionに統合する
    • 冷静に考えると最後のActionをFinisherにすればそれでいいので、Finisherなんていらんかったんや、ってこのエントリ書いてて気づいた。
  • ランダムなURLを発行して、それをTriggerにする
  • 認証
    • URLさえ知ってればokみたいになってるので、超簡易的な認証をつけときたい
  • 他のChatツールに対応したい
    • なんとかなると信じてる

まだ途中だけど、作ってる感想としてはNuxt.jsがとても楽でいい感じ。 router書かなくて良いのは最高です。

Nuxt.jsのbuild&startが何をやってるのかをコードから追ってみる

以前、Herokuで簡易的なNuxt.jsのアプリケーションのSSRを確認するエントリーを書いた。

mugi1.hateblo.jp

書いたはいいけど、で...

なんでこれ動いてんの?

デプロイできたやった〜と言いたいところだけど、あまりにも動作がブラックボックスすぎるので、一体何がどうなっているのかを調べてみることにする。

Herokuへのデプロイ

node.jsアプリケーションをHerokuにデプロイする際は、デフォルトではpacakge.jsonに記載のstart scriptが実行される。

(参考: Deploying Node.js Apps on Heroku | Heroku Dev Center)

そしてさらに、デプロイ時には以下の設定を追加する必要がある。

 "heroku-postbuild": "npm run build"

つまりデプロイ時には以下の流れでビルドが実行されていることがわかる。

  1. nuxt build
  2. nuxt start

オフィシャルのガイドによると

  • nuxt build - アプリケーションを Webpack でビルドし、JS と CSS をプロダクション向けにミニファイします
  • nuxt start - プロダクションモードでサーバーを起動します(nuxt build 後に実行してください)

とのこと。

調べること

というわけで、以下を調べてみることにする。

  • next build では何をビルドして何を作っているのか
  • nuxt start では何を起動していて、どこでSSRをやっているのか。

nuxt スクリプト

そもそも nuxt というスクリプトのコードを確認してみる。

nuxt.js/nuxt at dev · nuxt/nuxt.js · GitHub

色々やってるけど、とりあえず

const bin = join(__dirname, 'nuxt-' + cmd)

という箇所があり、cmd の部分に指定したオプションが渡る。

つまり、nuxt-buildnuxt-start といったスクリプトが実行される。

nuxt-build

nuxt.js/nuxt-build at dev · nuxt/nuxt.js · GitHub

オプションのロードなどの後に、以下のようなコードがある。

...

if (options.mode !== 'spa') {
  // Build for SSR app
  builder.build()
    .then(() => debug('Building done'))
    .catch((err) => {
      console.error(err)
      process.exit(1)
    })
} else {
  ...

options.mode !== 'spa' という記述があるが、Nuxt.jsではSSRを使わないアプリケーションの構築もサポートしているので、その場合にビルド方法が変わるものと思われる。

https://ja.nuxtjs.org/guide#シングルページアプリケーション-spa-

デフォルトではSSRは有効なので、どうやら builder.build() がビルドの本体のようだ。

実態は Builderというクラス。コード的にはこのあたり。

https://github.com/nuxt/nuxt.js/blob/dev/lib/builder/builder.js#L107

処理の中で特に重要そうなものだけピックアップすると

  • this.nuxt.ready()
  • this.generateRoutesAndFiles()
  • this.webpackBuild()

かな〜と思われる。

this.nuxt.ready()

実体は Nuxt#ready が該当する。

https://github.com/nuxt/nuxt.js/blob/dev/lib/core/nuxt.js#L51

内部ではさらにModuleContainer#readyRenderer#readyを呼び出しており、デフォルトのものに加えて、任意に追加されたミドルウェアやモジュールをロードしている。

this.generateRoutesAndFiles()

webpackでビルドする前のファイルをテンプレートをもとに生成している。

例えば、pages/ の配下にコンポーネントを突っ込むだけで自動的にルーティングが生成されるのは、このあたりで動的にファイルを取得した上でテンプレートファイルからvue-router向けのjsファイルが出力されている。

pages/ に限らず、Nuxt.jsがいい感じにに解決してくれているものの多くがlib/app 配下にテンプレートとして用意されているっぽいので、生成されるファイルの元となるものが見たい場合はここを参照するとよさそう。

https://github.com/nuxt/nuxt.js/tree/dev/lib/app

this.webpackBuild()

みんな大好きwebpackビルド。

lib/builder/webpackの配下にwebpackビルドで必要なコンフィグファイルがまとまっている。

クライアント側とサーバ側でエントリーファイルが異なるので、client.config.jsserver.config.js が存在しており、それぞれについてビルドが実行される。

最終的な成果物はデフォルトだとプロジェクトルートから見て .nuxt/dist 配下に出力される。

nuxt-start

nuxt.js/nuxt-start at dev · nuxt/nuxt.js · GitHub

Nuxt#listen を実行している。

実行前に .nuxt/dist や、SSRを行う場合には .nuxt/dist/server-bundle.json の存在をチェックしているので、あらかじめ nuxt build が実行されていないと落ちる。

Nuxt#listen

https://github.com/nuxt/nuxt.js/blob/dev/lib/core/nuxt.js#L124

this.renderer.app.listen を実行しており、この app の実体は connectインスタンス。これで実際にNuxt.jsアプリケーションが待ち受け状態になる。

github.com

サーバサイドでのルーティングについては、初期化の過程でRenderer#readyがコールされており、そこからさらに辿ると以下のコードが見つけられる。

// Finally use nuxtMiddleware
this.useMiddleware(this.nuxtMiddleware.bind(this))

useMiddlewareは、connectインスタンスに対してMiddlewareを登録している。

// Use middleware
this.app.use(path, handler)

nuxtMiddlewareについては、内部で renderRoute をコールしており、この中で実際にレンダリングしたHTMLを返却している。

// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well)
let APP = await this.bundleRenderer.renderToString(context)

bundleRendererにはvue-server-renderer#createBundleRenderer で生成されたバンドルレンダラがセットされており、.nuxt/dist/server-bundle.json が元になっている。細かいところまで追えてないが、URLとのマッピングはこのへんで解決しているものと思われる。

というわけで

コードを追ってみることで、事前のビルドで何をしていて、どのあたりでサーバサイドでHTMLを生成しているのかはなんとなくわかった。(ような気がする。)

深く読み込むというよりかは、大体の流れを追うようにコードを見ていったので、もしかすると一部で間違いとかもあるかもしれない。そうだったらスイマセン。

とりあえず、自分の中で完全に闇だった部分が少しわかったのは良かった。

感想としては、コードを見ていると「よくこんなの作ったな...」という気持ちになってきた。OSSコントリビュータに感謝しよう。