コンテンツにスキップ

バンドルサイズ

ベンチマークは各ライブラリの速さを測ります。このページは、各ライブラリを配布するのにかかるサイズを測ります。シナリオごとに、その実装がライブラリから import しているエクスポートを、それが依存するものすべてと一緒にバンドルし、minify して圧縮します。値は apps/synstate-docs の pnpm run bundle-size で計測し、ページと一緒にコミットしています。計測したバージョンは「結果」に載せています。synstate-dom と UI フレームワークの比較は synstate-dom のバンドルサイズにあります。

  • すべてのエクスポート: ライブラリのエントリポイントのエクスポートをすべて丸ごと残したもの。synstate・rxjs・mobx・jotai/vanilla と jotai/vanilla/utils・@reduxjs/toolkit・solid-js・@preact/signals-core・zustand/vanilla・valtio/vanilla です。上限として読んでください。アプリケーションに残るのは import したものだけです。Jotai は、React バインディングを除いて同じ関数をエクスポートする vanilla のエントリポイントで測っています。
  • シナリオの行: そのシナリオの実装が import しているエクスポート(たとえばダイヤモンドでは synstate の createState・derive・map)を、tree-shaking で落ちないようにすべて生かしたもの。バンドルはその実装が必要とするライブラリのコードです。実装自身のループと、ベンチマークのハーネスが呼ぶ ts-data-forge のヘルパーは数えません。数えると、ts-data-forge に自分で依存している SynState 以外のすべてのライブラリに上乗せされてしまうからです。ベンチマークが 1 つのライブラリを 2 通りで測っているシナリオでは、「(naive …)」と付いていない自然な書き方の方を使います。ダッシュは、そのライブラリを測っていないシナリオです。
  • 依存込み: synstate は ts-data-forge・ts-std-forge と、Redux Toolkit は Redux・Immer・Reselect と、RxJS は tslib と一緒にバンドルします。これがアプリケーションがダウンロードするものです。トップページ・はじめに・README に載せているサイズは、これとは違って synstate 単体の値です。すべてのエクスポートを、ここと同じ esbuild の設定で、ただし ts-data-forge と ts-std-forge を除いてバンドルし、minify + gzip で 7.7 kB です。サイトのジェネレーターが現在のソースから計測するため、下の表より新しいことがあります。

どのバンドルも、ブラウザ向けに minify し、すべてのライブラリを本番ビルド(process.env.NODE_ENV と import.meta.env.MODE を production)で含めて、2 回ビルドします。本番モードで browser と production の export 条件を解決する Vite と、ライセンスコメントを除いた esbuild です。2 つのバンドラは tree-shaking で落とすコードが同じではなく、その差はライブラリによって大きく違うので、両方を載せます。gzip はレベル 9、Brotli は品質 11 です。1 kB は 1,000 バイトで、小さいほど良い値です。

Vite 8.3.5 と esbuild 0.28.2 でバンドル。synstate、ts-data-forge、ts-std-forge はこのリポジトリのソースからビルドしており、マニフェストのバージョン(synstate 7.0.0, ts-data-forge 14.11.0, ts-std-forge 0.5.3)にまだ含まれない変更を含むことがあります。その他のバージョン: rxjs 7.8.2, jotai 3.0.1, mobx 7.1.0, @reduxjs/toolkit 2.13.0, zustand 5.0.15, valtio 2.3.2, solid-js 1.9.17, @preact/signals-core 1.14.4。

シナリオ (kB)SynStateRxJSMobXJotaiReduxSolidPreact SignalsZustandValtio
すべてのエクスポート8.417.515.14.814.28.42.00.31.5
単一ステート3.23.311.32.47.12.31.60.21.0
派生チェーン3.83.611.32.48.52.41.6——
ダイヤモンド依存関係4.16.011.32.48.52.41.6——
ディープチェーン3.46.111.42.53.82.41.6——
カスケードダイヤモンド4.16.011.32.48.52.41.6——
条件付きファンアウト4.35.711.32.43.82.41.6——
等値カットオフ3.86.011.32.48.52.41.6——
シナリオ (kB)SynStateRxJSMobXJotaiReduxSolidPreact SignalsZustandValtio
すべてのエクスポート7.615.813.74.413.07.71.80.31.4
単一ステート3.03.010.32.26.52.11.50.20.9
派生チェーン3.53.210.32.27.82.21.5——
ダイヤモンド依存関係3.85.510.32.27.82.21.5——
ディープチェーン3.05.510.42.33.42.21.5——
カスケードダイヤモンド3.85.510.32.27.82.21.5——
条件付きファンアウト4.05.210.32.23.42.21.5——
等値カットオフ3.55.510.32.27.82.21.5——
シナリオ (kB)SynStateRxJSMobXJotaiReduxSolidPreact SignalsZustandValtio
すべてのエクスポート9.818.615.85.115.28.72.00.31.6
単一ステート3.83.512.02.58.52.71.60.31.0
派生チェーン4.43.811.92.510.12.81.7——
ダイヤモンド依存関係4.76.411.92.510.12.81.7——
ディープチェーン3.96.412.12.75.12.81.7——
カスケードダイヤモンド4.76.411.92.510.12.81.7——
条件付きファンアウト5.06.111.92.55.12.81.7——
等値カットオフ4.46.411.92.510.12.81.7——

