キーポイント
- While the speed with which JavaScript was adding new UI frameworks to build interactive web applications has slowed down in recent years, we have seen new interesting frameworks in 2020 that focus on simplicity and performance
- Brahmos strives to implement the known React APIs with a different and potentially faster method that also leverages a standard feature of JavaScript: template literals. Brahmos thus strives to implement React’s hooks, context, concurrent mode, and more
- Brahmos is among the very few UI frameworks that implements the experimental concurrent mode API sponsored by React. Other frameworks may be waiting out, or discarding the feature entirely.
- While the bar for new JavaScript frameworks is continually raised by the dominant actors in that space (e.g. by partnering with browser vendors to add APIs favoring a given approach, renderers for other output devices, baking UX practices into the framework), the newcomers show that there is still space to innovate
Sudhanshu Yada氏が開発したBrahmousは、インタラクティブなWebアプリケーションを開発するための新しいフロントエンドフレームワークです。最新のReact APIにほぼ準拠していますが、その実装方法は異っていて、仮想DOMをまったく使用していません。代わりにBrahmosでは、JavaScriptのES6/ES2015イテレーションで導入された最新機能である、テンプレートリテラルを活用しているのです。意図されたメリットは、フレームワークのコードベースを小さくすることによるパフォーマンスの向上と、DOM部分の更新処理の高速化です。
現在の実装では、関数コンポーネント、フック、コンテキスト、ref、フォワードref、サスペンス、コンカレントモードなど、React APIの大部分が実装されています。これらのAPIを使用したサンプルコードを、コードプレイグラウンドで入手することができます。
Brahmosは、Reactのパフォーマンス向上を目標とした、新たなフロントエンドフレームワークです。DOMレンダリングをターゲットとして、より小さなコードベースでそれを実現しようとしているものに、Preactがあります。Reactとは違い、そしてPreactとは同じように、Brahmosもまた、DOMベースでない出力デバイス(モバイル、PDF、WebGLなど)を対象にすることはできません。
Brahmosの興味深い点のひとつは、コンカレントモードを実装していることです。これはReactに数年前に導入され、現在も開発中の機能で、公式リリースはまだ保留されています。
当面の目標は、既存のReactコンポーネントを再利用可能にすることです(現在は未実装)。Brahmosのアプローチを評価するためのパフォーマンスベンチマークの実施も、現時点では保留となっています。
Sudhanshu Yadav氏に、このフレームワークの基本的な考え方、目標、付加価値、ロードマップについて聞きました。より深く知りたいという読者には、紹介ビデオがオンラインで公開されています。
InfoQ: 読者のために、自己紹介をお願いします。
Sudhanshu Yadav: HackerRankでフロントエンドアーキテクトとして働いています。自分が使用しているテクノロジの内部を理解することに深い関心があって、機能方法に関する理論を立てることが好きです。ミートアップグループも組織していて、さまざまなテクノロジの内部について議論しています。それ以外にも、アーキテクチャやパターン、ツーリング、設計システムを調べ続けています。
私はオープンソースを強く支持していて、Brahmosの他にもreact-number-format(npmから毎月100万件近くインストールされています)、packagebind、その他のOSSツールやライブラリを開発しました。
InfoQ: 先日リリースされたBrahmosについて、最新のReact APIを再現するユーザインターフェース構築のためのフロントエンドライブラリである、と説明されていますが、この特徴は、PreactやNervといった、Reactにインスパイアされた他のライブラリと同類であるということになります。Preactはサイズがコンパクト(4KB)であることを強調し、NervはIE8にまで及ぶブラウザ互換性を選択していますが、Reactに対するBrahmosの大きな違いについて、どのように説明されるのでしょうか?Brahmosを書いた動機は何でしたか?
Yadav: おもな開発動機は、アプリケーションのパフォーマンスを向上することです。Brahmosは、アプリケーションを静的部分と動的部分に分割するという、lit-html/hyper-htmlのアイデアに大きな影響を受けています。これによってトラバーサルや一連の処理を、ReactのようなO(ノード)ではなく、O(動的ノード)で実行することが可能になっているのです。このレンダリングパターンは静的な最適化に関しても、多くの可能性を開いています。以前lit-htmlに関する講演を聞いた時に、アプリケーションの大部分は静的であって、変化する動的部分はわずかである、というこのアイデアに興味を持ちました。
Reactとその宣言型APIを気に入っていたので、同じパターンをReactで試してみたいと思ったのです。最初に、2つの選択肢を考えました。
ひとつはReact Rendererを書くことでしたが、この実装パターンにはReact Elementと仮想DOMという大きな問題があったため、実現できませんでした。React Elementは静的要素と動的要素を区別していないため、複数の静的要素をひとつの要素として組み合わせるのが難しかったのです。
2つめはlit-htmlをレンダリングエンジンに使用して、その上にReact APIをラッパとして記述する方法でしたが、lit-html APIとReact APIを直接マップすることができませんでした。
結局、どちらの選択肢もだめでした。そこで、まったく違うレンダリングパターンを使って、React APIをスクラッチから実装したライブラリを書くことにしたのです。
Reactが内部的にどのような動作をしているのか、本格的なUIライブラリがどのように構築されているのかを理解することも、この開発を始めた動機のひとつでした。
InfoQ: フロントエンドフレームワークの重要な部分のひとつとして、開発者がアプリケーションを作るビューを定義できるようにすることがあります。これにはおもに2つの選択肢があります — 機能を限定した独自DSLでテンプレートを使用する方法と、もうひとつは、JavaScriptやTypeScriptといった完全なプログラム言語でレンダリング関数を記述する方法です。Reactではレンダリング関数を使用しますが、VueやSvelteはJavaScript以外のファイルに記述されたテンプレートを使用しています。Brahmosはこのいずれとも違って、JavaScript言語の比較的新しい機能であるタグ付きテンプレートリテラル — nanohtml、lighterhtml、lit-html、あるいはMicrosoftのfast-elementなどを活用しています。テンプレートリテラルのもたらすメリットや、それらがBrahmosでどのように使用されているのか、読者に説明して頂けますか?
Yadav: ES6にはテンプレートリテラルとともに、テンプレートにタグを付けるという機能も追加されているのですが、ほとんど評価されていません。このテンプレートリテラルタグは一種の関数のようなもので、文字列の配列(リテラル、すなわち静的部分)を最初の引数として、動的な記述部分を以降の引数として受け取ります。タグ関数のもうひとつのユニークな動作は、基盤となっているリテラル文字列が変わらなければ、文字列配列の参照も同じままである、という点です。
Brahmosに関して説明すると、JSXをタグ付きテンプレートリテラルにトランスパイルして、ネイティブな要素を静的部分に、JSXの記述を動的な記述部分に変換します。これによってコンテントの静的部分と動的部分が識別されて、それぞれの最適化が可能になるのです。
完全に静的な部分はひとつの仮想ノードとみなすことができるため、アプリケーション内の仮想ノードの数が大幅に削減されると同時に、動的部分全体をトラバースして変更を識別する作業も高速化できます。
文字列配列の参照はテンプレートに対して一定のままなので、文字列の解析結果(HTMLテンプレートタグへの変換)はキャッシュが可能です。これにより、大規模なリスト内で同じコンポーネントを複数回レンダリングしても、同じ処理を繰り返す必要はなくなります。この方法で、コンポーネントを繰り返し利用する場合のパフォーマンスを改善することができるのです。
カスタムテンプレートと比較した場合でも、テンプレートリテラルにはメリットがあります。テンプレートリテラルはJavaScriptの標準機能なので、JavaScriptのすべてのパワーを動的表現の記述に使用することができるのです。これに対して、(VueやSvelteが使っているような)カスタムテンプレートの動的ロジックでは、制限のある専用の構文が使用されています。
さらにテンプレートリテラルには、要素変換を行う上で、JSXの処理結果であるオブジェクトリテラルよりも、スクリプトロード時にパーザフレンドリであるという利点もあります。オブジェクトリテラルのオンロード解析を防ぐために使用する同じようなテクニックとして、JSON文字列に変換するという方法もあります。
InfoQ: Reactには最近、新しいAPIが立て続けに追加されていますが、いくつかについては実験的(experimental)のままです。中でも特にコンカレントモード(Concurrent Mode)は、それによって開かれる可能性と、コンカレントレンダリングによって引き起こされる複雑性の両面から、コミュニティからの関心を集めています。実際にいくつかのフレームワークは、この機能を取り入れないことを決めています。コンカレントレンダリングの機能について教えてください。Brahmosでは、コンカレントレンダリングをどう扱う予定ですか?この機能には、複雑性に見合うだけの価値があると思いますか?
Yadav: コンカレントモードは、コミュニティの一部の人々から誤解を受けているように思います。コンカレントモードはビューの更新時のパフォーマンス(アップデートパフォーマンス)だけではなく、ビューの生成(マウントパフォーマンス)も改善します。コンカレントモードを使えば、ビューがロードされる方法とタイミング、レンダリングされる順序を開発者がコントロールして、理想的なユーザエクスペリエンスを提供することが可能になるのです。
コンカレントモードによって、アプリケーションがバックグラウンドで動作している間でも、UIをインタラクティブに保つことができます。現状では、ブラウザには単一のスレッドしか存在しないので、バックグラウンドとフォアグラウンドの処理は同じスレッドで実行されるのですが、UIライブラリ(React/Brahmos)がスケジュールを行って、その間をスイッチしているのです。
この動作を説明するには、バージョン管理のメタファが一番でしょう。一般的なgitのワークフローでは、機能を実装する場合、ブランチアウトして別のブランチに移動した上で作業し、結果をマスタにマージします。しかしその間に、もしも重大なバグの修正が必要になった場合には、機能ブランチをいったん離れて重大なバグの作業を実施し、変更をマスタにプッシュした後に、機能ブランチでの作業を再開することになります。gitがなければ、まず機能実装が完了するのを待たないことには、バグの修正作業に取り掛かることさえできません。
これと同じように、ブラウザのすべての変更が、画面に表示する上で同じ優先度を持っている訳ではないのです。ユーザにとって最も重要なのは知覚可能なエクスペリエンスなのであって、ライブラリやアプリがレンダリング処理に要する時間ではありません。コンカレントモードは、ヒューリスティックスや更新ソース(ユーザ操作、JavaScriptコールバックなど)、宣言型のヒント(useTransition、useDeferredValueなど)を使って優先順位を決めることで、ユーザの認知するエクスペリエンスを最善にしようというものです。ユーザエクスペリエンスを改善するために、その時点で最も重要なことをライブラリに実行させます。
このパターンに検討の価値があることは間違いありません。アプリケーション自体のコンカレンシを向上することも(デバウンスやレースコンディションの管理、プライオリティの管理を自分で行えば)できなくはありませんが、アプリケーションに与える複雑性は膨大なものになります。コンカレントモードを備えたReactでは、複雑性はライブラリ自身の中に隠蔽されて、アプリケーションがレンダリングする上でのヒントを与える宣言型APIが提供されます。
コンカレントモードの実装は難しく、ユースケースもたくさんあります。Brahmosのコンカレントモードの実装中も、ひとつのユースケースを解決したとたん、新たなユースケースが現れて、もう一度考え直さなくてはなりませんでした。それでも、それが開く可能性を考えれば、このパターンへの投資は理に適っています。
現在のBrahmosは、"fiber architecture"、"time-slicing"、"transitions"、"suspense for data fetch"、"suspense list"など、予定されているコンカレントモード機能をすべてサポートしています。
APIは変わっていませんが、コンカレントモードを解決するためにBrahmosが採用しているアーキテクチャやアプローチは、Reactとは少し違います。例えば、
Brahmosでは、動作やヒューリスティックを複数のプロパティに分割するのではなく、アップデートを3つのカテゴリに分割しています。
まず最初に、同期的なレンダリングとコミットの必要な部分 — (イベントによるアップデートのような)処理中に停止できないようなアップデートを実行します。
その次は、一時停止は可能だが値の変更ができない(setStateで発生するように、ユーザのインタラクションに起因しない)アップデートです。
第3として、遅延、一時停止、延期、破棄の許されるアップデート(非同期アップデート、トランジション内部のアップデートなど)を行います。フォアグラウンドの状態が変化した場合には、非同期アップデートや遅延アップデートが破棄されることもあります。
同期的なフラッシュが必要な優先度の高い変更については、フォアグラウンドのファイバツリーを直接操作します。
Brahmosはトランジション毎に分離されたアップデートリストを持っているため、個別に実行することが可能です。ひとつのトランジションが他をブロックすることはありません。
InfoQ: APIをReactに近いものにしたことで、既存のReactコードのBrahmosへのマイグレーションはどの程度簡単になるのでしょうか?あるいは、新規開発アプリケーションでの使用を推奨しますか?
Yadav: Brahmosの最終的な目標は、Reactからのマイグレーションを名称変更のみに近いような、簡単なものにすることです。これを実現する上で、当面の大きな障害となっているのが、子コンポーネントに関連するReact APIです。Brahmosは静的ノードをすべてひとつにまとめているため、子コンポーネントがReactとは違うのです。回避策はあるのですが、サードパーティのReactコンポーネントを扱うためのサポートが必要です。
Brahmosが実行するおもな最適化は、静的部分と動的部分を分割することと、JSXのタグ付きテンプレートリテラルへの変換なのですが、サードパーティ製のモジュールのcreateElement構文をタグ付きテンプレートリテラルに変換するポストプロセスを行う方法についても、現在検討を行っています。createElementはBrahmosでもサポートしているのですが、動的部分として扱っているのです。
既存のReact AppをBrahmosを使って最適化することに向けた計画もいくつかありますが、まだしばらく先のことになります。
InfoQ: Brahmosが他のフレームワークに比較して優れている部分について、いくつか例を挙げて頂けますか?
Yadav: Brahmosのおもな目標は、サーバとブラウザでのUIレンダリングのパフォーマンス向上にあります。現在はまだ開発モードなので、ベンチマーク結果は取得できていませんが、パフォーマンス向上のために試しているいくつかの方法をご紹介しましょう。
1. バンドルサイズ(縮小化と圧縮)
Brahmos: ~12 kb (公開予定のコンカレントモードを含む、ほとんどのReact APIをカバー)
Preact + Preact compact: ~9 kb (ただしPreactではコンカレントモードはサポートされていない)
React + React-dom: 38.5kb (コンカレントモードをサポートした場合、さらに大きくなる可能性がある)
Brahmosのサイズ縮小のアイデアがまだいくつかあるのに加えて、Reactのサードパーティライブラリのサポートではツリーシェイクの導入も検討しています。
2. アプリケーションバンドルパフォーマンス
BrahmosのJSXトランスパイル出力は、Reactのアプリケーションコードよりもコンパクトです。さらに、文字列であるタグ付きテンプレートリテラルでは、オブジェクトリテラルのようなスクリプトロード時の解析が不要になることから、オンロード解析時間全体の縮小も期待できます。
3. レンダリング/アップデート時間
静的部分をひとつのノードにまとめることで、トラバーサルがReactのO(ノード数)に対してO(動的ノード数)になっています。これによってBrahmosでは、DOMに適用する変更を検索するためのトラバーサル時間全体が短縮されています。
4. サーバサイドレンダリング
JSXをタグ付きテンプレートリテラルにトランスパイルすることによって、すでに文字列化されているため、VDOM(Reactの要素)から文字列にレンダリングする場合に比較して、アプリケーションへのレンダリングのパフォーマンスが大幅に向上します。
サーバサイド非同期レンダリング(開発中)
パフォーマンス改善とは別の話になりますが、サーバサイドでの非同期レンダリングを実現する計画もあります。Brahmosは最初から非同期レンダリングを念頭に置いて設計されているので、サーバサイドでの非同期レンダリングのサポートもアーキテクチャ的に可能なのです。このレンダリングにより、サーバとクライアントのロジックの同一性が向上すると同時に、ルートやAPIコールの識別に必要なコンベンションの多くを取り除くことが可能になります。
InfoQ: 逆にBrahmosよりも、一般的な旧Reactで行った方が簡単なことは何かありますか?
Yadav: Brahmosはブラウザとサーバのレンダリングのみをサポートします。他のターゲットはサポートしていませんし、その予定もありません。ReactではVDOMによって複数のターゲットを簡単にサポートできますが、BrahmosにはVDOMがないので、他のレンダラをサポートすることができないのです。
また、動的部分を中心に構成された(データ可視化のような)アプリでは、Brahmosによるメリットをあまり期待できない可能性があります。データ可視化を多用するアプリや大部分が動的部分のアプリは、Brahmosには向いていないでしょう。
InfoQ: 今後のロードマップについて教えてください。
Yadav: まずはReactサードパーティライブラリ、次のサーバサイドレンダリング(SSR)が目標です。この方法が、コミュニティにメリットを提供する一番の近道だからです。SSRで非同期レンダリングサポートをサポートする計画もあります。これが実現すれば、SSRアプリの現在のアーキテクチャとは大きく差別化されることになります。
Brahmos用のReact Devツールのサポートも検討しています。
インタビュー回答者について
Sudhanshu Yadav氏はJavaScriptとReactのファンで、フレームワークの内部構造や動作原理に強い関心を持っています。HackerRankのフロントエンドアーキテクトに勤務し、BrahmosJSやreact-number-format、packagebind、その他いくつかのOSSツールを開発しました。さまざまな技術的スタックの内部構造を議論するミートアップグループの運営もしています。