コンポーネントに期待していたことと分かったことについて考える - 2023秋(1)

※ これは「巨大 SPA が主戦場ではない」エンジニア視点のメモです。

最初から

  • 大規模開発
  • 巨大 SPA
  • 細粒度コンポーネントの大量更新コストが課題

といった React にピッタリな要件ではないところで「コンポーネントとはそもそもなんだっけ?」を考えてみた。

Aral Balkan — Sites vs. Apps defined: the Documents‐to‐Applications Continuum. における「アプリ」に振り切ったものでない場合、どこかで React 的なアプローチから距離を取った方がよい境界が生まれるはずだが、その境界を超えた場合のベストプラクティスはあまり語られることがないように思う。また実際には「アプリ」でないから伝統的な静的 HTML + jQuery で十分かと言うとそんなはずはなく、React 以降の考え方を部分的にでも導入し、コードを長期間に渡ってメンテナブルな状態に保つ努力は必要だろうと感じている。(それが文字通り「すぐに捨ててよいもの」なら話は変わるが。)

これはコンポーネントをいったんキーワードにした思考過程の垂れ流しである。

めちゃくちゃ長くなったので3分割した。

フロントエンド領域の課題私観

  • ユーザーの目と手に触れるものである以上、デザイン的にそれなりのクオリティであってほしい(特に toC 領域の場合)
  • デザインの作り込みと仕組みの作り込みのスキルは基本的にはかなり異なる(単にピクセルデザインをコードに起こすだけの場合、完全にアリモノのフレームワークだけで解決する場合を除く)
  • 現在は摩訶不思議な「コンポーネント」という概念で一括りになってしまっており、これがややこしさに拍車を掛けている

結果、デザイナーには Web フロントエンドへの参加のハードルは上がり続ける1 、あるいはエンジニアにはデザインの「組み込み」が発生し続け、コミュニケーションコストやミスコミュニケーションが発生し、お互いに好ましくない体験が生まれやすい領域になっている。

※ くり返しになるが、アプリアプリしているもの、「システム」として完成しているものについては除外して考えている。「サイト」の一部が「アプリ」っぽく動くものを想定している。

従来の課題

HTML, CSS は書ける。これを動的に書き換えることでデザインや機能を実現することは可能だが、それに対してレール、ガイドのない状態で無作法な書き換えを気ままにやった結果管理できなくなった jQuery 時代があった。

それから Backbone.js, AngularJS, Twitter Bootstrap, Ember.js などを経て現在は React 風の何か、コンポーネントと呼ばれる何かを組み立てることで JavaScript 側のコードの DOM への影響のコントロールについては一定程度克服できたように見える。

コンポーネント時代の問題

一方でこの「コンポーネント」が実にやっかいなシロモノになってしまったと個人的には感じている。

  • コンポーネント、再利用難しくない?
  • コンポーネント設計、難しくない?
  • コンポーネントの実装技術、妙に注文多くて難しくない?
  • やたらめったら小さいコンポーネントが生まれて管理難しくない?
  • コンポーネントの中にストアを持ってそいつがめちゃくちゃいろんな機能を実現するから、結局コンポーネントそのものが複雑なものになってるよね?
  • スタイリングについてはあんまり解決してなくない?2

個人的には

  • Web Components は本来再利用可能な Custom Elements を実現するものだったはず
  • 一方で、React 以降、「コンポーネント」はコントローラともモデルとも密になってしまいやすく、なんならコードのエントリポイントであり、そもそも再利用を難しくする要素に溢れている3

という食い違いを強く意識するようになった。

コンポーネントに求めていたものはなんだったっけ?

一度引いて、これまであれこれ触ってきたなかで感じたよかったこと、そうでもなかったことを振り返りながら、そもそも何を求めていたのか、求めているのかを改めて書き出してみたい。

HTMLとCSSは実は密であり、だったら密なままにしたい

以前から

HTMLとCSSを分離してHTMLの構造に依存しないCSSを実現するため、そしてCSSの影響が漏れないようにするため、「妙に長い名前を付け、それをセレクタにCSSを適用させる」BEM以降の潮流があるが、名前付けのコストを常時払っている割に結局HTMLの構造が変わった時には壊れないかもしれないけど名前を考え直す難しさと付き合わないといけない、しかも「機能で付ける名前とスタイリングのための名前」は意外と合っておらず両方考える必要があってコスパが悪いのでは?

と感じていた。この部分については少し動的な要素が入ってきたら、

Vue の Single File Component + Scoped CSS がよさげ

に見えていた。ボイラープレートも少なく、かつやりたいことと学習曲線のバランスが適切な解決策に見えていた。

2021 年に TailwindCSS に触れて以降、コンポーネントという言葉の示す範囲はもう少し自由に考えていいんだなと思えるようになった。

  • HTML 断片でもある程度まとまった役割を持っていたり、再利用したいものをコンポーネントと呼んでよい
  • その際、コンポーネントの単位の方に注目して CSS の影響が外にはみ出なければそれでよい

