Closed17

[Draft]VueとSvelteのサイズを比較検証した「vue-svelte-size-analysis」を掘っていく

tomoamtomoam

VueやViteなどの作者として知られる天才Evan You氏がVueとSvelteのサイズについて比較検証を行い、一部で話題になりました。

https://github.com/yyx990803/vue-svelte-size-analysis

このスクラップでは、比較検証の内容についてちょっと掘り下げてみていきたいと思っています。いい感じにまとまったらちゃんとした記事にするかもしれません。しないかもしれません。

tomoamtomoam

色々書いていく前に私の立場や考えを明らかにしておきます。

私はSvelteの日本語翻訳をしていてもちろんSvelteを気に入っていますが、Vueも好きで良く使っています。フロントエンドがメインではありませんので、知識が足りないかもしれません。変なところや間違っているところがあったらコメントで指摘してくださると嬉しいです。

フロントエンド界隈のバチバチやり合っている雰囲気はとても良いと思っています。ちゃんとコードを書いてベンチマークをとってそれを公開し、指摘し合ってお互いを高めていっているように見えます。結果として素晴らしいフレームワークやライブラリとなり、開発者がそれを使えるようになり、ユーザーに素晴らしい体験を届けられる、というのは素敵なことです。

tomoamtomoam

この検証を見る前に、VueとSvelteのバンドルサイズを比較するときに一般的にどんなことが言われていたかおさらいします。

VueはReactと同様、仮想DOMを活用した小さいライブラリです。何に比べて小さいかというと、Angularに比べると小さいということを主張しています。

https://jp.vuejs.org/v2/guide/comparison.html#サイズ

というわけで、バンドルサイズという観点では非常に優秀でした。
そこに、Svelteを含むコンパイラフレームワークやalpine.jsなどのもっと小さいライブラリが出てきたりしました。フロントエンドは次から次へと新しいアイデアが形になってすごいですね。

で、Svelteですが、Svelteはコードとマークアップをコンパイルすることによってランタイムを必要最小限にします。その結果としてバンドルサイズを小さくなったと主張しています。

https://svelte.jp/blog/frameworks-without-the-framework
https://svelte.jp/blog/virtual-dom-is-pure-overhead

実際、RealWorldやjs-framework-benchmarkでは、サイズはもちろんLighthouseのスコアやコード量の少なさでReactやVueより優れているという結果が出ています。

https://medium.com/dailyjs/a-realworld-comparison-of-front-end-frameworks-2020-4e50655fe4c1
https://krausest.github.io/js-framework-benchmark/2021/table_chrome_91.0.4472.77.html

tomoamtomoam

バンドルサイズやパフォーマンスにおいてAngularより優れていると主張していたVueが、今度は追い抜かれる側となってしまうのか、というところで「待った!」をかけたのがこの「vue-svelte-size-analysis」です(Evanさん、痺れる!)

この検証を行う出発点は大体こんな感じの疑問点から始まっていると推測します。

確かにVueはたった1つのコンポーネントに対しても約16kbのランタイムが必要で、Svelteはそれが必要ない。でもその分コンパイルしてできたコンポーネント1つ1つが大きくなってるはず。コンポーネントの数次第でバンドルサイズは逆転するのでは?

実際、バンドルサイズが逆転する分岐点はあります。

2020年の春に行われたSvelte Society Day 2020というイベントのFAQで、分岐点はあると解答していて、例えば比較対象がpreactだったらその分岐点は早めに訪れること、実際のアプリにおけるコンポーネントは単純じゃないこと、コード分割のほうが重要であること、そのページにおいて使用するJavaScriptの量が少ないことのほうが大事であることなどをSvelte作者のRich Harris氏が話しています。

※ 9:43~

YouTubeのvideoIDが不正ですhttps://youtu.be/luM5uobewhA?t=582

tomoamtomoam

では、本題の「vue-svelte-size-analysis」について見ていきましょう。

https://github.com/yyx990803/vue-svelte-size-analysis#comparing-generated-code-size-of-vue-and-svelte-components

Svelteの公式にあるsvelte-todomvc をベースにしたコンポーネントと、それと同じことができるVueのコードを用意し、それぞれのサイズを比較しています。

まずランタイムの差です。

Vite vendor chunk (min+brotli) - VueとSvelteのランタイムサイズの比較

見ておわかりの通り、これはVueが大きいですね。その差が15.04kb
この検証ではここがスタートです。この差が、TodoMVCのコンポーネント何個分で埋まるか、という部分がこの検証で比較したいことです。

tomoamtomoam

そして次にコンポーネントの差。

Vite component chunk (min+brotli) - VueとSvelteのサイズの差

この差が 0.78kb

ランタイムの差が 15.04kb、そしてTodoMVCコンポーネントの差が 0.78kb。
つまり、15.04 / 0.78 ~= 19、つまり TodoMVCコンポーネントと同じくらいのコンポーネントが19個あったらバンドルサイズが逆転する、ということを検証結果に書いています。

tomoamtomoam

さらに、これがSSR用の出力であればコンポーネントの差が大きくなり、もっとコンポーネントが少なくてもバンドルサイズが逆転することも付け加えられています。

この検証の結びに「どのフレームワークが優れているか示すものではない」と書きつつも「Svelteは中規模から大規模のアプリでは不利である、特にSSRでは」という煽りを忘れないあたりEvan You氏の闘志を感じられていいですね。

では、ここからはこの検証に使用したコードや、材料にしたTodoMVCが比較としてフェアなのか、そしてバンドルサイズ以外のパフォーマンスについて見ていきたいと思います。

