12k
All articles

Gea 入門:コンパイラファーストの UI フレームワークを見る

GeaはJSX、クラス型ストア、直接DOMパッチを使うコンパイラ先行のJavaScript UIフレームワークです。記事ではprops、サイズ、成熟度を検証します。

OpenReplay Team
OpenReplay Team
Gea 入門:コンパイラファーストの UI フレームワークを見る

Gea は コンパイラファーストの JavaScript UI フレームワークである。Vite プラグインがビルド時に JSX を読み取り、どの DOM ノードがどの state に依存しているかを割り出し、DOM への直接パッチを出力する。仮想 DOM は存在せず、実行時に差分を取るものは何もない。

ランタイムをコンパイルで消し去ると謳うフレームワークは数か月おきに現れ、ローンチ記事はたいていセールストークと数値を並べるだけで、トレードオフにはほとんど触れない。Gea のページも似たような読み心地なので、本稿ではプロジェクト自身が説明するモデルを整理し、それぞれの数値の出どころを示し、React や Vue と本当に異なる唯一の点、すなわち双方向のオブジェクト props を最も厳しく見ていく。

要点

  • Gea はリアクティビティをコンパイル時に配線する。Vite プラグインが JSX を解析し、state を DOM ノードにマッピングし、仮想 DOM を配布する代わりに対象を絞ったパッチを出力する。
  • モデル全体は 3 つのルールだけだ。store は Store を継承したクラス、コンポーネントは template() メソッドを持つクラスまたは素の関数、算出値は getter である。
  • オブジェクトや配列の prop は、親が保持しているのと同一のプロキシを子に渡す。したがって子側での書き込みは親の state も動かす。プリミティブは代わりにコピーされる。
  • プロジェクトは hello world で brotli 121 B、インタラクティブな todo で 4.9 kb、js-framework-benchmark のスコア 1.02 を報告している。いずれも Gea 1.3.0 時点で、メンテナ自身がメンテナ自身のビルドに対して計測したものだ。
  • メンテナは 1 人、バージョン番号は初期段階、第三者によるレビューもない。Gea は本番投入の賭けではなく、午後のスパイク(試作調査)に値する。

Gea とは何か

Gea はリアクティビティをランタイムからビルドへ移す。プロジェクトサイトによれば、Vite プラグインがコンパイル時に JSX を読み取り、どの DOM ノードがどの state 断片に依存するかを判定し、それらのノードだけに触れるパッチを配線する。Vue との比較はその対比を率直に述べている。Vue が render 関数を再実行して結果のツリーを差分比較するのに対し、Gea はコンパイラがすでに生成したパッチ関数を実行するだけだ。state は素のクラスに置かれ、Gea がそれをディーププロキシでラップするので、this.count++ のような通常の代入だけで DOM が動く。ラップすべきものはなく、同期を保つべき依存配列もない。README はこの系譜を、メンテナの以前のライブラリである erste.js と regie の延長線上にあり、そこにコンパイル時 JSX 変換が加わったものとして提示している。

Gea のモデルは 3 行で言うと何か

Gea の表面積はすべて3 つのルールに収まる。store は Store を継承したクラス、コンポーネントは template() メソッドを持つクラスまたは素の関数、算出値は通常の getter である。README のカウンターがそのすべてを示している。

// counter-store.ts
import { Store } from '@geajs/core'

class CounterStore extends Store {
  count = 0
  increment() { this.count++ }
  decrement() { this.count-- }
}

export default new CounterStore()
// app.tsx
import { Component } from '@geajs/core'
import counterStore from './counter-store'

export default class App extends Component {
  template() {
    return (
      <div>
        <h1>{counterStore.count}</h1>
        <button click={counterStore.increment}>+</button>
        <button click={counterStore.decrement}>-</button>
      </div>
    )
  }
}

コンポーネントのドキュメントは、Vite プラグインがビルド中に関数コンポーネントをクラスコンポーネントへ書き換えること、そしてコンポーネントの template は一度だけ実行されることを説明している。それ以降はすべて再レンダリングではなくパッチである。