という割り切り方の一つとしてユーティリティファーストはアリかも、と思った。少なくとも TailwindCSS を利用すれば Vue + Scoped CSS に限定されず、CSS の名前付け地獄から解放されるかもしれないという期待感があった。4

一方で実際に使ってみると TailwindCSS は

  • 👍 自分がコードを書き始めて短期記憶に収まっている分には名前を考えるストレスから解放され、非常に書きやすい
  • 👎 もはやレビュー対象にするのは不可能
  • 👎 当然だが class がめちゃくちゃ長くなり、コンテンツなどが埋もれやすくなり、チェックしにくい
  • 👎 素の CSS の理解度の高い人からはマップし直す必要がある
  • 👎 調整の粒度が TailwindCSS の範囲内に収まっていない場合やベースのカラーリングなどのカスタマイズに JS の知識が必要になる
  • 🤔 結局ある程度名前を付け直して apply したくなる

といった具合で、メリットもあるがデメリットもあり、「全面的に TailwindCSS などでよいのでは」と考えるのはやや無理があるかも?と思うようになった。

それでもコンポーネントに対する考え方の自由度が増し、自分が「HTML と CSS はけっきょく密であり、CSS の名前付けだけでものごとを解決しようとするのは無理がある」という意識を持っていることが改めて分かった。

学ぶこと、作ることのコストを上げたくない

Vue 2 が出たての頃は、

  • React のように面倒な準備も要らないし、なんなら生 DOM にも template を書いてしまえる
  • 副作用を自分で書かずに更新が反映される reactive という考え方
  • Single File Component で上記の密な関係は密なまま解決
    • 記法の問題はエディタ、IDE の拡張で乗り越えられる

というメリットが非常に眩しく見え、jQuery などで荒れやすく、CSS の名前付け問題も起きるフロントエンドに対してある程度の秩序を作るのに向いているツールとして Vue コンポーネントはアリと思うようになった。

しかしいくつかのシーンで利用するにつれ、 VDOM は生 DOM と違いひとたび VDOM にしたらそれ以下はすべて VDOM にせざるを得ないため、どんどん VDOM コンポーネントを量産するハメになり5、またどうしても「機能を含む」ため、素の HTML, CSS よりも書き始めが重くなり、書き間違えたら素の HTML, CSS と違って「とりあえずなんか出るからそれ見ながらどうにかゴニョゴニョしてるうちにゴールに向かう」アプローチに対して一定程度の拒絶が生まれ6てしまい、「サポート」の必要性が増してしまいやすい。レビューもしっかりした方がいい。

自分たちの欲しかった「コンポーネント」を作るための要件は満たすが、全体的に HTML, CSS に動的な要素を加えることに対して必要以上に高コストであり、本来やりたかったこととマッチしていないと思うようになった。

その後、そもそも React が解決するような

  • 大規模開発
  • 細粒度のコンポーネントを大量に多数回更新

みたいなものは要件に含まれていないのだということに Stimulus の登場で気付かされることになり、このアプローチは真に自分たちの求めていたものではなかったのかもしれないと思うようになった。

※ もちろん必要なら使えばよいけど、必ずしもこのアプローチにこだわる必要はない、と割り切れるようになった。

Stimulusの「アプローチ」はまどろっこしいけどヒントになる

Stimulus は JavaScript 側に Controller を書き、HTML には適切なマーキングを施すことで、特定の DOM 要素以外とは通信しないことだけを強制するツールである。あとは Web Components の「ような」lifecycle の考え方があるだけ。

素朴な HTML に必要なだけの Controller を加えるアプローチなので、コンポーネントが増殖してしまう問題は解消されるが、reactive でもなければ最初の HTML の markup がややまどろっこしいうえに、密なはずの CSS との関係を維持するための仕組みも何もない。7

ただし、通常の DOM なので何かあれば普通に Custom Event を投げればいいし、DOM 同士を結びつけるような Controller があれば複数要素の連携も jQuery ほどの無作法さを排除しつつ実現できる。デザインはあくまで普通に HTML, CSS で実現できるし、普通に DOM スクリプティングが書けるなら特別なことは何もない。

とても素朴なアプローチで、VDOM を目指すよりむしろある程度までなら生産性は高いのではないかと思うようになってきた。VDOM のアプローチは VDOM で閉じる課題が多ければ多いほど有用だが、逆に VDOM に頼らずに実現できる課題なら生 DOM のままの方が都合がよいと思えた。

少なくとも View フレームワークのバージョンアップに振り回される必要はないし、期待していないコンポーネントを作る必要もない。ただ、HTML も JS も CSS もそれぞれバラバラではあるので、再利用しやすいか?というとそこは難しいと感じた。

