🧊

SvelteKitの特徴をざっくり理解する

GitHub - sveltejs/kit: A monorepo for SvelteKit and friends

SvelteKitは、Svelteでハイパフォーマンスなアプリを作ることができるフレームワーク

`v1.0`を目指しているところで、いま時点での進捗は37%というところらしい。

つまり、世界的に知見もたまってないし、めちゃめちゃ頻繁にアップデートされるし、この記事で書いた内容もすぐに陳腐化する可能性があるということ・・・。

という感じのものをここ数日ずっと触ってて、それでもまあ色々わかったこともあるので、その整理を兼ねてメモっておこうかと。

ドキュメントはこちら。

Docs • SvelteKit

`/routes`

  • Nextとかと一緒で、ファイルベースのルーティング
  • `.svelte`を置くと、それがクライアントで表示できるページになる
  • `.(js|ts)`を置くと、それはサーバーで動くエンドポイントになる
    • クライアントにDLされないコード
    • なので、DBアクセスもできるし環境変数とかシークレットキーとか使える
    • Nodeのサーバーを手軽に書けるというだけ
  • `_`からはじまるファイルは、ルーティングに含まれない

共通レイアウト・エラー

Nextのそれと基本的には同じイメージで使えるけど、`_`とか共通テンプレが持てるとか、利用ハードルが下がってる感じ。

`load()`

ページを構成する`.svelte`に定義できる関数で、そのコンポーネントが初期化される前に呼ばれる。

つまり、

  • 初回アクセス時にSSRが走る場合ならサーバーで
  • その後の回遊時には、クライアントで
  • 後のコードをブロッキングしながら

そのページの起点となる処理が書ける。
(Nextのありし日の`getInitialProps()`相当・・?)

<script context="module">
  export const load = async ({ page, fetch, session, context }) => {
    page; // その時のパスやクエリパラメータなど
    fetch; // クライアントでもサーバーでも使えるfetch(つまりnode-fetchいらず)
    session; // いわゆるセッション情報を入れておける便利スペース
    context; // 上層から下層のコンポーネントに引き渡すことのできる状態
  };
</script>

(`context=module`は、頻繁に登場するSvelte特有のシンタックスで、FWメタな処理をするところって感じ)

これを具体的に何に使うのかというと、

  • クライアントで`onMount`で`fetch`してたような、初期データの取得処理
    • `return { props: data }`する
    • ただ、初回アクセスではその待ち時間はブランクになってしまう
    • そしてローディング表示も出せない
  • セッションの状態によってリダイレクトする
    • `return { refirect: href, status: 300 }`する
  • エラーにする
    • `return { error: err, status: 400 }`する

HTTPなら直接どこぞのAPIを呼びに行ってもいいし、自分で定義したエンドポイントを叩いてもよい。

この関数に処理を寄せることで、

  • 初回表示のSSR時にサーバーで実行するコードと
  • その後の回遊でクライアントが実行するコードが

同じ場所に書けるよ!という機能らしい。

サーバーでもクライアントでも動くというわけで、基本的にはクライアントから叩けてしまって問題ないデータにアクセスする処理を書くところ。

サーバーでもクライアントでも動くIsomorphicなコードを書くところ。
どうしてもって場合は、従来どおり`onMount`にくるんだり、後述のフラグで処理を分けたりする。

ブロッキングするの、初回アクセスのSSR時はそもそも仕方ないとして、クライアントでの回遊時は後述の`navigating`でローディング表示も出せるので、まあ理解できるかなと。

ただSSRしない場合は、ブロッキングしてまでやるべき初期化処理なんか存在しないし、手動でローディング表示も出したいので、正直使いみちがない気がしてる。

特別なモジュール

  • `$app/env`: 実行環境についての情報
  • `$app/navigation`: プリフェッチとページ遷移
  • `$app/paths`: パス情報(何に使うのか)
  • `$app/stores`: コンテキストにある各種ストアへのショートカット
    • `navigating`: ページ遷移中を表す`store/readable`
    • `page`: `load()`で見れたようなパスとかクエリ
  • `$lib`: `src/lib`へのショートカット
    • なぜかこれだけ明示的に設定してくれてて謎
    • 実体はViteやTSのパスエイリアス指定なので、別に自分で増やせるし消せる・・
  • `$service-worker`: SWで使える便利なやつら

src/hooks.js

というファイルを置くと、それがSSR時に実行されるコードで使えるようになるらしい。

export const getContext = async () => {};
export const getSession = async () => {};
export const handle = async () => {};

SSRする気がないのでよく見てない)

Adapter

SvelteKitでコードを書くと、

  • `routes/*.svelte`によるページ
  • `routes/*.(js|ts)`によるエンドポイント

この2つができる。

で、それらをどんな環境にデプロイしたいかを選べるのがこのAdapterという仕組み。

  • `adapter-static`
    • 全ページを事前レンダリングして静的に配置する用
    • エンドポイントは使われない
  • `adapter-node`
    • SSRするNodeのサーバーにする用
  • `adapter-XXX`
    • XXXは、NetlifyだったりVercelだったりCloudflare Workersだったり
    • Nodeのサーバー相当の処理をするところを、それぞれのFaaSにできる