Gea の JSX は React とどう違うのか

Gea の JSX は React のそれに似ているが、1 つだけ本当に変えなければならない慣習がある。React が className を求めるところで class と書くことだ。イベントはもっと寛容である。READMEclickinputchange のような小文字の属性として記述しているが、ドキュメントの Vue 比較では onClickonInputonChange も受け付けられることが確認できるので、React スタイルのハンドラはそのまま移植できる。そして ref は ref オブジェクトを取らない。Gea はレンダリング完了後、DOM ノードを直接コンポーネントのプロパティに載せる。

// React habit          // Gea equivalent
<div className="card"   <div class="card"
  onClick={save} />       click={save} />

ドキュメントの ref パターンは、null で初期化したクラスフィールド、要素上の ref={this.videoEl}、そしてレンダリング後の this.videoEl の直接利用という形をとる。

Props: オブジェクトは親のプロキシそのもの

React や Vue からの最大の逸脱は、プリミティブでない prop に何が起こるかである。Gea のコンポーネントドキュメントは、子が親の保持しているまさに同じプロキシを受け取ると説明している。したがって子が行う書き込みはすべて親の state に届き、親の DOM に現れる。プリミティブは JavaScript の関数引数と同じ振る舞いをする。子はコピーを受け取り、それを再代入しても子の外側は何も変わらない。ドキュメントには、まさにこれを行う子の例が載っている。

export default class Editor extends Component {
  rename() {
    this.props.user.name = 'Bob'   // parent's DOM updates too
  }
  template({ user }) {
    return <button click={this.rename}>{user.name}</button>
  }
}

このルールはツリーがどれだけ深くても成り立つ。同じ参照を孫に渡し、孫がそのオブジェクトに書き込めば、そのデータを監視しているすべての祖先が再描画される。コールバックで持ち上げる必要はなく、間に emitv-model のステップも入らない。

子から親を更新するReactVueGea
オブジェクト/配列コールバック propsemit / v-model共有プロキシの直接ミューテーション
プリミティブコールバック propsemit / v-model不可能(値渡し)

ドキュメントはこれを純粋に利点として提示しており、未解決の問いは答えられないままだ。40 個のコンポーネントからなるツリーで、このオブジェクトを変更したのはどのコンポーネントなのか。React の一方向の慣習が存在するのは、変更に追跡可能な起点を持たせるためでもある。Gea はそれを直接性と引き換えにしているが、その代償が規模の拡大に伴ってどれほどになるかを書き上げた者はまだいない。コンパイル時配線そのものについても同様だ。コンパイラが静的に見通せないコードをどう扱うかは、ドキュメントが触れていない論点である。

サイズと速度、プロジェクトの報告どおりに

Gea に関する性能数値はすべて、プロジェクト自身が、メンテナ自身による競合フレームワークのビルドに対して計測したものだ。README のサイズ表は、hello-world アプリを brotli 圧縮後の JavaScript で 121 B とし、プロジェクトによる Solid(3.6 kb)、Svelte(8.5 kb)、Vue(20.7 kb)、React(50.8 kb)のビルドと並べている。インタラクティブな todo は 4.9 kb だ。どちらの数値群も、Gea 1.3.0 時点で Vite 8.0.10 のクリーンな production ビルドから取得されている。

js-framework-benchmark のスコア 1.02(1.00 が手書きのバニラ JavaScript)は、公式に公開されたラウンドではなく、プロジェクト自身による Chrome 147 上でのスイート実行によるものだ。ホームページにある「最速のコンパイル型 UI フレームワーク」という一文は、同じ自己実行の数値に基づくプロジェクトの主張であり、第三者による評価ではない。

Gea と一緒に提供されるものは何か

