Railsで普通のJSを書くためにやった手順

Paddle Up というVR卓球で夜な夜な練習していたら、少しほっそりとしてきたきがします。

どうも、オースガです。

生まれてこの会社に入るまでRailsに触ったことが無かったんですが、いざ使ってみるとSprockets辛すぎて、よくみんな平然とした顔でコーディングしてるなと思って、聞いてみたらやっぱり辛いらしいってことなんで、普通のJSの開発が出来るように工夫してみた。

というのが本日のお話となります。

事前情報

扱わない話 / そもそもやってない話

  • 既存のコードをSprockets管理から外す
  • 既存のコードをCIする
  • Sprocketsを捨てる
  • 技術選定の基準

既存のコード、つまり application.jsをCIする話 は チームメンバの石井がきっとやります。

状況

  • Rails 4.2.7.1
  • Sprocketsでコンパイルされたapplication.jsが10万行ぐらいある
  • ばりばりjQuery
  • JSはCIされてない

既存の類似した話

そんなに目新しいはなしでもなく、過去に同様のチャレンジを行っている方がいます。
いくつかピックアップしてみました。
採用した技術などの細かい点では本稿と違うところがあるので、参考になるかと思います。

ゴール

「普通のツール群と普通のライブラリで普通にJSの開発が出来る」

普通ってなんだよ?って話ですが、普通 は変わってしまうので現在に限った話で言うと大体以下のようになります。

  • node環境でnpm / yarn などのパッケージマネージャの恩恵をうけられる
  • ブラウザ不要のユニットテスト & CI
  • browserify / webpack / babel / react / redux などのコンパイル / トランスパイル前提の技術の利用

更に加えるなら、「普通に 普通 を更新していける」ことも重要かなと考えます。

最終的に採用された技術

  • browserifyでバンドル
  • babelでesの新しい記法やjsx
  • mochaでテスト
  • assertはpower-assert
  • gulp

サンプルプロジェクト

ゴールに向かうためにRailsプロジェクト上に加えた変更を専用のリポジトリで再現しました。
example-browserify-without-sprockets-on-rails4
(差分)

解説のために /examples/show というページが用意してあります。

ここからは上記のプロジェクト + PRを元にやったことを段階的に説明していきます。

1. ディレクトリの役割を決める

npm initをのぞくと最初にやらなければいけないのは、「何のファイルをどのディレクトリに置くか?」を決めることでした。

次の図は、どのようなディレクトリ構成でファイルを管理するのかを示したものです。多少プロダクトごとに差異はありますがだいたいこのような配置を取ることにしました。( グレーアウトしてあるのはSprockets関連のファイル)

  • /client JSは大体ここの下におく
  • /public/client/builds 本番で読み込むファイルをおく
  • ルートディレクトリにdigestを管理するファイル(rev-manifest.yml)を置く (詳しくは後述)
  • この時点でRailsからどのようにjsを読み込むかも検討済み (詳しくは後述)

サンプルページのJS

すでに作成済みの /examples/show に対応するJSを置いたのが この差分 です。

  • 上記の決定に従って /client/src/examples/show.js を追加
  • ページで使うサブモジュールはサブディレクトリに作成
    • 〜/examples/components/HelloExample.jsx など
  • 対応するテストを /client/src/spec/node/examples
    • /client/spec/examples/components/HelloExample.js

reactを使ったページを想定してこのタイミングでbabelなどの設定をしています。
この段階ではユニットテストは出来ますが、ブラウザ用のJSはビルドできません。

2. ビルドするようにする

gulpを使ってビルドし、そのときにファイルにdigestをつけるようにします

2.1. gulpタスク

本番で読み込むためにビルドタスクを実装します。差分

コレで npm run build:development などによりプロダクションコードを生成できるようになりました。
あとwatchも出来るようになりました。

依存モジュールが増えたりコードが増えたりしてますが、オーソドックスなgulp + browserify + babelの構成だと思うのでここでは省略します。