※ 個人的には petit-vue もアリだなと思ったが、Vue のようで Vue でないものを増やすのは混乱を生みやすいかもしれないと思った。

結局、何に困っているのか?

  • 多くの JS ベースのコンポーネント設計はビジュアルデザインと機能が密になりやすく再利用を難しくしており、中長期的なコストは微妙かも?
    • コード自体はカオスになりにくいとは思うのでそこはよいが、場合によってはライブラリの非互換のアップデートや依存関係に苦しむこともある
  • デザインについては CSS フレームワークの採用で解決する方法はあるが、以下のような課題もある
    • カスタマイズの容易性
    • カスタマイズとも関係するがセレクタとして「大きな名前」が予約されてしまって後から導入しにくい問題
      • カスタマイズのためには結局 JS のビルドシステムが必要だったり
    • 下手に View フレームワークと密になっててアップデートがつらくなったり
  • 機能ではないビジュアルデザインの部分においてデザイナーが CSS や HTML template 部分を書きやすいツールであってほしい
    • その際、名前付けに個人の癖や練度がめちゃくちゃ強く影響しない方がなお嬉しい
    • CSS-in-JS とか考えたくない。普通に CSS が書けて普通にハイライトされてくれれば十分

どうも特定のツールを導入すれば簡単に解決できるものではないような気がする。

実は解決方法は増えていた

Litベースのデザインシステム

今回、実際のところデザインに関するコードの再利用性をどうにかできないかという疑問が強くあったのでデザインシステムを調べていたんだけど、いくつか発見があった。

めちゃくちゃでかい規模になるけど、いわゆるデザインシステムを Web Components として実装する例が増えている。もちろんエコシステムの大きさと実績から React で作ることを優先している企業も多いが、Web Components で十分実現できるのだと気付かされた。そして上記デザインシステムは Microsoft 以外は Google の Lit をベースにしている。(Microsoft は自前の Web Components ライブラリ Fast を持っているので当然それを使うことになる。)

しかも Carbon と Spectrum に関しては CSS だけで実現する方法も用意されており、手始めに CSS を整え、プラスして HTML 構造を Web Components で固定し、再利用しやすくする、というアプローチがポピュラーになっているのだと感じた。

従来の CSS フレームワークは CSS に合わせて特定の HTML 構造を再現しなければならない格好になるが、HTML と CSS の噛み合わせについてはそれが正しく合っていなくてもバグではないので機械的にチェックさせながらコードを書く作業をスムーズに行えない。8 しかし Web Components というか Custom Elements になっていれば 「HTML, CSS の組み合わせを正しく書かなければいけない」というハードルは解決できる。そのうえで variant や custom が実現できればなおよい。

CSS標準の進歩

Custom Elements については2年前も調査しているが、CSS の方は抜けていた。実は

主要ブラウザでサポートされるようになっており、IE 11 の終了を以て Shadow DOM に対するスタイリングの課題は解消されたと言ってよい状態になってきている。ということは実は Vue + Scoped CSS じゃなくても、ユーティリティファーストのツールを導入しなくても、CSS の標準的な技術だけで Custom Elements で影響範囲のコントロールをしつつスタイリングすることが可能になっている。

ということで次回以降は Lit を具体的に試すところから。

  1. なんならエンジニアにとってもよく分からんがずっと新しいツールの話をしてる領域と思われているフシもある 

  2. 一部は Scoped CSS など積極的だけど、CSS を JS で書かせるとかとかちょっとあんまりだと思う。 

  3. Angular はちょっと違うアプローチだけど 

  4. 例えばゴリゴリに動く必要はないけどサーバサイドで変数が多くて partial や ViewModel に抜き出しておきたいものに関して、抜き出した View と CSS の距離が離れているのは、あまり扱いやすくはない。書き始めの時期はともかく、時間経過の中で変更したいという時には「これに該当する CSS はどこにあったっけ」となってしまう。そして名前付けは名前付けの徹底、学習し直しがつらくなる。 

  5. Vue には Web Components と同様の slot があるので丸ごと子要素を slot にするという方法もあるが、props で渡したいものがあるとかだと割と絶望的。まぁ今は Provide / Inject があるとは言え。勢い、全部 Store に入れてみんなグローバルな Store に依存しろとか、「ほんとにこれでよかったんだっけ?」みたいなことは起きやすい。 

  6. HTML, CSS を書きたい人たちは決して JS のエラーと戦いたいわけではない 

  7. TailwindCSS などを導入すれば密にできるが、コンポーネントとして解決策を持っているわけではない。 

  8. もちろん慣れはあるが特に機能の方に注力したいエンジニア視点だと CSS のルールに合わせた HTML のコーディングは「探り」の要素が強く、効率の悪いものに感じる。ユーティリティの要素の強いものはまだ試行錯誤しやすいのでエンジニアに受け入れられやすいのかも? 

More