ジンジャー研究室

長めのつぶやき。難しいことは書きません。

JavaScript フレームワークを巡った話

ポエムです。

自分の今の立場としては「Elm の人」ということになってるんだけど、どういう変遷でここまできて今どういうスタンスなのかっていうのはあんまり話す機会がない。だから整理のために考えてることを書いていくよ、というのがこの記事の趣旨。

非 Web の立場から

そもそも自分は「Web 系」の出身ではない。新卒入社したワークスでは ERP パッケージを提供するのに画面を Web 技術で作ってるというだけで、別に SEO の順位を競ったり広告をどうという話ではないし、瞬時に画面が表示されないと離脱率が〜という話でもない。ただ、画面はとにかく複雑で設定項目とががうじゃうじゃある。

あと、学生時代に PC に触れたのが Windows で「黒画面なにそれ美味しいの?」くらいに GUI に染まりきってたというのがある。工学系の研究を効率化するために C#GUI を作ってたら、なんかソフトウェア業界に入ることになってしまった。 CLI は今でも得意でなく、黒画面を前に「今なにをしようとしてたんだっけ」と手が止まってしまう。

話を戻すと、とにかく複雑で高機能な画面を簡単に作りたいわけである。仕事もそうだし、趣味の話で言えば音楽制作ソフトの華である DAW とかが作りたかったりする。

jQuery

最初は Java でスタンダードな画面遷移する Web アプリの作り方を学んでたりしたんだけど、なんかのタイミングで「 JavaScript を使えばグリグリ動く画面が作れるらしい」「とにかく jQuery を入れるんだ」という情報を仕入れて、まあやってみたら本当にグリグリ動いた。 あと社内でアリエルの製品を使ってて、かなり高機能な画面が JavaScript でゴリゴリ動いてるのを知っていたので、「そうかイケてる画面はこうやって作るのか〜」とか思いつつ、とりあえず目標としては JavaScript で高機能な画面を作ることとなった。

ところが自分が当時関わっていた社内システムは理想とは程遠く、よくある「とりあえずサーバー側で作った画面を jQuery でいじり倒す」という行き当たりばったり状態。まあそもそも JavaScript もほとんど分からない状態だったので、ポップアップを出すライブラリを適当に取ってきて用途に合わない動きをちょっとハックしてなんとか動かすみたいなことをしてた。

「このままじゃ全然イケてる画面になる気がしない・・・」と思っていた矢先、社内で「クライアントサイドの基盤でも作ろうか」みたいな話が持ち上がった。一応、技術基盤をやる部署だったので。

Ember.js

最初に検証の対象に持ち上がったのが Ember.js である。なぜこいつが最初だったのかはよく覚えていないが、確かにチュートリアル通りに手を動かすとイケてる感じのポップアップがポンと出てきて「お〜」みたいな感じになった。 これは本当にちょっとしかやってないからほとんど覚えてないけど、まあ JavaScript 自体が初心者ということもあり使い方が複雑で難しく、自力で組み立てようにもまずフレームワークが難しいような状態で断念。(あとマスコットキャラクターもあんまり好みじゃなかった。)

コンポーネント

フレームワークはよく分からなかったが、とりあえず目標としているのはネイティブ GUI のような高機能画面で、それらの特徴は「四角い部品」の寄せ集めで出来ているということだった。 当時「コンポーネント」という言葉を使ってたかは忘れたが、とにかく jQuery で HTML を直にいじるのはよくないから、もっと粒度の大きい部品を作って API 越しにアクセスすればいいんじゃないかという考えになった。で、そのコンポーネントを全体でツリー状にネストさせれば良いのだと。

Haxe

順番は忘れたが、 AltJS の波が来て「 Haxe っていうイケてる言語があるらしい」ということで、上記のコンポーネントはそれで作っていた。この頃には前よりいくらか JavaScript にも慣れていて Haxe 製の Haxe エディタなども作ったりした。

