続・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の書き味について
ファイルサイズが小さい = ランタイムが小さいは正義。
なので、あとはその元となるコード、その書き味がいったいどんなものか。
いくつか書き残しておくべきポイントをメモっておく。
そういえば、実際のコードが見れるリポジトリはこちらです。
コンポーネント.svelte
書き味としてはやはり`.vue`を思い出す・・。
`markup` / `script` / `style`を1つのファイルに書く必要があって、開発UXはこれに馴染めるかに引っ張られるなーと。
状態やロジックを`.svelte`ではなくただの`.js`に逃がすこともできるけど、100%ではない。
というか、Svelteの良さを活かすには、むしろ積極的に`script`部分に書いていく必要がある気もしてる。
Write less code.
保守性って意味だと、このSvelteの哲学とのバランスが一番難しいところかな〜と個人的には思った。
ちなみに今回のリライトでは割と保守性を重視してて、`.svelte`ではあまり状態を宣言せず、`svelte/store`を使うようにしてるけど・・。
(Svelteの段階的な状態管理についてみたいな記事は書けるかもしれない)
Svelte言語(markup)
最初見たときは、Handlebarsかと思った(ちがった)!
JSXの三項演算子より単項`if`はやっぱ見やすいし、`v-for`とか書くより`#each`のほうが直感的なので、個人的には好み。
ただ現状`slot`を使うときに、JSXでいう`Fragment`みたいなやつがなくて、そこだけが不満。
Svelte言語(script)
Svelte is a language.
なので、普通のJavaScriptの用途では見慣れない記法があったりする。
そしてそれがなかなか重要な概念になってたりする・・。
このセクションのすべてを理解することが、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
いきなりあれこれ試すのは悪手だと思ってるので、今回は採用しなかった。
調べてみた感じ、
- 使えるのは使える
- が、一部のシンタックスに制限がでるなど不便がある
- https://github.com/sveltejs/svelte-preprocess/issues/144
- LSPもまだWIP
まぁESLintのプラグインを入れるだけでも、変数名のチェックくらいはできるので、もうちょっと待っていいかなーという気持ち。
というかSvelteは言語というならば、既存の仕組みを利用しようとする限り、どうしても相容れないところは残りそう・・・。
まとめ = おすすめできるか
つまりは、Reactやら既存のFWを差し置いて、採用するか?という問い。
そんなものはもちろん要件次第である!
ただ個人的な手応えからすると、SPAを作りたいなら普通に採用していいかなーと思った。
回線速度が遅かったり、端末が低スペックだとかって話は割とよくあるので、ランタイムの小ささはそれだけでえらい。
あとは規模が小さいものをシュッと作りたいって場合も、Reactよりいいなと思った。
というか、規模が小さいのにReactを採用するなという気持ちが強まったっていうほうが正しいかもしれないw
ただscopedにしたいからってだけでCSS-in−JSしちゃうケースも多いと思うし、そこもSvelteのデフォルトでカバーされるならなおさら。
この学習コストで、(コンパイラによってチューニング済の)軽量なアプリがさくっと書けるのは、コスパ良すぎると思う。
懸念としては、`v3`になってまだ1年くらいなため、コミュニティがまだ未成熟なところ。
ちょっとしたUIもすぐ`npm`で探してしまうしまうとか、なんかあったときの腕力に自身がない場合は、あんまりおすすめできないかもしれない・・。
ここ最近ずっとIssueとPRをwatchしてるけど、ちょいちょいコーナーケースを踏んでる人はいるっぽいし。
というのが現時点でのお気持ちでした。
ファイルサイズのインパクトがでか過ぎて、Web開発で素のReactをもう使う気になれず、使うにしてもPreactしかないな〜とか思ってる自分がいる。