RailsプロジェクトにBowerを導入してHerokuへデプロイする

おぐらです、RubyKaigi 2013の会場からこんにちは。

現在、弊社で開発中のqnypというサービスは、少し前にRails 3.2.13からRails 4.0.0.rc1へとアップデートを行いました(サービス自体はまだ非公開なのでアクセスはできません)。

このアップデート作業を通じて、

  1. Railsに依存するgemを減らしていく
  2. JavaScriptライブラリをラップしただけのgemを使わないようにする

という点について重視するようになりました。

1. Railsに依存するgemを減らしていく

特にActiveRecordなどRailsの内部に深く関わるようなgemにおいて、Railsのメジャーアップデート時の対応速度が迅速ではなさそうなものがあったため、できるだけそれらのgemを利用しないようになりました。例えばauditedは、現時点の最新版がactiverecord ~> 3.0という依存性を持っていたり、Rails 4.0で廃止となったObserverを利用しているため、使わないようにしました。

本質的なコードが数十行程度のgemであれば同等の機能をアプリケーション内で持つようにしたり(これはこれで別な問題をはらむことになるとも言えますが)、できるだけ依存関係がシンプルなgemを優先して利用するようにしています。

2. JavaScriptライブラリをラップしたgemを使わないようにする

今回、特に大きな方針転換だったのは2つ目の「JavaScriptライブラリをラップしたgemを使わないようにする」でした。

具体的に対象となったのは、momentjs-railsspinjs-railsjquery-raty-railsといったgemで、このようなgemには管理が容易になるメリットがある一方で、以下のようなデメリットもあります。

  • rails ~> 3.2.3railties >= 3.1のようなRailsに対する依存関係を持っている
  • 本家のJavaScriptライブラリのアップデートに迅速に追随しないことがある
  • リリースサイクルがgemメンテナーのモチベーションに依存する

ただし、単純なラッパーではなくRubyレベルのDSLを含んだgemもあるため、そういったものについては、メリットとデメリットを考慮したうえで対応することになります。

BowerによるJavaScriptライブラリの管理

qnypのRailsアプリケーションでは、

  • 必要なJavaScriptライブラリがインストールできること
  • できるだけシンプルな仕組みであること
  • プロジェクトにある程度の継続性が望めること

といった点を考慮して、JavaScriptライブラリの依存性管理にBowerを採用することにしました。

RailsアプリケーションにBowerを導入する手順は、おおまかに以下の通りです。

  1. Nodeを利用できるようにする
  2. Bowerをインストールする
  3. .bowerrcを作成する
  4. component.jsonを作成する
  5. bower installを実行する
  6. Asset Pipelineの設定を変更する

1. Nodeを利用できるようにする

あらかじめ、環境に合わせてNodeをインストールしておきます。

2. Bowerをインストールする

npmコマンドでbowerをインストールします。

$ npm install -g bower

3. .bowerrcを作成する

Bowerの設定を記述した.bowerrcファイルを作成して、Railsアプリケーションのルートディレクトリに配置します。

{
  "directory": "vendor/assets/components",
  "json":      "component.json"
}

directoryには、JavaScriptライブラリがインストールされるディレクトリパスを指定します(このディレクトリは.gitignoreに指定してリポジトリの管理対象外にしておきます)。

jsonには、インストールするJavaScriptライブラリの名前とバージョンが記述されるファイルのパスを指定します。

パスはいずれもRails.rootからの相対パスを記述しておきます。

4. component.jsonを作成する

Railsアプリケーションが利用するJavaScriptライブラリの情報をcomponent.jsonに記述します。

qnypのアプリケーションの場合は以下のようになっています。

{
  "dependencies": {
      "jquery": "1.9.1",
      "morris.js": "0.4.1",
      "raphael": "2.1.0",
      "raty": "2.5.2",
      "moment": "2.0.0",
      "spin.js": "1.3"
  }
}

Bowerでインストールできるライブラリについては、Bower componentsを参照してください。

5. bower installを実行する

ここまでの設定が済んでいると、bower installでJavaScriptライブラリがインストールできるようになります。

$ bower install
bower cloning git://github.com/components/jquery.git
bower cached git://github.com/components/jquery.git
bower fetching jquery
(snip)

ここでインストールされるファイルはリポジトリに含めないため、この作業は開発者毎の環境およびデプロイ先の環境で必要に応じて行うことになります(bundle installと同様)。

