Closed61

Svelte 公式チュートリアル Part 4: Advanced SvelteKit をやっていく

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Hooks / handle

https://learn.svelte.dev/tutorial/handle

src/hooks.server.js
export async function handle({ event, resolve }) {
	if (event.url.pathname === '/ping') {
		return new Response('pong');
	}
	
	return await resolve(event, {
		transformPageChunk: ({ html }) => html.replace(
			'<body',
			'<body style="color: hotpink"'
		)
	});
}

フックとは SvelteKit のデフォルト動作を上書きする機能のこと。

最も基本的なフックは handle であり、src/hooks.server.js を作成して handle() 関数をエクスポートすることで作成できる。

handle() 関数の第 1 引数としてオプションを受け取り、オプションには event や resolve が含まれる。

event にはアクセスされたページの URL などが含まれる。

resolve はルートの +page.server.js や +page.svelte の実行結果の Promise を返す関数であり、第 2 引数のオプションで transformPageChunk を指定することで HTML の書き換えなどができる。

handle() 関数は Response オブジェクトを返せば良いので必ずしも resolve() を呼び出す必要はない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Hooks / The RequestEvent object

https://learn.svelte.dev/tutorial/event

src/hooks.server.js
export async function handle({ event, resolve }) {
	event.locals.answer = 42;
	return await resolve(event);
}
src/routes/+page.server.js
export function load(event) {
	return {
		message: `the answer is ${event.locals.answer}`
	};
}

handle() 関数に渡される event オブジェクトは API routes やフォームアクション、ページの load() 関数に渡されるものと同じ。

RequestEvent のドキュメントにあるように様々なプロパティが含まれている、特に fetch() 関数が含まれているのが興味深い。

locals はアプリで使用するデータを格納するのに適している。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Hooks / handleFetch

https://learn.svelte.dev/tutorial/handlefetch

src/hooks.server.js
export async function handleFetch({ event, request, fetch }) {
	const url = new URL(request.url);
	if (url.pathname === '/a') {
		return await fetch('/b');
	}

	return await fetch(request);
}

event オブジェクトには fetch() 関数が含まれている。

この fetch() 関数は拡張されており、cookie や authorization ヘッダーの引き継ぎや相対リクエストが可能。

API route などで fetch() 関数を呼び出した場合は HTTP リクエストは行われず、直接 GET() 関数などが呼ばれる。

handleFetch() フックは fetch() 関数の動作をカスタマイズするために使用される。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Hooks / handleError

https://learn.svelte.dev/tutorial/handleerror

src/hooks.server.js
export function handleError({ event, error }) {
	console.error(error.stack);

	return {
		message: 'everything is fine',
		code: 'JEREMYBEARIMY'
	};
}
src/routes/+error.svelte
<script>
	import { page } from '$app/stores';
</script>

<h1>{$page.status}</h1>
<p>{$page.error.message}</p>
<p>error code: {$page.error.code}</p>

handleError() フックは予期しないエラーが発生した時の動作をカスタマイズできる。

デフォルトの動作はコンソール出力だが例えば Slack などに通知することも可能。

handleError() 関数の戻り値は +error.svelte ページで page ストアを使って参照できる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Page options / Basic

https://learn.svelte.dev/tutorial/page-options

+page.js や + page.server.js、+layout.js や +layout.server.js では ssr や csr などのページオプションをエクスポートすることで設定できる。

レイアウトの設定はすべての子孫に反映され、子孫のページやレイアウトは設定を上書きできる。

ルートレイアウトでデフォルトの設定をしておき、個々のページやレイアウトで設定を変更するのがメジャーな使い方になる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Page options / ssr

https://learn.svelte.dev/tutorial/ssr

src/routes/+page.server.js
export const ssr = false;

SvelteKit では SSR がデフォルトになっている。

コンポーネントによってはサーバー側でレンダリングできないものもある。

一例として window オブジェクトに即座にアクセスするコンポーネントが考えられる。

SSR を CSR に切り替えるには +page.server.js で export const ssr = false; と書く。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Page options / prerender

https://learn.svelte.dev/tutorial/prerender

src/routes/+page.server.js
export const prerender = true;

事前レンダリングとはリクエストの度ではなくビルド時に 1 回だけレンダリングすることを意味する。

