Falcon Heavyの打ち上げは、紛れもなく僕のアポロだった

1969年7月20日 アポロ11号月面着陸

人類が初めて地球以外の天体に降り立ったこの日、全世界が熱狂した。

この「一人の人間にとっては小さな一歩」が灯した火は半世紀を経た今もなお、多くの人の心の中で輝き続けている。

ある者はその「一歩」を追って月への想いを焦がし、またある者は小惑星探査へと野望を抱き、そしてある者は火星へと夢を見た。

 


 

2018年2月7日 午前5時44分

「T-30」のアナウンスが流れた瞬間、僕の身体に電撃が走り全身の毛が逆立つのを感じた。

前の晩から興奮で眠れずとうに疲労の限界を迎えていたけれど、歴史的瞬間を前にして僕の脳からは猛烈な量のアドレナリンが分泌されていた。

25歳の冬、紛れもなく僕は「熱狂」していた。

 


 

はじめて宇宙に想いを馳せたのはおそらく9歳か10歳のころ。

小学生の僕が父とお風呂に入るときは必ず父が広い世界の話をしてくれた。ときには歴史の話だったり、物理の話だったり、地理の話だったり、社会のしくみの話だったり、そして宇宙の話だったり。

当時札幌に住んでいた僕は年に数回は青少年科学館に足を運び、プラネタリウムや天体模型に夢中になっていた。いつかの冬休みには4日間の宇宙講座に参加して、ロケットのしくみを勉強したり宇宙食を食べたりした記憶が今でも残っている。

 

それからしばらくの間、宇宙への想いを忘れていた僕がふたたび「それ」と出会ったのは大学1回生の夏休みのこと。

 

いわゆる「ブラックバイト」で消耗していた僕は、なけなしのバイト代をはたいて種子島に旅をすることに決めた。そのころから新海誠作品のファンだったこともあり、なんとなく「秒速5センチメートル」に出てきた風景に惹かれて南の島に逃げたくなったのだ。

f:id:andoshin11:20180209032102j:plain

f:id:andoshin11:20180209032134j:plain

せっかくの島旅なので北の端から南の端まで巡る計画を立て、宇宙センターの近くにも宿を取った。この時泊まった門倉荘という宿はどうやらJAXAの関係者がよく利用する宿だったようで、食堂の壁にも宇宙飛行士のサインがズラッと並ぶなどしていた。

結局、宇宙一色の町の雰囲気に押されるがまま宇宙センターの見学を申し込む形にはなったが、この時暇そうにしている僕に宇宙センター見学を勧めてくれた宿のおばちゃんには心から感謝している。

f:id:andoshin11:20180209032443j:plain

f:id:andoshin11:20180209032506j:plain

f:id:andoshin11:20180209032530j:plain

 

生まれて初めてロケットを見た

 

生まれて初めてロケットのエンジンを見た

 

生まれて初めてロケットの格納庫を見た

 

生まれて初めてロケットの発射台を見た

 

心の底から何かをカッコイイと思うのは、多分初めての経験で、胸の奥から熱いものが込み上げてくるのを感じた。人の手で組み上げられたものが大空の彼方まで飛んでいくのだという「現象」の衝撃が、確かな実感として心に刻み込まれた。

 

その日、宿に戻って食事をしてから種子島の夜道を2時間ほど散歩した。

 思えばこの時が人生で初めてしっかりと自分の将来について考えた瞬間なんだと思う。それまでもぼんやりと「ここの高校に行きたい」「ここの大学に行きたい」「こんな仕事をしたい」などと考えたことはあったけれど、「何を成し遂げて死ぬのか」を明確にしたことはなかった。

 

宇宙センターでのことを思い出し、夜道を照らす星々の美しさに感謝し感激し、それまでの人生について振り返り、それまで訪れた様々な場所や出会った人々のことを思い出し、そしてなにより若くして命を落とした友人たちの生き様を振り返った時に、19歳の僕は「宇宙を見てから死のう」と決意した。

 


 

25歳の僕は、まだ夢を叶えていない。

 

 

この6年間でいろいろな回り道をしてきた。

短い間だが大学では歴史のロマンに魅せられて西洋史を専攻した(結局中退した)

経営者として経済的成功を勝ち取ろうとして会社を興し、テレビや新聞に取り上げてもらって浮かれていたりもした。当時は本気で億単位の金を40歳までに稼いで宇宙旅行に行こうと考えていたのだ。(結局メンタルが持たずに多くの人に迷惑をかけた)

そして現在は社会人になってから始めたプログラミングの面白さに魅せられ、技術力を高めつつ社会復帰することに身骨を砕いている。

いろいろやってきたが、多分これが一番性に合っていそうだし夢への近道なのだと最近は思っている。

 

結局のところ、僕は大富豪になって「宇宙観光」がしたいわけではなく、人類のフロンティアである宇宙開発に貢献し、歴史上誰もまだみたことのない世界を切り拓くことにワクワクするのだと。

 

 

去年の夏に、NASAJPL無人探査機の研究開発を行なっている小野雅裕さんの講演を拝聴する機会を得られた。

彼の立っている場所は、まさに僕の夢の番地だった。

技術者としてエンケラドスの氷の底を観測しようという小野さんが積み重ねてきた努力や熱意は僕が描く将来の自身のイメージそのもので、これから進む道が間違っていないのだという希望と勇気をもらえるとても素晴らしい瞬間だった。

 

人類がエウロパやタイタンの海を開拓していく瞬間に携わりたいと今では強く思うし、プロキシマ・ケンタウリbのような系外惑星まで衛星や探査機を届ける技術的な貢献がしたいと、空を見上げるたびに想っている。

 


2月7日の早朝、200万人が見つめる中でFalcon Heavy打ち上げのカウントダウンが始まった。

 それまでもSpaceXのFalcon 9の打ち上げ中継を欠かさず見ていた僕だったが、この時の興奮はいつもの比ではなかった。

2004年にイーロン・マスクがFalcon Heavyの構想をぶち上げてから、多くの資金難、技術的困難、社会的バッシングがあった事を僕たちは知っている。

それら全てを笑い飛ばすかのようにFalcon Heavyは大空へと美しいを弧を描いた。

 

f:id:andoshin11:20180209143310p:plain

 

魔法のように地上へ戻ってくるロケットの姿を見て、全ての人が「これからはロケットも飛行機のように何度も再利用される時代がくるのだ」と考えるより先に肌で感じた。

火星に向けて射出されたロードスターとスターマンを見て、全ての人が「これは他の惑星に荷物や人を届けるためのロケットなのだ」と考えるより先に直感した。

 

この日、もう一つ個人的に思い出深かった事としては、打ち上げの瞬間を母親と一緒に見られたことだ。

ロケットやSpaceXのことはおろか宇宙のことなどほとんど何も知らない母が、この時ばかりは見たこともないくらい興奮し、「熱狂」していた姿がとても印象的だった。

 

人が空を見上げる時、そこには年齢も性別も国家も人種も壁なんて存在しない。

なにせあの冷戦時代のアメリカとソ連でさえ宇宙では手を取り合ったのだから(1975年7月17日 アポロ・ソユーズテスト計画)

 

2018年に全人類にそんな「空を見上げる」機会をプレゼントしてくれたイーロン・マスクSpaceXのスタッフには賞賛の声を送っても送りつくせない。

打ち上げの成功を喜びあう彼らの笑顔と涙を、僕は一生忘れないだろう。

 

あのカウントダウンの瞬間からスターマンの中継が途切れる最後の瞬間まで、彼らが与えてくれた感動と希望の灯火を胸にこれからも夢に向かって歩んで行こうと思う。

 

最短距離ではないだろうしこれからも多くの人に迷惑をかけるだろうけど、もしも僕の「回り道」の途中でその灯火を誰かに分け与えることができたのなら、人類に対して少しは貢献できたと言えるんじゃないかな。

 

2018年2月7日、200万人と共に「熱狂」したFalcon Heavyの打ち上げは、紛れもなく僕にとってのアポロだった。

 

2018年2月9日 好日

 

f:id:andoshin11:20180209132715p:plain

 

今度こそVimを攻略したい:neovimをインストールして最高のdev UXを手に入れるまで(その1)

先日の記事に様々な反響をいただいたことをきっかけに「アウトプットやっぱ大事だなー」と思ったので、アドベントカレンダーに関係ないタイミングでも記事を書いてみようと思います。