1コードで、複数のデプロイ先を渡り歩けるよって感じ。

どこかの発表で、サーバーレスファースト!って言ってたのはこういう意味やったのね。

基本的にこのいずれかを指定してから、ビルドコマンドを叩く。

ssr / hydrate / router / prerender

個人的に一番わかりにくいなーと思ったのがこれらの設定。

まずSvelteKitには、アプリ単位のグローバルな設定ファイルである`svelte.config.cjs`というのがある。

https://kit.svelte.dev/docs#configuration

さっきのアダプターの指定をしたり、Viteの設定をしたり、ディレクトリのエイリアスをしたり。
それらに加えて、SSRやハイドレーションの挙動についても指定ができるようになってる。

で、これらの指定はアプリ単位でもできるし、各ページ`.svelte`の`context=module`の中でフラグを返すことでも指定できるようになってるのである。

<script context="module">
  export const ssr = false;
</script>

こうすると、このページではこの設定がアプリ設定よりも優先して使われる。

で、このあたりの挙動について指定できるオプションが4つある。

  • ssr
  • router
  • hydrate
  • prerender

https://kit.svelte.dev/docs#ssr-and-javascript

まず前提として、SvelteKitはSSRで動かす前提のデザインになってる。
(とはいっても全ページではなく、初回アクセスのページだけで、その後はクライアントでSPAライクに動くし、アダプターによってはその限りではないけど、基本的なスタンスという意味で)

ssr

その名の通り、サーバーでレンダリングしないようにするオプション。

つまり、そのページはクライアントですべて描画するということになるので、いわゆるSPAにしたい場合に`false`を指定することになる。

どうやら`adapter-static`を使ったビルド時にもSSRされなくなるので、いわゆる事前レンダリングをしたい場合には、`false`にしてはいけないっぽい。(事前レンダリングのそれとSSRは違うのでは?って気がするけど・・)

(このへんがデザインなのかバグなのか想定外の用途なのかは、いまいちわかってない)

ちなみに、`ssr: false`にしていても、`svelte-kit dev`で開発してる間はSSRされるし、`svelte-kit build`して生成するときもSSRされるので、おもむろに`localStorage`とか書いていいというわけではない。

hydrate

初回アクセス時にSSRされた結果をクライアントで解釈するステップのためのオプション。

つまりこれが`false`だと、クライアント側でJSを実行しないので、`load()`はおろか通常の`script`の処理も何も実行されない。クリックなどのイベントハンドラも何も。

しかし初回アクセスではなく、回遊した結果のアクセスであれば、ちゃんと実行される。

(これがデザインなのかバグなのか想定外の用途なのかは、いまいちわかってない)

router

端的に、JSによるページ遷移を制御するオプション。

Nextだと`next/link`でつなげたところだけがつながるけど、SvelteKitはトップページから`a`要素をたどって自動的につなげてくれるので、それを制御するためのもの。

`false`を指定すると、ページ間のつながりを断ち切れて、リンクを踏むとHTMLがリクエストされるようになる。

あとは、特別なモジュールである`$app/navigation`の`goto()`なども使えなくなる。

さっきの`hydrate`とあわせて`false`にすると、クライアントに落ちてくるJSがなくなる!「0kb JS pages!」っていう触れ込み。

prerender

これだけデフォルトが`false`。
というのも、SvelteKitはSSRをデフォルトで想定してるから。

なので基本はSSRでいいけど、このページは事前レンダリングできる!という場合に、例外的にマーキングするためのもの。

ページ単位では`true`を指定するし、アプリ単位ではもう少し細かい指定ができるようになってる。

で、全ページ事前レンダリングしたいという場合に、`adapter-static`を使うというわけらしい。

まとめ

とまあそれぞれ言いたいことはわかるが、それぞれの相関とか、おそらくこういうパターンで使うだろうとか、そういうガイドが欲しい。

まだこれから先、フィードバックやらで洗練されていくとは思うけど、現時点では手探りでやるしかないので非常につらいものがある。

特にいわゆるSPAを作るためには、フォールバックのページ指定だったりいろいろ仕組みが足りないらしく、まだまだどうなることやら・・って感じ。

True SPA mode · Issue #754 · sveltejs/kit · GitHub

手元のコードでは`adapter-static`を使ってるけど、

  • `ssr: false`にしなかった場合、初回ロード時には事前レンダリングSSR時にキャッシュされたコードがあるからか、ハイドレーションが動かない
  • `hydrate: true`にしても、`ssr: false`にしてないと、初回アクセス時にルーターが動かない
  • etc..

などなど絶賛ハマってて、どういう理由でそうなるのか、設定を誤解してるのかただのバグなのかも判断できないのが現状。(コードを読む限りはデザインな気もするけど、なんしか釈然としない・・)

とりあえずここまでの経験としては、

  • 部分的な事前レンダリング + SPAライクな挙動
  • 1HTMLファイルによる完全なSPA

少なくともこの2つは現状だと実現できないという認識。

というわけで

1.0 Milestone · GitHub

もう少し気長に待ちましょう・・。