サーバサイドフレームワークの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 を触る部分に関して新しいノウハウを必要としないのがメリットの一つである。 

Stub と Mock の違いが分かってきた気がする

mock という言葉だけは知っていたが、実際に mock を作って使ったことはなかった。いよいよ mock を使わないとなぁと思い始めたところで mock は stub じゃないと聞こえてきた。えー。stub ってなんだろう。

今回は RR ( Double Ruby ) の v 1.0.2 を試しながら stub と mock の違いを学んだ話。

btakita/rr - GitHub

stub

例えば RR では

stub( object ).method_name { return_value }

という書き方で object の method を stub 化したものが得られる。戻り値が、ではなく object そのものが stub 化している。

object.method_name      # => return_value
object.method_name( 1 ) # => return_value

この object の .method を呼ぶと規定通りの return_value が返る。引数を指定せずに stub 化したのでどう呼んでも同じ値が返る。1

特定の引数に対して stub を設定する場合は

stub( object ).method_name( arg ) { return_value }

という書き方になる。この場合は arg 以外の引数を与えた場合には動作しない。

ある Klass クラス のオブジェクトすべてを stub 化したい場合は以下のようにする。

stub.instance_of( Klass ).method_name { return_value }

で、これを利用して何らかの動作をするオブジェクトをテストする。stub 化した object を直接テストすることも可能だけど、それはやっても意味ないよね。あくまで stub 化した object を「使う」オブジェクトのテスト用だろう。最もよくあるのは I/O を担うオブジェクトを stub 化することでテストに必要な準備を減らし、テストのスローダウンを避けるといった用途になる。あるいは未実装の機能を呼び出したり外部 API へのアクセスをなくすとか。

スタブは主に生成したりコントロールしたりするのに手間がかかるオブジェクトをもみ消す [stub out] のに使われる。典型的なのはデータベース接続だろう。[手間のかかるオブジェクトを"もみ消す"ので、] 結果としてスタブを見かけるのは、ほとんどの場合、システムの外部との境界や、システム内の複雑なオブジェクトの塊のあたりだったりすることになる。スタブは、実際のオブジェクトと代替できるようインターフェースの実装をし、実際のメソッドをシンプルな [テスト用に] 準備されたデータを使うメソッドで置き換えることで作られる。

ということは stub で覚えてしまおうとしないで stub out で覚えるべきなんだな。

まとめ

RR では

  • 引数を(与えずに|与えて) stub 化
  • 戻り値を(与えずに|与えて) stub 化
  • (クラス|特定のオブジェクト|あるクラスの全インスタンス) の stub 化

を組み合わせて使える。

用途に応じて使い分けるといい。

例えば Rails の Model を stub 化する場合はある id の場合に nil が、ある id の場合にはインスタンスが返るようにしておくとそれっぽく Controller をテストできる。2

mock

mock は stub に似ているが expectation を object に加えることができる。mock を作っておいて呼ばないテストは失敗する。つまり mock は所定のメソッドがちゃんと呼ばれているかどうかをテストするために使う。値も返せるので返る値を使った他のテストも行える。ただし戻り値を利用したテスト3を重視するなら stub を使えということらしい。

stub の場合と同じく RR では

mock( object ).method_name { return_value }

で mock を定義する。上の場合は method は引数を受け取らない。受け取れない。

mock( object ).method( 1 ) { return_value }

の形で定義すれば引数として 1 を受け取ることができるようになる。

イメージとしては mock オブジェクト自身がテスト主体となるような感じか。RR の場合は RR.verify を呼ぶことで、定義した mock が想定通りに実行されていない場合に例外を投げてくれる。

例えば上の定義にしたがって RR.verify を実行する場合、object.method( 1 ) が 2回呼び出されたらエラーになる。何回読んでもよい mock にしたい場合は

mock( object ).method( 1 ).times(any_times) { return_value }

のように記述する。