tomoamtomoam

では次にVue側です。VueでSvelteのTodoMVCコンポーネントと大体同じようなコードを書いているように見えます。
vue-svelte-size-analysis/todomvc.vue

ただ、ごく一部に若干の違いが見られます。例えばこれです。
vue-svelte-size-analysis/todomvc.vue L6-L10

Svelteのコードではtodoの配列をフィルタリングするのに毎回 items.filter(...) と書いているのに対し、Vue側では最初のほうにまとめていますね。

他には、プロパティ名が変更されていますね。
vue-svelte-size-analysis/todomvc.vue L28

Svelteではこうなっています。
vue-svelte-size-analysis/todomvc.svelte L47

Svelteではtodoアイテムのプロパティ名が description ですが、Vue版では title になっていますね。

その他、Svelteでは JSON.parse のときに try catch を書いているがVueでは書いていないとか、SvelteではVueの v-if にあたる操作をしている部分を v-show にしていたり、細かい違いがありますね。

これらを修正したからといってコンポーネントサイズの差が大きく変わるわけではないと思いますが、一応やって試してみましょう。

tomoamtomoam

次に、SvelteのTodoMVCコンポーネントにSvelteの現在の構文を適用してみます。

実はこのTodoMVCコンポーネント、最後の更新が2019年で、しかもその更新内容はあくまで依存モジュールのバージョンの更新です。つまり2019年以降に追加された構文が使われていません。

具体的には、class ディレクティブが使えそうです。

https://svelte.jp/docs#class_name

この構文を適用したコードがこちらです。
https://github.com/tomoam/vue-svelte-size-analysis/commit/286c4bb2b6c9ca9ddd362043123ba3849bb5e304

その結果、ランタイムのコードが 0.03kb増え、コンポーネントのコードが0.06kb減りました。

tomoamtomoam

私がコードを見て変だなと思った部分はそれくらいです。
ここまでの結果をみてみましょう。

まずランタイムの差です。
ランタイムの差は15.01kb。この差がコンポーネント何個分で埋まるか、でしたね。

コンポーネントの差はこちら。0.69kb

ランタイムの差が 15.01kb、そしてTodoMVCコンポーネントの差が 0.69kb。
つまり、15.01 / 0.69 ~= 21、つまり TodoMVCコンポーネントと同じくらいのコンポーネントが21個あったらバンドルサイズが逆転する、ということになります。

TodoMVCに限って言えば、これが正確な数字だといえるでしょう。

tomoamtomoam

次にこのTodoMVCコンポーネントが、アプリケーションのバンドルサイズの比較において適切かどうかということを見ていきますが、その前に、スベルトのコードがコンパイラによってどのように生成されるかをおさらいします。

Svelteのコンパイラはコードを読み取ってDOM要素を直接操作するコードを生成してくれます。特にコード量が多くなってしまうのは、コンポーネントのトップレベルの DOM 要素そのものが状態の変化に応じて追加/削除されるようなケースです。逆にattributeや値の操作だけならばそこまで生成されるコード量は多くなりません。

それを踏まえてTodoMVCコンポーネントを見てみましょう.

TodoMVCコンポーネントは、コンポーネントにあるDOM要素のほとんどが追加/削除されるようなコンポーネントです。Svelteの中では生成されるコード量が最も多くなるタイプのコンポーネントだと言って良いでしょう。それは即ち、Vueと比較したときにコード量の差が出やすいコンポーネントだということです。

つまりこの比較結果はこう言い換えることができます

もしあなたのアプリケーションが、状態の変化に応じて新規作成/削除されるDOM要素だけで構成されるようなケースにおいては、そのコンポーネントのサイズがTodoMVCサイズでありその数が21個を超える場合、SvelteとVueの総バンドルサイズは逆転する。

そんなケースがあるのかどうかはともかく、こういうケースにおいてSvelteの総バンドルサイズがVueの総バンドルサイズを超えるのは事実だと言えるでしょう。

(多分Evan Youさんは色々わかった上で比較して煽っている気もしますw)

もちろんこんな極端な例は別として、分岐点は存在します。

tomoamtomoam

では次に、なぜその点が不利であるにも関わらずSvelteのほうがパフォーマンスが良いとされているのか見ていきます。

例えばRealWorld example app。
https://medium.com/dailyjs/a-realworld-comparison-of-front-end-frameworks-2020-4e50655fe4c1

Svelteにおいてはコンポーネント数(.svelteファイル数)が23個ありました。それら全てが内包するDOM要素を追加/削除するようなものでないにしろ、もっとVueとSvelteのスコアは肉薄していても良い気がします。
(SSRだとコンポーネント1つあたりのサイズはもっと広がるはずですからね!)

tomoamtomoam

ちょっとワクチン接種の副反応が出てきたみたいなので続きは後日書きます。多分。

書こうと思ってたことをメモしておく

  • 初期ロードの量(大きいランタイムが必要なフレームワークの不利な点)
  • コード分割とプリフェッチによって総バンドルサイズのデメリットは小さくなる
  • 性能が低いデバイスで動くということは何を意味するか
  • preactの凄さ
  • solidjs凄そう
  • petite-vueへの大きな期待
tomoamtomoam

SvelteやVue、Viteなどのコードを読み始めたらいつの間にかこのスクラップとは関係ないIssueやPRを作るようになってしまい興味が別の方向に向かってしまったので一旦Closeします。

このスクラップで書こうと思っていたことより素晴らしい内容が下記の記事からお読み頂けるのでご参考までに。

https://dev.to/ryansolid/series/14561

このスクラップは2021/09/22にクローズされました