Svelte 公式チュートリアル Part 3: Basic SvelteKit をやっていく
このスクラップについて
このスクラップでは Svelte 公式チュートリアル Part 3: Basic SvelteKit を手を動かして学んでいく過程を記録する。
前のスクラップ
Part 3 / Introduction / What is SvelteKit?
Svelte がコンポーネントフレームワークであるのに対し、SvelteKit はアプリケーションフレームワークであり、次のような機能を提供する。
- ルーティング
- サーバーサイドレンダリング
- データ取得
- サービスワーカー
- TypeScript との統合
- 事前レンダリング
- シングルページアプリケーション
- ライブラリのパッケージ化
- 本番環境に最適化されたビルド
- 様々なプロバイダーへのデプロイ
SvelteKit の特徴
- デフォルトではサーバーサイドでレンダリングする(初回ロードや SEO のため)。
- その後はクライアントサイドでのナビゲーションに移行する(ユーザー体験のため)。
プロジェクト構造
- package.json:依存関係やスクリプトが含まれる。
- svelte.config.js:プロジェクトの設定が含まれる。
- vite.config.js:Vite の設定が含まれる、SvelteKit は Vite を使っている。
- src:アプリのソースコードを保存する。
- src/app.html:ページのテンプレート、
%sveltekit.head
と%sveltekit.body
が置き換えられる。 - src/routes:アプリのルートに関するディレクトリ。
- static:Favicon や robot.txt などが含まれる、ビルド時に単純にコピーされるのかな?
10/31 (火) はここまで
15 分経過したので累計 15 分、次はプロジェクト構造の説明を読んでから Routing について調べる。
11/1 (水) はここから
今日も 20 分程度学んでいく。
Part 3 / Routing / Pages
<nav>
<a href="/">home</a>
<a href="/about">about</a>
</nav>
<h1>about</h1>
<p>this is the about page.</p>
SvelteKit ではファイルシステムベースのルーティングを使っている。
ファイル名には +page.svelte を使用する。
例えば /about ページを作りたい場合には src/routes/about ディレクトリを作成し、このディレクトリ内に +page.svelte ファイルを作成する。
初回ロードはサーバー側で描画されるがそれ以降のページ移動ではページは更新されず、ページの中身が更新される。
これにより、高速な初回ページ読み込みと高速なページ移動の両方が実現される。
この振る舞いがデフォルトだが、ページオプション設定を行うことで変更できる。
Part 3 / Routing / Layouts
<nav>
<a href="/">home</a>
<a href="/about">about</a>
</nav>
<slot />
各ページに共通するヘッダーやフッターなどは +layout.svelte ファイルにまとめることができる。
+layout.svelte は同じディレクトリと下位ディレクトリのページに適用される。
レイアウトはネストすることができる。
11/1 (水) はここまで
20 分経過したので累計 35 分、次は下記ページから始める。
11/2 (木) はここから
今日は 25 分くらい学んでいこう。
Part 3 / Routing / Route parameters
<h1>blog post</h1>
[slug]
のようなディレクトリ名を作成すると動的なルートを作成できる。
この点は Next.js と似ているので覚えやすい。
なんと [bar]x[baz]
のようなルートも作成できる。
Part 3 / Loading data / Page data
import { posts } from './data.js';
export function load() {
return {
summaries: posts.map((post) => ({
slug: post.slug,
title: post.title
}))
};
}
<script>
export let data;
</script>
<h1>blog</h1>
<ul>
{#each data.summaries as { slug, title }}
<li><a href="/blog/{slug}">{title}</a></li>
{/each}
</ul>
import { error } from '@sveltejs/kit';
import { posts } from '../data.js';
export function load({ params }) {
const post = posts.find((post) => post.slug === params.slug);
if (!post) throw error(404);
return {
post
};
}
<script>
export let data;
</script>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
- SvelteKit のコア機能はルーティング・ローディング・レンダリングの 3 つ。
- +page.svelte と同じ階層に +page.server.js を作成し、load() 関数をエクスポートすることでローディングを行うことができる。
- +page.server.js は常にサーバーで実行され、+page.js はサーバーまたはブラウザのいずれかで実行される。
- コンポーネントでローディングされたデータを受け取るには script タグ内で
export let data;
と受け渡し用の変数を用意する。 - エラーページを表示するには
@sveltejs/kit
から error() 関数をインポートして使用する。
Part 3 / Loading data / Layout data
<script>
export let data;
</script>
<div class="layout">
<main>
<slot />
</main>
<aside>
<h2>More posts</h2>
<ul>
{#each data.summaries as { slug, title }}
<li>
<a href="/blog/{slug}">{title}</a>
</li>
{/each}
</ul>
</aside>
</div>
<style>
@media (min-width: 640px) {
.layout {
display: grid;
gap: 2em;
grid-template-columns: 1fr 16em;
}
}
</style>
+layout.server.js ファイルを作成することで下位のルートに共通するデータを読み込むことができる。
レイアウトのデータに加えてさらにページ固有のデータローディングが必要な場合はどうすれば良いのかな?
ドキュメントを読もう。
11/2 (木) はここまで
25 分経過したので累計 60 分、次は下記ページから始める。
11/3 (金) はここから
今日も 25 分くらいやっていこう。
Part 3 / Headers and cookies / Setting headers
export function load({ setHeaders }) {
setHeaders({
'Content-Type': 'text/plain'
});
}
load() 関数で setHeaders() 関数を使うことで HTTP ヘッダーを設定できる。
まだ学んでいないがフォームアクション、フック、API ルートでも同じことができるようだ。
Part 3 / Headers and cookies / Reading and writing cookies
export function load({ cookies }) {
const visited = cookies.get('visited');
cookies.set('visited', 'true', { path: '/' })
return {
visited
};
}
setHeaders() 関数では Set-Cookie ヘッダーを設定できないのでその代わりに cookies API を使用する。
クッキーの読み込みには cookies.get() メソッドを使い、書き込みには cookies.set() メソッドを使う。
クッキーを書き込む時には path オプションを明示的に指定することが推奨される。
なぜなら現在のパスの 1 階層上がデフォルトの path になるため。
cookies.set() 関数を実行すると Set-Cookie ヘッダが書き込まれるが、それに加えて内部のクッキーのマップが書き換えられる。
したがって cookies.set() 関数を実行した後に cookies.get() を実行すると、cookies.set() 関数で設定された値が読み込まれる。
Part 3 / Shared modules / The $lib alias
<script>
import { message } from '$lib/message.js';
</script>
<h1>a deeply nested route</h1>
<p>{message}</p>
<script>
import { message } from '$lib/message.js';
</script>
<h1>home</h1>
<p>{message}</p>
src/routes ディレクトリ以下にモジュールやコンポーネントを置くこともできるが、共通に使用されるものを置くには src/lib ディレクトリが便利。
相対パスでインポートする代わりに $lib
のエイリアスでインポートすることができる。
11/3 (金) はここまで
20 分経過したので累計 80 分、次は下記ページから始める。
11/6 (月) はここから
今日は 20〜30 分くらいやっていこう。
Part 3 / Forms / The form element
<form method="POST">
<label>
add a todo:
<input
name="description"
autocomplete="off"
/>
</label>
</form>
export const actions = {
default: async ({ cookies, request }) => {
const data = await request.formData();
db.createTodo(cookies.get('userid'), data.get('description'));
}
};
+page.server.js で actions オブジェクトをエクスポートすることでサーバー側でフォームから送信されたデータを処理することができる。
fetch() 関数などを一切呼び出さずにサーバーとクライアントの間でデータをやり取りできる。
仮に JavaScript が無効になっていても動作するので堅牢性も高い。
Part 3 / Forms / Named form actions
export const actions = {
create: async ({ cookies, request }) => {
const data = await request.formData();
db.createTodo(cookies.get('userid'), data.get('description'));
},
delete: async ({ cookies, request }) => {
const data = await request.formData();
db.deleteTodo(cookies.get('userid'), data.get('id'));
}
};
<form method="POST" action="?/create">
<label>
add a todo:
<input
name="description"
autocomplete="off"
/>
</label>
</form>
<ul class="todos">
{#each data.todos as todo (todo.id)}
<li>
<form method="POST" action="?/delete">
<input type="hidden" name="id" value={todo.id} />
<span>{todo.description}</span>
<button aria-label="Mark as complete" />
</form>
</li>
{/each}
</ul>
actions のフィールド名に default 以外を指定することで名前付きのアクションを作成できる。
フォーム側では action="?/create"
のようにアクションを指定する。
名前付きアクションを使う場合はデフォルトアクションは使用できない。
他のページのアクションも実行することができる。
11/6 (月) はここまで
20 分経過したので累計 100 分、次は下記ページから始める。
11/7 (火) はここから
今日は 20 分くらいやっていこう。
Part 3 / Forms / Validation
<script>
export let data;
export let form;
</script>
<div class="centered">
<h1>todos</h1>
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
<form method="POST" action="?/create">
<label>
add a todo:
<input
name="description"
value={form?.description ?? ''}
autocomplete="off"
required
/>
</label>
</form>
<ul class="todos">
{#each data.todos as todo (todo.id)}
<li>
<form method="POST" action="?/delete">
<input type="hidden" name="id" value={todo.id} />
<span>{todo.description}</span>
<button aria-label="Mark as complete" />
</form>
</li>
{/each}
</ul>
</div>
export function createTodo(userid, description) {
if (description === '') {
throw new Error('todo must have a description');
}
const todos = db.get(userid);
if (todos.find((todo) => todo.description === description)) {
throw new Error('todos must be unique');
}
todos.push({
id: crypto.randomUUID(),
description,
done: false
});
}
export const actions = {
create: async ({ cookies, request }) => {
const data = await request.formData();
try {
db.createTodo(cookies.get('userid'), data.get('description'));
} catch (error) {
return fail(422, {
description: data.get('description'),
error: error.message
});
}
},
delete: async ({ cookies, request }) => {
const data = await request.formData();
db.deleteTodo(cookies.get('userid'), data.get('id'));
}
};
fail() 関数を使うことでエラーコードと共にサーバーのアクションからデータを返却できる。
クライアント側では export let form;
と書いてデータにアクセスできる。
アクションから普通にデータを return しても同じことができる。
Part 3 / Forms / Progressive enhancement
<script>
import { fly, slide } from 'svelte/transition';
import { enhance } from '$app/forms';
export let data;
export let form;
</script>
<div class="centered">
<h1>todos</h1>
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
<form method="POST" action="?/create" use:enhance>
<label>
add a todo:
<input
name="description"
value={form?.description ?? ''}
autocomplete="off"
required
/>
</label>
</form>
<ul class="todos">
{#each data.todos as todo (todo.id)}
<li in:fly={{ y: 20 }} out:slide>
<form method="POST" action="?/delete" use:enhance>
<input type="hidden" name="id" value={todo.id} />
<span>{todo.description}</span>
<button aria-label="Mark as complete" />
</form>
</li>
{/each}
</ul>
</div>
form に特に何も指定しなければ送信時にページ再読み込みが行われる。
use:enhance
を指定することで JavaScript が実行されるようになる。
そのためには import { enhance } from '$app/forms';
が必要になる。
エンハンスメントを有効にすると遷移を行えるようになる。
11/7 (火) はここまで
20 分経過したので累計 120 分、次は下記ページから始める。
11/8 (水) はここまで
今日も 20 分くらい学んでいこう。
Part 3 / Forms / Customizing use:enhance
<script>
import { fly, slide } from 'svelte/transition';
import { enhance } from '$app/forms';
export let data;
export let form;
let creating = false;
let deleting = [];
</script>
<div class="centered">
<h1>todos</h1>
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
<form
method="POST"
action="?/create"
use:enhance={() => {
creating = true;
return async ({ update }) => {
await update();
creating = false;
};
}}
>
<label>
add a todo:
<input
disabled={creating}
name="description"
value={form?.description ?? ''}
autocomplete="off"
required
/>
</label>
</form>
<ul class="todos">
{#each data.todos.filter((todo) => !deleting.includes(todo.id)) as todo (todo.id)}
<li in:fly={{ y: 20 }} out:slide>
<form
method="POST"
action="?/delete"
use:enhance={() => {
deleting = [...deleting, todo.id];
return async ({ update }) => {
await update();
deleting = deleting.filter((id) => id !== todo.id);
};
}}
>
<input type="hidden" name="id" value={todo.id} />
<span>{todo.description}</span>
<button aria-label="Mark as complete" />
</form>
</li>
{/each}
</ul>
{#if creating}
<span class="saving">saving...</span>
{/if}
</div>
use:enhance
にコールバックを指定することでフォーム送信時の動作をカスタマイズできる。
コールバックはフォーム送信時(正確にはフォーム送信の直前)に呼び出されるようだ。
コールバックは戻り値として非同期関数を返すことができる。
これはおそらくサーバー側でフォームが処理された後に呼び出される。
update() 関数は data や form を更新するために呼び出す必要があるのだろう。
詳しくはドキュメントを参照。
11/8 (水) はここまで
20 分経過したので累計 140 分、次は下記ページから始める。
11/9 (水) はここから
今日は 20〜30 分くらい学んでいこう。
Part 3 / API routes / GET handlers
import { json } from '@sveltejs/kit';
export function GET() {
const number = Math.floor(Math.random() * 6) + 1;
return json(number);
}
routes ディレクトリの子孫として +server.js ファイルを作成することで API ルートを設けることができる。
+server.js では GET や POST など HTTP メソッドと同じ名前の関数をエクスポートする。
これらの関数では Response オブジェクトをリターンする。
レスポンスを JSON 形式で返す場合は import { json } from '@sveltejs/kit';
を利用できる。
Part 3 / API routes / POST handlers
<script>
export let data;
</script>
<div class="centered">
<h1>todos</h1>
<label>
add a todo:
<input
type="text"
autocomplete="off"
on:keydown={async (e) => {
if (e.key === 'Enter') {
const input = e.currentTarget;
const description = input.value;
const response = await fetch('/todo', {
method: 'POST',
body: JSON.stringify({ description }),
headers: {
'Content-Type': 'application/json'
}
});
const { id } = await response.json();
data.todos = [...data.todos, {
id,
description
}];
input.value = '';
}
}}
/>
</label>
<ul class="todos">
{#each data.todos as todo (todo.id)}
<li>
<label>
<input
type="checkbox"
checked={todo.done}
on:change={async (e) => {
const done = e.currentTarget.checked;
// TODO handle change
}}
/>
<span>{todo.description}</span>
<button
aria-label="Mark as complete"
on:click={async (e) => {
// TODO handle delete
}}
/>
</label>
</li>
{/each}
</ul>
</div>
import { json } from '@sveltejs/kit';
import * as database from '$lib/server/database.js';
export async function POST({ request, cookies }) {
const { description } = await request.json();
const userid = cookies.get('userid');
const { id } = await database.createTodo({ userid, description });
return json({ id }, { status: 201 });
}
POST や PUT ではリクエストのヘッダーや本文に含まれるデータを取得できる。
取得の仕方は +page.server.js の load() 関数やフォームアクションと同じ。
例えば POST メソッドでリクエストの本文とクッキーにアクセスする場合は export async function POST({ request, cookies }) { ... }
のように書く。
可能なケースではフォームアクションを使った方がコーディング量も少なく、JavaScript が無くても動くので堅牢性が高い。
Part 3 / API routes / Other handlers
import * as database from '$lib/server/database.js';
export async function PUT({ params, request, cookies }) {
const { done } = await request.json();
const userid = cookies.get('userid');
await database.toggleTodo({ userid, id: params.id, done });
return new Response(null, { status: 204 });
}
export async function DELETE({ params, cookies }) {
const userid = cookies.get('userid');
await database.deleteTodo({ userid, id: params.id });
return new Response(null, { status: 204 });
}
<script>
export let data;
</script>
<div class="centered">
<h1>todos</h1>
<label>
add a todo:
<input
type="text"
autocomplete="off"
on:keydown={async (e) => {
if (e.key === 'Enter') {
const input = e.currentTarget;
const description = input.value;
const response = await fetch('/todo', {
method: 'POST',
body: JSON.stringify({ description }),
headers: {
'Content-Type': 'application/json'
}
});
const { id } = await response.json();
data.todos = [...data.todos, {
id,
description
}];
input.value = '';
}
}}
/>
</label>
<ul class="todos">
{#each data.todos as todo (todo.id)}
<li>
<label>
<input
type="checkbox"
checked={todo.done}
on:change={async (e) => {
const done = e.currentTarget.checked;
await fetch(`/todo/${todo.id}`, {
method: 'PUT',
body: JSON.stringify({ done }),
headers: {
'Content-Type': 'application/json'
}
});
}}
/>
<span>{todo.description}</span>
<button
aria-label="Mark as complete"
on:click={async (e) => {
await fetch(`/todo/${todo.id}`, {
method: 'DELETE'
});
data.todos = data.todos.filter((t) => t !== todo);
}}
/>
</label>
</li>
{/each}
</ul>
</div>
{ params }
引数を使ってパスパラメーターを取得できる。
204 No Content レスポンスを返すときは return new Response(null, { status: 204 });
とする。
11/9 (木) はここまで
30 分経過したので累計 170 分、次は下記ページから始める。
11/10 (金) はここから
今日も 20〜30 分学んでいこう。
Part 3 / Stores / page
<script>
import { page } from '$app/stores';
</script>
<nav>
<a href="/" aria-current={$page.url.pathname === '/'}>
home
</a>
<a href="/about" aria-current={$page.url.pathname === '/about'}>
about
</a>
</nav>
<slot />
SvelteKit では page / navigating / updated の 3 つの読み込み専用ストアが提供される。
これらは $app/stores からインポートできる。
page ストアでは現在のページの URL を示す url や パラメーターを示すparams などにアクセスできる。
通常のストアと同様にストアの値にアクセスするには $
を前置する。
Part 3 / Stores / navigating
<script>
import { page, navigating } from '$app/stores';
</script>
<nav>
<a href="/" aria-current={$page.url.pathname === '/'}>
home
</a>
<a href="/about" aria-current={$page.url.pathname === '/about'}>
about
</a>
{#if $navigating}
navigating to {$navigating.to.url.pathname}
{/if}
</nav>
<slot />
navigating はいわゆるページ移動を表現するストアのようだ。
普段は null だがリンクなどでページ移動が発生する時にオブジェクトになる。
主なプロパティは from / to / type で from と to は始点と終点のページの URL などを示し、type はリンクなどのページ移動の種類を示す。
詳しいドキュメントは下記ページ。
Part 3 / Stores / updated
<script>
import { page, navigating, updated } from '$app/stores';
</script>
<nav>
<a href="/" aria-current={$page.url.pathname === '/'}>
home
</a>
<a href="/about" aria-current={$page.url.pathname === '/about'}>
about
</a>
{#if $navigating}
navigating to {$navigating.to.url.pathname}
{/if}
</nav>
<slot />
{#if $updated}
<p class="toast">
A new version of the app is available
<button on:click={() => location.reload()}>
reload the page
</button>
</p>
{/if}
updated ストアはページが表示されてからその時点までに新しいバージョンのアプリがデプロイされたかを示す真偽値が格納される。
このストアを使うには svelte.config.js で kit.version.pollInterval を設定する必要がある。
このストアは本番環境でのみ使うことができる。
アプリのバージョン確認は自動でポーリングによって行われるが、updated.check() 関数を呼び出して手動で行うこともできる。
11/10 (金) はここまで
30 分経過したので累計 200 分、次は下記ページから始める。
11/11 (土) はここから
今日も 20〜30 分、学んでいこう。
Part 3 / Errors and redirects / Basics
エラーには予期しているエラーと予期していないエラーの 2 種類がある。
予期しているエラーは error() 関数を使って作成され、throw 文で投げられる。
一方、予期していないエラーは error() 関数を使って作成されないが throw 文で投げられる点は同じ。
予期しているエラーではエラーコードが指定できるが、予期していないエラーは 500 になる。
予期しているエラーのメッセージはユーザーに表示されるが、予期していないエラーのメッセージはコンソールログに出力され、ユーザーには Internal Error と表示される。
Part 3 / Errors and redirects / Error pages
<script>
import { page } from '$app/stores';
import { emojis } from './emojis.js';
</script>
<h1>{$page.status} {$page.error.message}</h1>
<span style="font-size: 10em">
{emojis[$page.status] ?? emojis[500]}
</span>
<h1>this error was expected</h1>
サーバーで load() 関数の実行中にエラーが発生した場合、エラーページが表示される。
エラーページは +error.svelte ファイルを作成することでカスタマイズできる。
+error.svelte のコンテンツには +layout.svelte のレイアウトが反映される。
routes ディレクトリの直下以外にも +error.svelte ページを作成することができ、ページごとにエラーページの表示内容を細かくカスタマイズできる。
Part 3 / Errors and redirects / Fallback errors
export function load() {
throw new Error('yikes');
}
<h1>Game over</h1>
<p>Code %sveltekit.status%</p>
<p>%sveltekit.error.message%</p>
レイアウトやエラーページのレンダリング中に例外が発生した場合には src/error.html ページが表示される。
このページでは %sveltekit.status% と %sveltekit.error.message% の 2 つの変数が利用でき、それぞれスターテスコードとエラーメッセージに置き換えられる。
Part 3 / Errors and redirects / Redirects
import { redirect } from '@sveltejs/kit';
export function load() {
throw redirect(307, '/b');
}
throw redirect(307, '/b');
のように書くことで他のページにリダイレクトできる。
第 1 引数はステータスコードであり、目的に応じて適切なものを選ぶことが望まれる。
フォームアクションが成功した時には 303 が最適らしい、知らなかった。
11/11 (土) はここまで
30 分経過したので累計 230 分、次はまとめから始める。
- Introduction
- Routing
- Loading data
- Headers and cookies
- Shared modules
- Forms
- API routes
- Stores
- Errors and redirects
11/14 (火) はここから
今日も 20〜30 分学んでいこう。
Introduction まとめ
What is SvelteKit
- SvelteKit はアプリケーションフレームワークであり、ルーティングやサーバーサイドレンダリングなどの機能を提供する。
- デフォルトではサーバーサイドでレンダリングし、その後はクライアントサイドでのナビゲーションに移行することで初回ロード/SEO とユーザー体験のバランスを取っている。
- ルートには package.json に加えて svelte.config.js や vite.config.js などの設定ファイルが含まれる。
- アプリのソースコードは src 以下に格納される。
- 静的ファイルは static ディレクトリに格納される。
Routing まとめ
Pages
- SvelteKit ではファイルシステムベースのルーティングを使っている。
- ファイル名には +page.svelte を使用する。
- 例えば /about ページを作りたい場合には src/routes/about ディレクトリを作成し、このディレクトリ内に +page.svelte ファイルを作成する。
- 初回ロードはサーバー側で描画されるがそれ以降のページ移動ではページは更新されず、ページの中身が更新される。
- これにより、高速な初回ページ読み込みと高速なページ移動の両方が実現される。
- この振る舞いがデフォルトだが、ページオプション設定を行うことで変更できる。
Layouts
- 各ページに共通するヘッダーやフッターなどは +layout.svelte ファイルにまとめることができる。
- +layout.svelte は同じディレクトリと下位ディレクトリのページに適用される。
- レイアウトはネストすることができる。
Route parameters
- [slug] のようなディレクトリ名を作成すると動的なルートを作成できる。
- この点は Next.js と似ているので覚えやすい。
- なんと [bar]x[baz] のようなルートも作成できる。
Loading data まとめ
Page data
- SvelteKit のコア機能はルーティング・ローディング・レンダリングの 3 つ。
- +page.svelte と同じ階層に +page.server.js を作成し、load() 関数をエクスポートすることでローディングを行うことができる。
- +page.server.js は常にサーバーで実行され、+page.js はサーバーまたはブラウザのいずれかで実行される。
- コンポーネントでローディングされたデータを受け取るには script タグ内で export let data; と受け渡し用の変数を用意する。
- エラーページを表示するには @sveltejs/kit から error() 関数をインポートして使用する。
Layout data
- +layout.server.js ファイルを作成することで下位のルートに共通するデータを読み込むことができる。
- レイアウトのデータに加えてさらにページ固有のデータローディングが必要な場合、+page.server.js に load() 関数を設ければ良い。
- この場合、レイアウトの load() 関数の戻り値にページの load() 関数の戻り値がマージされる。
Headers and cookies まとめ
Setting headers
- load() 関数で setHeaders() 関数を使うことで HTTP ヘッダーを設定できる。
- フォームアクション、フック、API ルートでも同じことができる。
Reading and writing cookies
- setHeaders() 関数では Set-Cookie ヘッダーを設定できないのでその代わりに cookies API を使用する。
- クッキーの読み込みには cookies.get() メソッドを使い、書き込みには cookies.set() メソッドを使う。
- クッキーを書き込む時には path オプションを明示的に指定することが推奨される、なぜなら現在のパスの 1 階層上がデフォルトの path になるため。
- cookies.set() 関数を実行すると Set-Cookie ヘッダが書き込まれるが、それに加えて内部のクッキーのマップが書き換えられる。
- したがって cookies.set() 関数を実行した後に cookies.get() を実行すると、cookies.set() 関数で設定された値が読み込まれる。
Shared modules まとめ
The $lib alias
- src/routes ディレクトリ以下にモジュールやコンポーネントを置くこともできるが、共通に使用されるものを置くには src/lib ディレクトリが便利。
- 相対パスでインポートする代わりに $lib のエイリアスでインポートすることができる。
Forms
The form element
- +page.server.js で actions オブジェクトをエクスポートすることでサーバー側でフォームから送信されたデータを処理することができる。
- fetch() 関数などを一切呼び出さずにサーバーとクライアントの間でデータをやり取りできる。
- 仮に JavaScript が無効になっていても動作するので堅牢性も高い。
Named form actions
- actions のフィールド名に default 以外を指定することで名前付きのアクションを作成できる。
- フォーム側では action="?/create" のようにアクションを指定する。
- 名前付きアクションを使う場合はデフォルトアクションは使用できない。
- 他のページのアクションも実行することができる。
Validation
- fail() 関数を使うことでエラーコードと共にサーバーのアクションからデータを返却できる。
- クライアント側では
export let form;
と書いてデータにアクセスできる。 - アクションから普通にデータを return しても同じことができる。
Progressive enhancement
- form に特に何も指定しなければ送信時にページ再読み込みが行われる。
- use:enhance を指定することで JavaScript でフォーム送信がエミュレートされる。
- そのためには
import { enhance } from '$app/forms';
が必要になる。 - エンハンスメントを有効にすると遷移を行えるようになる。
Customizing use:enhance
-
use:enhance
にコールバックを指定することでフォーム送信時の動作をカスタマイズできる。 - コールバックはフォーム送信時(正確にはフォーム送信の直前)に呼び出される。
- コールバックは戻り値として非同期関数を返すことができる。
- これはおそらくサーバー側でフォームが処理された後に呼び出される。
- update() 関数は data や form を更新するために呼び出す必要がある。
11/14 (火) はここまで
20 分経過したので累計 250 分、次は API routes のまとめから始める。
11/15 (水) はここから
今日も 20〜30 分学んでいこう。
API routes まとめ
GET handlers
- routes ディレクトリの子孫として +server.js ファイルを作成することで API ルートを設けることができる。
- +server.js では GET や POST など HTTP メソッドと同じ名前の関数をエクスポートする。
- これらの関数では Response オブジェクトをリターンする。
- レスポンスを JSON 形式で返す場合は
import { json } from '@sveltejs/kit';
を利用できる。
POST handlers
- POST や PUT ではリクエストのヘッダーや本文に含まれるデータを取得できる。
- 取得の仕方は +page.server.js の load() 関数やフォームアクションと同じ。
- 例えば POST メソッドでリクエストの本文とクッキーにアクセスする場合は
export async function POST({ request, cookies }) { ... }
のように書く。 - 可能なケースではフォームアクションを使った方がコーディング量も少なく、JavaScript が無くても動くので堅牢性が高い。
Other handlers
{ params }
引数を使ってパスパラメーターを取得できる。
204 No Content レスポンスを返すときは return new Response(null, { status: 204 });
とする。
Stores まとめ
page
- SvelteKit では page / navigating / updated の 3 つの読み込み専用ストアが提供される。
- これらは $app/stores からインポートできる。
- page ストアでは現在のページの URL を示す url や パラメーターを示すparams などにアクセスできる。
- 通常のストアと同様にストアの値にアクセスするには
$
を前置する。
navigating
- navigating はいわゆるページ移動を表現するストア。
- 普段は null だがリンクなどでページ移動が発生する時にオブジェクトになる。
- 主なプロパティは from / to / type で from と to は始点と終点のページの URL などを示し、type はリンクなどのページ移動の種類を示す。
updated
- updated ストアはページが表示されてからその時点までに新しいバージョンのアプリがデプロイされたかを示す真偽値が格納される。
- このストアを使うには svelte.config.js で kit.version.pollInterval を設定する必要がある。
- このストアは本番環境でのみ使うことができる。
- アプリのバージョン確認は自動でポーリングによって行われるが、updated.check() 関数を呼び出して手動で行うこともできる。
Errors and redirects
Basics
- エラーには予期しているエラーと予期していないエラーの 2 種類がある。
- 予期しているエラーは error() 関数を使って作成され、throw 文で投げられる。
- 一方、予期していないエラーは error() 関数を使って作成されないが throw 文で投げられる点は同じ。
- 予期しているエラーではエラーコードが指定できるが、予期していないエラーは 500 で固定。
- 予期しているエラーのメッセージはユーザーに表示されるが、予期していないエラーのメッセージはコンソールログに出力され、ユーザーには Internal Error と表示される。
Error pages
- サーバーで load() 関数の実行中にエラーが発生した場合、エラーページが表示される。
- エラーページは +error.svelte ファイルを作成することでカスタマイズできる。
- +error.svelte のコンテンツには +layout.svelte のレイアウトが反映される。
- routes ディレクトリの直下以外にも +error.svelte ページを作成することができ、ページごとにエラーページの表示内容を細かくカスタマイズできる。
Fallback errors
- レイアウトやエラーページのレンダリング中に例外が発生した場合には src/error.html ページが表示される。
- このページでは %sveltekit.status% と %sveltekit.error.message% の 2 つの変数が利用でき、それぞれスターテスコードとエラーメッセージに置き換えられる。
Redirects
-
throw redirect(307, '/b');
のように書くことで他のページにリダイレクトできる。 - 第 1 引数はステータスコードであり、目的に応じて適切なものを選ぶことが望まれる。
- フォームアクションが成功した時には 303 が適している。
おわりに
20 分経過したので累計 270 分だった。
Svelte の学習も楽しかったが SvelteKit の学習はもっと楽しかった。
現在進行形で SvelteKit で簡単なアプリ開発をしているのでためになることばかりだった。
最後の Advanced SvelteKit も楽しく学んでいこう。
次のスクラップ