mock( object ).method(anything).times(any_times) { return_value }

とすれば引数には何でも与えることができる。ただしこの場合は一つ。二つの場合は

mock( object ).method(anything, anything).times(any_times) { return_value }

になる。

また、すべてのインスタンスを mock 化したい場合は

mock.instance_of(Klass).method_name

になるが、この場合もメソッドは1回しか呼ぶことを想定していない形になるので、

mock.instance_of(Klass).method_name.times(any_times)

にするとあらゆるオブジェクトが何回呼ばれても大丈夫になる。

※ ただし1回しか呼ばれないことを想定しているなら、当然この場合は mock としての要件を満たしていない。

mock ライブラリはテスト対象の object を受け取り、mock 自体が、テストしたい振る舞いが object に存在し、正しく呼び出されているかどうかを確認する機能を提供している。(つまり BDD で言う matcher もこの mock ライブラリが用意する。)それ以外の使い方も「一応」できるように return_value の定義もできるが、これは半分おまけのようなものと考えればいいのかな。

感想

mock, mock と言ってきたけど、自分の求めていたものは stub あるいは fake なのだなと思い始めている。少なくとも「呼び出され方」などは今までテスト可能だと思ったことすらなかった。恥ずかしながら。

mock の使いどころはもう少し考えるとしてまずは stub をきちんと使えるようになろうと思う。これは mock の使いどころがまだよく分かっていないためでもあるけど、

RR は明らかに stub 定義の方が簡単

という辺りに、そういう「意図」がこのツールにあると思えるからでもある。

参考

  1. return_value は必ず定義しなければいけないわけではない。その場合は予想通り nil が返る。 

  2. ただし本当にうまく活用するためには scope を使って検索条件をシンプルなメソッドのようにしておいてそれを stub out する、という手間が要る。ここら辺の扱いが上手くなると開発効率はだいぶ高くなるはず。 

  3. これを mock と stub の違いを説明する際には「状態中心のテスト」と呼ぶ 

iPod touchからのdelicous post復活

FLDR の pin を purge するときに delicious に投げられないと困るのだが、いつからか、それ以前には動いていたはずの bookmarklet が動かなくなって、なんでだろうなんでだろうと思っていた。ちゃんと注意して delicious を利用する記事を読み直していたらアプリのことが書いてあった。

何のことはない、必要なアプリを意図せずアンインストールしていただけだった。そうか、Mobile Safari だけで bookmarklet を動かすことはできないのか?

以下、昨日1の Twitter のログ。

22:35:11 >wtnabe< できた。何もかも解明。1) 長いこと iPod touch で
delicious に投げられないと思っていたのは必要なアプリをアンインストール
していたから。
22:36:44 >wtnabe< 2) これは、アプリのインストールとbookmarkletの設定を
非同期に調べて非同期に実行したため、bookmarklet の実行にアプリが必要と
いうことがまったく分かってなかったから。アプリの名前は Delicious
Bookmarks
22:37:21 >wtnabe< 3) なぜあるときこれをアンインストールしたかというと、
これはブックマークの同期に時間が掛かりすぎてこけてしまうことがあったた
め、同期目的では Yummy を入れたから。
22:40:44 >wtnabe< ということで同期の設定を切ってbookmarklet動作用アプ
リとして復帰してもらった。なんかちょっとアホくさいけど仕方ないな。

ということで

Yummy
同期、参照用
Delicious Bookmarks
bookmark post用

の2アプリ体制になってしまった。

cf.

DeliciousのiPhoneアプリ"Bookmarks"でどこでもブックマークできるようになった - Blog.IKUBON.com

  1. 日記の日付上 

今度は Thunderbird の署名設定の振り返り

Thunderbird の署名なんてどうやって作ってるのかすっかり忘れていたけど、自分の環境は以下のようになっていた。

  • Signature Switch を使って署名の切り替えと外部エディタでの編集を可能に
  • 導入部も何もかも署名の中に入れて Thunderbird のテンプレート関係の機能は一切使わない