事前レンダリングはサーバー負荷や表示速度の面でメリットがある。

一方、ビルド時間が長くなることや更新に再ビルドと再デプロイが必要になることがデメリット。

事前レンダリングはすべてのページで有効化できない。

すべてのリクエストに対して同じページ内容を返し、かつフォームアクションを使っていないページだけが事前レンダリングできる。

ルートパラメーターを使っているページでも事前レンダリングを有効化できる。

その場合は prerender.entries の設定を行う必要がある。

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

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Page options / trailingSlash

https://learn.svelte.dev/tutorial/trailingslash

src/routes/always/+page.server.js
export const trailingSlash = 'always';
src/routes/ignore/+page.server.js
export const trailingSlash = 'ignore';

trailing slash とは URL 末尾のスラッシュ(/)のこと。

SvelteKit では trailing slash はデフォルトで削除されるようになっている。

逆に trailing slash を付加したい場合は export const trailingSlash = 'always'; を実行する。

trailing slash の削除を無効化したい場合は export const trailingSlash = 'ignore'; を実行する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

https://learn.svelte.dev/tutorial/preload

src/routes/+layout.svelte(抜粋)
<nav>
	<a href="/">home</a>
	<a href="/slow-a" data-sveltekit-preload-data>slow-a</a>
	<a href="/slow-b">slow-b</a>
</nav>

a タグに data-sveltekit-preload-data 属性を設けることでホバー/タップ時に事前読み込みができるので表示が速くなる。

この属性は個々の a タグに付与することもできるが body などに付与することで全てのリンクに設定を反映できる。

data-sveltekit-preload-data は指定がなければ hover になるが、tap や off も指定できる。

data-sveltekit-preload-data の代わりに data-sveltekit-preload-code を使うことで JavaScript コードのみを読み込むこともできる。

$app/navigation から preloadCode() や preloadData() 関数をインポートして script タグ内で事前読み込みを行うこともできる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

https://learn.svelte.dev/tutorial/reload

src/routes/+layout.svelte
<nav data-sveltekit-reload>
	<a href="/">home</a>
	<a href="/about">about</a>
</nav>

SvelteKit ではページ間のナビゲーションはページの再読み込みなしで行われる。

a タグやそのコンテナに data-sveltekit-reload 属性を付与することでページの再読み込みを行うように設定できる。

この他にもいくつかのリンクオプションがある。

  • data-sveltekit-replacestate:ページ履歴を追加するのではなく書き換える。
  • data-sveltekit-keepfocus:フォーカスを維持したままにする、検索ページなどで便利。
  • data-sveltekit-noscroll:ページ内リンクをクリックした時にスクロールしないようにする。

https://kit.svelte.dev/docs/link-options

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced routing / Optional parameters

https://learn.svelte.dev/tutorial/optional-params

src/routes/[[lang]]/+page.server.js
const greetings = {
	en: 'hello!',
	de: 'hallo!',
	fr: 'bonjour!'
};

export function load({ params }) {
	return {
		greeting: greetings[params.lang ?? 'en']
	};
}

ディレクトリ名を [param] ではなく [[param]] とすることでパスパラメーターを省略可能にできる。

省略可能にしたことで +page.svelte と [[param]]/+page.svelte が衝突してしまうので、その場合は +page.svelte の方を削除する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced routing / Rest parameters

https://learn.svelte.dev/tutorial/rest-params

ディレクトリ名を [path] の代わりに [...path] とすることで複数のパスセグメントにマッチできる。

[...path] ディレクトリと同じ階層に他のディレクトリが存在しても問題ない。

その場合は他のディレクトリへのマッチが優先され、いずれにもマッチしない場合に [...path] ディレクトリにマッチするようになる。

レストパラメーターは末尾にある必要はなく /items/[...path]/edit/items/[...path].json にすることも可能。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced routing / Param matchers

https://learn.svelte.dev/tutorial/param-matchers

src/params/hex.js
export function match(value) {
	return /^[0-9a-f]{6}$/.test(value);
}

パスパラメーターを正規表現などでチェックしたい場合には param matchers を利用できる。

param matchers を利用するには src/params に .js / .ts ファイルを作成し、match() 関数をエクスポートする。

