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コントリビュータに感謝しよう。

parcelでビルド結果から別ファイルを出力するプラグインをつくるまでの道のり

Misoca Advent Calendar 2017 - Qiita : 18日目のエントリーです。


以前、RailsでWebpackerをparcelに置き換えるにはどうしたら?という記事をQiitaに書いた。

qiita.com

この中で、しれっと以下のようなことやっている。

  • ビルド結果からfingerprint付きのファイルを出力したい
  • マッピングが定義されたjsonファイルを吐き出したい
  • というプラグインを作った

実際に作ったプラグインはこれ。

github.com

コード自体は大したことないが、作成する際にドキュメントに書かれている方法だけでは厳しく、parcel自体のコードを読むことでそれっぽいものが作れたという経緯があった。なかなか面白かったので完成までの道のりを記事にしてみる。

欲しかったもの

元の記事にも書いてある内容と同じになりますが、簡単に言うと entry.jsというファイルをビルドした場合に、

  • entry-aUjc10lx83jjXwlechKSaxa3Le9kSjfe.js
  • manifest.json

のようなファイルを吐き出し、manifest.jsonには

{
  "entry.js": "entry-aUjc10lx83jjXwlechKSaxa3Le9kSjfe.js"
}

が記録されている、みたいなことをしたい。

プロダクションで利用する際に、ビルド結果に差分があった場合にキャッシュではなく新しいファイルを参照するように、fingerprintが付与されたファイルを配信したいが、都度scriptタグのsrcを書き換えるのはメンテナンスのコストが高く、別途jsonファイルにマッピングを記録し読み替えるようにしたい、といった具合。

つまり、

  • View上 : 何らかのヘルパー経由でentry.jsを指定
  • 何らかのヘルパー : manifest.json経由で実際に読み込むjsファイルを解決
  • ブラウザ : entry-aUjc10lx83jjXwlechKSaxa3Le9kSjfe.js をロード

といった流れになる。

parcel自体の機能で実現できないか?

そのような機能はまだサポートされていないように見えた。

オフィシャルドキュメントのProductionビルドのあたりを見てみると、htmlからjsをロードした状態とし、htmlをビルド対象とすることでjsのファイル名をユニークなものにしてくれる、いった機能はすでに提供されているっぽい。

SPAを構築する場合などではこれで充分そうだけど、今回の目的とは少し違うようだった。残念。

参考: https://parceljs.org/production.html#set-the-public-url-to-serve-on

自力でなんとかする

ポイントとしては、 「ビルド結果が出力された後の時点で実行したい」といった一点かと思う。

parcelのオフィシャルドキュメントを見ていると、3つの拡張方法が存在するようで、以下のように理解した。

  • Asset
    • ファイルに応じてパース、依存解決、transformなどを行い、変換後コードを生成する
  • Packager
    • 出力ファイルタイプに応じて、Assetで変換したものを取り纏めて実際のファイル出力を行う
  • Plugin
    • parcelビルドを非標準的な方法で自由に拡張する場合に利用する

単純に扱えるファイルの種類を増やしたい場合などはAsset/Packagerを利用するのが正しいアプローチのようだが、今回実現したいことにはファイルタイプは関係なく、純粋に出力結果となるファイルのみを考慮したいので、Pluginを利用するのが正しそう。

プラグインを作る