やっていることはシンプルで /client/src/examples/show.js を エントリーポイントとして、必要なファイルをbundleして/public/client/builds/examples/show.js にビルドします。

2.2. digestをつける

cache bustingのためにbuildしたファイルにdigestをつけます。

gulp-rev というgulpのpluginを使っているだけでとくに工夫はありません。

3. Railsから読み込む

ここまででjs側でやることはほぼ終わりました。
一方でscriptタグはまだハードコーディングでとても継続的に運用出来るものではありません。

3.1. ApplicationHelperの修正

いつまでもハードコーディングするわけにいかないので仕組み化します。(差分)

  • 各ページで読み込むJSの名前を {controller_path}/#{action_name}.js とする
  • ApplicationHelperで digest付のパスに変換する
  • そのために rev-manifest.yaml を生成しておく
  • 対応するjsが無かったら何もしない

これで新しくページが増えたときも命名規則に従ってjsファイルを追加すれば自動的に読み込まれるようになりました。

3.2. 開発時のrev-manifest.ymlのリロード

Rails.application.config.assets.rev_manifest を initializerで設定しているので、このままだとJSを変更するたびに再起動が必要でわずらわしいので、開発中のみ更新時にリロードするようにします。差分

補足

「開発中はdigestつけなければ良いのでは?」というもっともな意見があると思います。
それは発想としてはシンプルですし、Sprocketsなどでも採用されている方法でいっけんスジのいい方法だと思えます。

しかし冷静に考えて「本番と開発中で別々のロジックが動く」のってリスクを感じませんか?
もちろん採用実績の豊富な仕組みならば信用できます。が、今回は自分たちで実装するロジックです。
この点においてより堅実な方法は無いか?とメンバーからの指摘と議論を経た結果がこの仕組です。

結果的により安心して保守運用していける構成になったかな思います。

〜 閑話休題 〜

4. リリースとCI

ここまでくればあとは本番デプロイ時にjsをビルドするのと、CIすれば終わりです。
既存のデプロイフローやCIになじみやすい方法を取りました。

  • ビルドは assets:precompile にフックする 差分
  • CI用シェルスクリプト 差分

まとめ

現状の普通を目指して、sprocketsの横にそっと環境構築をしました。
大きくnode側でやったこととrails側でやったことがあり、それぞれ以下のようになります。

  • node
    • ライブラリなど
      • npm / node
      • browserify
      • babel
      • gulp
      • mocha
      • power-assert
      • sinon
      • enzyme
      • react
    • エントリーポイントのバンドル
    • ファイルへのdigest付与と変換用のrev-manifest.ymlの生成
  • Rails
    • コントローラ名とアクション名を使ってJSを読み込ませる
    • 開発中の利便性のためにFileUpdateCheckerの採用

感想

はたして我々は「普通になったのか?」かはまだ良くわかりません。

しかし、確かな実感としてこの仕組の上で作られた機能は作り込みの限界が高くなっていると感じます。

さすが普通。

いまの普通は昔の普通よりずっとすごい。

こういう地道に普通を追い求めることで 本当に付加価値のある部分で無茶出来る余地 を残せるように、これからも普通のことをやっていこうと思います。
 

最後に

マネーフォワードでは、エンジニアを募集しています。
ご応募お待ちしています。

【採用サイト】
マネーフォワード採用サイト
Wantedly | マネーフォワード

【プロダクト一覧】
自動家計簿・資産管理サービス『マネーフォワード』
Web
iPhone,iPad
Android

ビジネス向けクラウドサービス『MFクラウドシリーズ』
会計ソフト『MFクラウド会計』
確定申告ソフト『MFクラウド確定申告』
請求書管理ソフト『MFクラウド請求書』
給与計算ソフト『MFクラウド給与』
経費精算ソフト『MFクラウド経費』
入金消込ソフト『MFクラウド消込』
マイナンバー管理ソフト『MFクラウドマイナンバー』
資金調達サービス『MFクラウドファイナンス』

メディア
くらしの経済メディア『MONEY PLUS』

Pocket