僕のエディタ遍歴

  • ~2014年夏:Pages

大学では歴史学を専攻していました。当時はレポートさえ書ければ良かったのでMacデフォルトのPagesを使用。プログラミングのプの字も知らなかった時代。

  • 2014年夏 ~ 2015年:Sublime Text 2

仕事の合間にdotinstallでHTMLとかCSS勉強し始めた時代。プラグインも何も入れずになんとなくかっこいいなーという理由でSublimeを使っていた。この頃はWordpressの本を読んで会社サイトとか作ってました

  • 2016年前半:Vim

社内で仕事をお願いしていたエンジニアが黒い画面でよく分からないことやってるの見てすごいなーという憧れでVimを使い始めた。なんとなく .dotfilesリポジトリとか作ってみたけど .vimrcの中身は全てネットで見たやつのコピペ。使ってる機能は1割くらいしかなかったと思います。

この頃からRails書き始めましたが、開発するには同時多発的にいろいろなファイルを触らなきゃいけないので当時のVim力では少し厳しかったです。 NERDTree入れてたけど全然活用できなかった思い出

  • 2016年後半 ~ 2017年春:Sublime Text 3

本格的にプロダクト作りたいなーとなって使い慣れたSublimeに戻ってきました。ショートカットやプラグインの使い方もいくつか覚えて体感でこれまでの3倍くらいに開発速度も向上。ブワーっとスクロールして任意のファイルの中身をザッピングできるGUIのありがたさを実感

  • 2017年春 ~ 現在:Atom

サイドバーのファイルアイコンが可愛いという理由だけでAtomに乗り換え

f:id:andoshin11:20171217010020p:plain

あとは痛エディタにしたくて背景画像もこんな感じにしてました

f:id:andoshin11:20171217010144p:plain

Atomプラグインやショートカットの手触り感はSublimeと大差ないのですが、いかせん重すぎて開く気にすらならない!!! 最近は案件でXcodeも動かすことが増えてきてだんだん辛くなってきました。。。

Vimに戻るかー

ということで主に以下の理由でVimに戻ることにしました。

  • いろいろなエディタを触る中で自分が開発に必要とする機能が具体的に見えてきた

  • Vimを使っていたころよりも髪の毛1本ほどは黒い画面の操作に慣れてきたので今度こそいけるやろ感

他のエディター候補としてはMSに行ったときに紹介していただいたVSCodeがとてもとても魅力的だったのですが、「Electronで動くアプリはSlackだけで十分!!」という強い意思を持ってVimを頑張っていきたいと思います。

要件

Vimは何度も挫折を繰り返しているので触り始める前に意識すべき点として以下の項目を挙げました

  1. 設定ファイルをキチッと管理すること
  2. カーソル移動のしやすさを最優先にすること
  3. シームレスにファイルツリーの確認とファイルの移動が行えること

1についてはいうまでもないですが、2と3は過去にVimを捨ててSublimeに走ったクリティカルな理由なので確実に攻略していきたいと思います

neovimのインストール

今回は以前から良いと評判を聞いていたneovimを利用します。(* Mac vimによくわからない破壊的変更を加えすぎて手がつけられない)

Homebrewから neovimをインストール

$ brew install neovim/neovim/neovim

設定ファイルにpathを通す

デフォルトでneovimは ~/.config/nvim/init.vimを設定ファイルとして参照しますが(vimでいう .vimrc)、dotfilesを一限管理したかったので以下のようにシンボリックリンクを貼りました

$ ln -sf ~/dev/dotfiles/nvim/init.vim ~/.config/nvim/init.vim 

これでdotfiles以下を変わらずGit管理できます

dein.vimプラグイン管理

昔は NeoBundleを用いてプラグインの管理を行なっていた記憶がありますが、今は dein.vimというのが良いらしいですね。

init.vimに以下のような設定を追記

let s:dein_path = expand('~/.vim/dein')

" dein Scripts-----------------------------------------------
if &compatible
  set nocompatible
endif

" Required:
set runtimepath+=~/.vim/dein/repos/github.com/Shougo/dein.vim

" Let dein mange itself
if dein#load_state(s:dein_path)
  " Required:
  call dein#begin(s:dein_path)

  " Add or remove your plugins here:
  call dein#add('Shougo/dein.vim')

  " Required:
  call dein#end()
  call dein#save_state()
endif

" Install uninstalled plugins on startup
if dein#check_install()
  call dein#install()
endif

" Required:
filetype plugin indent on
" End dein Scripts-------------------------------------------

syntax enable

call dein#add(<plugin name>)というように追加したいプラグインを記述するとneovim起動時にそのプラグインが自動でインストールされます。

また、まだ試していませんが TOMLという形式のファイルを別に作成してそれを dein.vimから読み込むこともできるらしいので、もう少しプラグインの数が増えたら試してみます

カーソルの移動速度を上げる

これは多分Mac独自のHack。「システム環境設定」 -> 「キーボード設定」を見るとキー入力に対するディレイとリピート速度が設定できるので、こちらから適度なスピードに変更。

f:id:andoshin11:20171218082114p:plain

tmux上でのモード切り替え速度を上げる

インサートモードからノーマルモードへの切り替えがもっさりしていたので調査。どうやらtmux側でescキーに対して入力待ちをしていたのが原因のよう。デフォルトで500msくらいの待ちが発生していたようなので ~/.tmux.confに設定を追記

set -g escape-time 0

tmuxをリフレッシュすると動作がサクサク快適になりました

NERDTreeを入れる

neovimの中から別のファイルを開く方法としては基本的に :e <file name>で十分な気もしますが、視覚的にディレクトリ構造を把握した上で任意のファイルを開きたいという欲求があったので NERDTreeをインストールしました。

インンストール方法自体は簡単で、上記スクリプト内に

call dein#add('scrooloose/nerdtree')

という一行を追記するだけ。 また、ファイルツリーを開く際にいちいち :NERDTreeToggleなどと打つわけにもいかないので以下のkeymapを init.vimに追加

map <C-t> :NERDTreeToggle<CR>

これで ctrl + tでファイルツリーを開けるようになりました。

また、 NERDTree上でのファイルの視認性が悪かったので、以下の設定を追記して拡張子ごとにHighlightするようにしました

" NERDTress File highlighting
function! NERDTreeHighlightFile(extension, fg, bg, guifg, guibg)
  exec 'autocmd filetype nerdtree highlight ' . a:extension .' ctermbg='. a:bg .' ctermfg='. a:fg .' guibg='. a:guibg .' guifg='. a:guifg
  exec 'autocmd filetype nerdtree syn match ' . a:extension .' #^\s\+.*'. a:extension .'$#'
