🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

React x MobXな趣味プロダクトをSvelteでリライトした

続・React x MobXな趣味プロダクトをTypeScriptでリライトした - console.lealog();

このシリーズの続編で、いわばSvelte人柱シリーズです。

どんなプロダクトなの

一言でいうと、ブラウザで動くiTunesみたいなSPA。
CDをリッピングしてMP3にしたものをVPSにおいてて、それをWebのUIから再生できるようにしてる。

使い続けてかれこれ3年くらい・・思えば最初はVueだった気もする・・。
TodoAppほど小さくはなくて、でも片手で数えられるくらいのルートしかなくて、非同期処理はあって、ちょいインタラクティブなUIになってる程度のサイズ。

なので正直いってそこまで大きくないので、どんなものを使ってもそれなりにきれいなコードは書けると思ってた。

なのでここは好奇心ドリブンで、最近イチオシになりつつあるSvelteを採用することにした。

ビフォーアフター

技術スタック

いままで。

{
  "mobx": "^5.5.2",
  "mobx-react": "^5.3.6",
  "react": "^16.6.0",
  "react-dom": "^16.6.0",
  "styled-components": "^4.0.2"
}

そしてこれから。

{
  "svelte": "^3.23.0"
}

Svelteは全部入りなので、依存が綺麗サッパリなくなった。

ビルドもwebpackからrollupにしたけど、まあもちろん困ることはなにもない。

Reactが嫌になったわけではなくて、Svelte試したいな〜というだけ。

ファイルサイズ

以下はすべて、minifyしてるけど、gzipしてない、ブラウザでのパースサイズ。

いままで。

index.html: 954B
main.js: 248KB

250KBくらい。(たしかgzipされると60KBくらいだったはず)

`styled-components`でCSS in JSしてるので、CSSファイルは存在しなかった。

これから。

index.html: 717B
global.css: 530B
bundle.css: 4.6KB
bundle.js: 34.3KB

なんと40KBになった。(gzipしてないのに)

これがSvelteのコンパイラの力!
正直ここまで小さくなるのか〜とちょっと感動した。

ちなみにいままでのファイルサイズで支配的だったものたちはこんな感じ。

  • `react-dom`: 110KBくらい
  • `mobx`: 45KBくらい
  • `styled-components`: 16KBくらい
  • 本体コード: 35KBくらい

たとえばPreactにして、CSS-in-JSをやめても、ファイルサイズではSvelteに遠く及ばない・・。
もちろん理論上、いつかランタイムに抱えたほうがサイズが小さくなるラインは訪れるけど、そんな巨SPAはそもそも採用しないので。

ちなみに、gzipしたSvelte版のJSのサイズは10KBくらい。ちっさ。

Svelteの書き味について

ファイルサイズが小さい = ランタイムが小さいは正義。

なので、あとはその元となるコード、その書き味がいったいどんなものか。

いくつか書き残しておくべきポイントをメモっておく。

https://github.com/leader22/mmss-client

そういえば、実際のコードが見れるリポジトリはこちらです。

コンポーネント.svelte

書き味としてはやはり`.vue`を思い出す・・。
`markup` / `script` / `style`を1つのファイルに書く必要があって、開発UXはこれに馴染めるかに引っ張られるなーと。

状態やロジックを`.svelte`ではなくただの`.js`に逃がすこともできるけど、100%ではない。

というか、Svelteの良さを活かすには、むしろ積極的に`script`部分に書いていく必要がある気もしてる。

Write less code.

保守性って意味だと、このSvelteの哲学とのバランスが一番難しいところかな〜と個人的には思った。

ちなみに今回のリライトでは割と保守性を重視してて、`.svelte`ではあまり状態を宣言せず、`svelte/store`を使うようにしてるけど・・。
(Svelteの段階的な状態管理についてみたいな記事は書けるかもしれない)

Svelte言語(markup)

https://svelte.dev/docs#Template_syntax

最初見たときは、Handlebarsかと思った(ちがった)!

JSXの三項演算子より単項`if`はやっぱ見やすいし、`v-for`とか書くより`#each`のほうが直感的なので、個人的には好み。

