SveltKitの概要

# SvelteKit キャッチアップ

Svelteをコンポーネントフレームワークとすると、SvelteKitはアプリケーションフレームワークと言えます。
各ページの書き方はNext.jsのApp Routerに似ていると感じました。

https://learn.svelte.jp/tutorial/page-data

> 本質的には、SvelteKit が行う仕事は次の3つに集約されます。
>
> ルーティング(Routing) — 受け取ったリクエストにどのルートがマッチするかを判断する
> ローディング(Loading) — ルートが必要とするデータを取得する
> レンダリング(Rendering) — (サーバー上で) HTML を生成する、または (ブラウザで) DOMを更新する

違う点はapp.htmlがページ全体のtemplateになっており、これに対してSvelteKitがコンポーネントの置き換えを行なっていく、というところだと思います。

## Routing

`src/route`内にある`+page.svelte`ファイルがアプリケーションのページを作成します。
例えば/aboutでアクセスしたいページを作成するためには`src/route/+page.svelte`を追加すればOKです。

### leyout

`+layout.svelte`を作成することでそのディレクトリ配下にある`+page.svelte`の共通部分を作成することができます。

### 動的ルーティング

[]で囲まれたディレクトリを作成することで、動的なパラメーターを持つルートを作成することができます。
例えば、`src/route/user/[id]/+page.svelte`というファイルを作成することで、/user/1や/user/adminのようなルートが作成されます。

## Loading Data

`+page.svelte`と同じ階層にある`+page.server.js`はサーバー側でデータの取得を行い、値をsvelteファイルに受け渡す。

```javascript:+page.server.js
import { posts } from '../data.js'
import { error } from '@sveltejs/kit'

export function load({ params }) {
	const post = posts.find((post) => post.slug === params.slug);

	if(!post) throw error(404);
	
	return {
		post
	};
}
```

このload関数が読み取りと受け渡しを行う。（関数名は固定）

### `+layout.server.js`

`+layout.svelte`と同様に`+layout.server.js`も配下のルートがよばれる前にloadが行われる。
`+layout.svelte`で読み取ることができる。

### Setting headers

load関数の中では、レスポンスのヘッダーに情報を追加することができる。

```javascript: +page.server.js
export function load({ setHeaders }) {
	setHeaders({
		'Content-Type': 'text/plain'
	})
}
```
### Setting cookies

同様にcookieの設定も行うことができます。

```javascript: +page.server.js
export function load({ cookies }) {
	const visited = cookies.get('visited');

	cookies.set('visited', 'true', { path: '/'});

	return {
		visited: visited === 'true'
	};
}
```

