やはりHTML/DOMは再発明されるべきじゃないか
と思う次第である。以下理由。
JavaScript, GUI設計の今
JSはそのプラットフォーム特性上、あらゆる言語の使用者の、あらゆる不満が集まる場所で、ヘイトを集めやすい環境だと思う。近年は npm というプラットフォームの登場でエコシステムが生まれ、思いつく限りあらゆるメソッドが適用されてきた。貧弱な言語基盤だが、その中で生き残った方法論が、今一番GUIの課題を上手く扱えている、と自分は考えている。
React/Redux や Angular によって、Flux/MVVMという抽象パターンが枯れてきたように思う。(この際、現場はまだ jQuery だぞ、みたいな話は無視する)。要は View は State の写像である、ということに尽きる。State はシリアライズ可能(JSON)で、Flux Action/Rx.Observable の Event Stream を入力とし、それぞれの状態はシリアライズ可能で、あらゆる状態が入力値によって予測可能(参照透明)で、決定的である。
要は、メタな設計というレイヤーでは、データの扱いを記述することが、アプリケーションを記述することと同義になりつつある。Redux も Rx も Event Stream からどうスナップショットを切り取るかの一つの方法論に過ぎない。(まだ新しい発明の余地はある、と思うが)
そこでは mutable なものは悪で、GUIの設計からは排除されるべきものになった。View とは本質的にシングルトン(哲学的には、そこで発生する「体験」もシングルトン)だが、そこに対する副作用は厳密に管理されなければいけない。でないと破綻する。ここで「発見」されたのは関数型的な手続き、FRPである。まあFRPは詳しく掘り下げるとボロが出る長くなるので、今回は置いておく。
Virtual DOM によるアーキテクチャの変化、最適化の限界
Virtual DOM の発明によって、State の写像である View の状態遷移は、人間の手を離れた。jQuery 時代のスクレイピング的手法から、自動的な木の構築が行われ、残されたのは、その破棄/生成のライフサイクルに対するフックになった。勿論、細かいインタラクションの最適化はまだ人間の手の余地が残されているが、管理すべき対象は大幅に減り、個人的にはウェブ開発の産業革命以前と以降のような劇的な変化だと思っている。詳しくは3年前に書いた なぜ仮想DOMという概念が俺達の魂を震えさせるのかを読んでほしい。
「賢い」データバインディングも結局はVirtualDOMとやることが同じである。木の枝ごとに値の監視範囲を知っていて、必要なパラメータだけを書き換える。DOMを書き換えた結果、レンダラに負荷がかかる。要はどちらも再生成するスコープが小さければ小さいほど、GPUを介して発行される Layouting や Painting のパフォーマンスが良くなる。当然だ。
ここで問題となるのは、結局 Virtual DOM は、HTML/DOM とは別の世界で定義された構造である、という本質的な部分だ。Virtual DOM は木に対してプリミティブな命令セットすぎるHTML/DOMを効率よくハックするために生まれたメタ概念であり、実際にはDOMのパラメータを変更した結果として描画命令が発行される。その為、逐次的なパラメータの更新は、ステップごとにその変更を通してサイズや位置が変更され、親は自身の大きさのバウンディングボックスの再計算を必要とし、それがルート要素まで連鎖し、まあ色々なんやかんやあって命令に対し影響範囲を予測することが難しい。
これに対する解として、同期フレーム内での DOMへの副作用をバッチにする requestAnimationFrame
や、予め変更があることを通知しておく css の will-change属性 がある。
しかし、 document.write
の存在(広告業界はいつ使うのをやめるんだ)や、float 属性が物事をややこしくしている。同期的に割り込まれたり、再計算範囲が事前にわからないそれらは、Webの黎明期に大きな役割を果たしたが、今や大いなる負債となって、最適化を妨げている。(と思っているが、本職のブラウザ開発者はまた別の意見がありそう。きいてみたい)
問題1: 参入障壁が大きく民主的ではない
問題の一つ。後方互換を配慮して積み重ねていた結果、実装が困難で、一部のパワーのあるベンダーしかブラウザを実装できない状態になっており、結果としてブラウザの開発は参入障壁が大きくあまり民主的ではない。
Chrome や Webkit は体裁としてオープンソースだが、一部のベンダ内にノウハウが偏っているし、ビルドやユニットテストに掛かる時間も膨大で、分散ビルド環境を持たないと開発に参加しにくい。
この実害として、僕は数年前まで vimperator というFirefox 拡張を愛用していたが、アドオン自体のコンセプトがもはやChrome に追従する Firefox の方向性と相容れなくなったため、アップデートごとにAPIが削除され、都度代替を探すということをしてるうちになんだか使いづらいものになってしまった。
問題2: セマンティクスは既に滅んでいる
問題の一つ。今のWebに、もはやHTML/CSSにセマンティクスなどはない。多重に重ねられた div と span が CSS の属性を通して何らかの意味を持っていて、外部から意図を読み取ることは困難だ。HTML5の、header, content, h1, main(:roll) 属性によってなんらかのセマンティクスを与えることが出来るも、今や付け焼き刃だ。
Google のクローラーは、おそらく最低限のセマンティクスを読み取った後は、各要素のバウンディングボックスの上からの出現順と全体を占める比重をみて、その中身のテキストの解析結果に重みを付けている、と予想できる。
解決のアプローチ
たぶん、ブラウザ開発の中の人達はそんなこと当然わかってて、何度も試作し、仕様を煮詰め、そして実装的な課題や、あるいは政治的な部分で諦めてきただろう。xhtml ですらダメだったのだ。でもその失敗は、時代的なものも大きかったと思う。数多の技術革新を経て、IE11 が EOLで、MS がIEでの反省の色を見せ、Safari が世界的に Android に追い詰められて標準化圧力が強い今、やってみるべきではないか。
第二次ブラウザ戦争はとりあえず Chrome の勝利ということで終わったが、モバイルの隆盛で、ブラウザというものが危機に瀕している。僕はブラウザとWebとその民主主義が好きなので死んでほしくはない、と思っている。
ブラウザが生きのこるのに必要なのは、後方互換を切り捨てた高速なレンダラー基盤と、そのためのシンプルな仕様なのではないか、というのが自分の思うところ。
既存の解決策1: WebComponents
WebComponents はタブ内で既存のUIスレッドから切り離された ShadowRoot 下に新しいページコンテキストを生成する。また自分でエレメント要素に名前を付けられるので、セマンティクスが今よりはマシだ。要はお行儀の良いiframeである。これによって全く別の最適化が施せる、余地がある。
ただ、Google肝いりであるはずの Polymer は、単純にパフォーマンスが悪い、大量のポリフィルが必要でオーバーヘッドが増す、HTML Import 周辺でおそらく合意がとれない、といった課題が山積している。リッチなアプリケーション基盤としても、WebComponentsが生成した中身のSEOが、どうクロールされ、どう扱われるかの声明がGoogleから出ていないので、前述の問題を含めて、個人的に使う気にはなれない。
既存の解決策2: AMP
最初に言っておく。AMP はGoogleの邪悪なHTMLサブセットだ。だがやりたいことは痛いほど分かる。要はレンダラーが予測可能な命令セットだけで、事前に最適化してしまいたいのだ。それにGoogle側のキャッシュプレロードが付いてくる。
AMPを使った場合、事前に動的なレイアウティングを排除され、すべての要素の width/height が事前に確定することが要求される。
amp-*
のタグを使って組み上げるのは、 WebComponents を使った体験に非常に近い。近いというか中身見る限りそのものだが。
既存の解決策3: React Native
なぜここで React Native の名が出るか、疑問に思う人も多いだろう。だが自分はこれが、Webを作り直す場合、この発展系が一番筋がいいのではないか、と思っている。
ここで React Native のコードを見てみよう。
// https://facebook.github.io/react-native/docs/props.html#content import React, { Component } from 'react'; import { AppRegistry, Text, View } from 'react-native'; class Greeting extends Component { render() { return ( <Text>Hello {this.props.name}!</Text> ); } } export default class LotsOfGreetings extends Component { render() { return ( <View style={{alignItems: 'center'}}> <Greeting name='Rexxar' /> <Greeting name='Jaina' /> <Greeting name='Valeera' /> </View> ); } } // skip this line if using Create React Native App AppRegistry.registerComponent('AwesomeProject', () => LotsOfGreetings);
知るべきは、View という抽象要素が、flex layout で動くことだ。これがどう達成されているかというと、yoga というflex layout の座標計算を行うレイアウトエンジンを下敷きになっている。flex layout は近年のCSSの課題が一通り解決されていて、一応この規格で今現在のレスポンシブなUIは一通り実現できる。
yue ってのもある。これは Electron の zcbens 氏が開発した cross platform な GUI ライブラリで、これも yoga エンジンの上に構築されている。おそらくは Electron で足りないパーツを補うためのものだろう。
AMPはHTMLとして書き込まれるものがコンポーネントごとに独立して稼働するのに対し、ReactNative は、それを生成するJS側が上位概念になる。AMPと違って自分でコンポーネントを自分で定義できるのが大きい。(AMPはやはり、ホワイトリストのコンポーネントしか使えないのが全く民主的ではないのが限界ではある)
レンダラーの提案
言いたいのは、ReactNative 最高って話じゃなくて、レンダラーの性能を最大限に引き出しつつ、十分な自由度でウェブアプリケーション開発基盤を作るなら、ReactNative周辺の発想が下敷きになるだろう、ということだ。毎フレーム document.body.innerHTML = ...
で書き換えるのではなく、レンダラーに向けた最重要エレメントとして、今のDOMではなく、 VDOM 要素とその差分更新を使う。
document.body.render(<View><text>Hello!</text></View>)
こんなAPIとすればよい。そしてそれは全部非同期で行われるとする。そして、その過程で密にレンダラー への副作用を最適化すれば良い。また、差分更新アルゴリズム自体をネイティブで記述できるので、高速化が見込まれる。課題があるとすれば、毎度巨大なステートツリーを生成する際のオーバーヘッドだろうか。
ReactNativeのView要素はAndoridのView, iOSのUIViewを抽象していてこういう名前になっているが、単に flex layout が動けばいいのなら、別に div でもいい。<html next>
みたいな定義を見たらこちらに切り替える、とか。例えばね。
この手法、もう一つ嬉しい点があって、実は画面遷移にJSを使わなくてもいい。同一オリジンないなら常にVDOMアルゴリズムを適用する、という仕様があれば、サーバーサイドで生成したHTMLを効率よく差分適用できる。大多数であろう、ヘッダだけ再利用して、コンテンツ書き換え、みたいなことが自然に行われる。
夢があると思いませんか。
ここで予想される批判は、Webは複雑化するのをやめ、ティム・バーナーズ=リーが目指したであろう、セマンティックなHTMLに回帰せよ、という主張。理解できるし気持ちはわかる。が、現状無理がある。Webに要求されるインタラクションや組版やタイポグラフィの水準は、もはやそこを遥かに越えていて、アプリケーションプラットフォームとして機能しないといけない時代に来ている。
少なくとも、「アプリケーションの」プラットフォームとしては、HTMLの別規格、あるいはサブセットとして、新しく生まれ変わる必要があるのではないか、というのが自分の主張である。
Facebook はブラウザを出すんじゃないだろうか
僕は数年以内に Facebook がブラウザを出すと思っている。その際に重要視してくるのは、ネイティブアプリと遜色ない体験を、一つのブラウザ内でシームレスに遷移できることだと思う。そのための基盤がReactで、少なくともその実験はしているのは間違いない。
その証拠と言っては何だが、 この前の BSD+Patent の騒動で、結局 ReactNative はライセンスを変更してない。これは対Google、対Apple の布石だと思っている。いや、元々センシティブなライブラリなので防衛的になるのもあるだろうが、単なるUIライブラリとしてはFacebookが力を入れすぎている、という感覚が昔からあった。だが、これが新ブラウザの布石なら納得行く話ではないだろうか。
書いてて思ったけど陰謀論っぽい。ちょっと落ち着こう…
まとめ
- ウェブフロントエンドの設計論ではHTML/DOMの後方互換性が足を引っ張るようになってきた
- HTMLサブセットの提案は、AMPなどですでにGoogleの目指すところとなっている
- ReactNative の方向でスクリプティングが発達すれば、少なくともレンダラーはネイティブアプリと遜色なくなる
- 今このタイミングで適切な次世代のWebの仕様を提案できれば、一発あるかもしれない
なんか興がのってでかいテーマで書いてしまったが、思考実験と言うか、話半分に聞いてほしい。Googleの中の人とかに話を聞いてみたいなぁ。