Streaming、snapshots、その他 SvelteKit 1.0 以降の新機能
SvelteKit 最新バージョンのエキサイティングな改善
翻訳 : Svelte 日本コミュニティ
原文 : https://svelte.dev/blog/streaming-snapshots-sveltekit日本語版は原文をよりよく理解するための参考となることを目的としています。
正確な内容については svelte.dev の原文を参照してください。
日本語訳に誤解を招く内容がある場合は下記のいずれかからお知らせください。
Svelte チームは SvelteKit 1.0 がリリースされたあとも懸命に取り組んできました。ローンチ後にリリースされたいくつかのメジャーな新機能についてご紹介します: streaming non-essential data、snapshots、そして route-level config です。
Stream non-essential data in load functionspermalink
SvelteKit は load 関数を使用してルート(route)のデータを取得します。ページ間で移動する場合、まず最初にデータを取得し、それからその結果を用いてページをレンダリングします。このため、もしデータの一部が他のデータよりも取得に時間がかかる場合、特にそのデータが重要ではない場合、問題になるでしょう – すべてのデータが揃わないと、ユーザーは新しいページのどの部分も見ることができないからです。
これを回避する方法もありました。具体的には、コンポーネント自体で遅いデータを取得することができるので、まず load
で取得したデータでレンダリングし、そのあとで遅いデータの取得を開始します。しかしこれは理想的ではありませんでした: クライアントがレンダリングするまでデータの取得を開始しないため、データはさらに遅延しますし、SvelteKit の load
の規約を破ることにもなります。
今回、SvelteKit 1.8 において、新たなソリューションを提供します: server load 関数からネストした promise を返すと、SvelteKit はそれが解決する前にページのレンダリングを開始します。ネストした promise は完了し次第、その結果がページにストリーミングされます。
例えば、次の load
関数について考えてみましょう:
ts
export constCannot find name 'PageServerLoad'.2304Cannot find name 'PageServerLoad'.load := () => { PageServerLoad return {Cannot find name 'fetchPost'.2304Cannot find name 'fetchPost'.post :(), fetchPost streamed : {Cannot find name 'fetchComments'.2304Cannot find name 'fetchComments'.comments :() fetchComments }};};
SvelteKit は自動的にこの fetchPost
の呼び出しを await してからページのレンダリングを開始します。なぜならそれがトップレベルだからです。しかし、ネストした fetchComments
の呼び出しが完了するのは待ちません – ページはレンダリングされ、data.streamed.comments
はリクエストが完了すると解決する promise となります。+page.svelte
で、Svelte の await block を使用してロード中の状態を表示することもできます:
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<article>
{data.post}
</article>
{#await data.streamed.comments}
Loading...
{:then value}
<ol>
{#each value as comment}
<li>{comment}</li>
{/each}
</ol>
{/await}
この streamed
プロパティに特別なものはありません – この動作をトリガーするのに必要なのは、戻り値のオブジェクトのトップレベル以外の場所にある promise だけです。
SvelteKit は、アプリのホスティングプラットフォームがストリーミングをサポートしている場合にのみ、レスポンスをストリーミングすることができます。一般的には、AWS Lambda を中心に構築されたプラットフォーム (例えば serverless functions) はストリーミングをサポートしていませんが、従来の Node.js サーバーや edge ベースのランタイムはサポートしています。プロバイダーのドキュメントで確認してみてください。
プラットフォームがストリーミングをサポートしていない場合でも、データは利用可能です。その場合レスポンスはバッファリングされ、すべてのデータが取得されるまでページのレンダリングは開始されません。
How does it work?permalink
データを server load
関数からブラウザに送信するには、データを シリアライズ する必要があります。SvelteKit は devalue というライブラリを使用しています。これは JSON.stringify
のようなものですが、より優れています — JSON では扱うことができない値 (例えば date や正規表現など) も扱うことができ、自身をその中に含むような (またはそのデータの中に何度も現れるような) オブジェクトもそのアイデンティティを破壊することなくシリアライズすることができ、そして XSS 脆弱性 から保護することができます。
ページをサーバーでレンダリングする際、promise を、deferred(遅延) を作成する function call としてシリアライズするよう devalue に指示します。これは SvelteKit がページに追加するコードを簡略化したものです。
ts
constdeferreds = newMap ();Property 'defer' does not exist on type 'Window & typeof globalThis'.Parameter 'id' implicitly has an 'any' type.2339window .= ( defer ) => { id
7006Property 'defer' does not exist on type 'Window & typeof globalThis'.Parameter 'id' implicitly has an 'any' type.return newPromise ((fulfil ,reject ) => {deferreds .set (id , {fulfil ,reject });});};Property 'resolve' does not exist on type 'Window & typeof globalThis'.Parameter 'id' implicitly has an 'any' type.Parameter 'data' implicitly has an 'any' type.Parameter 'error' implicitly has an 'any' type.2339window .= ( resolve , id , data ) => { error
7006
7006
7006Property 'resolve' does not exist on type 'Window & typeof globalThis'.Parameter 'id' implicitly has an 'any' type.Parameter 'data' implicitly has an 'any' type.Parameter 'error' implicitly has an 'any' type.constdeferred =deferreds .get (id );deferreds .delete (id );if (error ) {deferred .reject (error );} else {deferred .fulfil (data );}};// devalue converts your data into a JavaScript expressionconstdata = {post : {title : 'My cool blog post',content : '...'},streamed : {Property 'defer' does not exist on type 'Window & typeof globalThis'.2339Property 'defer' does not exist on type 'Window & typeof globalThis'.comments :window .(1) defer }};
このコードは、サーバーレンダリングされた一部の HTML と一緒にブラウザにすぐに送信されますが、コネクションは開いたままになっています。その後、promise が解決すると、SvelteKit は追加の HTML チャンクをブラウザにプッシュします:
<script>
window.resolve(1, {
data: [{ comment: 'First!' }]
});
</script>
クライアントサイドナビゲーションの場合は、少し異なるメカニズムを使用します。サーバーからのデータは newline delimited JSON としてシリアライズされ、SvelteKit は devalue.parse
で似たような遅延メカニズムを使用してその値を再構築します:
ts
// this is generated immediately — note the ["Promise",1]...[{"post":1,"streamed":4},{"title":2,"content":3},"My cool blog post","...",{"comments":5},["Promise",6],1]// ...then this chunk is sent to the browser once the promise resolves[{"id":1,"data":2},1,[3],{"comment":4},"First!"]
このように promise はネイティブにサポートされているため、load
から返されるデータのどこにでも置くことができます (トップレベルは除く。トップレベルは自動的に await するからです)。そして devalue がサポートするあらゆるタイプのデータを解決することができます — もちろんさらに多くの promise も!
注意事項: この機能には JavaScript が必要です。そのため、重要でないデータのみ、ストリーミングすることを推奨します。すべてのユーザーがエクスペリエンスのコアを利用できるようにするためです。
この機能の詳細については、ドキュメントをご覧ください。デモは sveltekit-on-the-edge.vercel.app (ロケーションデータをわざと遅延させ、ストリーミングしています) でご覧頂けますし、ご自身で Vercel にデプロイすることもできます。Vercel では Edge Functions と Serverless Functions のどちらもストリーミングをサポートしています。
私たちは、Qwik、Remix、Solid、Marko、React などの、このアイデアの先行実装からインスピレーションを受けました。深く感謝します。
Snapshotspermalink
以前までの SvelteKit アプリでは、フォームに入力を開始したあとで移動して、そのあと戻ってくるとフォームの state は復元されず、デフォルトの値でフォームが再作成されていました。場合によっては、ユーザーはフラストレーションが溜まるかもしれません。SvelteKit 1.5 以降は、これに対応するための方法が組み込まれています: それが snapshots です。
現在、+page.svelte
や +layout.svelte
で snapshot
オブジェクトをエクスポートすることができます。このオブジェクトには、capture
と restore
という2つのメソッドがあります。capture
関数は、ユーザーがページを離れたときにどの state を保存するかを定義します。SvelteKit はその state を現在の履歴エントリに関連付けます。ユーザがページに戻った場合は、以前に設定した state を引数に取って restore
関数が呼び出されます。
こちらは textarea の値を capture し、restore する方法の例です:
<script lang="ts">
import type { Snapshot } from './$types';
let comment = '';
export const snapshot: Snapshot = {
capture: () => comment,
restore: (value) => (comment = value)
};
</script>
<form method="POST">
<label for="comment">Comment</label>
<textarea id="comment" bind:value={comment} />
<button>Post comment</button>
</form>
フォームの input の値やスクロールポジションなどは一般的な例で、JSON-serializable なデータならなんでも snapshot に保存することができます。snapshot のデータは sessionStorage に保存されるので、ページがリロードされたときや、ユーザーがまったく別のサイトに移動したときにも保持されます。sessionStorage
に保存されるため、サーバーサイドレンダリング中にアクセスすることはできません。
詳細は、ドキュメントをご覧ください。
Route-level deployment configurationpermalink
SvelteKit はプラットフォームごとに固有の adapter を使用してプロダクションへのデプロイ用にアプリのコードを変換しています。これまでは、デプロイメントの設定をアプリ全体レベルで行わなければなりませんでした。例えば、アプリを edge function としてデプロイするか、serverless function としてデプロイするか、どちらか一方は可能でしたが、両方同時に行うことはできませんでした。これでは、アプリの一部だけを edge にするというメリットを得ることができません – もし Node API を必要とするルート(route)がある場合、アプリ全体を edge にデプロイすることができないのです。リージョンの選択やメモリ割り当てなど、デプロイ設定の他の側面についても同様です: アプリ全体、すべてのルート(route)に適用される1つの値を選択しなければならなかったのです。
そしてこの度、config
オブジェクトを +server.js
、+page(.server).js
、+layout(.server).js
ファイルでエクスポートすることができるようになり、これらのルート(route)をどうやってデプロイするかコントロールできるようになりました。+layout.js
でこれを行うと、そのすべての子ページに設定が適用されます。config
の型は、デプロイ先の環境に依存するため、各 adapter ごとにユニークです。
ts
import type {Cannot find module 'some-adapter' or its corresponding type declarations.2307Cannot find module 'some-adapter' or its corresponding type declarations.Config } from'some-adapter' ;export constconfig :Config = {runtime : 'edge'};
Config はトップレベルでマージされるため、レイアウトで設定された値をツリーのさらに下のページで上書きすることができます。詳細はドキュメントをご覧ください。
Vercel にデプロイする場合、最新バージョンの SvelteKit と adapter をインストールすることでこの機能のメリットを享受することができます。ルートレベル(route-level)の config をサポートする adapter は SvelteKit 1.5 以降が必要であるため、adapter のバージョンを大幅にアップグレードする必要があるかもしれません。
npm i @sveltejs/kit@latest
npm i @sveltejs/adapter-auto@latest # or @sveltejs/adapter-vercel@latest
今現在は、Vercel adapter のみがルート固有(route-specific)の config を実装していますが、他のプラットフォーム向けでもこれを実装するためのビルディングブロックがあります。もしあなたが adapter の作者なら、この PR の変更点を参照し、要求事項を確認してください。
Incremental static regeneration on Vercelpermalink
ルートレベル(Route-level)の config では、もう1つ要望の多かった機能も使えるようになりました – Vercel にデプロイされる SvelteKit アプリで、incremental static regeneration (ISR) が使用できるようになりました。ISR は、プリレンダリングされたコンテンツにおけるコストとパフォーマンスの優位性と、動的なレンダリングコンテンツの柔軟性の両方を提供します。
ISR をルート(route)に追加するには、config
オブジェクトに isr
プロパティを追加します:
ts
export constconfig = {isr : {// 必須のオプションについては Vercel adapter のドキュメントをご覧ください}};
And much more...permalink
- OPTIONS method が
+server.js
ファイルでサポートされました。 - 別のファイルに属するものをエクスポートしたときや、+layout.svelte にslot を置くのを忘れたときのエラーメッセージが改善されました。
- app.html でパブリックな環境変数にアクセスできるようになりました
- レスポンスを作成する新たな text ヘルパーが追加されました
- そしてたくさんのバグフィックス – リリースノートの全文についてはchangelogをご覧ください。
SvelteKit にコントリビュートしてくれた皆様、SvelteKit をプロジェクトで使ってくださっている皆様、ありがとうございます。以前にもお伝えしましたが、Svelte はコミュニティプロジェクトであり、皆様のフィードバックやコントリビューションがなくては成り立たないものです。