🐈

Svelte と Sveltekit を触ってみた

2021/06/09に公開

追記(2022年12月15日)

sveltekit v1 リリースされました!🎉
https://svelte.dev/blog/announcing-sveltekit-1.0

はじめに

こんにちは、久しぶりに Zenn に記事を書くので手が震えてます。
うそです、ZennZenn 緊張してません。
最近、個人ブログ を Sveltekit で作り直しました。

※自分用メモとしての側面が強いかもしれません。

ということで Svelte と Sveltekit についてまとめていきます。

Svelte とは

Svelte とは、React や Vue のように宣言的に UI を宣言できるフロントエンドのフレームワークです。
が、下で説明しますが Svelte は VDOM を用いません。なぜなら Svelte はコンパイラだからです。

また、Svelte はバンドルサイズが非常に小さいです。また、部分的に適用することもできるので小規模なアプリケーションでは使いやすいのかなと思います。(もちろん大きなアプリケーションにも使うことはできますが)

Svelte の特徴

公式ドキュメント の先頭には以下の3つの特徴があると書かれています。

コードの記述量が少ない

1つ目の特徴として、アプリケーションの構築に必要なコードの記述量が減ることが挙げられます。
全体的にシンプルな記述に加えて、双方向バインディング(Vue にあるやつ)や state の更新はミュータブルに行うので記述量が減っているようです。
また、同じことをするために必要なコード量が少なくなることもドキュメントで言及されています。
どうやら React で442文字、Vue.js で263文字、Svelte は145文字で実現できるみたいです。しゅごい。

hello world だけしたければ以下のように書くことができます。

<script>
	let name = 'world';
</script>

<h1>Hello {name}!</h1>

また、よく見るカウンターアプリも以下のコードで書くことができます。

<script>
	let count = 0;

        const  handleClick = () => {
		count += 1;
	}
</script>

<button on:click={handleClick}>
	Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

とても簡単です。

全てのソースコードにはバグが含まれています。バグの可能性を減らす手段の一つとして、ソースコードの量を少なくすることが挙げられます。Svelte はそれを実現しています。

VDOM を用いない

React や Vue との大きな違いとして VDOM を用いない点があります。
また、Svelte の立ち位置としてはコンパイラです。
React や Vue のように VDOM をランタイムで実行するのではなく、Svelte で記述されたコードはコンパイルされ、JS は vanilla JS ファイルとして、CSS は CSSファイルとして出力されます。

VDOM ライブラリを自作するとわかりますが、VDOM のツリーを構築してそれを実DOM に反映して、変更があれば oldNode と newNode のような二つの変数を使用して値を比較し差分を更新するというコードはどうしても大きくなってしまいます。

また、Virtual DOM is pure overhead では、「仮想 DOM は速い」という神話を完全に撤回しましょう。 と書かれています。確かにな〜と思って読んでました。

また、コンパイル後のコードは必要な処理だけが入る(ツリーシェイキングしてくれる、なんと CSS も削ってくれる)ので無駄な処理が入ることもなく、バンドルサイズも小さくなるので高速で動作してくれます。

とってもリアクティブ

Svelte はリアクティブなコードを書くことができます。Svelte で生成されるコードは state の変化を検知して DOM に直接反映する コードです。
そのリアクティブなコードは代入によって表現されています。

そのため、値の更新をする際には

count += 1;

と表現され、コンパイル後のコードでは、値が変更される処理は

count += 1; // 代入がトリガーになる
$$invalidate('count', count); 

と表現されます。

書き方

上でも少し書いていますが、テンプレートにいろいろ埋め込むことができます。

基本的には下のような形で書くことができます。
もっと知りたい方は ドキュメント を見てください。

<script>
	// JS/TS のロジック
</script>

<script context="module">
	// コンポーネント、インスタンスごとではなく、モジュールが最初に評価されるときに 1 回実行される
	// script タグにアクセス可能だけど逆はできない
	// default export はできない