まずはドキュメントに記載されているプラグインの説明を見てみる。 (https://parceljs.org/plugins.html#plugin-api)

Parcel plugins are very simple. They are simply modules that export a single function, which is called by Parcel automatically during initialization. The function receives as input the Bundler object, and can do configuration such as registering asset types and packagers.

Publish this package on npm using the parcel-plugin- prefix, and it will be automatically detected and loaded as described below.

記載されているサンプルコードも抜粋。

module.exports = function (bundler) {
  bundler.addAssetType('ext', require.resolve('./MyAsset'));
  bundler.addPackager('foo', require.resolve('./MyPackager'));
};

上記を見るからに、単一のプラグインをpackage.jsonに追加するだけで、一気に対応するサポートファイルを追加したりすることができるようだ。

これはこれでとても便利そうですが、Asset/Packageは今回は使わないので、違うアプローチを取る必要がありそう。

では、引数で与えられている bundler が一体何者なのかを追ってみる。

Bundler

実際のコードは以下。

https://github.com/parcel-bundler/parcel/blob/master/src/Bundler.js

Bundlerクラス自体は、CLIから実行した際にインスタンス化されており、外部からはbundleメソッドのみがコールされる。

parcelによるビルド処理本体の起点であり、本体といっても良さそう。

ただ、addAssetType/addPackagerといったメソッドは存在するが、他にそれらしいメソッドは存在せず。どうやってビルド終了時に処理を実行させれば良いのだろう?

そこで、ポイントとなるのはクラス定義となる部分。

class Bundler extends EventEmitter {

nodejsを利用している方ならお馴染みの、EventEmitterを継承している。 (https://nodejs.org/api/events.html)

つまり、プラグインが受け取るbundlerは何らかのタイミングでイベントをemitしてくる(かもしれない)ことがわかる。

emitしている箇所

以下の2つが該当する。

  • this.emit('buildEnd'); - code
  • this.emit('bundled', bundle); - code

ビルド終了時に処理をしたいので、buildEndかな?と思ったけど、buildEndは成功・エラーにかかわらずemitされてしまう模様。

引数のbundleを参照することで引数に指定した出力ディレクトリなども動的に解決することが出来るので、bundledを参照するのが正しそう。

最終的に完成したプラグインのコード

というわけで、上記を元に作ったプラグインが以下。それほど大きくないので全部貼ります。

const path = require('path');
const hasha = require('hasha');
const fs = require('fs');

module.exports = function (bundler) {
  const logger = bundler.logger;

  const readManifestJson = (path) => {
    if (!fs.existsSync(path)) {
      logger.status('✨', 'create manifest file');
      return {};
    };

    logger.status('🖊', 'update manifest file');

    try {
      return JSON.parse(fs.readFileSync(path, 'utf8'));
    } catch(e) {
      logger.error('manifest file is invalid');
      throw e; 
    }
  };

  bundler.on('bundled', (bundle) => {
    const dir = path.dirname(bundle.name);

    logger.status('📦', 'PackageManifestPlugin');
    logger.status('📁', `     dir : ${dir}`);

    const f = bundle.name;
    const hash = hasha.fromFileSync(f, { algorithm: 'sha256' });
    const ext = path.extname(f);
    const basename = path.basename(f, ext);
    const hashFile = path.join(dir, `${basename}-${hash}${ext}`);

    logger.status('✓', `  bundle : ${bundle.name}`);
    logger.status('✓', `        => ${hashFile}`);    

    // create hash included bundle file
    fs.createReadStream(f).pipe(fs.createWriteStream(path.resolve(dir, hashFile)));

    const manifestPath = path.resolve(dir, 'parcel-manifest.json');

    logger.status('📄', `manifest : ${manifestPath}`);

    const manifestValue = readManifestJson(manifestPath);
    manifestValue[path.relative(dir, f)] = path.relative(dir, hashFile);

    fs.writeFileSync(manifestPath, JSON.stringify(manifestValue));
  });
};

parcel-plugin-bundle-manifest/BundleManifestPlugin.js at master · mugi-uno/parcel-plugin-bundle-manifest · GitHub

だいたい以下のような流れで処理してる

  • bundler.on('bundled', ... が処理の起点
  • bundle.name で出力されたファイルへのパスを取得する
  • ファイル内容からhashを作成し、fingerprint付きのファイルをコピーする
  • manifest.jsonファイルを作成する。すでに存在する場合はマージする
  • manifest.jsonファイルを書き込む

終わってみれば大した内容ではない。

あとは、名称を parcel-plugin-xxx といった形でnpmに公開すると、parcelビルド時にpackage.jsonのdependenciesを元に自動的にプラグインが実行されるようになる。

最初は parcel-plugin-manifest とかにしようかと思ったけど、私が一等地っぽい名前を取ってしまうのは如何なものかと思ったので、parcel-plugin-bundle-manifestにした。

www.npmjs.com

作ってみて

EventEmitter周りとかは後々追記される可能性もありそうだけど、ドキュメントだけではなく、コードも見るのが大事だよな〜と改めて感じた。

プラグインを作る過程の中で、基本的な動作とかもなんとなく理解できるようになってくるので、とても勉強になってよかった。

manifestファイルの吐き出しについては、もしかすると将来的にparcel自体の機能としてサポートされるようになるかもしれない。自分としてもそちらのほうが安心なので、ぜひ期待したい。

parcelについて

少しズレるけど、parcel自体についての個人的な所感。(個人的なものです)

まるで設定なしで魔法の如く動いているように理解してしまいそうだけど、実際にはwebpackのloaderなりpluginなりがやってくれていたことを裏側で隠蔽して意識しないで良いようにしてくれているだけなので、フロントエンドのビルド周りで理解しないといけないことが格別減るというわけではないと思う。

(知らないまま使っても良さそうだけど、ハマったときに即死しそう。)

でも、ゼロコンフィグで動かそうというのはとても快適に感じたし、cssファイルをエントリーに取ってビルドできる点などは、webpackのExtractTextPluginにつらみを感じていたので「そうそう、これがやりたかったんだよ!!」という気持ちになった。

これからも出来ることは増えていくだろうし、webpackに取って代わる日が来るのも有り得るかもしれない。

とりあえずどうなっても良いように、webpackへのガッツリ依存している部分を少しずつ外しておくことで、いつか幸せになれる日が来ることを信じている。


さて、明日は弊社代表の@toyoshiがラズパイで頑張った話を書いてくれるようです。

前回のエントリが400近いブックマーク数になるほど読まれていたので、今回も注目ですね!!

Nuxt.jsとHerokuでお手軽にSSRを体験する

これは、Misoca Advent Calendar 2017 の6日目のエントリーです。

SSR

フロントエンドを駆使したアプリケーションを構築すると、 どこかのタイミングでSSR(ServerSideRendering)と向きあうことがある。

最近はクローラが優秀なので、SSRを行わないSPAでもいい感じにインデックスしてくれたりするので、そこまで神経質になることもないことが多いですが、ファーストビューの速度などを突き詰めていくと、やっぱり選択肢としては使える状態にしておきたいところ。

実際にやろうとすると簡単なものではなく、Railsの場合はreact_railsのようなgemに頼ったりすることになったり、頼っても大変だったりするのが現実だと思う。

Next.js & Nuxt.js

といったわけで、Next.js/Nuxt.jsの名前をちらほら見かけるようになったと思う。

オフィシャルのガイドなどをざっと見て「ふむふむ」程度で終わっていたけど、触りだけでも一度試してみることにした。

個人的に、以前はReactがメインだったが、最近ではVueを使うことのほうが多いので、今回はNuxt.jsのほうをやってみることにする。

Nuxt.js

https://ja.nuxtjs.org/guide

Nuxt.js とはユニバーサルな Vue.js アプリケーションを構築するためのフレームワークです。

だそうです。

本気でVueでのSSR可能なSPAを組もうと思うと、以下のような要素が出てくる。

  • Vue
  • vue-router
  • vue-server-renderer
  • vuex
  • babel
  • webpack
  • 場合によってはexpressなど

その上で、アプリケーション自体の構成からルーティングルールを検討した上で、サーバサイド、フロントエンドどちらのレンダリングでも耐えうる形で作っていく必要があり、普通につらい。

Nuxt.jsを使うことで、ざっくりこのあたりを隠蔽してくれるので、とりあえずの出だしとして細かいことを考えずにコードを書き始めることができる。

Nuxt.jsの敷いたレール上に乗るぶんには楽できるけど、カスタマイズしようと思うと細かい知識が必要になるよ、みたいな話なので、考え方としてはRailsWayとかに近いのかなって印象。

やってみよう!

ゴール

  • Heroku上でNuxt.jsで作ったアプリケーションのSSRを確認できる

やらないこと

  • 凝ったカスタマイズは一切しない

インストールする

スターターテンプレートというものが用意されているので、そちらを利用する。

https://ja.nuxtjs.org/guide/installation

$ npm install -g vue-cli 
$ vue init nuxt-community/starter-template nuxt-trial

# 色々聞かれるけどデフォルトで良いのでEnter連打

$ cd nuxt-trial
$ yarn install

試しに動かしてみる。

$ npm run dev

http://localhost:3000/ にアクセスすると、以下のようなページが表示される。

f:id:mugi1:20171203001108p:plain

なんか動いてるようだ。

ちなみにnuxtのコードを見てみると、サーバ自体はconnectを利用してるっぽかった。

このままHerokuに放り込んでみる

インストールされたものはとりあえずあとで見るとして、このままHerokuに放り込んで動くかやってみよう。

FAQにずばりそのままのページが用意されている。

https://ja.nuxtjs.org/faq/heroku-deployment

心を無にして上から叩く。 (herokuのcliはインストールしてあるものとする)

$ heroku create
$ heroku config:set NPM_CONFIG_PRODUCTION=false
$ heroku config:set HOST=0.0.0.0
$ heroku config:set NODE_ENV=production

package.jsonに追記して、Heroku側でよしなにビルドしてくれるようにscriptsを足す。

  ~
  "scripts": {
    ...
    "heroku-postbuild": "npm run build"
  },
  ~

デプロイする

git push heroku master

これでブラウザからHerokuのアプリケーションを見てみると、ローカルと同様に動いていることが確認できる。

簡単。

SSRをやってみよう

では、肝心のSSRがちゃんと動くかを確認してみる。

確認のために以下のようなページを作ってみる。

  • 2ページあって行き来できる
  • ページごとに初期化時に何らかのAPIのデータを取得して表示する

API自体はなんでもいいので、THE COLOR APIというサイトから適当にカラー情報を取得することにする。

http://www.thecolorapi.com/

コンポーネントをつくる

非同期データのためにaxiosを追加しておく。

yarn add axios

色情報を取得するAPIとカラースキーマを取得するAPIの2つがあるので、その2つをページにしてみる。 内容は、ランダムなカラーをもとにデータを表示する超簡易的なもの。

pages/color.vue

<template>
  <section class="container">
    <div>
      <span>color</span>
      <span>|</span>
      <span><nuxt-link to="/scheme">scheme</nuxt-link></span>
    </div>

    <div><img :src="data.image.named" /></div>
  </section>
</template>

<script>
import axios from 'axios'

export default {
  async asyncData () {
    const hex = Math.floor(Math.random() * 16777215).toString(16)
    const { data } = await axios.get(`http://www.thecolorapi.com/id?hex=${hex}`)
    return { data }
  }
}
</script>

pages/schema.vue

<template>
  <section class="container">
    <div>
      <span><nuxt-link to="/color">color</nuxt-link></span>
      <span>|</span>
      <span>scheme</span>
    </div>

    <div><img :src="data.image.named" /></div>
  </section>
</template>

<script>
import axios from 'axios'

export default {
  async asyncData () {
    const hex = Math.floor(Math.random() * 16777215).toString(16)
    const { data } = await axios.get(`http://www.thecolorapi.com/scheme?hex=${hex}`)
    return { data }
  }
}
</script>

この内容でコミットし、改めてHerokuにpushする。

動きを確認してみる

/color

f:id:mugi1:20171203001140p:plain

/scheme

f:id:mugi1:20171203001455p:plain

ちゃんと動いてるっぽい。

(ちなみに画面上で遷移しようとすると、クライアント上でhttpsからhttpのXHRとなるので、Mixed Contentのエラーで落ちる。対処するのが面倒なので、Chromeの場合はURLバー横のアイコンから強制的に許可して回避する。)

Chrome DevToolsのNetworkタブを使って、画面上のリンクで遷移した場合のリクエストを見てみても、初回表示以外はxhrリクエストになっていることがわかる。

f:id:mugi1:20171203001635p:plain

肝心のSSRはということで、ダイレクトアクセスした直後にページのソースコードを見てみる。

f:id:mugi1:20171203002004p:plain

API経由で取得する画像へのURLがファーストビューとなるHTMLに含まれていることが確認できる。

というわけで

これだけであれば、たいした苦労もなく、SSR可能なVue.jsアプリケーションをHerokuで動かすことができた。

使い込んでいくと細かいカスタマイズなどで苦労することはありそうだけど、この簡単さは強力だな〜という印象。

もう少しちゃんとしたアプリケーションを作って検証してみてもいいかなという気持ちになった。

明日は @oosawatechによる「野生化」の話です。

野生化...!?

「プロを目指す人のためのRuby入門」が躊躇なくオススメできる完成度だった

前々から色々とお世話になっている西脇.rbの伊藤淳一さん(@jnchito)が、Rubyの入門書を出版されることになり、ちょうど私が読者のターゲットに近い存在ということで1冊見本誌を頂きました。

というわけで、書評というと生意気ですが、写経も交えつつ読了したので、感想を書いてみます。

ちなみに私自身は

  • プログラマとしては9年くらいやってる
  • Ruby歴自体は2年くらい
  • 業務で使うようになったのはここ1年くらい

といった具合。

実践感の強さ

まず、3章(=かなり序盤)の段階で「例題解くときはテスト自動化をしような」という説明が出てきたときにこれを感じた。

写経できるような入門書はいくつか見てきましたが、初っ端から「テストは自動化して効率よくやっていこうな」というスタンスの本はなかなか無いのでは。

個人的な経験では、通常入門書を写経していくと、「とりあえずコードを書く→動す→なんか落ちる→コードとにらめっこする」みたいなことになることが多かった覚えがある。(そしてイヤになる)

本書の場合、まずはテストを書いてエラーにしてから、実際のコードを書いてテストが通るという流れを踏んでいくので、1つずつ着実に理解していける感がある。

(さらにRubyの入門しながら、しれっとTDDの基礎的な部分も触れられるのでお得感がある)

現実を教えてくれる

読み進めていくと、

  • 実際にはこれは使わないことが多い
  • 幾つか書き方があるけど、こっちが主流
  • こういうのが不具合の原因になりやすい

みたいな、言語仕様・文法だけを学んだだけでは把握できない部分に言及している箇所がかなり多いことに気付く。

実際このあたりは業務で使うときにかなり重要で、学ぶタイミングが少なかったり、単純にググるのが難しい概念だったりもする。

そういったところで都度道筋を教えてくれるのは初心者には嬉しいように感じた。

また、英語で出てくるバックトレースなどについて「英語が苦手な人も辞書を片手に頑張ってね」という記述があるのもなかなか新鮮だった。 わりと入門書では「今は理解しなくていいですが〜」みたいなことを書いてあったり、そもそも触れられないみたいなことも多いですが、ぶっちゃけ理解しなくていいわけがないので、そのあたりの現実突きつけてくれるのは大事。

実際のところ私自身も英語は苦手なので「うっ、すいません..!」って感じだった。

プログラミング初心者向けではなく、Ruby初心者向け

書籍内の冒頭でも触れられていますが、プログラミング自体が初心者という人にはハードルが高めかもしれない。基本的な概念の説明は無いので、「変数...?」みたいな状態だと厳しい。

逆に言うとそのぐらいちゃんとターゲットを絞った本なので、合致した層が手に取ると、かなり参考になる内容が多いと思う。

Rubyを使っていて、「自分はもうRubyバリバリ使えるぜイェーイ!」って言える人以外は、とりあえず買って目を通してみるといいかもしれない。私自身も、もう完全な初心者ではないかな〜と勝手に思っていたけど、普通に知らないことがボロボロ出てきたので、いい感じに振り返れてとても良かった。

写経しやすい

全体を通して、そもそも写経を想定して作られてるなって感じがした。

基本的には各章に1つ練習問題があるので、ざっくりとした理解チェックをするには最低限それをやればいいし、ガッツリ学びたければ、都度記載されてるサンプルコードを全て写経していくこともできる。

ちなみに紙質も違うらしく、開いたときに維持しやすいらしい。

実際維持しやすかった。すごい。

とりあえず理解度が全然違うので、写経しながら読むことをオススメする。

適宜必要な前提知識を教えてくれる

人によっては正規表現の章で、「正規表現知らないならそっち勉強してから戻ってきてね!」と、一度書籍の外に追い出されるという体験をすることになる。

このあたりは「プロを目指す人のための」という名前がピッタリだなという印象だった。実際のところ、根本の部分がフワフワしてると理解できないので、そのあたりをいい加減にしたままRubyでの正規表現の使い方を教えられても困るわけで、「知らないなら知ってから戻ってきてね!」というスタンスはとても潔いし、そうだよな、って感じがした。

トータルでの感想

私の個人的な感情とかは無視しても、かなりの良書だと思った。

Qiita等での伊藤さんの記事で普段お世話になっている人も多いと思うけど、あのわかりやすさがそのまま本になったような感覚。

周りにRubyに精通した人がいて質問できる環境なら良いが、そうじゃない場合には学ぶのが難しい要素というのが実際それなりにあって、本書の内容である程度カバーしてくれるのがとてもいいと思う。

2年前のRuby勉強し始めたころにこの本があれば〜〜〜!!と何度も思った。SIerからRuby使うようなWeb系への転職を考えている人とかには最適の1冊になるのでは。

ターゲットを絞っているぶん、合致するとかなり刺さる内容が多いのかもしれない。

  • 「これからRuby勉強したいんですけど〜」
  • 「それなりに書けるようになったけど、いまひとつ自信ないわ〜」

みたいな人がいたとすれば、今後はこの本をオススメすることになると思う。(私は後者だったので刺さった)

さいごに

見本誌で頂いたという立場を考えると、サクラのような書評になってしまっているように見えるかもしれないけど、内容をじっくり読んだ上で良い内容だと判断したので、躊躇なく書かせて頂きました。

これはもう、「プロを目指す人のためのRails入門」の出版が待ち遠しいですね!

RSpec/Capybaraのテストコードを画面操作から出力するChrome拡張をつくった

ほぼ表題の通りの内容で、Chrome拡張を作ってみた。

完成度としてはまだまだだけど、とりあえずざっくり触れる程度にはなったので公開した。

f:id:mugi1:20171001221735g:plain

github.com

chrome.google.com

イコン画像は、カピバラの写真を適当になぞって作った。

なんでこんなものを?

Capybaraのテストコードを書くのが面倒だなって感じることが多かったのが1つの理由。

TDD的に、先にテストコードを書いていけるのが理想だな〜とは思うものの、Webアプリケーションの開発をしていると、現実には一度は画面から一通り確認して、その後にfeature specを書く流れが多いように感じる。

そしてCapybaraでfeature specを書こうと思うと、

  • 「これはテキストじゃなくてIDで選択しないとダメか〜」
  • 「あ〜、これはfindしてからsetしないといけないのか〜」

という感じで、スムーズに書けない自分がいる。

そのあたりを都度試行錯誤しているのが面倒だな〜と思い始めて、「どうせ最初に一通り画面を触ってるんだから、そのときの操作がそのままfeature specとして出力できればいいじゃん!」と思って作り始めた。

あとは、Chrome拡張って興味があったけどなにげに作ったことがなかったので、やってみたかったというのもある。

つかったもの

ContentScript ⇔ Background ⇔ DevTools という流れでデータのやり取りを非同期で行う必要があった。

ごちゃごちゃしそうだったので、非同期になる箇所はすべてeventsでラップして、全データがDevTools上にVue&Vuexで構築たストアに集約するようにしたところ、わりとスッキリした。

役割としては、

  • ContentScript : 操作の監視 & 情報を集約してeventsをpublish
  • Background : ContentScriptとDevTools間を中継するのみ
  • DevTools : ContentScriptのデータをsubscribeしCapybaraのコードを生成

みたいな感じになった。

できること

  • テキストフォーム・テキストエリアの入力 → fill_in の生成
  • ラジオボタンの選択 → choose の生成
  • チェックボックスの選択 → check/uncheck の生成
  • セレクトボックスの選択 → select の生成
  • 要素クリック → find(xxx).click / click_link / click_button の生成

あたりはできている。

一番やりたかった部分として、何でその要素を特定できるかで生成コードが変わるようになってる。

fill_in '一意のラベル', with: '入力値' # ラベルで特定できる
fill_in 'id', with: '入力値' # idで特定できる
find('.class').set('入力値') # classで特定できる
find(:xpath, '/div[1]/input[1]').set('入力値') # 上記全てが無理な場合はxpath

できないこと

まだまだ発展途上感がある。 現時点で以下は対応できていない。

  • 画面遷移後の継続動作
  • visit とかも作る
  • fill_in 前の要素の click が不要なので省く
  • などなど..

というわけで

公開してあるので、興味のある方はどうぞ。

個人的には、label/id/class/xpathの自動判別が作れたのである程度満足しているけど、機能的にもっと充実させていきたい。

要望などがあればissueにもらえると嬉しいです。

RailsDevelopersMeetupでWebpackerの話をした

2017.8.24に行われたRailsDevelopersMeetupにて、機会を頂いたので10分枠でLTをさせていただいた。

rails-developers-meetup.connpass.com

突然のメッセージ

ここ数日はコードも書かずにスプラトゥーンをやる日々だったけど、ある日Twitter経由で、主催の@yoshi_hiranoさんから「地域.rb発のLTを実現できないかと思い、Toyama.rb主催ということでリモートでLTをお願いできないか」という主旨のメッセージをいただいた。

「えっ、自分!?」と思い、宛先を間違ってるんじゃないかと何度もメッセージを見返したけど、やっぱり自分だった。

第1〜3回目の登壇者には著名な方も多く、参加者の期待に応えられる話がちゃんとできるかな〜?と心配だったけど、「せっかくの機会なのでやってみよう!」ということで、発表させてもらうことにした。

なお

自分は10分のLT枠だったけど、Keynote枠のうちのお一人が伊藤さん(@jnchitoさん)だったのにはかなり驚いた。

Toyama.rbを作ったそもそものきっかけは伊藤さんと言っても過言ではないので、まさかそれが同じイベントでリモート発表する日が来るとは予想もしてなかったし、とても嬉しかった。

事前練習

妻にお願いして、少しだけ練習した。

「何もわからなかったし、聞いてる途中ちょっと寝てたわ」という感想だった。

まあそうですよね

話した内容

ぶっちゃけた話、業務でのRails歴がまだ浅いので、人前で話せるレベルのスキルも知識も持っていない。

ただ、唯一Webpackerにはがっつり関わった経験があり、良いこともツラいこともそれなりに味わったので、今回のテーマにした。

というわけで資料です。

speakerdeck.com

10分間

10分と時間が決まっていたので、最終的にかなり内容を削った。

本当はもう少し具体的に移行手順的な話もしたかったんだけど、「結局どう感じたのか?」「よかったの?どうだったの?」といった、自分の感想的な部分を強めに出してみた。

おそらくフロントエンドにはそこまで深く関わってない方も多く参加されてただろうと予想しているので、結果的には踏み込みすぎないで丁度いい具合の仕上がりになったかな〜と勝手に思っている。(本当か?)

リモート発表

今回は、普段お仕事をしている富山の自宅からリモートでの発表だった。

初めての体験で、聞いている方の顔や雰囲気が感じ取れないのが少しさみしい感じはあったものの、意外となんとかなるもんだな、と思った。

Hangoutは負荷が高いと色々トラブルが起きやすいイメージがあったけども、そんなこともなく、基本的にはスムーズだった。

手応え的に

Twitterで感想とかを見ていると、わりとWebpackerの話聞きたかったという方も多かったようで、完全な空振りとかではなかったようで安心した。

そしてすでに使ってるという方には、ツラかったポイントは響いたようだった。そうだよね。わかるわかる。

ちなみに

音が聞き取りやすいけどマイクが良いのか部屋が静かなのか、といった感想をみかけた。

部屋は普通なのでマイクを紹介しておくと、Yetiをつかっております。

Blue Micro Yeti USB 2.0マイク 15374

かなりでかい。
また、このマイクのおかげで、たまに「YouTuberだ!」という扱いを受けている。

やってみて

今までは北陸界隈や社内での発表ばかりで、ある意味安全な範囲で活動をしていたけども、今回始めて外に飛び出しての発表だった。(正確には家の中だけど)

正直かなり緊張していたけど、自分なりにちゃんとやれたと思うし、自信もつけられた。

あと、これは毎回思うけど、発表するとそのために知識や資料を精査する必要があるので、どちらかというと自分が学ぶことがかなり多くてとても良い。

今後も積極的にやっていきたい。

あらためて

当日聞いていただいた皆様、ありがとうございました!
主催者の皆様、おつかれさまでした!

このような機会を頂けたことに感謝しています。ありがとうございました!