cookieの設定には`cookies.set(name, value, options)`を使用します。
optionsには[こちらのドキュメント](https://github.com/jshttp/cookie#api)のparce,serializeオプションに対応しているようです。

## Shared modules

モジュールやコンポーネントは使用するpageファイルの近くに置くのが一般的ですが、複数のページで使用されるものは共通の置き場があると便利です。
SvelteKitではそのディレクトリを、`src/lib`になっています。
インポートの際には、`import { name } from '$lib/name.js';`のように使用します。

## Form

### formが一つの場合

データの送信には通常のHTMLと同様にformタグを使用します。

```html:+page.svelte
<form method="POST">
    <label>
        add a todo:
        <input 
            name="description"
            autocomplete="off"
            />
    </label>
</form>
```

action要素はここでは書かず`+page.server.js`に`actions`として記述します。

```javascript:+page.server.js
export const actions = {
	default: async ({ cookies, request }) => {
		const data = await request.formData();
		db.createTodo(cookies.get('userid'), data.get('description'))
	}
}
```

### formが複数ある場合

defaultのみだと２種類以上のformには対応できないので名前をつけて呼び出すactionを振り分けます。

```html:+page.svelte
<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>
```

action要素の値は?から始まっていますが、これはroute直下にある為です。
todoディレクトリは以下にある場合は、"/todo?/create"のようになります。

```javascript:+page.server.js
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'));
	}
}; 
```

### Validation

必須項目などはHTML部分で防ぐことができます。
inputタグ内にrequiredを追加します。


```diff_html:+page.svelte
<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>
```

```diff_javascript:+page.server.js
+ import { fail } from '@sveltejs/kit';

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 }) => {
        // ...
    }
};
```

failでラップした値はformとして`+page.svelte`に返すことができます。

#### enhance

`import { enhance } from '$app/forms';`をして`use:enhance`をFomrタグに追加することでページをリロードするのではなくなったため、トランジションなどが効くようになります。

`use:enhance`は独自にカスタマイズを行うことができます。送信中の表示（sending...など）はこれを使って操作できます。

```html
<form
    method="POST"
    action="?/create"
    use:enhance={() => {
        creating = true;

        return async ({ update }) => {
            await update();
            creating = false;
        };
    }}
>
```

## API Route

ディレクトリ内に`+server.js`ファイルを追加し、そこでHTTP メソッド GET、PUT、POST、PATCH、DELETE に対応する関数をエクスポートすることで、 API ルート(API routes) を作成することができます。

```javascript: src/route/roll/+server.js
import { json } from '@sveltejs/kit';

export function GET() {
	const number = Math.floor(Math.random() * 6) + 1;

	return json(number);
}
```

呼び出す側では`fetch('/roll');`のように、ディレクトリ名をURL部分に設定する。

## Store

SvelteKit では、3つの読み取り専用 store (page、navigating、updated )を $app/stores から使用できます。

### pageで取得できる情報

- url
  現在のページの URL
- params
  現在のページのパラメータ
- route 
  現在のルート(route)を表す id プロパティを持つオブジェクト
- status 
  現在のページの HTTP ステータスコード
- error 
  現在のページのエラーオブジェクト 
- data 
  現在のページの data。全ての load 関数からの戻り値が足されたもの
- form 
  form action から返されるデータ

他の store と同様、コンポーネントでは $ シンボルを先頭に付けることでその値を参照することができます。例えば、現在のパス名は $page.url.pathname でアクセスできます:

### navigatingで取得できる情報

リンクをクリックしたり、ブラウザの '戻る/進む'、またはプログラムで goto を呼んだときに値を持つ。
navigating の値は以下のプロパティを持つオブジェクトになります:

- from/to
  params、route、url プロパティを持つオブジェクト
- type
  ナビゲーションのタイプ(link、popstate、goto)

### updatedで取得できる情報

updated store は true または false を持ちます。これは最初にページを開いてからそれ以降にアプリの新バージョンがデプロイされたかどうかを表しています。動作させるには、svelte.config.js で kit.version.pollInterval を指定する必要があります。

バージョンの変更はプロダクションでのみ発生し、開発時には発生しません。そのため、このチュートリアルでは $updated は常に false となります。

pollInterval とは関係なく、updated.check() を呼び出すと手動で新バージョンがデプロイされたかチェックできます。

## error

SveltKitには想定される(expected)エラーと、予期せぬ(unexpected)エラーの二種類があります。

- expected error
  想定されているエラーは500以外のエラーのことです。
  エラーの原因がわかっているものなので適切なエラーコードと共にthrowします。

  ```javascript: +page.server.js
	import { error } from '@sveltejs/kit';

	export function load() {
		throw error(420, 'Enhance your calm');
	}
  ```

- unexpected error
  予期せぬエラーの場合は、アプリのバグであると考えられます。予期せぬエラーがスローされた場合、そのメッセージとスタックトレースがコンソールにログ出力されます。
  画面にはエラーの内容ではなく、'Internal Error' メッセージと 500 ステータスコードが表示されます。

### `+error.svelte`

src/routes/+error.svelte コンポーネントを作成することで、エラーページをカスタマイズすることができます。

エラーが起きた階層から一番近い`+error.svelte`ページが表示されます。

最上位(root)のレイアウトでデータをロードしているときや、エラーページのレンダリング中にエラーが発生した場合、SvelteKit は静的なエラーページにフォールバックします。


`src/error.html`ファイルを作成することで、フォールバックエラーページをカスタマイズすることができます。

```html: src/error.html
<h1>Game over</h1>
<p>Code %sveltekit.status%</p>
<p>%sveltekit.error.message%</p>
```
このファイルは以下の項目を含めることができます。
%sveltekit.status% — HTTP ステータスコード
%sveltekit.error.message% — エラーメッセージ

### redirect

throwの仕組みを応用して、別ページへのリダイレクトを行うことができます。
`throw redirect(dode,route)` は、load 関数、form actions、API ルート、そして後の章で説明する handle hook の内側で使うことができます。

よく使用されるステータスコードはこちらです。

- 303
    form actions で、送信に成功したあと続いて使用されます
- 307
    一時的なリダイレクトに使用されます
- 308
    恒久的なリダイレクトに使用されます