どの param matchers を使用する場合はディレクトリ名を例えば [param=hex] のようにする。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced routing / Route groups

https://learn.svelte.dev/tutorial/route-groups

src/routes/(authed)/+layout.server.js
import { redirect } from '@sveltejs/kit';

export function load({ cookies, url }) {
	if (!cookies.get('logged_in')) {
		throw redirect(303, `/login?redirectTo=${url.pathname}`);
	}
}
src/routes/(authed)/+layout.svelte
<slot />

<form method="POST" action="/logout">
	<button>log out</button>
</form>

レイアウトを変更せずに共通のアプリケーションロジックを適用したい場合は route groups を利用できる。

典型的な例としてはユーザー認証が挙げられる。

route groups を利用するには (authed) のような丸カッコでくくった名前でディレクトリを作成し、共通のアプリケーションロジックを適用したいディレクトリを移動する。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced routing / Breaking out layouts

https://learn.svelte.dev/tutorial/breaking-out-of-layouts

レイアウトをリセットするには +page@b.svelte のようにファイル名に @ を入れる。

a/b/c/+page@b.svelte は c のレイアウトではなく b のレイアウトが使われる。

ルートレイアウトにリセットするには +page@.svelte のように書く。

ルートレイアウトはリセットすることができない。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced loading / Universal load functions

https://learn.svelte.dev/tutorial/universal-load-functions

src/routes/+layout.svelte
<script>
	import { page } from '$app/stores';
</script>

<nav
	class:has-color={!!$page.data.color}
	style:background={$page.data.color ?? 'var(--bg-2)'}