endfunction
call NERDTreeHighlightFile('py',     'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('md',     'blue',    'none', '#3366FF', '#151515')
call NERDTreeHighlightFile('yml',    'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('config', 'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('conf',   'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('json',   'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('html',   'yellow',  'none', 'yellow',  '#151515')
call NERDTreeHighlightFile('styl',   'cyan',    'none', 'cyan',    '#151515')
call NERDTreeHighlightFile('css',    'cyan',    'none', 'cyan',    '#151515')
call NERDTreeHighlightFile('rb',     'Red',     'none', 'red',     '#151515')
call NERDTreeHighlightFile('js',     'Red',     'none', '#ffa500', '#151515')
call NERDTreeHighlightFile('php',    'Magenta', 'none', '#ff00ff', '#151515')
call NERDTreeHighlightFile('vue',    'Green',   'none', '#4fc08d', '#4fc08d')

その他

Vueファイルのシンタックスハイライトを追加する

Vue.jsのSingle File Componentである .vueだけシンタックスハイライトがなくて寂しかったので以下のプラグインを追加しました

github.com

まれに動作が不安定な時もありましたが、以下の設定を init.vimに追記して解決

" autohighlight vue file
autocmd FileType vue syntax sync fromstart

emmetを入れる

本業がマークアッパーなのでemmetは必須。ということでdein.vimから emmet-vim入れました

call dein#add('mattn/emmet-vim')

また、デフォルトだとemmetのkeymapが <C-y>なのでAtomSublimeを使ってた人間からするとちょっと不便。

" Configure emmet-vim
" set trigger key
let g:user_emmet_leader_key='<C-e>'

こんな感じに書くとAtom風にkeymapを変更できます。 実際に呼び出す際は ctrl + e + ,というように末尾にカンマが必要なので注意

動いた 👏👏

個人的に便利だなーと思ったこと

今回新しくゼロから学んでみて、個人的に便利だなーと思ったneovim(vim)の機能を羅列します。始めてVimを触ったときに知っておきたかった...

  • oで次の行を空けてインサートモードに移行:

なんでいままで行末に移動 → インサートモードに移行 → 改行、なんて面倒なことをやっていたんだろうw

  • 0で前の行を空けてインサートモードに移行:

上に同じ。

  • rキーで1文字だけreplace

TypoScriptの使い手なので「1文字だけ修正したい」というケース非常に多いのでとてもありがたい。

  • Iで行の先頭に移動してインサートモードに移行:
  • Aで行末に移動してインサートモードに移行:
  • ctrl + oで直前に開いていたファイルに戻る
  • ctrl + i上記の逆

などなど挙げればキリがないので、続きはまた別の記事にまとめたいと思います。

Vim楽しくなってきたのでもっと使いこなすぞー

Storybook for Vueでエンジニアとデザイナーの協業を加速させる

この記事はCAMPHOR- Advent Calendar 2017の23日目の記事です。

はじめに

アプリケーションの機能を「カプセル化」、「コンポーネント化」しようというのは、フロントエンド/バックエンド問わずに近年のWeb開発におけるトレンドのひとつです。

Web業界も5年もの、10年ものと呼ばれるシステムが増える中で「最初から完璧なシステムを作ることなんて無理なんじゃないか」と多くの人が気づきはじめました。 その流れを受けて「どうせ改修が必要なら、小さなスコープで新しいものに置き換え可能な仕組みを作ろうぜ!」というのが「カプセル化」、「コンポーネント化」の大きなモチベーションであると、個人的には理解しています。

Vue.jsとコンポーネント

JavaScriptフレームワークであるVue.jsも、例に漏れずコンポーネントドリブンな開発に適した仕組みの1つです。

SFC(Single File Component)として知られる .vueファイルに templatescriptを切り離せるだけでなく、 Scoped CSSの仕組みを用いてスタイリングでさえ1つのコンポーネントに限定して適用できるなど、非常に再利用性の高いコンポーネント設計が行えて重宝しています。

またVue.jsの大きな特徴として、既存のHTMLとさほど変わらないシンタックスコンポーネントが記述できるという点が挙げられます(もちろんJSXで記述することもできます)。HTMLやCSSについての基本を抑えていればJSが苦手なWebデザイナーさんにも作成・改修依頼が投げられるというのは、常に人材不足に泣かされる中小Web制作会社としては非常にありがたいですね!

今回紹介するSrotybook for Vueは、そんな現場レベルでデザイナーさんとフロントエンドエンジニアの協業を加速させるためのツールです。

Storybookとは

Storybookとは、iframeを用いたサンドボックス上でコンポーネントの挙動をチェックできるUI開発環境です。ただのテストツールとしてではなく、スタイルガイドとしての利用も想定されているため、エンジニアだけでなくデザイナーさんにも役立つツールとして期待されています。

github.com

StorybookはもともとReactユーザー向けのツールでしたが、 v3.2.0からVueもサポートされ、現在はReact Native Webにも対応しました。

事例諸々

公式ページより様々な企業の導入事例を見ることができます。

https://storybook.js.org/examples/

f:id:andoshin11:20171222172336p:plain

各社ユニークなStorybookを用意していて眺めているだけで面白いです。会社の顔にもなるようなスタイルガイドを簡単に作れるなんてワクワクしますね。

今回はこのStorybookの導入 → Storyと呼ばれるStorybook内のページにVueコンポーネントを追加するまで、の流れを追っていきたいと思います。

本記事の対象

  • ES6分かるマン/ウーマン
  • Vue.jsなんとなく分かるマン/ウーマン
  • Storybookにとりあえず興味あるマン/ウーマン

本記事のゴール

いつも前置きが長く申し訳ないですが、本記事は以下のことを目的として書かれています

  • Storybookの概要を知ってもらう
  • 「Storybook、意外とカンタンやん!」と思ってもらう
  • 社内で投入する前に個人的に試した内容の作業ログを残したい

まぁ、ほとんど3つ目がメインです。それでは上記を踏まえた上で早速Storybookを動かしてみましょう。

Storybookを導入する

まずはvue-cliでアプリを作成

$ vue init webpack sample // console上で対話しながらアプリケーションを作成
$ cd sample
$ yarn
$ yarn run dev

Storyobookの公式ページにはQuick start guideの他に、Slow start guideというページが用意されているので今回はそちらを参考にします。

$ yarn add -D @storybook/vue // devDependenciesにstorybookを追加

Yarn scriptに起動コマンドを記述

{
  "scripts": {
    "storybook": "start-storybook -p 9001 -c .storybook"
  }
}

.storybook/config.jsに設定を記述

import { configure } from '@storybook/vue'

// ストーリーの読み込み
function loadStories() {
  require('../src/stories')
}

configure(loadStories, module)

読み込むStoryを作成する前に、ボタンを表示するコンポーネントを雑に用意します

<template>
  <button>Hello Vue</button>
</template>
<style scoped>
  button {
    color: green;
    border-radius: 5px;
    font-size: 18px;
    line-height: 1.5;
    padding: 0 10px;
  }
</style>

上記コンポーネントを表示するStoryを作成

// src/stories/index.js
import { storiesOf } from '@storybook/vue'

import MyButton from '../components/MyButton.vue'

storiesOf('Buttons', module)
  .add('simple', () => ({
    components: { MyButton },
    template: '<MyButton/>'
  }))

Yarn scriptで Storybookを起動

yarn storybook

ブラウザで localhost:9001にアクセスしてStorybookが表示されていることを確認します

f:id:andoshin11:20171217165029p:plain

無事にコンポーネントがiframe上に描画されました。

またWebpackのHMR(Hot Module Replacement)が有効になっているので、コンポーネントファイルを編集すると変更が動的に反映されることが確認できます。

Addonsを使う

このままでは少し寂しいのでStorybookにAddonsを追加して機能や見た目を拡張していきましょう

Storybook Addon Actions

Storybook Addon Actionsイベントハンドラから渡ってきたデータの確認やロギングができるAddonです。

github.com

Yarnでインストール

$ yarn add -D @storybook/addon-actions

新しくAddonsを追加する際は .storybook/addons.jsに追記する必要があります

// .storybook/addons.js
import '@storybook/addon-actions/register'

Addonから action関数をimportして、ボタンの onClickイベントと紐づけてみます

import { storiesOf } from '@storybook/vue'
import { action } from '@storybook/addon-actions'

import MyButton from '../components/MyButton.vue'

storiesOf('Buttons', module)
  .add('simple', () => ({
    components: { MyButton },
    template: '<MyButton @click.native="action"/>',
    methods: { action: action('button-clicked') },
  }))

コンポーネント@clickに対して .native修飾子をつけると、root要素のイベントを監視できるんですね。初めて知りました。

上記のコードを確認してみます

Storybook内に「ACTION LOGGER」というパネルが表示され、コンポーネントのイベントにフックしたログが表示されるようになりました。これを活用すると、より細かくコンポーネントの挙動を確認することができますね!

storybook-addon-vue-info

storybook-addon-vue-infoは、コンポーネント情報の表示を改善してくれるAddonです

github.com

Yarnでインストール

$ yarn add -D storybook-addon-vue-info

Exampleを参考に decoratorとして実装

import { storiesOf } from '@storybook/vue'
import { action } from '@storybook/addon-actions'
import VueInfoAddon from 'storybook-addon-vue-info'

import MyButton from '../components/MyButton.vue'

storiesOf('Buttons', module)
  .addDecorator(VueInfoAddon)
  .add('simple', () => ({
    components: { MyButton },
    template: '<MyButton @click.native="action"/>',
    methods: { action: action('button-clicked') },
  }))

ついでに MyButton.vueも少し改修

<template>
  <button>{{ text }}</button>
</template>
<script>
  export default {
    props: {
      text: {
        type: String,
        default: 'Hello Vue'
      }
    }
  }
</script>

propsでボタンに表示するテキストを受け取るようにしました。デフォルトは "Hello Vue"にしています。この状態で表示チェック

f:id:andoshin11:20171222093101p:plain

このように、 decoratorをつけたStory内のコンポーネントについての使用例とコンポーネントの持つことができるprops等が体系的に表示されました!

お手軽便利系のAddonです。

Knobs

GUIからコンポーネントの持つプロパティの情報を更新できるAddonです。

github.com

Yarnでインストール

$ yarn add -D @storybook/addon-knobs

addons.jsに追記

import '@storybook/addon-knobs/register'

decolatorとしてStoryに追加します。今回は propsとして labelbackgroundColorを受け取る ButtonWarningコンポーネントを用意しました

import { storiesOf } from '@storybook/react'
import { withKnobs, text, color } from '@storybook/addon-knobs'

import ButtonWarning from '@/js/components/ButtonWarning'

...

storiesOf('Buttons', module)
  .addDecorator(withKnobs)
  .add('warning', () => {
    const label = text('Label', 'Warning')
    const backgroundColor = color('Color', '#ffffff')
    return {
      components: { ButtonWarning },
      template: `<ButtonWarning label="${label}" backgroundColor="${backgroundColor}" />`
    }
  })

text(), color()という関数を利用することで、それぞれの値に対する inputフォームのようなUIが表示されるようになります

色の調整や文字列の変更が誰でもできるようになって最高 :clap::clap:

Links

Story内から別のStoryを表示する(遷移する)ためのAddon。

github.com

詳細は割愛

その他のAddon

基本的にUIをスッキリさせる系のAddonsが多い印象ですが、今回は全て試せませんでした。公式にGalleryが用意されているので、興味のある方は以下のリンクからご確認ください。痒いところに手がとどくAddonが見つかるかもしれません

https://storybook.js.org/addons/addon-gallery/

実際に使ってみた

手元にあったコンポーネントを突っ込んで作成したStorybookがこちらになります

デザイナーはすぐに見た目のレビューができるし、エンジニアはデザインの変更を差分で管理できるしで、なかなか幸せな世界に近づいたのではないでしょうか

まとめ

Storybook本体のVue.js対応が行われたばかりということもありまだVue.jsに対応していないAddonsが多いようですが、スタイルガイドとしては必要十分な仕組みが備わっているように感じました。

最初の設定はクセがありますが、一度導入してしまえば機械的コンポーネントを追加していくだけなので中規模以上のSaasを運用するケース、受託案件を小さく大量に回すケース、どちらでも活用できるのではと思います。

社内での本格的な実践投入はこれからですが、再利用可能なコード資産が1つでも多く残せるよう時間をかけて育てていきたいです。

明日はohmurakenの記事です。お楽しみに!

Yet Another Bundler - Parcel + Vue.jsの可能性を模索した話 -

この記事は Vue.js #1 Advent Calendar 2017 - Qiita 15日目の記事です。

今年もアドベントカレンダーの季節がやってきました! ブログ書くのは1年ぶりですねw

まずはネタ探しということで最近はReact Nativeを書いている時間が長いのもあり、はじめはVue.jsでモバイルアプリが作れるWeexについての記事を書こうといろいろ準備していました。

そんな穏やかな冬の午後・・・

またなんか出た

qiita.com

ちょっと待ってくれ

先週社内でWebpackの勉強会したばっかなんだけど・・・

f:id:andoshin11:20171214210218p:plain

↑↑ やりきった感のある投稿の様子

正直、

(ほんともう勘弁してくれ〜〜〜)

って感じだしBrowserifyやWebpackをはじめとしたバンドラ論争に振り回されたみなさんの中には怒りすら覚えた人もいるのではないでしょうか

f:id:andoshin11:20171214211638j:plain

とは言っても

触ってみないことには良いも悪いもわからないので、話題のParcelについて調査してみました。

github.com

  • 筆者はフロントエンド歴1年未満
  • Parcelを触りはじめてから1週間くらい経つけど、実質触ったのは3日くらい
  • Parcel自体の開発速度が早く、閲覧いただいている時点では情報が古いかもしれません。記事執筆時点では v1.1.0を使ってます

上記にご留意の上、お楽しみください

本記事の対象

  • webpack.config.jsを書いたことがある人
  • なんとなく webpack.config.jsが読める人
  • とりあえず新しい技術に飛びついてみたい人

「フロントエンドよくわからん」って人にはよくわからんかも知れないけど、僕もよくわからんのでそのへんは雰囲気で

Parcelとは

本記事のテーマであるParcelとは、AdobeのエンジニアであるDevon Govettさんが開発したJavaScript製モジュールバンドラです。

モジュールバンドラとは複数のファイル(モジュール)に書かれたJSのコードを1つに合体させて(バンドリング)吐き出してくれるツールで、同様の機能を持ったものとしてはBrowserifyやWebpackなどが有名です。

f:id:andoshin11:20171214212523p:plain

開発自体がスタートしたのは今年(2017年)の8月のようですが、爆発的にGitHubスター数が増えはじめたのは12月の頭に v 1.0.1がリリースされてからのこと。 記事執筆時点ではそれから9日間で10,000近くのスターを集めています。

何が新しいの?

フロントエンドエンジニアの中には兼ねてよりバンドラ周りの設定の複雑さに辟易とする人が多くいました。(いわゆる「Webpack疲れ」)

エントリポイントと出力先を指定するだけなのに、なんでわざわざ外部ライブラリまで使ってあんな面倒な設定書かなあかんねんというご意見には、僕もわかりみしかない。。。

さらにそこにローダーの設定とそれらを通したいファイルの拡張子を正規表現で指定して、対象外のディレクトリを指定して、プラグインを設定して、といったオプションが上積みされていき、気づいたときには立派な秘伝のタレが出来上がっているわけです。(メンテを押し付けてしまった後輩くん、本当に申し訳ない・・・)

f:id:andoshin11:20171215002244p:plain

そんな中、流星の如くParcelは現れました

zero configuration web application bundler

というメッセージからもわかるように、Parcelはカオスな複雑化の一途を辿るフロントエンド界に新しい風を吹かせようという想いで産み出されたツールです。 後ほどまた触れますがParcelの主な特徴は以下の通り

  • 設定ファイルを全く書かなくてもモジュールバンドリングできる(zero config)
  • プロジェクト内でトランスパイラの設定ファイルを検知すると、自動でトランスパイルしてくれる(Parcelの設定は必要なし)
  • デフォルトでHMR(Hot Module Replacement)に対応済み
  • 速い!(*公式ベンチで対Browserify比最大8.7倍、対Webpack比最大7.8倍)

これが全て本当ならワクワクしますね。さっそく試してみましょう!

まずは動作確認

Yarnでインストール

$ yarn init -y
$ yarn add parcel-bundler

Parcelはモジュールバンドリングのエントリーポイントとしてどのような拡張子のファイルも受け取れるようですが、公式ではHTMLもしくはJavaScriptファイルを指定する事が推奨されています。

今回は index.htmlを起点にパス解決を試してみましょう。

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Parcel Test</title>
</head>
<body>
  <h1>Hello World!</h1>
  <script src="./index.js"></script>
</body>
</html>
// index.js
import { hoge } from './hoge';
console.log(hoge);
//hoge.js
export var hoge = 'Hoge';

ファイルの準備ができたら parcelコマンドにエントリーポイントとなるファイル名を指定して実行してみます。シンプルで分かりやすいですね。

$ node_modules/.bin/percel index.html

上記コマンドを実行するとNodeのホスティングサーバーが立ち上がるはず

デフォルトのポート番号として 1234が割り当てられるので http://localhost:1234/にブラウザからアクセスしてチェック・・・

ところがエラー!!

どうやらPercelがコンパイルしたファイルに構文エラーが含まれているようです

いろいろ試しても解決方法がわからず困っていたところ、コミュニティに質問を投げると秒速で回答をいただきました

感謝感謝!!

手元のNodeバージョンが v7.10.0だったので、アドバイスに従ってnodebrewで v8.1.0に変更 & 再チャレンジ。

今度はちゃんとモジュールバンドリングに成功しました :muscle: :muscle:

↑↑ hoge.jsの中身が index.htmlに読み込まれて出力されている様子

github.com

公式のissueにも上がっていましたね。

毎回コマンドを打つのが大変なので起動スクリプトを設定しておきましょう。

{
  "name": "parcel",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "./node_modules/.bin/parcel index.html",
    "watch": "./node_modules/.bin/parcel watch index.html"
  },
  "dependencies": {
    "parcel-bundler": "^1.1.0"
  }
}

また、Parcelを実行する際に watchコマンドを実行すると配信サーバーは起動せずにファイルの変更だけ検知させることができます

Parcelは何をしているのか

もう少し詳しくParcelの挙動を見ていきましょう

files tree

上記Parcelコマンドを実行するとディレクトリ内に /.cache/distという2つのディレクトリが新たに生成されます。

実際に ./dist/parcel.jsをのぞいて見ると、Webpackで生成した bundle.jsにそっくりなモジュールバンドリング済みJSファイルであることがわかりますね。

Parcelは起動時に /.cacheディレクトリの中身をヒントにコンパイルを行い、 /dist以下のファイルをNodeサーバーから配信しているようです

Babelでトランスパイルする

ここまで設定なしでモジュールバンドリングをすることができました。 今度はES7のトランスパイルを行なってみましょう。

Webpackでは正規表現でターゲットのファイルタイプを指定して babel-loaderを設定することでビルド時にトランスパイルが行えましたが、Parcelは実行したディレクトリ内に .babelrcがあるのを検知すると自動でやってくれます(← 便利)

まずは babelのインストール

$ yarn add babel-core babel-preset-env babel-polyfill

.babelrcを記述

{
   "presets": ["env"]
}

ES7からの新機能である async/awaitを用いてParcelのGitHhubレポジトリにいくつスターが付いているか fetchしてみます。

// index.js
import "babel-polyfill";
import { fetchStars } from './fetchStars';

const SOURCE = "https://api.github.com/repos/parcel-bundler/parcel";

fetchStars(SOURCE);
// fetchStars.js
export const fetchStars = async source => {

  // fetch
  console.log("Start fetching...")
  let response = await fetch(source);

  // parse
  console.log("Start parsing...");
  const json = await response.json();
  const name = json.name;
  const stargazers_count = json.stargazers_count;

  console.log(`${name} has ${stargazers_count} stars!`);
}

$ yarn run startを実行してParcelを起動

f:id:andoshin11:20171215024424p:plain

.babelrcを記述するだけで、無事にES7のコードが動きました!! うーん確かにこれは便利だ。

その他のローダーへの対応

筆者は未確認ですが、記事執筆時点で以下の項目についてzero config(Parcel側の設定なし)でトランスフォームしてくれるらしいです

  • Babel (.babelrc)
  • PostCSS
  • PostHtml
  • CSS Module
  • LESS
  • SASS
  • Stylus
  • TypeScript

Parcel + Vue.js

ParcelとVue.jsの相性はどうでしょうか?

まずはVueインスタンスを生成して動作確認してみます。

$ yarn add vue
<body>
  <div id="app">
    <h1>{{ msg }}</h1>
  </div>
  <script src="./index.js"></script>
</body>
// index.js
import Vue from 'vue/dist/vue.esm.js';

const vm = new Vue({
  el: "#app",
  data () {
    return {
      msg: "Hello Vue!"
    }
  }
})

ここまでは動作成功

シングルファイルコンポーネントとどう戦うか

Vue.jsにはSFC(Single File Component)という仕組みが備わっており、.vueという拡張子のファイルにHTMLライクなシンタックスコンポーネントの要素を記述できます。

これまで、SFCを組み込んだアプリケーションのビルド時にはvue-loader、もしくは vueifyというライブラリをWebpackまたはBrowserifyから呼び出すことでSFCコンパイルを行なっていました。

ではParcelでこの .vueを読み込むにはどうすれば良いのでしょうか?

先に結論

先に結論を言ってしまうと、

ParcelでSFCを扱うことはできませんでした!!

f:id:andoshin11:20171215022750j:plain

力及ばずでほんとうに申し訳ない・・・

だけど可能性は見えた(気がする)

「できませんでした」

だけでは師走の記事として少し寂しいので、今後チャレンジする人のために自分が試したことをツラツラと書き出してみたいと思います

1. Parcel Pluginについて調べてみた

f:id:andoshin11:20171215011412p:plain

Parcelの公式ドキュメントから「Advanced」の項目を見るとプラグインの作り方や、Parcel内部で任意のファイルタイプがどのようにパースされ、トランスフォームされるのかという概要を把握できます

🔌 Plugins

これを見ながらParcelでSFCを処理するためのダミープラグインをまずは作ってみました。また、Parcelがバズった初日に神速でTS向けのプラグインを作ってくれたfathybさんのソースがとても参考になったので感謝です

github.com

2. .vueファイルをパースしてみた

SFCtemplate, script, styleという3つの要素で構成されているため、コンパイルを実行する前に一度パースしてあげる必要があります。 パーサーを自分で書く能力がなかったのでこちらは本家の vue-template-es2015-compilerを利用しました

github.com

vue-template-es2015-compilervue-loadervueifyの内部で利用されているライブラリです。 ドキュメントが用意されていなかったので、実際の利用方法は vue-loadervueifyのソースを読んでぼんやり把握しました。

parse()メソッドを使ってSFCのパースに一応成功

3. コンパイルしてみた(かった)

実際に .vueファイルを .jsファイルにコンパイルする処理です。

これは現在も嵌り中...

vue-template-compilerだけでは上手くできなかったり、 vueifyのそれっぽいAPIもそれ単体で外部から呼び出してもうまく動かなかったり、そもそもParcel自身がコンパイルをミスってたりで記事執筆時点ではまだ成功していません。無念。

Parcel + Vue.jsのこれからとvue-component-compiler

今回の「 SFCをParcelで読み込みたい」という取り組みの中で、同様の思いを持った多くの人々に助けていただきました。 特に12/9(バズった日)より参加させていただいているParcelの公式Slackコミュニティではたくさんの「ParcelとVue.jsを一緒に使いたい!」という人々に出会う事ができ、12/12には #vueチャンネルが立ち上がるまでになりました

f:id:andoshin11:20171215013850p:plain

現在もこちらのSlackでParcelのアップデートやプラグインの追加について日夜議論が繰り広げられています(Slackの通知がほとんどこれになったw)

個人的にこの規模のOSSコミュニティの立ち上げを目にする経験は初めてなので、とても良い刺激をもらっています。

また、SFCコンパイルについて調べていくうちにVue公式チームが vue-component-compilerというライブラリをリリース予定であることも分かりました。

github.com

現在はまだDesigning Threadの状態ですが、「バンドラに依存しないSFCコンパイルAPI」という位置付けで2018年にリリース予定とのことです。 確かに今回 vue-loadervueifyのソースを読む中でロジックが全く共通化されてなくてマズイなーという感想を抱いたので、 vue-component-compilerがリリースされるのが楽しみです。

f:id:andoshin11:20171215015602p:plain

みんなでkazuponさんを信じて待ちましょう!!

(ParcelでSFC使えるようになったらvue-cliのテンプレート作ってOSS活動したいなー)

結局Webpackから乗り換えるべきなの?

今回実際にParcelをいろいろと触ってみましたが、

まだWebpackは手放せない!

というのが正直な感想です。

確かに「Zero configuration web application bundler」という触れ込みは大変魅力的です。

僕自身、みなさんと同じよう近年のフロントエンドの煩雑さは常軌を逸していると感じている一人であり、Webpackの設定など苦痛そのものだと考えています。

だからといって、

「煩雑な設定が多すぎる」

という問題に対して

「なにも設定しなくて大丈夫!」

というソリューションを採用するのもいささか極端な発想であり、みんなが幸せになる考えでもない気がしています。

最新のECMAScriptで全てのコードが記述され、あらゆるデータが型を持ち、そして地球上の全てのブラウザが遍く最新のWeb標準仕様に対応した世界こそ我々フロントエンドエンジニアにとってのユートピアですが、残念なことにまだ人類はその理想郷にたどり着く事ができていません。

これからもしばらくはプロジェクトごとに .babelrcを書き、ファイルの種類ごとに適切なローダーを割り当てる運用はなくならないでしょう。 そう考えたときに、まだリリース直後のParcelには自由度が少なすぎるため、開発要件を満たすのに必要十分とは言えません。

今後の可能性に期待!

Parcelはまだ歩き始めたばかりのプロジェクトです。裏を返せば伸び代しかありませんw この1週間、Slack上で秒速でバグがつぶされていく様子や、爆速で新機能が追加されている様子をみていると、その将来はとても明るいものになる気がしています。

また、コミュニティ全体で「zero config」の哲学が浸透しているだけでなく、

Zero config doesn't mean zero configurable

という合言葉も頻繁に用いられていることからもわかるよう、様々なユースケースに対応したPluginsやExamplesが絶賛開発中です。 本番投入はまだ無理でも、個人で開発しているプロダクトでガンガンドッグフーディングしていこうと思います。

長文に最後まで目を通していただき本当にありがとうございます。 Happy Vue Life!!

追記(2017/12/15 16:08)

lc60005457さんが parcel-plugin-vueを作ってくれたようです!

github.com

実際に .vueファイルを用意して試してみたところちゃんと動作しました!どうやら vueifyAPIを利用しているようですね。 これでVueアプリケーションの開発が捗ります。ありがとうございます!!

【第二回】Rubyで作ったSlack BotとSpotify APIで遊んでみた

この記事はCAMPHOR- Advent Calendar 2016の17日目の記事です

謝罪

いきなり謝罪です。Advent Calendar上で予告していたタイトル「Rubyで作ったSlack BotとRhymerで遊んでみた」ですが、大嘘であることをここに謝罪します。ごめんなさい。

もともとは今年の中頃に一部界隈をおおいに賑わせたRhymerを使って遊ぼうと思っていたのですが、諸般の事情(スベりそう)から止むを得ず内容を変更させていただきました。興味のある方は以下のリンクより Rhymerをお楽しみください↓↓

qiita.com

ごあいさつ

先日の記事は多くの方に読んでいただけたようで非常に嬉しく思います。本日は予告通り2本立ての2本目にあたる内容です。

みなさん、Spotify使ってますか?

Subscription系の音楽サービスとしては近年「Apple Music」「AWA」「LINE Music」など多くのプレイヤーが参入してきましたが、特に洋楽好きの方などはまだまだ「Spotify」人気が根強いのではないでしょうか。今回はみんな大好きSpotifyAPIを使ってSlack Botを強化していきたいと思います。

概要

  • Bot本体は前回の記事で使用したファイルをそのまま使ってます
  • bgmというコマンドを入力することでSlackのタイムライン上にSpotifyの埋め込みリンクを表示するBotを作ります
  • 楽曲、アーティスト、アルバム、プレイリストのそれぞれで検索機能を実装
  • ジャンルをもとにしたレコメンド機能を実装
  • より複雑な機能を実装しようとおもったけど断念(後述)
  • 正規表現の理解度がダメダメなのでツッコミお待ちしています

目次

  1. RSpotify
  2. 検索機能の実装
  3. レコメンド機能の実装
  4. 個人利用のためのカスタマイズ(断念)

環境

  • macOS Sierra 10.12.2 Beta
  • MacBook Air (13-inch, Early 2014)
  • ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

参考

やっていくぞ!!

1. RSpotify

RSpotifyGuilherme Sad氏によるSpotify Web APIRubyラッパーです

1.1. 開発用キーの設定

Spotify Developer Pageから新規アプリケーションを作成し、 Client IDClient Secretの2つのトークンを取得します。

f:id:andoshin11:20161216070927p:plain

f:id:andoshin11:20161216071103p:plain

トークンを取得したらそれぞれ SPOTIFY_CLIENT_IDおよび SPOTIFY_CLIENT_SECRETという環境変数として保存します。方法は前回の記事を参考にしてください。

1.2. RSpotifyのインストール

前回作成した Gemfileに追記

# Gemfile
source 'https://rubygems.org'
ruby "2.3.1"

gem 'http'
gem 'json'
gem 'faye-websocket'
gem 'eventmachine'
gem 'rspotify'

$ bundleコマンドでGemをインストール f:id:andoshin11:20161216051148p:plain

1.3. RSpotifyの動作確認

RSpotifyと関連するGemのインストールが完了したら動作をテストしてみましょう。前回同様、 bot.rbに追記していきます。

# bot.rb
require 'http'
require 'json'
require 'eventmachine'
require 'faye/websocket'
require 'rspotify' # RSpotifyの読み込み

# アプリの認証情報
RSpotify.authenticate(ENV['SPOTIFY_CLIENT_ID'], ENV['SPOTIFY_CLIENT_SECRET'])

(省略)

  ws.on :message do |event|
    data = JSON.parse(event.data)

    # data['text']が存在しなければスキップし、存在すればmsg変数に代入
    next if data['text'].nil?
    msg = data['text']

    p [:message, data]

    if msg == 'こんばんは'
      ws.send({
        type: 'message',
        text: "こんばんは <@#{data['user']}> さん"+data['text'].split(" ").first,
        channel: data['channel']
        }.to_json)
    end

    # "bgm"という単語から始まるテキストが入力された時の処理
    if msg.match(/^bgm/)
      test_url = RSpotify::Track.find('5CwhBU7eqQgkOVMnRNuzzT').external_urls['spotify']
      ws.send({
        type: 'message',
        text: test_url,
        channel: data['channel']
        }.to_json)
    end
  end

(省略)

bgmという単語から始まるテキストがSlackに投稿された時に Spotifyから 5CwhBU7eqQgkOVMnRNuzzTというIDに該当する曲を探し、その埋め込み用リンクを返すような処理です。

このコードを実行してみましょう。 f:id:andoshin11:20161216053008p:plain

https://gyazo.com/d978f4f1449f7ab0e7f33db822a1ab1f.gif

ID: 5CwhBU7eqQgkOVMnRNuzzTの曲、つまりGet Wildの埋め込みリンクが無事タイムラインに投稿されました!

2. 検索機能

2.1. 楽曲を検索できるようにする

無限にGet Wildを聴き続けるのもよいですが、せっかくなら楽曲を検索して表示したいものです。

bgm song (クエリ)というテキストが入力されたらクエリに該当する楽曲を検索

# bot.rb
...

    if msg.match(/^bgm/)
      text = "No result"
      if matched_msg = msg.match(/^bgm song (.*)/)
        track = RSpotify::Track.search(matched_msg[1]).first
        text = track.external_urls['spotify'] unless track.nil?
      end
      ws.send({
        type: 'message',
        text: text,
        channel: data['channel']
        }.to_json)
    end
...

実行

https://gyazo.com/4bbcd364454951083817bb7f1f97e580.gif

2.2 アーティスト、アルバム、プレイリストの検索機能

上記のコードと同じ要領でアーティストやアルバム、プレイリストについても検索機能を実装していきます

# bot.rb

...
    if msg.match(/^bgm/)
      text = "No result"
      if matched_msg = msg.match(/^bgm song (.*)/)
        track = RSpotify::Track.search(matched_msg[1]).first
        text = track.external_urls['spotify'] unless track.nil?
      elsif matched_msg = msg.match(/^bgm artist (.*)/)
        artist = RSpotify::Artist.search(matched_msg[1]).first
        text = artist.external_urls['spotify'] unless artist.nil?
      elsif matched_msg = msg.match(/^bgm album (.*)/)
        album = RSpotify::Album.search(matched_msg[1]).first
        text = album.external_urls['spotify'] unless album.nil?
      elsif matched_msg = msg.match(/^bgm playlist (.*)/)
        playlist = RSpotify::Playlist.search(matched_msg[1]).first
        text = playlist.external_urls['spotify'] unless playlist.nil?
      end
      ws.send({
        type: 'message',
        text: text,
        channel: data['channel']
        }.to_json)
    end
...

実行!

https://gyazo.com/1f7ad569f004bab3d9c34ca81085536f.gif

https://gyazo.com/c46fa903e6348523f8463957e55ad0a2.gif

YOSAGE(ガッツポーズの絵文字)

3. レコメンド機能

音楽のジャンルごとに楽曲をレコメンドしてくれる機能を実装します。 bot.rbに追記

# bot.rb
...

    if msg.match(/^bgm/)
      text = "No result"
      if matched_msg = msg.match(/^bgm song (.*)/)
        track = RSpotify::Track.search(matched_msg[1]).first
        text = track.external_urls['spotify'] unless track.nil?
      elsif matched_msg = msg.match(/^bgm artist (.*)/)
        artist = RSpotify::Artist.search(matched_msg[1]).first
        text = artist.external_urls['spotify'] unless artist.nil?
      elsif matched_msg = msg.match(/^bgm album (.*)/)
        album = RSpotify::Album.search(matched_msg[1]).first
        text = album.external_urls['spotify'] unless album.nil?
      elsif matched_msg = msg.match(/^bgm playlist (.*)/)
        playlist = RSpotify::Playlist.search(matched_msg[1]).first
        text = playlist.external_urls['spotify'] unless playlist.nil?

      # 追記部分
      elsif matched_msg = msg.match(/^bgm recommend (.*)/)
        result = RSpotify::Recommendations.generate(seed_genres: [matched_msg[1]]).tracks.sample
        text = result.external_urls['spotify'] unless result.nil?
      end

      ws.send({
        type: 'message',
        text: text,
        channel: data['channel']
        }.to_json)
    end
...

bot recommendから始まる入力が与えられたときに RSpotify::Recommendations.generateを実行しています。 RSpotify::Recommendations.generateからはおすすめの track20曲分(デフォルト時)の配列が得られるため、その中からランダムで1曲を抜き出して埋め込みリンクを表示するような処理です。

レコメンド機能を試してみましょう。

https://gyazo.com/9f6a6eb6f3641eb62b28b76173c9fa8d.gif

こちらもYOSAGEですね。ちなみに2016年12月16日現在レコメンド機能がカバーしているジャンルは以下の通りです

  {
  "genres" : [ "acoustic", "afrobeat", "alt-rock", "alternative", "ambient", "anime", "black-metal", "bluegrass", "blues", "bossanova", "brazil", "breakbeat", "british", "cantopop", "chicago-house", "children", "chill", "classical", "club", "comedy", "country", "dance", "dancehall", "death-metal", "deep-house", "detroit-techno", "disco", "disney", "drum-and-bass", "dub", "dubstep", "edm", "electro", "electronic", "emo", "folk", "forro", "french", "funk", "garage", "german", "gospel", "goth", "grindcore", "groove", "grunge", "guitar", "happy", "hard-rock", "hardcore", "hardstyle", "heavy-metal", "hip-hop", "holidays", "honky-tonk", "house", "idm", "indian", "indie", "indie-pop", "industrial", "iranian", "j-dance", "j-idol", "j-pop", "j-rock", "jazz", "k-pop", "kids", "latin", "latino", "malay", "mandopop", "metal", "metal-misc", "metalcore", "minimal-techno", "movies", "mpb", "new-age", "new-release", "opera", "pagode", "party", "philippines-opm", "piano", "pop", "pop-film", "post-dubstep", "power-pop", "progressive-house", "psych-rock", "punk", "punk-rock", "r-n-b", "rainy-day", "reggae", "reggaeton", "road-trip", "rock", "rock-n-roll", "rockabilly", "romance", "sad", "salsa", "samba", "sertanejo", "show-tunes", "singer-songwriter", "ska", "sleep", "songwriter", "soul", "soundtracks", "spanish", "study", "summer", "swedish", "synth-pop", "tango", "techno", "trance", "trip-hop", "turkish", "work-out", "world-music" ]
}

4. 個人利用のためのカスタマイズ(...だがしかし断念)

当初の予定ではここからユーザー情報で OAuth認証してSlackから個人のプレイリストに曲追加したりいろいろやりたいことがあったけど、Advent Calendar担当日のタイムリミットが来たので断念...

近いうちにリベンジしたいです。

おわりに

日本でのサービスイン以前からSpotifyを利用しているヘビーユーザーなのでAPIを叩くだけで遊べるのはとてもありがたいです。 Slack Botに限らず今後もいろんな機会で使っていきたいと思います。

明日はmurata_atsumiの記事です

Let's Get Wild!

open.spotify.com

【第一回】超簡単!RubyでSlack Botを作る方法

この記事はCAMPHOR- Advent Calendar 2016の13日目の記事です

ごあいさつ

今年もAdvent Calendarの季節がやってきましたね。 せっかくなのでこれを機にTech関連の記事を残すため、はてなブログはじめました。 相変わらず見習いエンジニアの域を出ない@andoshin11 です。 今回の記事は2本立ての構成になっています。(2本目の記事は12/17に公開予定)

またまた長文になりますがお付き合いよろしくお願いします。

昨年度の記事はこちら↓↓

tech.camph.net

概要

  • プログラミング初心者でも記事を読めばBotが作れるよう意識して書きました*1
  • 昨年は Googla Apps Scriptを使用しましたが、今年は RubyでSlack Botを実装します
  • Slack公式が提供する Real Time Messaging APIを利用します
  • Botを作成し、外部サーバー(Heroku)で動かすところまでを目標にします
  • rubotylitaのようなライブラリは利用せず、勉強も兼ねて自前での実装を目指します
  • 質問あればコメントください!

目次

  1. Slack API入門
  2. Real Time Messaging API
  3. Herokuにデプロイするぞ

環境

参考

やっていくぞ!!

1. Slack API 入門

幸いなことにSlack自身がすでに豊富なAPIを整えてくれているので、その使い方さえ理解すれば比較的容易に手元のマシンからでもメッセージの送受信ができます。

Slack API

1.1. APIのテスト

何はともあれ RubySlack APIを叩いて遊んでみましょう! まずは作業ディレクトリ内に Gemfileを作成します。

# Gemfile
source 'https://rubygems.org'

gem 'http'
gem 'json'

次に本体である test.rbの作成です

# test.rb
require 'http'
require 'json'

response = HTTP.post("https://slack.com/api/api.test")
puts JSON.pretty_generate(JSON.parse(response.body))

2つのファイルの用意ができたら $ bundle exec ruby test.rbスクリプトを実行します。

f:id:andoshin11:20161209230148p:plain

POSTしたリクエストへのレスポンスとして "ok":trueが返ってきましたね。Slack本体のサーバーと対話ができた証拠です。

1.2. Authentication

次はもう一歩踏み込んでチーム固有の情報を取得できるよう、 Authenticationを行なっていきたいと思います。

先ほどはhttps://slack.com/api/api.testというURLにリクエストを送りましたが今度はhttps://slack.com/api/auth.testというアドレスにリクエストを送ってみます。

test.rbを編集

# test.rb
require 'http'
require 'json'

response = HTTP.post("https://slack.com/api/auth.test")
puts JSON.pretty_generate(JSON.parse(response.body))

実行

f:id:andoshin11:20161209230259p:plain

"ok":false, "error": "not_authed"と怒られてしまいました。これはリクエスト時に Tokenが設定されていないことが原因です。 Tokenは、管理画面から Bots Integrationを追加することで取得できます。 f:id:andoshin11:20161209190042p:plain f:id:andoshin11:20161209190056p:plain

発行された Tokenをコピーして環境変数として保存しましょう。

f:id:andoshin11:20161209230425p:plain

POSTリクエストに params[:token]を追加。

require 'http'
require 'json'

response = HTTP.post("https://slack.com/api/auth.test", params: {
    token: ENV['SLACK_API_TOKEN']
  })
puts JSON.pretty_generate(JSON.parse(response.body))

実行

f:id:andoshin11:20161211222749p:plain

成功しました!今度は認証が認められ、加えて Tokenに紐づいたチームの情報やユーザーの情報も返ってきています。 余談ですがSlack内で割り振られている team_idは先頭が Tから始まり、 user_idUから始まるという仕様のようです。

1.3. Post Message

認証が通るようになったので、実際にSlackへメッセージを投稿してみましょう。

メッセージ投稿用APIのURLはhttps://slack.com/api/chat.postMessageです。パラメータに投稿先のチャンネルや、投稿したいテキストを指定してリクエストを送ることでメッセージの投稿が可能になります。

test.rbを編集

require 'http'
require 'json'

response = HTTP.post("https://slack.com/api/chat.postMessage", params: {
    token: ENV['SLACK_API_TOKEN'],
    channel: "#general",
    text: "こんにちは!",
    as_user: true,
  })
puts JSON.pretty_generate(JSON.parse(response.body))

as_user: trueとしているのはデフォルトの Bots Integrationとしてではなく、自分で ConfigureしたBotユーザーに投稿させたいからです。

スクリプトを実行

f:id:andoshin11:20161211223037p:plain

f:id:andoshin11:20161211223050p:plain

メッセージの投稿に成功しました!このときレスポンス内に投稿されたメッセージの関する情報が含まれているのもわかりますね(タイムスタンプ等)

ここまでで基本的な Slack APIの使い方を把握できました! Cron等で定期的にこのメッセージ投稿APIを叩くだけでも簡易Botが作れそうですね。

2. Real Time Messaging API

前節ではあくまで一方通行のメッセージの投稿方法を確認しました。 ですが実際の運用を想定すると常時双方向通信を監視し、ユーザーからの投稿に合わせて適切な出力を返してあげる必要があるでしょう。 ここからはSlackの備える Real Time Messaging APIと低コストで双方向通信を可能にする Web Socketの仕組みを利用して、より実用的なBotを作っていきます。

2.1. Real Time Messaging APIとは

Slackには既に Outgoing Webhookというユーザーの投稿内容を外部のサイトやアプリに出力してくれる仕組みがありますが、 Outgoing Webhookの扱える情報は特定のキーワードから始まるメッセージのみです。

対して Real Time Messaging APIはその名の通り24時間チームを監視しユーザーの全ての投稿内容だけでなく、「チャンネルの新規作成」「チャンネルへのユーザーの参加/脱退」などといった1つ1つのイベントの情報すら出力してくれる仕組みです。

この仕組みを用いることでより複雑なBotの実装が可能になります。

2.2. Real Time Messaging APIでデータを取得する

まずは Gemfileを編集して2つの新たなGemを追加します

# Gemfile
source 'https://rubygems.org'

gem 'http'
gem 'json'
gem 'faye-websocket'
gem 'eventmachine'

faye-websocketによってRubyでWeb Socketサーバーを立てることが容易になり、 eventmachineは並列処理等を可能にしてくれるものです

早速、https://slack.com/api/rtm.startにリクエストを送り Web SocketのURLを確認しましょう。

# test.rb
require 'http'
require 'json'
require 'eventmachine'
require 'faye/websocket'

response = HTTP.post("https://slack.com/api/rtm.start", params: {
    token: ENV['SLACK_API_TOKEN']
  })

rc = JSON.parse(response.body)

puts rc['url']

実行

f:id:andoshin11:20161209232226p:plain

レスポンスの JSONの中の['url']にアドレスが含まれているのが分かりますね。このアドレスに対して双方向通信を確立するよう、 Web Socketを動かしていきます。 Real Time Messaging APIからは常に情報が降ってくるので、並列処理のための Event Machineもお忘れなく。

# test.rb
require 'http'
require 'json'
require 'eventmachine'
require 'faye/websocket'

response = HTTP.post("https://slack.com/api/rtm.start", params: {
    token: ENV['SLACK_API_TOKEN']
  })

rc = JSON.parse(response.body)

url = rc['url']

EM.run do
  # Web Socketインスタンスの立ち上げ
  ws = Faye::WebSocket::Client.new(url)

  #  接続が確立した時の処理
  ws.on :open do
    p [:open]
  end

  # RTM APIから情報を受け取った時の処理
  ws.on :message do |event|
    p [:message, JSON.parse(event.data)] 
  end

  # 接続が切断した時の処理
  ws.on :close do
    p [:close, event.code]
    ws = nil
    EM.stop
  end

end

実行!

f:id:andoshin11:20161209232530p:plain

一番上に接続が確立したことを示す [:open]が表示されていますね。

二行目の [:message]に含まれる {"type"=>"hello"}というのはSlack側が接続確立時に送信してくるものです。三行目は無視して構いません。

四行目の内容ですが {"type"=>"presence_change"...}というメッセージからも分かる通り、ユーザーのステータスが変わったことを示すイベントをキャッチしてReal Timeでこちらの Web Socket Serverに送信してくれています。

このプログラムを走らせたまま、もう少しSlack側でいろいろいじってみましょう。

https://gyazo.com/7c40944aa3c8d36adccac896dd444e5b.gif

https://gyazo.com/059890bb5da0f256b26665cbae0901de.gif

みなさんお気付きのように Real Time Messaging APiではユーザーが投稿する時だけでなく、「入力中」であることや「リアクション」を取った事すらも細かく取得することがわかりました。これで様々な種類のBotが実装できますね!

2.3 ユーザーの投稿に合わせてメッセージを返す

ここまでで常時ユーザーの投稿内容が取得できるようになりました。 次は投稿に合わせたレスポンスを返せるよう test.rbを修正していきます。

# test.rb
...

  ws.on :open do
    p [:open]
  end

  ws.on :message do |event|
    data = JSON.parse(event.data)
    p [:message, data]

    if data['text'] == 'こんにちは'
      ws.send({
        type: 'message',
        text: "こんにちは <@#{data['user']}> さん",
        channel: data['channel']
        }.to_json)
    end
  end

  ws.on :close do
    p [:close, event.code]
    ws = nil
    EM.stop
  end

...

ユーザーが「こんにちは」と投稿したら、「こんにちは (ユーザー名)さん」と同じチャンネルで返すようなシンプルなスクリプトです。

実行!

https://gyazo.com/4eedd5ba19eacd9def0f7ee0ccd8e4dc.gif

意図した通りに動きましたね。

1つ注意しなくてはいけないのが、Botが投稿した内容もまた RTM APIによって返ってくるということです。Bot同士の会話が一生終わらないという事態が無いように気をつけてください。 f:id:andoshin11:20161209202001j:plain

RTM APIで受信したJSON内に含まれる情報を上手く利用することで双方向対話ができるようになりました。おめでとうございます!

3. 作ったBotを公開する

ここまででLoaclのマシン上でBotが動くようになりましたが、せっかくなので外部サーバーでホスティングして常時稼働させてあげたいものです。

今回は Herokuにデプロイすることでこの課題を解決していきます。 事前準備として heroku toolbeltを導入するところまでは進めておいてください。

参考リンク:Heroku登録〜Macで環境整備〜お試しWebアプリを作るまで

3.1. Gitの設定

Heroku上で動かすにあたって GemfileRubyのバージョンに関する情報を追記します。

# Gemfile
source 'https://rubygems.org'
ruby "2.3.1"

gem 'http'
gem 'json'
gem 'faye-websocket'
gem 'eventmachine'

また test.rbの名前をカッコ悪いので bot.rbに変更し、「こんばんは」というコマンドに反応するよう修正しました。

# bot.rb
...

  ws.on :message do |event|
    data = JSON.parse(event.data)
    p [:message, data]

    if data['text'] == 'こんばんは'
      ws.send({
        type: 'message',
        text: "こんばんは <@#{data['user']}> さん",
        channel: data['channel']
        }.to_json)
    end
  end

...

お決まりの $ git init $ git add . $ git commit -m "Initial commit"までの流れで Gitの初期化を行います

f:id:andoshin11:20161210011218p:plain

3.2. Heroku側の設定

Heroku のアカウントを取得し Heroku Toolbeltの設定も済んでいる方はコマンドラインからログインができます。

$ heroku login実行後ログイン情報を入力

f:id:andoshin11:20161210004318p:plain

初期状態では Heroku上に Rubyの実行環境が存在しないため、公式の提供する Heroku Buildpack for Rubyを利用してアプリケーションの初期化を行います。

$ heroku create --buildpack https://github.com/heroku/heroku-buildpack-ruby.gitを実行

f:id:andoshin11:20161210004746p:plain

$ git push heroku masterでファイルをデプロイ!

f:id:andoshin11:20161210011545p:plain

TokenHeroku環境変数として登録します

f:id:andoshin11:20161210011706p:plain

さぁ、ここまでで準備完了です。実際に Heroku上で Rubyスクリプトを動かしてみましょう!

$ heroku run bundle exec ruby bot.rbを実行

f:id:andoshin11:20161210012017p:plain

https://gyazo.com/5abef68450c61f6c09c56f31323952c7.gif

動いたー!!

おわりに(...そして予告)

お疲れ様でした。Slackの強力なAPIWeb Socketの力でシンプルなスクリプトでもBotを作れるようになりましたね。やっている事は本当に単純なのでどんな言語でも応用が効くことでしょう。

冒頭でもお伝えしましたが今回は2本立てです。 次回の内容は、今回作成したプログラムにアップデートを加えてより実用的(????)なBotを作る手段を記事にします。 お楽しみに!

明日はsiriusjackの記事です

*1:僕自身が雑魚エンジニアなので...

*2:MBAディスコンになってしまったのでかなしい...