なるほど。試してみるとテンプレート機能ははっきり言って全然使いものにならない。(Thunderbird 2 時点では。)

※ Signature Switch を使えば署名ファイルの存在そのものは直接気にしなくてもよくなるかなーと思ったけど、それは Signature Editor の機能だった。

というわけで Windows 用の Thunderbird もこんな感じで環境を作ればいいのか。

iPhone かぁ

iPhoneを動かしているムービーや写真いろいろ - GIGAZINE

この程度のことなら日本だとW-ZERO3シリーズで実現可能な気もしないでもない。

まぁねぇ。画面も VGA だし。(漢字文化圏ではやっぱ VGA はほしい。)

iPhone では OSX が動くってことであれもこれもできるんじゃないのかなぁと思うけれど、そんなに自由は与えてもらえないんだろな。セキュリティ対策とか考えるの面倒だし、SHARP とは設計思想が違うだろうし。SHARP のあえてソフトキーボードでないところ、VGA を積んでくるところはやはりかなりグッとくるけれど、キー入力を伴わない部分でのデフォルトの見た目や使い勝手はやっぱこっちが上のような気がする。

見た目大事。デフォルト大事。もうね、カスタマイズとか基本的にどうでもいいから。何もしないで扱いやすい状態が手に入るならその方が嬉しいもの。安定感についてはたぶんどっちもどっちだろうなー。OSX 自体はすこぶる安定してるけど、組み込みでの実績ってあるんだっけ? ないよね?

と、普段ガジェット系の話を書くことはまずないんだけど、最近実は、ノートよりも小さくて寝ながらとかどこでも使えるデバイスがちょっとほしくて ZERO3 とか調べていたから気になったのでした。田舎は車社会なので両手と目が自由になる移動は少なく、ハイパーな携帯としてのこの手のガジェットはほとんど利用価値がないんだけど、無線LAN があれば家の中とかでは自由だなーと。こう、堕落した感じでね。とりあえず bloglines と del.icio.us とあと iCal と Wiki くらい使えればいいよという、そんな感じ。主要な作業をサーバ上で行えるようにしておけば、本体の方で凝る必要はないんで、あんまりあれもこれもできなくていい。というかそういうカスタマイズは、自分は絶対に面倒くさくて初回以外やらない。もう断言できる。

ただ、Apple 製品は安くならないので、こんだけの値段になっちゃうんだったら MacBook ほしくなるだろなー。どうせたいしたことできないのに iPod の倍くらいの値段になっちゃうのはやっぱたぶん納得できない。まぁ、日本で販売するときに何がどうなってるのかは全然分からないわけだけど。

[2007-01-11 追記]何やらたださんがまだまだだと書いているけど、こういう「思い切った割り切り」という名の、日本の携帯端末好きの目からすると中途半端な仕様になっちゃうのはまぁ Apple なら当然かと思いますよ。サードパーティから OSX 搭載の iPhone コンパチマシンが出るなら話は別だろうけど。というか Windows Mobile ってそんなに困った OS なのか…。これも予想はしていたけども、まぁ iPhone が出れば Microsoft はまたおいしいとこパクってくれる切磋琢磨してくれるだろうし、今後に期待ってことで。

[さらに追記]スラッシュドット ジャパン|iPhone商標侵害でCiscoがAppleを提訴

解決してなかったんだ…。やるなぁ、Apple.

xyzzy 用 javascript-mode のようなもの、発見

xyzzy に対応モードがないと何もできない軟弱な身体なワタシ。ずっと JavaScript が扱いにくくていやだなぁと思っておりました。(どうせたいしたことしてないんだけど。)

何度か検索していた javascript-mode、ようやくそれらしいものを見つけました。

kia's website - xyzzy関連

jscript で検索しないといけなかったみたい。なぜ jscript-mode なのだろう。。。

About

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