2019-01-09

サーバサイドフレームワークのUrlHelperとフロントエンドアセットバンドラの組み合わせの考え方

具体的に言うと Rails と Webpack などの組み合わせ方の話だけど、ツールではなく考え方の方を整理しておくことで、それぞれ変わった時にも再利用できるノウハウにしたいと考えていて、現時点で分かっていることをダンプしておこうと思う。

うまくいくかどうかは分からないけど。

なお、アセットバンドラの設定方法、依存解決時に考えなければいけないこと、同じくトランスパイラの設定などについては無視している。あくまでサーバサイドフレームワークと組み合わせる際に、特に cache buster を考えた際のアセット管理についてのメモになっている。

cache busterとasset digest (hash name)

Railsだけで完結していた時代のアセット管理

Rails 3.1 以降を使っていて助かるなぁと思ったことの一つに Asset 管理がある。Rails を使っているとたいして何も考えることなく img や script などの cache buster が有効になり、それもコンテンツの digest を基本にファイル名の書き換えで実現され、CDN も簡単に設定できた。

これは当時かなり先進的な機能だったように思う。自分がよく関わるのは他に PHP 系のサイトだったんだけど、PHP の世界ではこの辺はまだ手動でバージョンを打つか、せいぜい timestamp ベースの revision を打つところまでだったように思う。1

Node.jsツールチェインの充実とESの進化とサーバサイドフレームワークとの齟齬

しかしこの Rails の asset digest の機能は CoffeeScript や Sass のコンパイルの機能と一体になってしまっているので、Node.js ツールチェインの充実と EcmaScript の進化とともに「悪しき習慣」の一つとして捉えられるようになった。

変換系は確かにサーバサイドで持つ必要はない。当初は CoffeeScript も Sass も Ruby 実装が先行したわけだが、これらのニーズを持つ人たちがみんな Ruby の必要なプロジェクトに関わっているわけではない。2

しかしではその成果物をサーバサイドフレームワークの View で取り込みつつ cache busting が有効に機能しないといけない。ここのすり合わせが難しさの一つだと考えている。

モダンフロントエンドを加えるために考えなければいけないこと

フロントエンドの世界はサーバサイドと違ってそれだけで完結せず、様々な環境に対応せざるを得ないために考えなければいけないことが多い。いつ、どこで、何を実行するのかをプロジェクトに合わせて全部設定する必要がある。

  • アセットバンドラ管理のアセットをどこに置くのか
  • サーバサイドフレームワークが配信するアセットはどこに置く必要があるのか
    • アセットバンドラはどこに成果物を出力すべきか
  • フロントエンド側の CI/CD
    • アセットバンドラをいつどこで動かすのか
  • cache buster

アセットバンドラやトランスパイラ自体の設定方法などについてはここでは無視する。

まずモダンフロントエンドのアセットはサーバサイドフレームワーク管轄外に置く

これは間違いなく基本になるだろう。

app/assets/    <- サーバサイドフレームワーク管轄
app/frontends/ <- サーバサイド管轄外

みたいなものでもよいし、

app/         <- サーバサイドフレームワーク管轄
    assets/
frontend/    <- フロントエンドフレーム管轄

みたいなものでもなんでもよいと思う。

そのうえであとの行程に選択肢が一応ある。

あえて既存のサーバサイドフレームワークの仕組みに乗せる

AssetPipelineに無理やり乗せる方法

一つの考え方は AssetPipeline の仕組みの上に無理やり乗せる方法である。数が少ないうちは一応可能。

これのメリットは「(枯れている)サーバサイドのアセット管理の仕組みから漏れるものがない」という点に尽きる。

しかしやはり無理があり、例えば Rails だと

  • sprockets が依存関係解決で呼び出していない、entry point として機能するアセットは precompile 指定が必要
  • かつ sprockets が依存解決しないように stub 指定が必要
  • stub 指定するためにはその成果物が存在する必要がある
  • うっかり git add されてもいけない

など、ハマりどころは多い。

それでも例えばデザイナが関与しない JavaScript を一つだけ Browserify で出力できればよい、みたいな場合は選択できなくもない。

AssetManifestだけ共有してあとは知らん顔

AssetManifestだけ共有してAssetPipelineを使わない方法

実は Webpacker が実現しようとしていることも、最近の「脱Webpacker」が実現しようとしているのもこれなんだなということが分かってきた。

Webpacker は

  1. Webpack を利用した Asset 管理
  2. AssetPipeline を利用した Asset 管理

の二つを並列で動くようにしており、それぞれ Helper も別々なので、

  1. javascript_pack_tag
  2. javascript_include_tag

の両方を使う必要がある。3

この時、 javascript_include_tag は AssetPipeline の生成した manifest を参照しており、javascript_pack_tag は Webpack の生成した manifest を参照している。

ということはモダンフロントエンドを組みわせるために本当に大事なことは

  • アセットバンドラの生成した manifest をサーバサイドから利用するための何か
  • 成果物を適切にサーバサイドで利用できるための(public URLなどの)設定

になる。

ここの解決がモダンフロントエンド導入の際に、フロントエンドフレームワーク選定とは関係なく避けて通れないポイントの正体と言ってよさそうだ。

※ くどいようだが、アセットバンドラの依存解決やトランスパイラの設定などに関してはここで無視している。そこが面倒くさいのは承知のうえ。

参考

  1. timestamp ベースの revision を付加するのは今もほとんどの Web サイトでお手軽に利用できて確実に cache busting できるので、何もないサバンナに降り立ってしまったのなら最初にやるとよい 

  2. 逆に今ならみんながみんな Node.js を必要としているわけではないと言えるのだが、エコシステムが圧倒的に強くなったので、そこにはもう選択の余地はないだろう。 

  3. 逆に、上で紹介した AssetPipline を利用した方法ではこれがすべて一つに集約されるので、Rails の View を触る部分に関して新しいノウハウを必要としないのがメリットの一つである。 

About

例によって個人のなんちゃらです