</script>

<style>
	/* css(sass) などの style */
</style>

<div>
	<!-- マークアップ -->
</div>

script

script タグの部分にはロジックを書くことができます。
svelte では export を使用して変数をプロパティとして宣言することができます。

<script>
	export let name;
	name = 'takurinton';
</script>

const, class, function を export するとコンポーネント外からは読み取り専用となるため注意が必要です。

<script>
	// 読み取り専用
	export const thisIs = 'readonly';

	// 読み取り専用
	export function greet(name) {
		console.log(`hello ${name}!`);
	}

	// これは更新可能
	export let format = n => n.toFixed(2);
</script>
  
カウンターを実装するならこうです。普通ですね。

```html
<script>
	let count = 0;
	function handleClick () {
		// マークアップ側で count という変数を更新する
		count = count + 1;
	}
</script>

JS の ラベルシンタックス を付けることでリアクティブにすることができます。これは依存する値が変更されるたびに、コンポーネントが更新される直前に実行されます。
依存する値のみが対象になるため、これは x が更新された時のみ Total が更新されます。

<script>
	let x = 0;
	let y = 0;

	function yPlusAValue(value) {
		return value + y;
	}

	$: total = yPlusAValue(x); // ここが変わった時のみ更新される、y はいないので干渉しない
</script>

Total: {total}
<button on:click={() => x++}>
	Increment X
</button>

<button on:click={() => y++}>
	Increment Y
</button>

store も用意されています。store はリアクティブなオブジェクトへの参照を可能にするコンストラクタです。
store への参照を行いたい場合は、$ を付けることにより、コンポーネント内のその値にアクセスできます。
また、.set を使用することにより値を登録することができます。また、代入を使用しても更新することが可能です。ただし、この変数は書き込み可能である必要があります。

<script>
import { writable } from 'svelte/store';

const count = writable(0);
console.log($count); // 0

count.set(1);
console.log($count); // 1

$count = 2;
console.log($count); // 2
</script>

style

style タグはコンポーネントごとに scoped です。
コンパイルした際にはクラス名に加えてコンポーネントごとのハッシュ値が適用されるため一意に識別されます。
また、グローバルに適用したい場合には :global(propaty) を使用します。
keyframs をグローバルに保持したい際には先頭に -global- を使用します。

<style>
/* そのコンポーネントの p タグにのみ適用される */
p {
   color: pink;
}
   
/* グローバルに適用される */
:global(body) {
   margin: 0;
}
   
/* -global- をつけるとグローバルになる */
@keyframes -global-animation {
   from {
   	transform: translateX(0%);
   }
   to {
   	transform: translateX(100%);
   }
}
</style>

template

tenplate にはマークアップを書くことができます。
html と同じですが、svelte 独自の便利な書き方がたくさんあります。

基本的な部分

まずは外部のコンポーネントは普通に使えます。

<script>
	import MyComponent from './MyComponent.svelte';
</script>

<div>
	<MyComponent/>
</div>

属性をつけることもできます。
デフォルトの属性は html と全く同じように機能します。

<!-- 普通に使える -->
<div class="hoge">
	<button disabled>disabled なのでクリックできません</button>
</div>

属性には JS の構文を含めることができます。

<a href="post/{slug}">post: {slug}</a>
<button disabled={!clickable}>...</button>

コンポーネントに引数を渡すこともできます。その際には複数の渡し方があります。

<!-- 何も考えずに渡すパターン -->
<MyComponent foo={bar} answer={42} text="hello"/>

<!-- オブジェクトを渡す、これも他のフレームワーク同様渡せる -->
<MyComponent {...items}/>

<!-- $$ をつけるとコンポーネントに渡される全ての props を参照する  -->
<!-- export されてないものも含まれる  -->
<!-- 最適化周りに問題があるので非推奨  -->
<MyComponent {...$$props}/>

<!-- restProps は export で宣言されていない props のみが含まれる  -->
<!-- これもまた非推奨  -->
<MyComponent {...$$restProps}/>

テンプレートの中で演算もできます。

<p>{x} + {y} = {x + y}</p>

条件分岐と繰り返し

条件分岐やループなどの処理もすることができます。
if 文であれば、{#if 式} {/if} で表現することができ、else ifelse も表現することができます。

<!-- 条件分岐 -->
{#if temperature > 30}
	<p>あちち</p>
{:else}
	<p>ぼちぼち</p>
{/if}

ループは each を使用します。
書き方は if と近いですが、{#each 式 as 別名} {/each} で表現することができます。

<h1>user list</h1>
<ul>
	{#each users as user}
		<li>{user.name}: {user.age}</li>
	{/each}
</ul>

他にも

{#each expression as name, index}...{/each}
{#each expression as name (key)}...{/each}
{#each expression as name, index (key)}...{/each}
{#each expression as name}...{:else}...{/each}

のような書き方もできます。

Promise の処理

await を使用して Promise の状態を表現することも可能です。

{#await promise}
	<p>pending</p>
{:then value}
	<!-- resolve -->
	<p>The value is {value}</p>
{:catch error}
	<!-- rejected -->
	<p>rejected: {error.message}</p>
{/await}

それぞれの状態を気にしない場合は省略することも可能です。
例えば、pending の状態に何もレンダリングする必要がない場合は以下のように書くことができます。

{#await promise then value}
	<p>value: {value}</p>
{/await}

key

key を作成してオブジェクトに固有の値を持たせることもできます。VDOM 自作するとここら辺の大切さがわかる気がします。

{#key value}
	<div transition:fade>{value}</div>
{/key}

デコレーション

{@html content} で囲うと html として認識されます。
例えば、ブログ記事のマークダウンのテキストを html に変換する際などに使用することができます。

<div class="blog-post">
	<h1>{post.title}</h1>
	{@html post.content} // マークダウンを html に変換した文字列
</div>

{@debug expresssion} を使用するとデバッグをすることができます。JS では console.log() がしばしば使用されますが、svelte の template 内でデバッグする際には debug を使用すると便利です。

<script>
	let user = {
		name: 'takurinton',
		age: 21
	};
</script>

{@debug user}

<h1>Hello {user.name}!</h1>

属性

イベントを処理する際には on:eventname={handler} 属性を使用します。
何回も出てくるカウンターはこんな感じです。<button on:click={handleClick}> のような形で表現します。
また、イベントハンドラは複数持つことが可能です。

<script>
	let count = 0;
	function handleClick(event) {
		count += 1;
	}
</script>

<button on:click={handleClick}>
	count: {count}
</button>

また、 インラインで記述してもパフォーマンスは失われないことが保証されてるので、このような簡単なカウンターなら以下のように記述しても問題ありません。これが not vdom の力か〜(あまり変わらない)

<button on:click="{() => count += 1}">
	count: {count}
</button>

bind:propaty={variable} を使用することもできます。通常、状態のフローは親から子に流れますが、bind を使用すると逆に流すことができます。

select box の状態を管理するとこのような形になります。

<select bind:value={selected}>
	<option value={a}>a</option>
	<option value={b}>b</option>
	<option value={c}>c</option>
</select>

video タグの bind はたくさんあるみたいです。あまり試してないですが、以下のものが使用できます。

<video
	src={clip}
	bind:duration
	bind:buffered
	bind:played
	bind:seekable
	bind:seeking
	bind:ended
	bind:currentTime
	bind:playbackRate
	bind:paused
	bind:volume
	bind:muted
	bind:videoWidth
	bind:videoHeight
></video>

bind はこの他にもたくさんありますが省略します。

style を指定する際に class:name={value} を使用することができます。
例えば、この2つは等価です。

<!-- active の時にだけ active という class を付与する -->
<div class="{active ? 'active' : ''}">...</div>
<div class:active={active}>...</div>

要素が作成されたときに呼び出される関数を定義したい場合は use:action={parameters} を使用することができます。
また、要素がアンマウントされた後に呼び出される destroy メソッドを使用してオブジェクトを返すことができます。

<script>
	function mount(node) {
		// マウントされた時の処理
		return {
			destroy() {
				// アンマウント時の処理
			}
		};
	}
</script>

<div use:mount></div>

transition は状態変化の結果が DOM に出入りする要素がトリガーとなり発火します。

{#if visible}
	<div transition:fade>
		フェードインとフェードアウト
	</div>
{/if}

また、transition に近いものとして、in:fn/out:fn があります。
transition が進行中にブロックが変更された際、in は out と共に実行され続けます。
ただし、out が中止された場合は transition は最初からやり直しとなります。

{#if visible}
	<div in:fly out:fade>
		フライで入ってきてフェードアウトしていく
	</div>
{/if}

each ブロックが並べ替えられるときにアニメーションを表現したい場合は animate:name={params} を使用します。
Animate は、key 設定された各ブロックの直接の子要素上にある必要があります。

{#each items as item, index (item)}
	<li animate:flip>{item}</li>
{/each}

コンポーネントは、要素と同じように子コンテンツを持つことができます。
コンテンツは、子コンポーネントがない場合に提供される <slot> 要素を使用して子コンポーネントのデフォルト値を定義することができます。

<!-- MyComponent.svelte -->
<div>
	<slot>
		子コンポーネントがレンダリングされない場合に表示される
	</slot>
</div>
<!-- Main.svelte -->
<MyComponent></MyComponent> <!-- 子コンポーネントがないと slot の内容がそのまま表示される-->

<MyComponent>
	<p>子コンポーネントがある場合は slot を上書きする</p>
</MyComponent>

タグ

<svelte:self> を使用するとコンポーネントを再帰的に呼び出すことができます。
注意点として、再帰的に呼び出すので無限ループを避けるために条件分岐を行わなければいけません。また、トップレベルに置くことは禁止されています。

<script>
	export let count;
</script>

{#if count > 0}
	<p>カウントダウン: {count}</p>
	<svelte:self count="{count - 1}"/>
{:else}
	<p>おしまい</p>
{/if}

<svelte:component> を使用すると、this に与えられたコンポーネントを動的に生成します。また、その状態が変化したときには破棄され、再生成されます。

<svelte:component this={MyComponent} />

<svelte:window> を使用するとコンポーネントが破棄されたときに eventListner を削除したり、SSR の際に window is not defined を回避するための条件分岐をしなくても eventListner を window に追加することができます。

<script>
	function handleClick(event) {
		document.getElementById('takurinton');
	}
</script>

<!-- これをサーバサイドでも使える -->
<svelte:window on:click={handleClick}/>

<svelte:body><svelte:window> と同様のことができますが、これはトップレベルで使用する必要があります。

<svelte:head> を使用すると、<head> タグを表現することができます。
これもトップレベルで使用する必要があります。
SSR をしている際、これはサーバ側で生成される html とは別になります。OGP などに使いたい場合はサーバ側で生成する必要があります。

<svelte:head>
	<link rel="stylesheet" href="./style.css">
</svelte:head>

<svelte:fragment> を使用すると、コンテナ DOM 要素でラップせずに slot にコンテンツを配置できます。

<!-- MyComponent.svelte -->
<div>
	<slot name="header"></slot>
	<p>hogehoge</p>
	<slot name="main"></slot>
</div>
<!-- App.svelte -->
<MyComponent>
	<h1 slot="header">Hello</h1>
	<svelte:fragment slot="main">
		<p>hello world</p>
	</svelte:fragment>
</MyComponent>

少し長くなりましたが Svelte に関してはこんな感じです。ランタイムでの API も提供されていますが下で説明する内容と多少かぶる節があるので省略します。

sveltekit とは

Svelte には React でいうところの Next.js のような立ち位置の svektekit があります。
サーバサイドレンダリングはもちろん、様々なラッパーやエコシステムがあります。
また、バンドルに vite を使用しており、非常に高速でバンドル(されてないんだけど)されるため、開発者体験が良いです。HMR 様様となります。

簡単にワークスペースを作成する際には以下のコマンドを叩きます。

npm init svelte@next my-app

基本的な機能

上で述べたように Next.js のような立ち位置なので似たような機能が提供されています。
基本的な機能を簡単に書いていきます。

ルーティング

Next.js のノリで routes/ の下に置いたファイルは自動的にそのパスでルーティングされます。
Next.js のノリなので [id].svelte[slug].svelte などの使い方ができます。
Next.js のノリではないところとして、拡張子が .svelte ではない場合(JS や TS)はサーバサイドで動くコードとして認識されます。

イメージとしては下の routes/post/util.ts みたいな感じ。
route

認識としては Node.js を実行したいときに使うものなので Next.js のノリでいうところの api/ が近いかもしれません。あれが書くディレクトリに自由度高く配置できるみたいな感じです。
また、アンダースコアから始まるファイル/ディレクトリはルーティングにはふくまれません。

複数の動的なパラメータを含むことも可能で routes/[category]/[slug].svelte のような書き方も可能です。
ファイルやパスの階層が不明な場合は ...slug のような書き方をすることでマッチさせることも可能です。

例えば GitHub のレポジトリを表現するとすると /[org]/[repo]/tree/[branch]/[...file] のようになります。

load 関数

Next.js でいうところの getInitialProps です。コンポーネントが初期化する前に呼ばれます。
つまり、ここで SSR をしている場合は SSR をした段階で走り、2回目以降はクライアントサイドで走ります。
load 関数は以下の引数を取ることができます。

<script context="module">
  export const load = async ({ page, fetch, session, context }) => {
    page; //パスやクエリパラメータなど
    fetch; //クライアントとサーバの両方で使える fetch 関数
    session; //セッションを格納することができる
    context; //コンテキストを格納することができる
  };
</script>

context="module" というのが出てきていますが、これは Svelte ではよく出てくる表現でフレームワークのメタ的な処理を行うときに使います。
context="module" を使用してる時はメタ的な処理、使用していない時には通常な処理が走るため、しばしば script タグが2つ出現したりします。
この load 関数の何が嬉しいかというと、isomorphic に使用できる部分じゃないかなと思います。Blitz には RPC 変換というクライアントからサーバの関数を使用できるメリットがありますが、それと近い感じかなと思います。
ブロッキングがだるいってこともありますが、クライアントでの処理を別で走らせることもできるのでそういうやり方もありなのかなと思います。個人的には嬉しいポイントなのかなとか思ったり思わなかったり。

ちなみに Svelte には React でいうところの useEffect である onMount という関数があります。load 関数を使用する場合はこれが不要になりますが、どうしても使いたい場合は使用することもできます。

共通で使えるレイアウト

ルーティングのために作ったディレクトリの下に $layout.svelte を作ると共通レイアウトとして扱ってくれます。
Next.js のノリで言うと ドキュメントのこの部分

また、エラーページも $error.svelte を作ると表現することができる。便利〜。

hooks.js

Next.js のノリでいうところの hooks ではなく(まああれは React のノリか)、src/hooks.js を配置すると SSR するときに実行するコードを表現することができます。

hooks には 2つの関数が用意されています。

  • handle
    • クライアントサイドとサーバサイドの両方のすべてのリクエストで実行され、レスポンスを返す
    • ルーターを元にレスポンスを生成し resolve という関数を受け取る
  • getSession
    • リクエスト オブジェクトを受け取り、クライアントでアクセスできるセッション オブジェクトを返す
    • ユーザーに公開しても安全である必要がある情報を入れる
    • ページがレンダリングされるたびに実行される
    • session はシリアライズ可能なデータを含める必要があり、関数やクラスを含んではいけない

定義されているモジュール

Sveltekit では独自で定義されている特別なモジュールがいくつか存在します。

$app/env

実行環境の設定が入っています。
4つの戻り値があるみたいです。

  • amp
    • amp が構成に入っているかどうかを表す boolean
  • browser
    • ブラウザ側かサーバ側かを表す boolean
  • dev
    • 開発環境か本番環境かを表す boolean
  • prerendering
    • prerendering をするかどうかを表す boolean

$app/navigation

prefetch とページ遷移で使用することができる便利なやつです。
4つの戻り値をとります。

  • goto(href, { replaceState, noscroll })
    • 指定された href にナビゲートするときに解決する Promise を返す
    • replaceState が true の場合は新しいエントリは作成されない
    • noscroll はナビゲーション後にページの先頭までスクロールしない(その viewport に留まる)
  • invalidate(href)
    • 問題のリソースをフェッチした場合に、今いるページの load 関数を再実行する
    • ページがその後更新されたときに解決される Promise を返す
  • prefetch(href)
    • prefetch を行う
    • prefetch されたときに解決される Promise を返す
  • prefetchRoutes(routes)
    • まだ ferch されていないルートのコードを prefetch する
    • 後続の navigation を高速化する
    • prefetch されたときに解決される Promise を返す

$app/paths

なんかよくわからないけど2つの戻り値があるみたいです。
使いどころがいまいち掴めないけどなんかあるんでしょう(適当)

  • base
    • config.kit.paths.base (絶対パス)に一致する文字列を返す
  • assets
    • config.kit.paths.assets(相対パス)に一致する文字列を返す

$app/stores

store は context に依存します。
store はルートコンポーネントの context に追加されます。これは session とページが同じサーバ上の全てのリクエストで共通して持つことができるわけではなく、サーバ上の各リクエスト固有のものであることを示します。
$app/stores は context にある各 store へのショートカットです。
4つの戻り値をとります。

  • getStores
    • { navigating, page, session } を返します(下の3つ)
  • navigating
    • 読み取り可能な store
    • ナビゲート開始時から終了時までの値をとることができ、ナビゲートが終了すると null になる
  • page
    • 読み込み関数に渡されるオブジェクトを値が反映する読み取り可能な store
    • load 関数で取ることができる値と同じ(path, params, query)
  • session
    • 書き込み可能な store
    • 変更がサーバに保持されることはない
    • 何かしたかったら自分で実装する必要がある

$lib

src/lib、または config で [config.kit.files.lib] と指定されてるディレクトリへのショートカットが表現できます。
通常、コンポーネントがネストしていると ../../../../lib/hoge.ts などのようにアクセスしないといけませんが、$lib を指定すればジャンプすることができます。

$service-worker

service worker で使用することができる便利なやつが入っています。
3つの戻り値を取ります。

  • build
    • vite で build した際に生成されたファイルを表す文字列の配列
    • cache.addAll(build) によるキャッシュに適している
  • files
    • static ディレクトリ、または config.kit.files.assets で指定されたディレクトリ内のファイルを表す文字列の配列
  • timestamp
    • build したときに Date.now() を呼び出した結果
    • service worker 内でユニークなキャッシュ名を生成して、デプロイした際に古いキャッシュを無効にする時に使える

adapter

sveltekit では複数の環境に対して1つのコードでいいようなアダプタが用意されています。

  • adapter-static
    • SSG をする用
  • adapter-node
    • SSR する Node.js のサーバーにする用
  • adapter-xxx
    • xxxの部分は vercel とか Cloudflare Workers だったりの外部サービスが入る
    • Node.js を動かす環境(サーバレスも含む)に対してそれぞれの build ファイルを作ることができる

anchor options

Sveltekit では、a タグにオプションをつけることができます。

sveltekit:prefetch

prefetch をつけるとその先のリンクを prefetch してくれます。Next.js のノリでいうところの Link コンポーネントです。

<a sveltekit:prefetch href="blog/what-is-sveltekit">What is SvelteKit?</a>

sveltekit:noscroll

内部リンクに移動すると、ブラウザはデフォルトのナビゲーション動作をします。(スクロール位置を 0 にする)
このオプションをつけるとそれを無効にし、遷移前のスクロール位置のまま遷移します。

<a href="path" sveltekit:noscroll>Path</a>

rel=external

Sveltekit は a タグを内部リンクとして認識し、自動的に相対パスとして認識するようになっています。

そのため、外部サイトに飛びたい場合は明示的に示す必要があります。

<a rel="external" href="path">Path</a>

SSR

Sveltekit はデフォルトで SSR を行うように設計されており、サーバ上でコンポーネントをレンダリングし、それを html としてクライアントに送信し、その後ブラウザで hydration します。

このデフォルトの構成を変えたい場合は、アプリごと、またはページごとで制御することができます。
ページごとの設定では context="module" を使用します。これはレイアウトコンポーネントではなくページコンポーネントで使用します。(もし両方で指定されている場合はページが優先される)

各種設定は個別に制御することができますが、ssr と hyddrate の両方を false にすることはできません(当たり前体操)

ここら辺についてはあまり手を動かしてないので分かりませんが、挙動が怪しいという記事も見かけたのでもしかしたら不安定なのかもしれません。

ssr

ssr を false にするとサーバサイドでのレンダリングが無効になり、Sveltekit は SPA として扱われます。
Sveltekit は SSR をすることを推奨してるみたいですね。
時と場合と気分によって変えましょう。

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

router

Sveltekit には、ブラウザーがリロードしてナビゲーションを処理するのではなく、ナビゲーション (ユーザーがリンクをクリックする、または進む/戻る) を握ることでページ コンテンツを更新するクライアントサイドのルーティングが含まれています。
これはオプションで無効にすることができます。(スクロール位置をキープしたい場合など)

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

hydrate

ssr オプション同様、hydrate オプションもありますが、どちらかは true にしておかなければなりません。
hydration をしない選択肢として考えられるのが、そのページで JS が全く使われていない場合です。この場合は hydration をせずにサーバサイドで生成された html のみを表示する方が少ないリソースでページを表示することができます。

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

prerender

hydration をする必要のないページ同様、単純な html で表すことができる場合は prerendering することができます。
これはアプリのルートから開始し、見つかったプリレンダリング可能なページの html を生成します。

<script context="module">
	export const prerender = true;
</script>

AMP

以下の効果を持つ AMP の設定を config のオプションを使用することで表現することができます。

  • ルーターを含むクライアント側の JavaScript を無効化されているときの挙動を表現することができる
  • <style amp-custom> を使用することで AMP ボイラープレートが挿入される
  • 開発環境でリクエストは AMP バリデーターに対してチェックされるため、エラーの早期警告が得られる

まとめ

ここまで Sveltekit についてまとめてきました。開発する中で知っといたほうがいいなあってことはこれくらいかなと思います。他にも config のオプションとか細かく設定できるので良いと思いました。

ただ、Sveltekit、いいんだけど、現時点ではバグが多かったり(僕も adapter や load 関数のバグにぶち当たって結構な時間とかした)してて不安定な面があるなあとも感じました。

それでも、出力サイズが小さいのは魅力的ですし、VDOM ではなく vanilla JS を吐き出すアプローチ方法は面白そうなのでぜひみなさんも触ってみてください。
ブログはこれからリファクタしていきます。

※ 後半疲れて「Next.js のノリ」ってのあまり書いてなかったな...
※ 筆者は Next.js ほぼ初心者です。

Discussion