🏊

脈々と継ぎ足されてきた秘伝のRailsソースの海を泳いできた

2023/11/27に公開

十うん年いろいろなエンジニアの手が加えられ継ぎ足されてきた Rails ソースコードの海を泳ぐという貴重な機会をいただいたので、どうやって泳いできたかを記しておきます。

フロントエンドのビルドの話(ローカル編)

はじめに

そのプロジェクトでは、バージョンは Rails 5.〇〇、アセットパイプラインという Rails の sprockets-rails という Gem(ライブラリ)が javascript や css などのアセットをまとめる(圧縮/バンドル/ビルド)するために使われていました。

公式ではこう書かれています。

アセットパイプラインとは、JavaScriptやCSSのアセットを最小化 (minify: スペースや改行を詰めるなど) または圧縮して連結するためのフレームワークです。アセットパイプラインでは、CoffeeScriptやSass、ERBなど他の言語で記述されたアセットを作成する機能を追加することもできます。

「フロントの言語だったらなんでもいいよ。」
Rails が適当にどうにかしとくから。という、まさに Rails ウェイ w ですね。

こういった文化の中で、様々なエンジニアが関わって、なんのルールもなく数年開発するとどうなるか。

・・・ソースコードは泥沼と化します。
ある人は CoffeeScript で書いて、ある人は ERB で Ruby と Javascript のハイブリットみたいなコードを書く...
そんなことを続けてきたら読みづらいどころか、どこがどう機能しているのかわからないソースコードが出来上がります。

さらに、 フロントエンドの現在のビルド周りのトレンドはどうか。
フロントエンド界隈の住人であれば聞いたことくらいはあると思いますが、10 年以上前に webpack というモジュールバンドラーが台頭し、現在はそちらが一般的な開発環境として採用されています。

yarn dev とか、npm start とかいうあれです。

Rails も例にもれず、Rails 7.0 が出ている現在の状況では、既存の sprocket から webpack をラップした webpacker というアセットパイプラインに変更になっています。

これは、webpack 周りの人口が多いので、自然と開発者も多くなり周辺のパッケージ(npm)が豊富に選択できる、言い換えると webpack 界隈のエコシステムを享受できるというメリットを選択した結果なのでしょう。

(フロントエンド界隈の潮流の速いトレンドに翻弄されてきたのは、エンジニアもフレームワークも同じなのかもしれません。)

トレンドなど無視してマイウェイをひた走るのは勝手ですが、同じ技術スタックを使う並走する世界中のエンジニア達がいなくなると、すべての問題を自分たちで解決しなければならなくなります。いままで Rails というフレームワークに頼ってきた部分がすべて負債に変わります。
これはとても恐ろしいことです。
例えるなら、いままでエンジニアたちは部屋の模様替えや掃除を一生懸命やってきたのに、突然床が抜け始めたり、壁が倒れ始めたり、屋根が吹き飛ぶようなものです。

そしていま、Rails が sprockets を捨てようとしています。このままでは確実に Rails のバージョンはアップデートできなくなるし、そうなるといくら頑張ってもセキュリティが担保されていないシステムを世に晒し続けることになります。

というわけで、長くなりましたが、極めて常識的なわたしは、フロントエンド周りを刷新しようと思い立ったのです。

Rais 公式の webpacker より素の webpack を選んだ(技術選定)

Javascript や Sass/Scss などのバンドルを webpack に頼るとして、Rails のプロジェクトで選択するには、 Rais 公式のwebpackerと 素のwebpack の 2 種類があります。

Rais 公式の webpacker 素の webpack
メリット 導入が楽 細かく設定調整できる
デメリット 設定が Rails 任せ 導入が難しい

それぞれ、メリットとデメリットが相互関係になっている感じです。

エンジニアとしては、導入コストより細かい設定調整ができたほうが嬉しいし、Rails 依存のフロントエンド環境には辟易してたので、素のwebpack を選びました。
webpack の知識に関しては受託開発案件で嫌というほど開発環境の共通化をしてきた経験があったのも幸いしました。
将来的にはバックエンドと決別し、疎結合化したシングルページアプリケーションへの道も見据えると、素のwebpack 一択でした。

webpack の設定ファイルは分割した(webpack 設定)

webpack のビルドは、開発環境と本番環境で共通化できるところは共通化し、環境ごとに設定できるように webpack-merge を使って設定ファイルを3つ(webpack.common.js / webpack.dev.js / webpack.prod.js)に分けました。

具体的には、以下の設定を行っています。

  • 開発環境のみで使用する設定:ローカルサーバの起動やホットリロード(HMR)ソースマップの追加など
  • 本番環境のみで使用する設定:圧縮(minify)やハッシュ値の追加やソースマップの削除など

そのほかのビルド設定

  • 静的型付けを行える Typescript に書き換えることを考えていたので、Typescript
  • よくあるエンジニア同士のダブルクォーテーションかシングルクォーテーションか、みたいな争いは不毛なので自由なルールで書いても同じルールに変換されるようにコードフォーマッターのPrettier、Lint ツールのeslint
  • コミット時にリントなどを走らせるように husky
  • ローカルサーバは Docker 環境をプロキシbrowser-sync-webpack-pluginした webpack-dev-server

などを入れました。

結構悩ましかったのですが、アセットパイプラインで使用しているレガシーな Javascript の記述をコピペして持ってこれるように、webpack 側にもレガシーのバンドラーを入れています。(jQuery, CoffeeScript, underscore.js など )

本当はコピペではなく、普通にメンテし易いように書き換えるのが望ましいと思います。

ディレクトリ構成(app/frontend)

webpack がバンドルする静的ファイルをどこに置くかという問題がありまして、sprockets 側のアセットファイルは app/assets に配置されているので、app/frontend というディレクトリを作成して、バンドル前の静的アセットを javascripts, stylesheets, images などに入れました。
ちなみに、ここにフロントエンドの README.md ファイルも入れさせてもらってます。
ビルド先は public/packs です。

JS のアーキテクチャや CSS のアーキテクチャに関しては、将来 SPA にしたときに CSSinJS でコンポーネントごとに管理できるように Rails の View と一対一になるような設計にしています。

これは後から気がついたのですが、Rails の View は使っているファイルと使っていないファイルが分かりづらいので、こうすることで一つ一つ確認しながら不要な View ファイルを削除するのにも役立ちました。

Rails 側で JS や CSS のバンドルファイルを扱う

Rails 側で webpack でビルドしたファイルを認識するには、もうひと手間必要です。
manifest.json にビルド後のファイルを記述して、Rails 側に教えてあげる必要があります。

webpack-manifest-plugin

  • 生成されたバンドルファイルのリスト(manifest.json)を吐き出す

webpack_bundle_helper.rb という helper を作成して、

  • javascript_bundle_tag, stylesheet_bundle_tag, image_bundle_tag という関数をつくって View からビルド後の静的ファイルを呼び出せるように

yarn dev でフロントエンドの開発がスタートできるように

初回は npm と yarn のインストールが必須ですが、 yarn dev または yarn dev を実行すると変更を感知しながらローカル環境下でブラウザで画面を確認しつつ、開発が行えるようにしました。
yarn コマンドは package.jsonscripts に記述しています。

Discussion