ただ現状`slot`を使うときに、JSXでいう`Fragment`みたいなやつがなくて、そこだけが不満。

Svelte言語(script)

Svelte is a language.

なので、普通のJavaScriptの用途では見慣れない記法があったりする。
そしてそれがなかなか重要な概念になってたりする・・。

https://svelte.dev/docs#script

このセクションのすべてを理解することが、Svelteの最初の壁かなーと。
あとは、`$`という文字列の持つ意味を理解したときに、アハ体験ができるはず。

Reactivity

  • 宣言した変数が更新されたら
  • それに依存してる関数やDOMが
  • 必要ならば再実行される

このへんはMobX推しとしても元から求めていたところ。

なのでMobXとの書き味の比較をしておくと一番の差異は、変数への「代入」がリアクティブになるところ。

つまり、

let numbers = [1, 2, 3];

const addNumber = (n) => {
  // NG
  // numbers.push(n);

  // OK
  numbers = [...numbers, n];
};

というように配列のアップデートは、イミュータブルに新しい配列を代入しないといけない。
逆に、オブジェクトへのプロパティの追加は自動的にトラッキングしてくれる。

なので`Map`や`Set`はそのまま使えないので、必要なら独自の`store`にラップすることになるはず。
ただ独自の`store`でラップすると値の参照にひと手間かかるようになるので、そこがな〜って感じ。

このあたりは、MobXのほうがよくできてたなと思う。

storeのテスト

ロジックを`script`部に書きたくない場合、ビルトインの`svelte/store`を使うのが鉄板。

で、そうしてしまえばただの`.js`なので、テストもいつもどおりにできる。

// store.js
import { writable } from "svelte/store";

const value = writable(1);
const update = (v) => value.set(v);

return { value, update };

こうなってしまえば、

// store.test.js
import { value, update } from "./store.js";
import { get } from "svelte/store";

test("should get default value", () => {
  expect(get(value)).toBe(1);
});

test("should update value", () => {
  update(3);
  expect(get(value)).toBe(3);
});

毎回`get()`するのだけが面倒くさいけど、`import/export`だけ何かしらで変換すればテストできる。

`.svelte`側にしっかり書いちゃった場合は、もうレンダリングするしかないので、こういうのでやるとか?

https://testing-library.com/docs/svelte-testing-library/intro

TypeScript

いきなりあれこれ試すのは悪手だと思ってるので、今回は採用しなかった。

調べてみた感じ、

まぁESLintのプラグインを入れるだけでも、変数名のチェックくらいはできるので、もうちょっと待っていいかなーという気持ち。

というかSvelteは言語というならば、既存の仕組みを利用しようとする限り、どうしても相容れないところは残りそう・・・。

まとめ = おすすめできるか

つまりは、Reactやら既存のFWを差し置いて、採用するか?という問い。

そんなものはもちろん要件次第である!

ただ個人的な手応えからすると、SPAを作りたいなら普通に採用していいかなーと思った。
回線速度が遅かったり、端末が低スペックだとかって話は割とよくあるので、ランタイムの小ささはそれだけでえらい。

あとは規模が小さいものをシュッと作りたいって場合も、Reactよりいいなと思った。
というか、規模が小さいのにReactを採用するなという気持ちが強まったっていうほうが正しいかもしれないw

ただscopedにしたいからってだけでCSS-in−JSしちゃうケースも多いと思うし、そこもSvelteのデフォルトでカバーされるならなおさら。

この学習コストで、(コンパイラによってチューニング済の)軽量なアプリがさくっと書けるのは、コスパ良すぎると思う。

懸念としては、`v3`になってまだ1年くらいなため、コミュニティがまだ未成熟なところ。
ちょっとしたUIもすぐ`npm`で探してしまうしまうとか、なんかあったときの腕力に自身がない場合は、あんまりおすすめできないかもしれない・・。
ここ最近ずっとIssueとPRをwatchしてるけど、ちょいちょいコーナーケースを踏んでる人はいるっぽいし。

というのが現時点でのお気持ちでした。

ファイルサイズのインパクトがでか過ぎて、Web開発で素のReactをもう使う気になれず、使うにしてもPreactしかないな〜とか思ってる自分がいる。