型もついて一件落着かと思ったが、「イベントをやり取りするのがめんどくさい」という課題が残った。例えば、「クリックしたら兄弟コンポーネントも更新する」ような場合に、一度親がイベントを拾って兄弟を描画し直さないといけない。あとは、遠く離れたコンポーネントを更新する場合には一度グローバルにイベントを渡すようなトリックも必要になった。 つまり、粒度は大きくなったが、やっていることとしては jQuery と同じ「ビューからイベントを受け取って別のビューを更新する」というフローになっていた。

これを打破するために必要になったのが MVC である。

Backbone.js

もはや名前も聞かないが、当時 MVC フレームワークと言えばとりあえずメジャーな Backbone.js かなという雰囲気があった。jQuery を使うのが前提になっているのも、その時代という感じがする。 こいつは何かというと「モデルが更新されたらビューが自動で更新されるようにしましょう」が軸で、手段としてはオブザーバーを使う。ビューはモデルを listen し、モデルのプロパティが更新されたという通知を受けたらなんらかの方法で再描画する。

仕事では試す機会がなかったので、趣味で Backbone.js で色々作ってみることにした。確か WebAudio API の UI を作ったのはこれ。で、色々問題が見つかった。

まず、イベントハンドリングがスパゲッティになる。モデルやそれの集合体であるコレクションを更新するととにかくイベントが飛びまくる。「1回の更新のはずなのになぜか3回再描画が走る」などのトラブルが起こり、原因を突き止めるのがとにかく大変。 次に、リスナーの削除が漏れる。ビューのライフサイクルの中できちんと unlisten できればいいのだが、上位コンポーネントが雑に innerHTML とかでまるっと消してしまうと、リスナーが削除されずに溜まっていく。 最後に、モデルを更新したら自動的にサーバー側と同期をとる仕組みが意味不明だった。そんなに上手くいくんだろうか? Rails の何かを真似した仕組みっぽいけど、 Rails の経験はなかったし(今もない)、そもそもサーバーと同じ仕組みで動くものだろうか。

あとは Backbone.js に何かの仕組みを付け足した派生みたいの(Marionet とか)が結構あったけど、どれも大成功しているようには見えず、モヤモヤしていた。

AngularJS

当時はとにかく AngularJS の登場が衝撃的で「すげー!」となった。(登場というか、当時はそんなにネットで情報収集していなかったので、すでに登場していたのかもしれない) もちろん Angular 1 のことである。

仕組みは Backbone.js よりもかなり単純で、モデルを更新したら次のターンでビューを描画する。テンプレートを使い、今の Virtual DOM に似た dirty checking という方法で変更差分だけを DOM に反映する。ただ、非同期処理が入ると描画がトリガーされないので、そこだけは上手く専用の API を呼ぶ必要がある。

つかみは非常に良かったのだが、それ以上複雑なことをしようとすると、どうすればいいのかたちまち分からなくなった。コンポーネントをツリー状にすれば良いんだろうと思っていたが、そんな説明はドキュメントのどこにもない。directive というカスタム要素を作る方法はあったが、ちょっとした用途向きで無限にネストすることは考えられていない。他にもコントローラーとかインジェクションとかも色々あったが、とにかくどう使えば良いのか分からない道具が並んでいるという印象。

当時「AngularJS を使っていると何度か精神的にアップダウンするけど、最終的には最高の状態に行きつける」みたいな言説があったけど、自分はアップダウンしている間に燃え尽きてしまった。

Polymer

Google が「時代は Web Components !」と言わんばかりに Polymer を突っ込んできた。 Web Components は自分の当初やりたかったコンポーネント構想に近かったのもあるし、 Web 標準にもなるというのでかなり期待が高かった。Chrome 35 だか 36 の時に Custom Elements が実装されて大はしゃぎした記憶がある。

