202
92

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

妄想的DHH理解

Last updated at Posted at 2021-09-21

Caution
この記事はDHHファンの妄想によるシナリオが多分に含まれます。 というかほとんどです。
成り立ちが間違ってることも当然あるように思うので話半分で読んでください。

これは一体

最近のRailsフロントエンドやDHHの活動には一連の流れがあるわけですが、一部トレンドに沿ってない部分がある故にそれが汲めないというところがあるのではと思います。
それらの流れを記憶が定かなうちにつないで記録しておこうという記事です。

前提知識

DHH

Railsの生みの親、Rubyist

Basecamp(社)

DHHがCTOやってる会社

Basecamp(サービス)

Basecamp(社)が開発してるプロジェクト管理ツール

Trixを開発してたある日

Basecamp(サービス)に組み込まれてるリッチテキストエディタのtrixcustomElements使って開発してたある日、DHHはあることに気づく。
「このcustomElementsのLifecycle callback使った実装のパターンはめちゃくちゃいけてるな、カスタム要素が出現と同時に振る舞い持つようになるのもええわ。 これってMutationObserverでDOM監視からのコールバック発火で普通のタグに対しても再現できるんやないか?」

アイデアがまとまってきたのでBasecampのメンバーに開発を依頼する

javan, sstephenson 「はいな、できたで! その名もStimulusや!」

※ 5月ごろ色々あって2人は今Basecampを離れています

Stimulusに未来を感じる

かくしてMutationObserverでDOMを監視して、要素が出現したらバチっと振る舞い適応して使えるようにしてくれるStimulusが出来上がった。

customElements
// <x-hoge />
customElements.define('x-hoge', class extends HTMLElement {
  connectedCallback() {
    this.changeBg('red');
  }
  changeBg(color) {
    this.style.backgroundColor = color;
  }
})
Stimulus
// <div data-controller="hoge" />
app.register('hoge', class extends Controller {
  connect() {
    this.changeBg('red');
  }
  changeBg(color) {
    this.element.style.backgroundColor = color;
  }
})

「ええ仕上がりやないか、このconnect/disconnectのハンドラはturbolinksとかとも相性ええし最高やな。
customElementsは要素と振る舞いが1:1になるけど、これはその制約うけへんから複数の振る舞い(コントローラ)適応させることもできるな。 なんか要素に振る舞いをMixinしてるみたいやな、Rubyみたいで気に入ったわ」

<div data-controller="hoge fuga piyo"> // divにhoge/fuga/piyoコントローラをMixinや!

「今まではHTMLに書いたタグをわざわざquerySelectorで探してJSアタッチしてたけど、自動で振る舞いがアタッチされるのって随分開発体験ええんやな〜。 今までこの要素探索とかに消耗しててんな。 このHTMLとJavaScriptの概念的な距離を圧縮する研究しよ」

「うおー、要素アクセスの概念的距離の圧縮方法みつけたぜ! targetや〜!」

// <div controller="hoge"><span data-hoge-target="foo" /></div>
connect() {
  this.fooTarget.textContent = `hello`
}

「うおー、イベントハンドラの割り当ても自動化や〜! actionいけてるやん!」

/*
<div controller="hoge">
  <button data-action="click->foo#changeColor" />change color</button>
</div>
*/
changeColor() {
   ...
}

コードの最小化に目覚める

この1つの要素に複数のコントローラをアタッチできるのってコードの再利用の観点でめっちゃええな。 小さいコントローラをいくつか作ってそれをペタペタするだけでUIが組み上がっていくのスマートや!

BasecampのLess Softwareの考え方的にもコード量は減らしていきたいし、このアプローチは最高やな。

俺たちのコードは大半なんらかの操作に伴ってAjaxで画面を部分更新したり、UI部分の作り込みが大半だ。
Stimulusで後者のコードの最小化が実現できたし、前者とも向き合うか...

Ajaxで要素更新するときも実装パターンほぼ決まってるしCRUDみたいなんがあるといいかもな〜

カスタム要素でCRUD的な表現できる要素作っといて、ライブラリがそれで自動的にappend/remove/update/replaceするみたいなのあったらさらにコード量最小化できるな〜

Turbolinks進化させてそういう機能組み込むか〜

進化版の「Turboできたで〜! サーバサイドから通信でこんな感じのカスタム要素返してくれたらactionの命令にしたがってtarget要素変更できるで〜!」

<turbo-stream action="append" target="dom_id">
  <template>
    Content to append to container designated with the dom_id.
  </template>
</turbo-stream>

これで開発者側はテンプレの再利用捗るし、おまけにJSいらんくなって最高やな〜

Stimulusによる新たな気づき