createState だけを import する SynState の単一ステートのバンドルを、コードがどのパッケージから来たかで分け、バンドルに占める割合で示したものです。esbuild は各モジュールの minify 後のバイト数を報告します。Vite の下の Rolldown はチャンクを minify する前の各モジュールの長さを報告するので、Vite の列は minify 前のコードに占める割合です。

パッケージViteesbuild
synstate57%47%
ts-std-forge19%31%
ts-data-forge24%22%
バンドル全体、minify + gzip (kB)3.23.8

最も大きな割合を占めるのは synstate 自身です。残りは ts-data-forge と、ts-data-forge が土台にして re-export している ts-std-forge です。synstate が import するのは、ts-data-forge/array サブパスからの Arr、両パッケージの単独の関数(たとえば ts-std-forge の panic)、そして ts-std-forge が定義し ts-data-forge が re-export する Optional と Result のオブジェクトです。単一ステートのバンドルが使うのは Optional です。esbuild はこうしたオブジェクトのメンバーを 1 つでも使うと全体を残すので、esbuild では ts-std-forge の割合に、synstate が呼ばない Optional のメンバーも含まれます。どちらのパッケージも 3 つ目のパッケージのコードを持ち込まない(もう 1 つの依存 ts-type-forge は型だけを提供する)ので、バンドルにほかのパッケージは入りません。すでに ts-data-forge を使っているアプリケーションでは、この部分は自分のコードと共有され、払うのは一度だけです。

  • SynState は、どちらのバンドラーでもシナリオの行がすべて 5 kB 以下です。どの行でも MobX より小さく、単一ステートと派生チェーン以外のシナリオ(ダイヤモンド、ディープチェーン、カスケードダイヤモンド、条件付きファンアウト、等値カットオフ)では RxJS よりも小さくなります。どのシナリオの行でも、esbuild の値と Vite の値の差は 1 kB 以内です。
  • RxJS は operator を 1 つずつ別のモジュールにしているので、行は実装が import する operator に応じて大きくなります。単一ステートでは BehaviorSubject だけ、ダイヤモンドでは combineLatest と map も加わります。
  • MobX は一部でも使うとライブラリの大部分が残るので、シナリオの行はほとんど変わりません。
  • Redux は、実装が createSlice を使う行で Immer を含みます。createSlice の reducer は Immer の draft を使うからです。ディープチェーンと条件付きファンアウトは createAction に照合する素の reducer で書いているので、Immer を含みません。
  • Solid は反応系のコアである solid-js だけを数えています。これらのシナリオは何も描画しないので、solid-js/web はバンドルに入りません。
  • Jotai・Preact Signals・Zustand・Valtio はどれもコアが小さく、どの行も SynState の同じ行より小さくなります。Zustand と Valtio はベンチマークと同じく単一ステートだけで測っています。vanilla API に派生値のノードがないためです。
ターミナルウィンドウ
pnpm run ws:build:deps # synstate とその依存は dist/ からバンドルされる
cd apps/synstate-docs
pnpm run bundle-size # 計測して src/data/bundle-size-comparison.json に記録し、表を書き換える

サイズはソースとインストールされたバージョンだけで決まります。計測をやり直すワークフローはないので、synstate・その依存・比較対象のライブラリ・バンドラーが変わっても、このコマンドを実行するまで表は記載のバージョンでの値のままです。サイトの生成スクリプトは記録された値から表を描き、「SynState のバイトの内訳」や「結果の読み方」のうち、チェックしている主張が成り立たなくなると失敗します。