トップ «前の日記(2014/06/16 (月) ) 最新 次の日記(2014/06/18 (水) )» 編集 RSS feed

HsbtDiary


2014/06/17 (火) [長年日記]

[ruby][rails] rails と bower を組み合わせて assets を良い感じに使う術

npm と bower と grunt を使って javascripts のテストに必要なライブラリをインストールするようにした by hsbt · Pull Request #414 · tdiary/tdiary-core で導入した npm, grunt, bower の組み合わせで javascript/css をかなり良い感じに organize するという仕組みを production の rails に投入した。

準備としてはまず nodejs を使えるようにするところから開始。これは cli さえ満足に動けばいいので xbuild を使ってビルドしたものを /opt/node-x.y.z とか /usr/local/node-x.y.z に置いて /usr/local/node として symlink 貼って、bin を PATH 通すということにした。もっと良い方法があったら教えてください。

これで node と npm が使えるようになったので、下のような package.json を作成。

{
  "name": "rails project",
  "repository": {
    "type": "git",
    "url": "git@github.com:rails_project/rails_application.git"
  },
  "devDependencies": {
    "bower": "*",
    "grunt": "*",
    "grunt-cli": "*",
    "grunt-bower-task": "*"
  }
}

こいつらは dev 環境でしか動かさないやつなので、強気に '*' 指定にした。作成後に npm i を実行して node_modules にインストール。gitignore にも追加した。

続いて bower で扱いたい javascript などを書く bower.json を作成する。こんな感じ。

{
  "name": "rails project",
  "dependencies": {
    "spin.js": "*",
    "backbone": "*",
    "underscore": "*",
    "handlebars": "*"
  },
  "exportsOverride": {
    "spin.js": {
      ".": "*.js"
    }
  }
}

ポイントは exportsOverride で、こう書くことで spin.js に含まれている jquery.spin.js も扱えるようにしている。書かない場合は spin.js の本体だけが bower の処理対象となる。

ここまできたら下のような Gruntfile を用意する

module.exports = (grunt) ->
  "use strict"
  grunt.initConfig
    dir:
      src: "src"
      dest: "dist"

    pkg: grunt.file.readJSON("package.json")
    bower:
      install:
        options:
          targetDir: "./vendor/assets/bower_components"
          layout: "byComponent"
          install: true
          verbose: false
          cleanTargetDir: true
          cleanBowerDir: true

  grunt.loadNpmTasks "grunt-bower-task"
  grunt.registerTask "default", ["bower:install"]
  return

突然の coffeescript. cleanBowerDir は true にするか悩んだけど、false だと試験的に入れてみた js が残り続けてそれらも展開されてしまうので、今のところ全消しするようにしている。ここまで来てからおもむろに grunt コマンドを実行すると

% grunt
Running "bower:install" (bower) task
>> Cleaned target dir /Users/hsbt/work/rails_project/vendor/assets/bower_components
>> Installed bower packages
>> Copied packages to /Users/hsbt/work/rails_project/vendor/assets/bower_components
>> Cleaned bower dir /Users/hsbt/work/rails_project/bower_components

Done, without errors.

という感じに、Rails.root の bower_components フォルダに bower が取得してきたファイル群、vendor/assets/bower_components に assets:precompile の対象となるファイル群が配置される。

最後に config/initializers/assets.rb に上記のファイル群を rails から見えるようにする設定を追加する。

Rails.application.config.assets.precompile << Proc.new do |path|
  if path =~ /\.(css|js)\z/
    full_path = Rails.application.assets.resolve(path).to_path
    app_assets_paths = [
      Rails.root.join('app', 'assets').to_path,
      Rails.root.join('vendor', 'assets').to_path
    ]
    app_assets_paths.any?{|app_assets_path| full_path.starts_with? app_assets_path }
  else
    false
  end
end

Rails.application.config.assets.paths << Rails.root.join('vendor', 'assets', 'bower_components')

とある rails application では、まだ application.js に全ての js を統合するというところまではたどり着いてないので、Rails の AssetsHelper から見えたものは追加するという workaround を入れている。

実際に使う時は

<%= javascript_include_tag 'spin.js/spin' %>

というように vendor/assets/bower_components 直下のファイルパスを指定する。

これで deploy 時の assets:precompile タスクの手前で grunt を実行することで外部 js/css を bower によって取得してから precompile が実行出来るようになった。超絶便利。

なんでこれが良いかというと、ライフサイクルが異なるものを単一の方法で管理するのをやめるという所にある。よくある例としては、全て yum で管理しているので ruby の新しいバージョンが〜とか、全部 bundler でやるようにすると rails-backbone のバージョンアップで assets も予期せぬ更新が〜というのがある。部分部分で依存関係をバラバラにして、適切なツールで更新頻度を管理しつつ、必要なものを適時更新ていくのが技術負債をため込まないポイントと思う。

ご活用ください。

追記

bower で参照可能なライブラリとして公開しないなら bower.json の ignroe はいらないみたいなので消した