StimulusはDOMから振る舞いつける要素とか探してきてくれる感じのやつで、ReactとかみたいにHTMLを生成したりはしない。 言って見れば従来通りのアプローチを限りなく自動化したものだ。

故にデータをもとにHTMLを作る工程は依然としてサーバサイドにある。 

XHRでフロントに返すものもJSONではなくHTMLにしてしまえばStimulusはそこをMutationObserverで勝手に検知して振る舞いをアタッチしてくれる。

これはよくあるテンプレート二重管理大変だ〜とかSSR難し〜みたいな問題と一切向き合わなくて済むし、もっとも専門性を要さずに効率よく開発できるアプローチなのかもしれない。

「フロントでは大きなデータ構造を扱うことが極端に減るので型の重要性も自然と下がる。 ほんならTSもさほど重要ではなくなるしconfig職人もいらんくなる...なんやこれ最高やん...」

よしもうこれ軸でいったろ!!

いつの日かわいの開発マシンからNodeが消える日もあるかもしれんな〜

JavaScriptのローダーに注目を始める

どのページでどのStimulusコントローラが必要とされるかみたいなのを管理するのって大変やな〜、その辺意識せずに済むならいいんやけど... 時代の流れにそってbundleすっか〜

でもよくよく考えたらbundleって色々問題点あるよな〜。 
10ファイルのうち1ファイルでも変更あったらbundleしてるファイル自体に変更があることになってキャッシュ破棄せなあかんもんな

MutationObserverでDOM監視して必要になるコントローラ探すわけやからその時にdynamic importしてやったら不要なJSも読まれんし、http2の恩恵もうけるしそれが最高なんとちゃう? ちょっとそんな感じのautoloader作ったろ

お〜ファイル名にコントローラ名を書く命名規則つくるだけで簡単に実現できたわ。 これでファイルごとに読み込めて最高やな〜

でもキャッシュの破棄ってどのタイミングですりゃいいんだ?

a.jsとb.jsがあって、a.jsに変更があったとする。
b.jsは変更ないけどa.jsをロードしてる場合ってそのimport文のhash digestを更新しなければいけなくて、それ更新するとb.jsにも変更が、、、ぐぬぬ、、、

/modules/a.js(chaged)
- console.log('hello');
+ console.log('world');
b.js
import from "/module/a-43yifhdst2t230.js"
// cache digest(43yifhdst2t230)に更新が入ってb.js自体にも変更がうまれる

この問題の解決のためにimportmap使えるんじゃね?

importmapとは...

<script type="importmap">
{
  "imports": {
    "moment": "/node_modules/moment/src/moment.js",
    "lodash": "/node_modules/lodash-es/lodash.js"
  }
}
</script>

みたいに書いといたらimport文でimport from 'moment'みたいに書いた時に/node_modules/moment/src/moment.jsにリクエストがいくようになるみたいな仕様である。

ここでcache digestなしのパスがありのパスに展開されるようにしとけばb.jsはdigestなしのパスでa.jsをimportできるので、b.jsに変更がないままになってキャッシュは破棄する必要のあるa.jsだけ捨てられて完璧やな!

es-module-shims使えばクロスブラウザで使えるしそんな感じのGemつくったろ!

importmap-railsできたで〜

CSSにも注目し始める。

JSのオートローダーみたくCSSもページごとの読み込み管理なくせたらいいんやけどな〜

そういや巷で流行ってるTailwindよさそうやな。

Utility CSSのアプローチやったらどのUIにどのCSSがいるかなんて管理する必要はなくって、DOMにもうガンガンスタイルかいていけばいいってなるし、それって最高やな。

そしたらもうSassのコンパイルとも縁きれるし、Webがより一層シンプルになってくな!

よっしゃRailsでもSassが唯一の答えって考えはやめてTailwindも使えるようにしていこ

Sprocketsがいらなくなってきたことに気づく

この流れで考えると、もうbundlingは不要になり、トランスパイラも重要度をなくします。

必要なのはcssのAsset管理くらいでSprocketsがかなり不要になりつつあることに気づいた。(これもTailwindで考えると出現度合いは随分少ないだろうが)

よっしゃ不要なもん削ぎ落とした新しいAssets管理ライブラリ作ったろ〜

Propshaftできたで〜

最後に

Propshaftが2日ほど前の話ですが、ここんとこのDHHは長く暗い道を抜けて、フロントエンド開発の新しいスタンダードを見いだしそれをRails7に全部ぶち込もうとしているように思います。

流れを組んでないとなんでHotwire(Stimulus)にそんな投資してんの? なんで急にSass外してTailwind推しだしたの? Sprocketsあるのになんでより機能少ないPropshaft作ったの?みたいな困惑がうまれると思います。

妄想垂れ流しの記事だけど誰かの理解に役立てば嬉しいです。

ついでに続編です。

202
92
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
202
92

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?