Ruby on Rails + webpack-dev-server で Development.
Rails で JavaScript が必要なプライベートプロジェクトでは、ながらく Npm + Watchify + Gulp + Rails を手動起動などしながらすすめていましたが、最近、意を決して Yarn + Webpack + Rails に変更しました。そこで、Rails と JavaScript の接合点も Railsway にのった設定に変えようと思いました。(今までは public/css を直接参照していた)
ざっと検索したところ、webpack-dev-server 経由で JavaScript を参照する場合、あらたな helper を用意する方式しか見つけられなかったので、assets の path を変更する方式をメモります。
概要
webpack-dev-server を port 5001 で起動し、Rails の assets 用のタグがそれを参照するようにします。
Rails configuration
#config/environments/development.rb Rails.application.configure do # snip config.action_controller.asset_host = 'http://localhost:5001' end
Webpack configuration
Files place
JavaScript ファイルとして app1.js, app2.js, app3.js が書きだされる想定です。
- app # 他 Rails のディレクトリ - public - lib - sass - common.sass - src - app1 - index.js - app2 - index.js - app3 - index.js - package.json
package.json
style-loader を噛ませるととても遅いので、sass は node-sass ダイレクトで書きだします。書きだし先のディレクトリを参照できるように、webpack-dev-server を --content-base でルートを public に合わせておきます。
"scripts": { "watch": "webpack-dev-server --progress --colors --config ./webpack/dev.config.js --port 5001 --content-base ../public", "watch-sass": "node-sass --watch ./sass/common.sass ../public/stylesheets/common.css" },
webpack.config.js
publicPath を javascripts という、Rails ネーミングに合わせておきます。
sass も webpack-dev-server を通す場合、こちらにも publicPath が必要ですが、こちらは ExtractTextPlugin 側に設定しなければならない点に注意します。
const entryPoints = require('glob').sync('./src/*/index.js').reduce((a, v)=> { a[v.split('/')[2]] = v return a }, {}) const jsConfiguration = { entry: entryPoints, output: { publicPath: 'javascripts', filename: "[name].js" }, module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] } } const ExtractTextPlugin = require('extract-text-webpack-plugin') const cssConfiguration = { entry: './sass/common.sass', output: { publicPath: 'stylesheets', filename: "common.css" }, module: { loaders: [ { test: /\.sass|\.scss/, loader: ExtractTextPlugin.extract({ fallbackLoader: "style-loader", loader: "css-loader?minimize!sass-loader", publicPath: 'stylesheets', }) } ] }, plugins: [ new ExtractTextPlugin("common.css") ] } module.exports = [ jsConfiguration, // cssConfiguration ]
できあがり
これで以下のようにコンパイルされ、webpack-dev-server は狙ったファイルを返すようになりました。
= javascript_include_tag('app1.js')
<script src="http://localhost:5001/javascripts/app1.js"></script>
なお Rails 起動時には webpack-dev-server も同時に起動することになりますが、Procfile.dev を用意して foreman で起動すると手間がかからなくて良いようです。
web: bundle exec rails server -p 5000 compile: cd lib/es && yarn run watch compile: cd lib/es && yarn run watch-sass
foreman start -f Procfile.dev
今年の洗濯回数は 8 回でした。
一人暮らしももう 20 年に近くなりますが、洗濯機が家にあったことがありません。家に洗濯機がないので、コインランドリーを利用しています。利用料金は洗濯に 1,200 円、乾燥機に 700 円ほどです。
15kgの洗濯物も22kg対応の横ドラムで一発よ
— おふくろさま (@o296sm) February 7, 2016
乾燥機は 25 kg対応の大型のものが 700 円で 56 分ほどまわります。本来はそこまでの容量のものを、そこまでの時間回す必要がないのですが、とにかくパリパリにするのが好きなのでまわします。
俺が洗濯機を買わないのは、適切に干すのが苦手で、なおかつ乾燥機でパリパリにするのが大好きなので業務用のでかいやつぐらいのパワーが欲しいからですが、toto当たったら家に業務用のガス乾燥機置きたいですね。
— おふくろさま (@o296sm) May 24, 2016
1 回目 2 月
洗濯物バッグにつめこんだら15kgだった。
— おふくろさま (@o296sm) February 7, 2016
2 回目 4 月
記録なし
3 回目 5 月
洗濯を終えて家に帰ってくると部屋が十分に暑かったので冷房をつけた。
— おふくろさま (@o296sm) May 23, 2016
4 回目 7 月
サッと運動済ませて洗濯行くか。
— おふくろさま (@o296sm) July 9, 2016
5 回目 8 月
就職しました。
昨日間違って寝る前に仕事してしまったのでかわりに洗濯に来た。涼しくてよい。乾燥機派だから洗濯後の天気は一切関係ないのが最高
— おふくろさま (@o296sm) August 23, 2016
これで 10 月まで洗濯しないですみそう。コインランドリーに行く毎に季節かわってんだよな。
— おふくろさま (@o296sm) August 24, 2016
6 回目 10 月
洗濯に来てるけどウルトラ蒸す
— おふくろさま (@o296sm) October 2, 2016
7 回目 11 月
洗濯中
— おふくろさま (@o296sm) 2016年11月20日
8 回目 12 月
洗濯 ok
— おふくろさま (@o296sm) 2016年12月20日
まとめ
冬場はパーカーが洗濯物に加わりますので、少しペースが早くなりますね。
今年も少し T シャツとブリーフが増えましたが、同じぐらい捨てました。来年も同様のペースになることでしょう。よろしくお願いいたします。
top コマンドで得られる結果をどう見ていけばいいのかわからないので、とりあえず視覚化した
もともと一つの処理であるとか、一つのメソッドであるとかの処理時間には興味があって、測定などをしていました。ActiveRecord を使うにあたって発行クエリを抑えることにより、本当に早くなるのか確かめたりするのがすきです。
全体的なパフォーマンスを測定し、改善に繋げる知識がない
そういう局所的なものは何度もまわして処理時間を取って、という方法でなんとなく測定をできるのですが、Web アプリケーション全体の負荷の把握方法や、運用においてのスローダウンの原因究明などに使える情報などの取得については、ほとんど知識がありません。
ぐぐることにより、各種コマンドで負荷を数値化できることはすぐにわかりましたが、その数値が何を意味するかはよくわかりません (負荷が高い低いぐらいはわかるが、どういう経緯でそうなったかわからない)。さらに、これを蓄積し、それをそのまま眺めたところで、勘の悪いわたしが何かを把握できるとは思えなかったので、せめてわかりやすい形に変換しようと、最近取り組んでいたのが top のグラフ化です。
これは Chrome で 70 タブぐらい一気に開いたときの図です。CPU は大暴れ、メモリ消費もアゲアゲで大変きれいですね。
どこかにありそうなソフトですが、こういうラッパーのようなものをつくると、そのコマンドと仲良くなれるのでまるっきり無駄ということはないと信じています。
勉強
現段階では単にグラフ化しただけなので、ここからどうつなげていくかは要勉強というところですが、これだけでも Chrome の大量のタブは大量のプロセスを産んで、それぞれ個別にちょっとずつメモリを消費して大変なことになるので、個別のメモリに注目していてはその本当の負荷はわからないということがわかりました。
いまは、翔泳社 + kindle のポイント還元セールで入手した以下の本で、パフォーマンスのあれこれに関する勘所を把握しようとしています。各分野においての基礎知識を軽く説明ののちに、パフォーマンスなどの原因や解決法の話に入るので、まるでわからない状態で聞かされることがなく、いい本だと思います。
- 作者: 小田圭二,榑松谷仁,平山毅,岡田憲昌
- 出版社/メーカー: 翔泳社
- 発売日: 2014/07/03
- メディア: Kindle版
- この商品を含むブログを見る
その他
プログラムのほうは、バックエンドは Go から SSH で対象サーバーに接続していますが goroutine の不適切使用により、同データへの同時アクセスが原因と思われる panic というのを経験して、非同期処理の経験値がアップしました。よかったですね。
Go 言語でつくったもののメモリとかをなんとなく見れるようにする
pprof を用いた詳細な情報を得る方法はさんざん紹介されており、しかしその詳細の情報のどこを見ればいいのかわからないので、とにかく簡単に見れるような施策を打ってみました。
これは社の Slack で動いてるボットの一つの強制停止画面ですが、ボットは継続して動いているので、メモリリークなどが気になるところです。ということで、グラフかつ、前後の増減がスッと把握できるようにしました。(これは短期すぎて役に立たなさそうですが)
グラフの描画は雑に C3.js で行っており雑な JavaScript なので特にあらためて何もないのですが、グラフを描画する用のデータ配列に関しては、今後もちゃんと把握して真面目に実装していきたいので、package 化して再利用できるようにしました。
go heaper.Run(1, 60) http.HandleFunc("/heaps", func(w http.ResponseWriter, req *http.Request) { heaps, _ := json.Marshal(heaper.Read()) w.Header().Set("Content-Type", "application/json") w.Write(heaps) })
こんな感じで pprof.WriteHeapProfile
で得られる情報を任意の間隔 (1 sec)、任意の量 (60) ためておき、見れるという感じで、今後、どこを見ればいいのかわかるようになれば、それなりに役に立ってくれるはずです、多分。ただ単に画面が動いてるのが好きなのでやってみたという気がしないでもない。
Go 言語で struct を url.Values に展開した
現在勉強用に作っているボットでは、Slack の API を使います。勉強用なので、ライブラリを使わず、独自に実装しています。API ではエンドポイント毎にさまざまな要求パラメーターがあって、その引数を自由な形式にすると間違いのもとになるので、専用の struct を用意して間違いが起こらないようにしています。
func (a *API) ChatPostMessages(p ChatPostMessageParameters) (ChatPostMessagesResponse, error) { var r ChatPostMessagesResponse err := a.postAndStruct(&r, "/chat.postMessage", p) return r, err }
public にしているメソッドはそのようなキッチリ定義した struct を引数として要求する一方で、色々なエンドポイントを叩くときのメソッド (postAndStruct など) は共通化したかったため、private にしてあるその共通メソッドの引数では、どんな struct でも受けつけて url.Values に変換、POST や GET したいと思いました。
というわけで、あまり推奨されていないであろう、reflect で struct をなんでも展開という方法を使います。
func (a *API) buildParameters(parameters interface{}) url.Values { values := url.Values{} r := reflect.ValueOf(parameters) rt := r.Type() for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) key := "" if to := f.Tag.Get("json"); to != "" { key = to } else { key = strings.ToLower(f.Name) } v := fmt.Sprint(r.Field(i)) if v != "" { values.Add(key, v) } } values.Add("token", a.Token) return values }
interfrace{}
として受け取った値を reflect.ValueOf(parameters)
するのがキモで、それ以外は流れ作業でした。どうやれば interface{} -> struct できるかというのは github.com/fatih/structs というライブラリを読んで知りました。ソースコードがスッと読める言語は、こういう時に便利ですね。
Go 言語でつくったボットを GitHub -> CircleCI -> Bluemix と自動デプロイできるようにした
Golang でのボット作成では、1 個目は自 PC で動かすことしか考えていませんでしたが、2 個目はきちんとどこかにデプロイすることを目標に作成していましたので、ついでに自動でやれるようにしました。
Heroku では 24 時間稼働のボット用途には不向きだと思われたので、IBM Bluemix にデプロイ先に選びました。Bluemix もデフォルトで Golang をサポートしていますが、ビルトインの buildpack だと Godeps のバージョンをきちんと読んでくれなかったり (これは気のせいかもしれない) したのでgithub にあるものを利用したりして色々とあれでしたが、うまく行きました。
circle.yml の様子。
machine: environment: REPO_ROOT: "${HOME}/.go_workspace/src/YOUR_DOMAIN/YOUR_PACKAGE_DIR" dependencies: pre: - mkdir -p ${REPO_ROOT} - cp -rf ./* ${REPO_ROOT} - go get github.com/tools/godep - curl -v -L -o cf-cli_amd64.deb 'https://cli.run.pivotal.io/stable?release=debian64&source=github' - sudo dpkg -i cf-cli_amd64.deb - cf -v test: pre: - go vet ./... override: - cd ${REPO_ROOT} && godep go test ./... post: - cf api https://api.au-syd.bluemix.net - cf login -u $BLUEMIX_USER -p $BLUEMIX_PASSWORD - cf target -o $BLUEMIX_ORG -s $BLUEMIX_SPACE - cf a deployment: production: branch: master commands: - cf push APP_NAME -b https://github.com/cloudfoundry/go-buildpack.git
とにかくプッシュ、テスト、デプロイまでをやれるようにとツギハギしたので、まだ無駄があると思いますが、とりあえずこれでアップできます。
つまずきポイントとしては、
${HOME}/.go_workspace/src/
へリポジトリを展開しないと、go get -t -d -v ./...
でリモートからパッケージ fetch しようとして死んだ (YOUR_DOMAIN を github ではないところにしているため、みつからない)cf api
でエリア指定を間違えた (だいたいの例はアメリカ南部になっているが、シドニーを使っている)cf
のインターフェースが gem install cf のものとちょっとちがう
などがありました。
基本的にはテストが通ったら cf で Bluemix にデプロイするというだけなので、すでに cf でのデプロイを済ませている人は、設定の際にあらためて考えることはない感じですね。