もう記憶がおぼろげだが、 Polymer は単なるポリフィルというよりもフレームワーク的なこともお世話してくれていた気がする。今は虫の息と言われている HTML Imports とかも、本番用に結合して配信するための vulcanize とかいうツールが作られたりした。作ったコンポーネントをシェアするためのリジストリなんかもあった。 Polymer のカンファレンスもあった。とにかく「これからみんな Polymer でやってくぞ」という雰囲気で宣伝も激しかった。

で、自分のホームページを Polymer で構築してみた。 ・・・当時は「未来の Web 標準」なんて Google が言い出したものなら「なるほどそうなのか!」と言って無邪気に信じたし、ライブラリだって「凄い人たちが作った凄いもので自分は使うだけ」と思ってたから、まさか数ヶ月後に突然ホームページが真っ白になるとは思わなかった。多分 Chrome の何かのアップデートで polyfill の一部が壊れたのだろう。 すっかり懲りてしまって、「まあ Google がなんか言っても話半分に聞いとくか」くらいのスタンスになった。

リアクティブプログラミング

そんなこんなしている時に、ひょんなことから次の記事が目に留まった。

なぜリアクティブプログラミングは重要か。 - Conceptual Contexture

概念としてすごく面白そうだと思って「これ MVC フレームワークに応用できるんじゃね?」となった。 アイデアとしては、モデルが変更されたら自動でビューが更新されるように連鎖してくれれば言いわけだ。AngularJS とかだとモデルとビューは1対1なんだけど、リアクティブプログラミングであれば「モデル => モデル => モデル => ビュー」のようにスケールしそうな気がする。

というわけで、それができそうな Bacon.js を触ってみる。だけど Bacon.js はイベントの起点が DOM になっていて、なんかちょっと違うなと思った。(少なくとも当時はそう思っていたけど、今は別におかしくないと思う) で、もう少し調べていると、なにやら Elm という言語が FRP(関数型リアクティブプログラミング)というのでそれっぽいことをしているらしい。こんにちは、FRP

ただ、当時の Elm は難しかった。Haskell を知っているのが前提みたいな空気があったし、何と言っても HTML タグが書けなかった。頑張って習得したところで canvas に5角形を書くくらいしかできない。「TodoMVC をなんとか Elm でも書けたぞ!」と Evan が得意げに YouTube に投稿するくらいである。 当然「流石にこれはオモチャでしょ...」となった。

React

これはもう魂が震えてしまったね。もはや説明の余地もない「宣言的ビュー万歳」である。 最初は「そんなバカスカ Virtual DOM 生成していいものか」という不安はあったが、使ってみてすぐにその懸念は払拭された。コネで CodeZine に2本ほど記事も書いた。

もちろん全てに満足していたわけではなくて、「わざわざシンタックスのためだけに JSX とか入れる?」とか、「 JS で Immutable 守り通すのきつくね?」とか、まあ色々思うところはあったが、出発点が jQuery のカオスだっただけに「まあ言っても昔に比べりゃ全然 OK なレベル」と概ね満足していた。

Elm

そんなところに「Elm が Virtual DOM を導入したらしい」というニュースが入ってきた。(これもニュースというよりは、少し経ってから知ったのだが)

「Elm で HTML が書けるってマジ?」

元々関数型も少しかじってたし面白そうだからやってみるか。で、試しに色々作ってたら「なんかこれ普通に実戦投入できそうじゃね?」になって今に至る。その間に Elm は FRP であることをやめてしまった。さようなら、 FRP! あと、ミートアップに顔を出したり主催してたら Idein 社に呼んでもらえた。ごめんな ERPFRP もやめてしまって。

仕事でも Elm を快適に使えているし、大体の用事は Elm でなんとかなる。 そのことに特に不満はない。シンプルながら最強の型システムを持ち、コンパイルは爆速、ライブラリはフロントエンドに特化しているし、関数単位でデッドコードを除去しながらバンドルまでしてくれて、いざとなれば JavaScript と連携可能である。これだけあって、至らない部分に文句を言うのは贅沢というものである。

