「JavaScript sprinkles in Basecamp turned Stimulus」より。Stimulusの設計意図を理解できる貴重な資料なのでまとめた。
DHHにとってプログレッシブエンハンスメントとは、コンテンツのプログレッシブ性を実現するためというよりは、アプリケーション開発のための道標のようなもの。ベースとしてのHTMLはまずサーバーに生成させて、動的部分としての少しの差分をJavaScriptに扱わせる。BasecampやGitHubのように動的部分の少ないアプリケーションにとってはそれが適切なやり方。React/ReduxやAngular、そのほかクライアントサイドのMVCフレームワーク等を試してきたが、それによってプロダクトのコードが良くなることはなかった。
BasecampにあるJavaScriptを調査して、どのような慣習があるか、それを踏まえてどのようなスタイルが受け入れられるかのインスピレーションを得た。メンタルモデルに合わせたやり方をフレームワークによってバックアップされるとより良いコードが書けるようになるはずであると。
StimulusによってBasecampのコードに見られた特定の問題や悪いパターンを解決したかった。まず要素選択の方法について。ある要素の親の親の親を取得するなどしてDOM構造にべったりだった。誰かがマークアップを少し変更するとこれは簡単に壊れてしまう。CSSクラスをターゲットにするパターンもあったが、CSSクラスはスタイリングを関心とした名前を持っているので、JavaScript側の関心と合わない。スタイル変更のためにマークアップを変更したりしてしまうとすぐまた壊れてしまう。
Stimulusではコントローラーに固有なターゲット名をつけることにした。選択する要素は明示的になり、コントローラーの中で好きな位置に移動することができて、input要素でもbutton要素でもspan要素にでもアタッチできる。それだけでなく、HTMLを読んだときに、どの要素がどの振る舞いに使われるのかという目的がかなり明確になる。
またBasecampでは同じような機能を実装する重複するコードがいくつも存在していたが、CSSクラス名やHTML要素名と密になっていたので汎用化できなかった。Stimulusによってマークアップと疎になることで再利用性が獲得できる。Reactのようなコンポーネント指向のJavaScriptでも、振る舞いがテンプレートから切り離せなくなって再利用性がなくなるので好きじゃない。
次にアクションについて。これまでBasecampが行っていたのは、HTMLにdata-behavior
属性と機能名を記述しておいて、イベントが発火されたときにこれらの属性を読み取って実行するというもの(Code Spelunking in the all new Basecamp)。DHHとしてはこれではHTMLを見ても何をやっているのかわからないのが嫌だった。このボタンをクリックするとどういうことが起こる、というのを明示的にしたかった。なのでHTMLに直接JavaScriptのイベントハンドラを記述するようにした。これでいちいちJavaScriptのコードを読む必要がなくなった。
これらをコントローラーというコンセプトのもとにカプセル化した。Basecampのさまざまな振る舞いを抽出するのに、ターゲットとアクション、コントローラーというシンプルな3つのコンセプトだけで十分だったのには驚いた。
また条件に応じてCSSクラス名を追加するような機能はどのようにして汎用化できるのかというと、StimulusのData MapsによってHTMLのdata-*
属性に個別のクラス名を渡せるようになっている。これによってデザイナーが些細な変更のためにコントローラーを触る必要もなくなる。コントローラー内にはCSSクラス名を直接含めるべきではない。
非常に複雑なUIを作ろうとしているのであればReactなどのような重いフレームワークは理にかなっているが、一方でGitHubやBasecampのようなアプリケーションが取り組んでいるフレームワークは過小評価されているように感じる。昨今のJavaScriptフレームワークの盛り上がりによって、サーバーはJSONを生成するだけのものになって、クライアントサイドでもふたたび以前のサーバーと同じような仕事をするためにいろんなことをいちからやり直している。Basecampでは、必要があればサーバーが返したHTMLの破片をStimulusが使う。
BasecampのコードベースをStimulusへ移行するに当たって、HTML内に自己文書化されたコードが記述されるようになることでドキュメントの量をかなり減らすことができた。大量のコードコメントが意味するのは、複雑なコードを書いてしまったことか、あるいは抽象化されることを待っている一連の規則の存在である。
Stimulusでは状態はHTML上に保持されている。コントローラーのルート要素にデータ属性として状態を格納できるが、基本的にはこれは最小限にする。多くの場合、HTMLに付与されているCSSクラス名や属性の有無などで大抵の状態は表現できる。
Turbolinksは便利だが、jQueryや通常のJavaScriptの実装と組み合わせるのに難があって避けられていた。StimulusはMutationObserverを採用して動的なページ書き換えに対応することによってこのような問題をきれいに解決できた。
しかしTurbolinksは再評価されるだろうか。ある痛みは何か別の利益を得るためのトレードオフとなるが、まずメリットを得られるかわからない場合はコストが高すぎるし、メリットがゼロだと感じるならその人は自分には価値がないと言うだろう。そしておそらく、昨今のモダンなJavaScriptのフレームワークを採用することで、かなりのコストもかかってしまうことに気づく人も増えていて、それらを経験した人は、これはもうただただ苦痛だったと語るだろう。Basecampが取り組もうとしているのはこの痛みを取り除くことだが、あなたがその痛みに苦しむまではBasecampがやろうとしていることを十分に理解できないだろう。痛みを知るまでは評価できないだろう。
実際の痛みから切り離されたベストプラクティスは意味をなさないことがよくある。人はそれが必要なときになるまで自分ごとにする準備ができていない。誰かが何かに苦しむまで価値は理解されない。
しかし私たちは協力して歴史的背景を人に伝えることもできる。The Origin of Stimulus(和訳)はそれを意図したもの。ある技術を採用するためには「FacebookがReactを使っているから」だけでは十分な理由にならない。Basecampで解決しようとしているのとは違う問題と背景がある。Facebook自身でさえ、最初からいま確立されているやり方で始めてしまっていれば、Facebookそのものが存在しなかっただろう。
Basecampがこれらを推進しているからといって、誰もがこれを使うようになるという幻想は持っていない。Reactなどモダンなツール群には勢いがあって、そういうものだと思うし、これらの支配に勝つ必要はない。開発コミュニティは市場シェアがすべてであるような考えを持っていることがあるが、ウェブの素晴らしさはその多様性である。バックエンドは必要に応じてあらゆる実装言語で書くことができて、だからより多くの人が関わることができるし、自分に合うものを選択できるので、このおかげでDHHはRubyとRailsに出会うことができた。さまざまなパラダイムが増殖する中で、トランスパイラなどを用いて、JavaScriptの世界でもこの多様性が進歩すれば良いことだと思う。