>
	<a href="/">home</a>
	<a href="/red">red</a>
	<a href="/green">green</a>
	<a href="/blue">blue</a>

	{#if $page.data.component}
		<svelte:component this={$page.data.component} />
	{/if}
</nav>

<slot />

<style>
	nav.has-color,
	nav.has-color a {
		color: white;
	}
</style>

+page.js や +layout.js は universal load functions と呼ばれ、サーバーとクライアントの両方で実行される。

server load functions ではシリアライズできなくてサーバーからクライアントに渡せないものを渡すのに便利。

universal load functions はページロード時はサーバーとクライアントの両方で実行される。

それ以降はクライアントのみで実行される。

https://kit.svelte.dev/docs/load#universal-vs-server

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced loading / Using both load functions

https://learn.svelte.dev/tutorial/using-both-load-functions

src/routes/+page.js
export async function load({ data }) {
	const module = data.cool
		? await import('./CoolComponent.svelte')
		: await import('./BoringComponent.svelte');

	return {
		component: module.default,
		message: data.message
	};
}

server load functions と universal load functions は併用できる。

server の方が最初に実行され、universal の方が後に実行される。

universal の方から data 引数を通じて server の戻り値にアクセスできる。

戻り値はマージされないので、universal の方で明示的にリターンする必要がある。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced loading / Using parent data

https://learn.svelte.dev/tutorial/await-parent

src/routes/+layout.server.js
export async function load() {
	return { a: 1 };
}
src/routes/sum/+layout.js
export async function load({ parent }) {
	const { a } = await parent();
	return { b: a + 1 };
}
src/routes/sum/+page.js
export async function load({ parent }) {
	const { a, b } = await parent();
	return { c: a + b };
}

load() 関数では parent() 関数を実行することでレイアウトの load() 関数の実行結果の Promise を取得することができる。

universal load functions は server と universal の両方のロード関数の実行結果を取得できる。

server load functions は server のロード関数の実行結果しか取得できない。

parent() を await する前にデータベースや API からデータを取得できる場合はそうした方が並列実行できるのでなるべく parent() は後から実行する方が良さそうだ。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced loading / Invalidation

https://learn.svelte.dev/tutorial/invalidation

src/routes/[...timezone]/+page.svelte
<script>
	import { onMount } from 'svelte';
	import { invalidate } from '$app/navigation';

	export let data;

	onMount(() => {
		const interval = setInterval(() => {
			invalidate('/api/now');
		}, 1000);

		return () => {
			clearInterval(interval);
		};
	});
</script>

<h1>
	{new Intl.DateTimeFormat([], {
		timeStyle: 'full',
		timeZone: data.timezone
	}).format(new Date(data.now))}
</h1>

SvelteKit は何か入力が変化したと思われる時だけロード関数を再実行する。

何も変化していないにも関わらずロード関数を実行したい場合は invalidate() 関数を実行する必要がある。

invalidate() 関数には文字列だけではなく関数を引数として与えることができる。

https://kit.svelte.dev/docs/modules#$app-navigation-invalidate

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced loading / Custom dependencies

https://learn.svelte.dev/tutorial/custom-dependencies

src/routes/+layout.js
export async function load({ depends }) {
	depends('data:now');

	return {
		now: Date.now()
	};
}
src/routes/[...timezone]/+page.svelte
<script>
	import { onMount } from 'svelte';
	import { invalidate } from '$app/navigation';

	export let data;

	onMount(() => {
		const interval = setInterval(() => {
			invalidate('data:now');
		}, 1000);

		return () => {
			clearInterval(interval);
		};
	});
</script>

<h1>
	{new Intl.DateTimeFormat([], {
		timeStyle: 'full',
		timeZone: data.timezone
	}).format(new Date(data.now))}
</h1>

load() 関数内で fetch() を使うと依存関係が設定される。

依存関係の設定だけをしたい場合は fetch() の代わりに depends () を使う。

depends() の引数は URL ライクなら何でも OK、正規表現では ^[a-z]+: になる。

https://kit.svelte.dev/docs/load#rerunning-load-functions-manual-invalidation

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Advanced loading / invalidateAll

https://learn.svelte.dev/tutorial/invalidate-all

src/routes/[...timezone]/+page.svelte
<script>
	import { onMount } from 'svelte';
	import { invalidateAll } from '$app/navigation';

	export let data;

	onMount(() => {
		const interval = setInterval(() => {
			invalidateAll();
		}, 1000);

		return () => {
			clearInterval(interval);
		};
	});
</script>

<h1>
	{new Intl.DateTimeFormat([], {
		timeStyle: 'full',
		timeZone: data.timezone
	}).format(new Date(data.now))}
</h1>
src/routes/+layout.js
export async function load() {
	return {
		now: Date.now()
	};
}

現在のページのロード関数をすべて再実行したい場合は invalidateAll() 関数を呼び出す。

この場合はロード関数内で fetch() や depends() が無くても再実行される。

invalidate(() => true)invalidateAll() は異なり、前者では fetch() や depends() の呼び出しが必要となる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Environment variables / $env/static/private

https://learn.svelte.dev/tutorial/env-static-private

.env
PASSPHRASE="open sesame"
src/routes/+page.server.js
import { redirect, fail } from '@sveltejs/kit';
import { PASSPHRASE } from '$env/static/private';

export function load({ cookies }) {
	if (cookies.get('allowed')) {
		throw redirect(307, '/welcome');
	}
}

export const actions = {
	default: async ({ request, cookies }) => {
		const data = await request.formData();

		if (data.get('passphrase') === PASSPHRASE) {
			cookies.set('allowed', 'true', {
				path: '/'
			});

			throw redirect(303, '/welcome');
		}

		return fail(403, {
			incorrect: true
		});
	}
};

SvelteKit では Vite を使っているので .env だけでなく .env.local や .env.development なども使うことができる。

process.env にある環境変数も $env/static/private からアクセスすることができる。

クライアントサイドで import { PASSPHRASE } from '$env/static/private'; をするとエラーになる。

このインポートは +server.js か .server.js で終わるファイル、src/lib/server 以下のファイルでのみ行うことができる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Environment variables / $env/dynamic/private

https://learn.svelte.dev/tutorial/env-dynamic-private

src/routes/+page.server.js
import { redirect, fail } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';

export function load({ cookies }) {
	if (cookies.get('allowed')) {
		throw redirect(307, '/welcome');
	}
}

export const actions = {
	default: async ({ request, cookies }) => {
		const data = await request.formData();

		if (data.get('passphrase') === env.PASSPHRASE) {
			cookies.set('allowed', 'true', {
				path: '/'
			});

			throw redirect(303, '/welcome');
		}

		return fail(403, {
			incorrect: true
		});
	}
};

アプリのビルド時ではなく実行時に与えられる環境変数を読み込みたい場合は $env/dynamic/private からインポートを行う。

インポートするものも env オブジェクトに変わる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Environment variables / $env/static/public

https://learn.svelte.dev/tutorial/env-static-public

.env
PUBLIC_THEME_BACKGROUND="steelblue"
PUBLIC_THEME_FOREGROUND="bisque"
src/routes/+page.svelte
<script>
	import {
		PUBLIC_THEME_BACKGROUND,
		PUBLIC_THEME_FOREGROUND
	} from '$env/static/public';
</script>

<main
	style:background={PUBLIC_THEME_BACKGROUND}
	style:color={PUBLIC_THEME_FOREGROUND}
>
	{PUBLIC_THEME_FOREGROUND} on {PUBLIC_THEME_BACKGROUND}
</main>

<style>
	main {
		position: fixed;
		display: flex;
		align-items: center;
		justify-content: center;
		left: 0;
		top: 0;
		width: 100%;
		height: 100%;
		font-size: 10vmin;
	}
</style>

PUBLIC_ から始まる環境変数はクライアント側でも読み込むことができる。

システム名などクライアント側で共通の情報を管理するのにも便利そう。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Part 4 / Environment variables / $env/dynamic/public

https://learn.svelte.dev/tutorial/env-dynamic-public

src/routes/+page.svelte
<script>
	import { env } from '$env/dynamic/public';
</script>

<main
	style:background={env.PUBLIC_THEME_BACKGROUND}
	style:color={env.PUBLIC_THEME_FOREGROUND}
>
	{env.PUBLIC_THEME_FOREGROUND} on {env.PUBLIC_THEME_BACKGROUND}
</main>

<style>
	main {
		position: fixed;
		display: flex;
		align-items: center;
		justify-content: center;
		left: 0;
		top: 0;
		width: 100%;
		height: 100%;
		font-size: 10vmin;
	}
</style>

プライベートな環境変数と同様にパブリックな環境変数でもビルド時ではなく実行時に読み込んだ値を使うことができる。

これは地味にすごい、同じことやろうとしたら結構大変だと思う。

内部的にはサーバーとクライアントでやり取りしていると思われる。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Hooks まとめ

https://zenn.dev/link/comments/58b3e9d94c0b5c

handle

  • フックとは SvelteKit のデフォルト動作を上書きする機能のこと。
  • 最も基本的なフックは handle であり、src/hooks.server.js を作成して handle() 関数をエクスポートすることで作成できる。
  • handle() 関数の第 1 引数としてオプションを受け取り、オプションには event や resolve が含まれる。
  • event にはアクセスされたページの URL などが含まれる。
  • resolve はルートの +page.server.js や +page.svelte の実行結果の Promise を返す関数であり、第 2 引数のオプションで transformPageChunk を指定することで HTML の書き換えなどができる。
  • handle() 関数は Response オブジェクトを返せば良いので必ずしも resolve() を呼び出す必要はない。

The RequestEvent object

  • handle() 関数に渡される event オブジェクトは API routes やフォームアクション、ページの load() 関数に渡されるものと同じ。
  • locals はアプリで使用するデータを格納するのに適している。

handleFetch

  • event オブジェクトには fetch() 関数が含まれている。
  • この fetch() 関数は拡張されており、cookie や authorization ヘッダーの引き継ぎや相対リクエストが可能。
  • API route などで fetch() 関数を呼び出した場合は HTTP リクエストは行われず、直接 GET() 関数などが呼ばれる。
  • handleFetch() フックは fetch() 関数の動作をカスタマイズするために使用される。

handleError

  • handleError() フックは予期しないエラーが発生した時の動作をカスタマイズできる。
  • デフォルトの動作はコンソール出力だが例えば Slack などに通知することも可能。
  • handleError() 関数の戻り値は +error.svelte ページで page ストアを使って参照できる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Page options まとめ

https://zenn.dev/link/comments/f077bcf9a125bf

Basic

  • +page.js や + page.server.js、+layout.js や +layout.server.js では ssr や csr などのページオプションをエクスポートすることで設定できる。
  • レイアウトの設定はすべての子孫に反映され、子孫のページやレイアウトは設定を上書きできる。
  • ルートレイアウトでデフォルトの設定をしておき、個々のページやレイアウトで設定を変更するのがメジャーな使い方になる。

ssr

  • SvelteKit では SSR がデフォルトになっている。
  • コンポーネントによってはサーバー側でレンダリングできないものもある。
  • 一例として window オブジェクトに即座にアクセスするコンポーネントが考えられる。
  • SSR を CSR に切り替えるには +page.server.js で export const ssr = false; と書く。

csr

  • csr を OFF にすることで JavaScript を実行しないようにできる。
  • おそらくデフォルトでは ON になっている。

prerender

  • 事前レンダリングとはリクエストの度ではなくビルド時に 1 回だけレンダリングすることを意味する。
  • 事前レンダリングはサーバー負荷や表示速度の面でメリットがある。
  • 一方、ビルド時間が長くなることや更新に再ビルドと再デプロイが必要になることがデメリット。
  • 事前レンダリングはすべてのページで有効化できる訳ではない。
  • すべてのリクエストに対して同じページ内容を返し、かつフォームアクションを使っていないページだけが事前レンダリングできる。
  • ルートパラメーターを使っているページでも事前レンダリングを有効化できる。
  • その場合は prerender.entries の設定を行う必要がある。

trailingSlash

  • trailing slash とは URL 末尾のスラッシュ(/)のこと。
  • SvelteKit では trailing slash はデフォルトで削除されるようになっている。
  • 逆に trailing slash を付加したい場合は export const trailingSlash = 'always'; を実行する。
  • trailing slash の削除を無効化したい場合は export const trailingSlash = 'ignore'; を実行する。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

https://zenn.dev/link/comments/efdab66fd72ece

Preloading

  • a タグに data-sveltekit-preload-data 属性を設けることでホバー/タップ時に事前読み込みができるので表示が速くなる。
  • この属性は個々の a タグに付与することもできるが body などに付与することで全てのリンクに設定を反映できる。
  • data-sveltekit-preload-data は指定がなければ hover になるが、tap や off も指定できる。
  • data-sveltekit-preload-data の代わりに data-sveltekit-preload-code を使うことで JavaScript コードのみを読み込むこともできる。
  • $app/navigation から preloadCode() や preloadData() 関数をインポートして script タグ内で事前読み込みを行うこともできる。

Reloading the page

  • SvelteKit ではページ間のナビゲーションはページの再読み込みなしで行われる。
  • a タグやそのコンテナに data-sveltekit-reload 属性を付与することでページの再読み込みを行うように設定できる。
  • この他にもいくつかのリンクオプションがある。
    • data-sveltekit-replacestate:ページ履歴を追加するのではなく書き換える。
    • data-sveltekit-keepfocus:フォーカスを維持したままにする、検索ページなどで便利。
    • data-sveltekit-noscroll:ページ内リンクをクリックした時にスクロールしないようにする。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Advanced routing まとめ

https://zenn.dev/link/comments/36008b31c5768c

Optional parameters

  • ディレクトリ名を [param] ではなく [[param]] とすることでパスパラメーターを省略可能にできる。
  • 省略可能にしたことで +page.svelte と [[param]]/+page.svelte が衝突してしまうので、その場合は +page.svelte の方を削除する。

Rest parameters

  • ディレクトリ名を [path] の代わりに [...path] とすることで複数のパスセグメントにマッチできる。
  • [...path] ディレクトリと同じ階層に他のディレクトリが存在しても問題ない。
  • その場合は他のディレクトリへのマッチが優先され、いずれにもマッチしない場合に [...path] ディレクトリにマッチするようになる。
  • レストパラメーターは末尾にある必要はなく /items/[...path]/edit/items/[...path].json にすることも可能。

Param matchers

  • パスパラメーターを正規表現などでチェックしたい場合には param matchers を利用できる。
  • param matchers を利用するには src/params に .js / .ts ファイルを作成し、match() 関数をエクスポートする。
  • どの param matchers を使用する場合はディレクトリ名を例えば [param=hex] のようにする。

Route groups

  • レイアウトを変更せずに共通のアプリケーションロジックを適用したい場合は route groups を利用できる。
  • 典型的な例としてはユーザー認証が挙げられる。
  • route groups を利用するには (authed) のような丸カッコでくくった名前でディレクトリを作成し、共通のアプリケーションロジックを適用したいディレクトリを移動する。

Breaking out layouts

  • レイアウトをリセットするには +page@b.svelte のようにファイル名に @ を入れる。
  • a/b/c/+page@b.svelte は c のレイアウトではなく b のレイアウトが使われる。
  • ルートレイアウトにリセットするには +page@.svelte のように書く。
  • ルートレイアウトはリセットすることができない。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Advanced loading まとめ

https://zenn.dev/link/comments/7814c242006420

Universal load functions

  • +page.js や +layout.js は universal load functions と呼ばれ、サーバーとクライアントの両方で実行される。
  • server load functions からはシリアライズできなくてサーバーからクライアントに渡せないものを渡すのに便利。
  • universal load functions はページロード時はサーバーとクライアントの両方で実行される。
  • それ以降はクライアントのみで実行される。

Using both load functions

  • server load functions と universal load functions は併用できる。
  • server の方が最初に実行され、universal の方が後に実行される。
  • universal の方から data 引数を通じて server の戻り値にアクセスできる。
  • 戻り値はマージされないので、universal の方で明示的にリターンする必要がある。

Using parent data

  • load() 関数では parent() 関数を実行することでレイアウトの load() 関数の実行結果の Promise を取得することができる。
  • universal load functions は server と universal の両方のロード関数の実行結果を取得できる。
  • server load functions は server のロード関数の実行結果しか取得できない。
  • parent() を await する前にデータベースや API からデータを取得できる場合はそうした方が並列実行できるのでなるべく parent() は後から実行する方が良さそうだ。

Invalidation

  • SvelteKit は何か入力が変化したと思われる時だけロード関数を再実行する。
  • 何も変化していないにも関わらずロード関数を実行したい場合は invalidate() 関数を実行する必要がある。
  • invalidate() 関数には文字列だけではなく関数を引数として与えることができる。

Custom dependencies

  • load() 関数内で fetch() を使うと依存関係が設定される。
  • 依存関係の設定だけをしたい場合は fetch() の代わりに depends () を使う。
  • depends() の引数は URL ライクなら何でも OK、正規表現では ^[a-z]+: になる。

invalidateAll

  • 現在のページのロード関数をすべて再実行したい場合は invalidateAll() 関数を呼び出す。
  • この場合はロード関数内で fetch() や depends() が無くても再実行される。
  • invalidate(() => true)invalidateAll() は異なり、前者では fetch() や depends() の呼び出しが必要となる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Environment variables まとめ

https://zenn.dev/link/comments/6bc4b6bea1f7c3

$env/static/private

  • SvelteKit では Vite を使っているので .env だけでなく .env.local や .env.development なども使うことができる。
  • process.env にある環境変数も $env/static/private からアクセスすることができる。
  • クライアントサイドで import { PASSPHRASE } from '$env/static/private'; をするとエラーになる。
  • このインポートは +server.js か .server.js で終わるファイル、src/lib/server 以下のファイルでのみ行うことができる。

$env/dynamic/private

  • アプリのビルド時ではなく実行時に与えられる環境変数を読み込みたい場合は $env/dynamic/private からインポートを行う。
  • インポートするものも env オブジェクトに変わる。

$env/static/public

  • PUBLIC_ から始まる環境変数はクライアント側でも読み込むことができる。
  • システム名などクライアント側で共通の情報を管理するのにも便利そう。

$env/dynamic/public

  • プライベートな環境変数と同様にパブリックな環境変数でもビルド時ではなく実行時に読み込んだ値を使うことができる。
  • これは地味にすごい、同じことやろうとしたら結構大変だと思う。
  • 内部的にはサーバーとクライアントでやり取りしていると思われる。
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

まとめ完了

25 分経過したので累計時間は 240 分だった。

Part 1 は 305 分、Part 2 は 415 分、Part 3 は 270 分だったので合計では 1230 分、時間単位だと 20.5 時間となる。

よくここまで続いたものだと自分で自分を褒めてあげたい。

来週からはしばらく止まっていたライフプラン作成でも再開しようかな。

https://zenn.dev/tatsuyasusukida/scraps/101d7d8a291af4

このスクラップは2023/12/08にクローズされました