ただ、個人的な問題としては、Elm Way が完成されすぎてて「もっとこうしたらどうか」と自由に遊べない。「遊んで時間を浪費するよりも余計なことを考えずにトレードオフを受け入れろ」という、なんとも仕事向けな奴なのである。(言語機能は汎用的だが、主要ライブラリの仕組みごと作り変えるコストが高い) 「宣言的ビューは善である」という仮説をある程度検証することは出来たけど、「じゃあ他の可能性はなかったの?」と思わなくはないわけである。

Web Components

Edge が開発を断念し、世はまさに Chromium 一強時代に突入しようとしているらしい。 となると、 Web Components (主に Custom Elements と Shadow DOM )が意外と早く陽の目を見るのでは、という見方からパラメーター配分を変えているのが現在の状況である。なんとなく v1 はこのまま行きそうだし、 Polymer に頼ることもなさそうだ。

Web Components はフレームワークではないので、素のまま使うのは難易度が高いかもしれないが、今はあえて「何も使わずにやる」という方針にしている。というのは、今のフレームワークって全部 Web Components がない前提で作られたものばかりなので、 Web Components があるとまた違った景色が見えてくるかもしれないから。「素のまま頑張るとこんなもんか〜」と生 DOM の感触を確かめつつ(一応 TypeScript で)色々と試している。

今のところの感想としては、素の Web Components は Backbone.js と書き味が近い。 ただ(自分の理解では) unconnectedCallback が必ず呼ばれるはずなのでリスナーの解除し忘れがないのが嬉しい。独自の仕組みとしてイベントに EventTarget を使っているが、モデルとは紐付けないようにしている。HTML をテンプレートで書くときも、VSCode の lit-html プラグインを使うとシンタックスハイライトが効いたりする。

その先

どうなるかは全然読めないけど、とりあえず Web Components をベースにガチャガチャやっていくことになるんだと思う。CSS は Shadow DOM の中で干渉なく使えて css-loader とかの存在意義が薄れる。モジュールは基本 ES6 Modules で書かれるけど HTTP/2 でサーバープッシュするプランが怪しいので、引き続きバンドラーを使うことにはなりそう。言語はそのうち TypeScript をブラウザが解釈するようになるんじゃないか、などと妄想しておく。

自分の探求する領域としては、 Virtual DOM alternative を考えようかなと思っているところ。 lit-html や hyperHTML みたいなものかもしれないし、別のかもしれない。Elm は仕事で継続してベストプラクティスを探求しつつ、 Web Components との組み合わせも試したい。そんな感じ。

おまけ:その他のライブラリ

  • jQuery UI: 単品でいくつか使ったことがある。
  • dojo: 聞いたことある程度。
  • Knockout.js: 聞いたことある程度。
  • ExtJS: 前職で使われていて良さそうだったが、あまり触ってない。
  • Google Closure Library: これも前職やアリエルで使われていたが、玄人向けで使いこなせる気がしなかった。
  • RxJS: 気になりつつ触ってない。
  • Angular(2-): ちょっと何がしたいのか良く分からない。
  • Redux: だいたい知ってるけど触ってない。昔の Elm の名残りで副作用周りがミドルウェアに追い出されてるように見える。
  • Vue: チュートリアルはやった。ルールが多いのとオブザーバーへのトラウマから食わず嫌い中。
  • Riot.js: 正規表現でゴリゴリパースしてるという話だけ聞いた。
  • Dart: 初代をちょっと触った。
  • Flutter: よく知らない。
  • Preact: よく知らない。
  • Mithril: 一時期速いって言われてたことだけ知ってる。
  • choo: Elm 再現系ね。
  • Cycle.js: あの人最近何やってるの。
  • Meteor: 密結合が怖くて触れなかった。
  • CoffeeScript: 触る前に消えた。2は知らない。
  • Reason (React): 型はあるけど Elm みたいに API ドキュメントが見当たらなくて辛かった。
  • PureScript (Halogen): 自分には難しそう。
  • ClojureScript (om): よく知らない。
  • Scala.js: 触ってない。
  • js_of_ocaml: あったね。