README のパッケージ表には、@geajs/core@geajs/vite-plugin、サーバーサイドレンダリング用の @geajs/ssr、スキャフォールディング用の create-gea に加え、@geajs/ui(Zag.js 上に構築されたヘッドレスコンポーネント)とモバイルプリミティブ用の @geajs/mobile が挙げられている。gea-tools は npm パッケージではなく、VS Code と Cursor の拡張機能である。

より変わっているのが AI ツールだ。npx skills add dashersw/gea を実行すると、リポジトリの .cursor/skills/gea-framework 配下に置かれた一連のエージェントスキルがインストールされ、AI コーディングアシスタントに、さもなければ推測するしかない規約――store の仕組み、コンポーネントの宣言方法、JSX の相違点――を渡してくれる。どのモデルも学習データを持たない若いフレームワークにとって、規約をエディタが消費できるスキルとして配布するのは実務的なオンボーディング策である。

成熟度: バージョン番号が語ること

README の比較表とサイズ表は Gea 1.3.0 で計測されており、その後 @geajs/core はプロジェクトのリリースノートによれば 1.4.0 を公開している。Gea は MIT ライセンスで、メンテナは Armagan Amcalar 氏 1 人だ。ベンチマークとサイズの数値はすべて自己申告であり、フレームワークに対する独立した技術レビューはまだ存在しない。チームにとっての結論は明快だ。モデルは一貫しており、午後いっぱいで評価できる程度に小さいが、単独メンテナ、初期段階のバージョン番号、検証されていない数値という条件は、プロダクトを賭けるだけの根拠がまだないことを意味する。

Gea の本当に興味深い主張はバンドルサイズではない。コンパイラが配線を担うなら、素のクラス、関数、getter だけで完全なリアクティビティモデルを担えるという点だ。その主張を判断する最短の方法は、上のカウンターと props の例をスキャフォールドし、双方向のオブジェクトセマンティクスが自分たちのコードベースにおいて明快さに感じられるのか、それともデバッグ上の負債に感じられるのかを見ることである。

FAQ

Gea は Solid や Svelte とどう違うのか?

3 つともコンパイラに依存しているが、リアクティブなプリミティブが異なる。Solid はブラウザ上で動作するシグナルの上にリアクティビティを構築し、Svelte 5 は独自のテンプレート言語からコンパイルされる rune 構文を使う。Gea はそのどちらでもない。state はディーププロキシがラップする素のクラスに保持され、Vite プラグインがビルド時に標準的な JSX を読んで DOM への直接パッチを配線する。

Gea のコンポーネントにはどんなライフサイクルフックがあるのか?

Gea のクラスコンポーネントは 4 つのフックを公開している。created(props) はコンストラクタと最初のレンダリングの間に発火し、ドキュメントは初期化ロジックをここに置いている。onAfterRender() はコンポーネントの要素がドキュメント内に入り、その子がマウントされた時点で実行される。onAfterRenderAsync() は次の requestAnimationFrame を待つ。dispose() はコンポーネントを DOM から取り除き、そのオブザーバと子を破棄する。これらのいずれかが必要になったときは、ドキュメントはクラスコンポーネントを使うよう案内している。

Gea は TypeScript で動作するのか?

動作する。クラスコンポーネントは 'declare props' で prop の形状を宣言する。これは JavaScript を出力しないアンビエント宣言だが、TypeScript を理解するエディタであればフレームワークのプラグインなしに属性の補完と型チェックを行える。template() の引数を this['props'] として注釈すれば、それらの型がメソッド内で分割代入された変数にも引き継がれる。これを省くと型は any にフォールバックする。関数コンポーネントも通常の引数注釈で同じ扱いを受けられる。

新しい Gea プロジェクトはどう始めるのか?

プロジェクトの create-gea ツールを使い 'npm create gea@latest' でスキャフォールドする。Gea のビルドは Vite 上に成り立っており、JSX 変換、リアクティビティの配線、ホットリロードを担うのが @geajs/vite-plugin である。ドキュメントには、ビルドステップなしで Gea を動かすためのブラウザ利用ガイドのほか、ルーター、UI キット、モバイルパッケージのガイドも含まれている。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.