6. Asset Pipelineの設定を変更する

Asset Pipelineを利用している場合は、インストールされたJavaScriptライブラリを読み込む指定をapplication.jsに記述します。

例えば、vendor/assets/components/fooというライブラリをインストールし、vendor/assets/components/foo/lib/foo.min.jsというファイルをAsset Pipelineに加える場合は、以下のようにvendor/assets/componentsからの相対パスを指定します。

//= require foo/lib/foo.min

これにより、Asset PipelineでJavaScriptファイルが読み込まれます。パスの指定が正しいかどうかは、アセットのプリコンパイルを実行することで確認できます。

また、Bowerによってvendor/assets/components以下にインストールされたJavaScriptライブラリに含まれるCSSファイルや画像ファイルは、ディレクトリ構成がそのままhttp://example.com/assets以下にコピーされます。

例えば、vendor/assets/components/foo/img/foo.pngというファイルは、http://example.com/assets/foo/img/foo.pngとしてアクセスできるようになります。アセットファイル用のヘルパーを利用する場合は、以下のように記述することで参照することができます。

<%= asset_path('foo/img/foo') %>

jQueryについて、jquery-rails.gemに含まれるものではなくBowerでインストールしたものを読み込む場合は、以下のようにパスを変更します。

-//= require jquery
+//= require jquery/jquery

カスタムBuildpackによるHerokuへのデプロイ

前述のとおり、Bowerを導入した場合はデプロイ時にbower installを実行する必要があります。

qnypはHeroku上で開発・運用をしていますが、標準のRuby用BuildpackではBowerに関する処理は行われません。これを解消するため、弊社ではheroku-buildpack-ruby-bowerというBuildpackを作成して、Herokuへのデプロイ時に、

  • Nodeのインストール(あらかじめビルドしたものをS3から取得して展開)
  • Bowerのインストール(あらかじめ作成したnode_modulesをS3から取得して展開)
  • bower installの実行

が行われるようにしています。

デプロイ時にこのBuildpackが利用されるようにするには、以下のようにHerokuアプリケーションが利用するBuildpackを変更しておきます。

$ heroku config:set \
  BUILDPACK_URL='git://github.com/qnyp/heroku-buildpack-ruby-bower.git#run-bower'

そしてHerokuへのpushを行うと、Bowerを利用したデプロイが行われます。

$ git push heroku master
(snip)
-----> Fetching custom git buildpack... done
-----> Ruby/Rails app detected
(snip)
       Your bundle is complete! It was installed into ./vendor/bundle
       Cleaning up the bundler cache.
-----> Using bower version: 0.9.2
-----> Installing JavaScript dependencies using bower 0.9.2
       bower cloning git://github.com/components/jquery.git
       bower caching git://github.com/components/jquery.git
(snip)

まとめ

Railsアプリケーションに新しい機能を加えるときに新しいgemを導入して対処するのは、とても簡単ですし、将来的な手間も一見少なくなりそうに思えます。

しかし、長期的には(とくに今回のようなRailsのメジャーアップデート時には)負債としての側面が顕在化することも少なくないため、寿命の長いアプリケーションを開発している場合は、導入しようとするgemがどのような依存関係を持っているのかを考慮することも重要になってきます。

Railsアプリケーションから依存性を減らしていく考え方は、FingertipsのブログにあるA quick note on dependencies in Ruby on Rails projectsという記事において “minimal dependency policy” として紹介されているので、そちらも一読をおすすめします。

また、この記事はRubyKaigi 2013の会場で書いているのですが、今まさに聽いている@sikachuさんの発表 You have to test multiple versions of your gem’s dependencies. You used Appraisal. It’s super affective! (video) もそのあたりに関係のありそうな内容です。

Sikachu's talk at RubyKaigi 2013
Sikachu’s talk at RubyKaigi 2013

アニメファンのためのオンラインコミュニティ

qnyp(キュニップ)に参加すると、感想を読み書きしたり、感想をもとに自分の視聴記録を確認することができるようになって、アニメを見るのがより楽しくなります。

qnypを見てみる

タグ

  1. 雑談 (7)
  2. イベント (4)
  3. 技術 (20)
  4. ビジネス (3)
  5. 統計情報 (2)
  6. サービス (15)
  7. 新機能 (11)

年別アーカイブ

  1. 2017 (7)
  2. 2015 (2)
  3. 2014 (7)
  4. 2013 (9)
  5. 2012 (16)