← ブログ

反転アーキテクチャー

ReactのコミットがDevToolsで1回のconsole.log呼び出しになる仕組み。

Adam

DOMフレームワークでは、コンポーネントはDOMノードになり、ブラウザーがページ内でレイアウトして描画します。Text.jsでは、コンポーネントはセルになり、変更されたセルの各フレームがDevToolsでの1回の console.log 呼び出しになります。これを反転アーキテクチャーと呼んでいます。

パイプライン

React ─▶ reconciler ─▶ Yoga ─▶ CellBuffer ─▶ row-hash gate ─▶ RLE ─▶ console.log

各段階が存在するのは、コンソールが特殊な表示先だからです。等幅で行を中心とした構造を持ち、%c でスタイルを設定し、再描画のタイミングはDevToolsが決めます。

リコンサイラー

@textjs/react はReact 19のリコンサイラー([email protected])で、2つのホスト要素 tx-box と tx-text、および文字列の子要素用のテキストインスタンスを備えています。<Box> から <Markdown> まで、インポートするものはすべてこれらを基盤に構築されています。コミットは requestAnimationFrame でスケジュールされ、キー入力や貼り付けでは同じタスク内で同期的にレンダリングされます。

レイアウト

WebAssemblyにコンパイルされたYogaが pointScaleFactor = 1 でツリーをレイアウトするため、すべてのボックスがセルの整数座標に配置されます。テキストは Intl.Segmenter と東アジア文字幅テーブルで測定するため、你好 は4セル、🚀 は2セルを占めます。編集によってテキストノードのサイズが変わり得ない場合、Yogaへの再計算の依頼はしません。

CellBuffer

フレームは、一度だけ確保される型付き配列のグリッドです。文字は Uint32Array、前景色と背景色はパックされた 0xRRGGBBAA、属性は Uint16Array のビットマスクに格納されます。描画ではこれらの配列に書き込み、どの行に触れたかを追跡します。

ゲート

各行には32ビットのFNV-1aハッシュがキャッシュされています。前回のフレーム以降に書き込まれた行だけ、ハッシュを再計算します。変更された行がなければ、ドライバーは文字列を組み立てる前に処理を戻し、コンソール呼び出しをしません。非表示タブのスピナーは動作していないため、コストはかかりません。

ランレングス符号化

変更された行を再走査して同じスタイルの連続区間にまとめ、各区間を1つの %c スパンにします。区間は改行をまたいで続きます。CSS文字列は整数のスタイルIDに対応づけてインターン化されるため、ホットパスで同じ文字列を2度組み立てることはありません。100×32のダッシュボードは3,200セル、149スパンです。

フォーマット文字列は、DevToolsの2つの挙動にも対処します。リテラルの % は %% と記述します。:// の後、www. の中、path:line の中にワードジョイナーを挿入し、DevToolsがテキストをリンクに変換して独自の色で再描画するのを防ぎます。

1回の呼び出し

変更された各フレームは、1回の呼び出しで出力されます。

console.log("%c\u200b%c\n%c▼ OpenCode Lite%c …", "margin-top:100vh;", "", "color:#fafafa;…", …);

フレームは追記され、DevToolsの50msのスロットラーに合わせた間隔で、1回の描画内で切り替わります。この部分はTurbolog:ちらつきの解消で説明しています。

入力

コンソールはキー入力を受け取れないため、ページが受け取ります。非表示の <textarea> がキー入力、貼り付け、IMEの変換入力を受け取り、wheel イベントが ScrollBox をスクロールさせます。textjs.orgでは、PlaygroundとOpenCodeのページ全体が1つの大きなフォーカスパッドです。コンソールを開き、ページをクリックして入力してください。

ページから見えるもの

ページが読み取れるのは自身のウィンドウのサイズだけで、コンソールのサイズは読み取れないため、Text.jsが推定します。ドッキングされたパネルは outer* を変えずに innerWidth または innerHeight を縮めるため、ドッキング位置の各仮説についてChromeのズームレベルに対応する解を求めます。最もよく一致するものを採用します。定数(水平方向のパディング56px、コンソールの外枠104px、行間隔14px)は、実際のDevToolsフロントエンドで測定しました。