レンダリングパイプライン
レンダリングパイプラインとは
SvelteKitのレンダリングパイプラインは、Svelteコンポーネントから最終的にユーザーに表示されるWebページまでの変換プロセス全体を指します。このプロセスは複数のフェーズに分かれており、各フェーズで異なる最適化が行われます。
コンパイルフェーズ
Svelteの最大の特徴は、コンパイル時の最適化です。ReactやVueのような仮想DOMを使わず、コンパイル時に効率的なDOM操作コードを生成します。
コンパイルプロセスの詳細
Svelteコンパイラは、.svelteファイルを解析し、最適化されたJavaScriptコードを生成します。このプロセスは、パース、AST生成、解析、コード生成の4つの主要なステップで構成されています。
Svelteコンパイラの動作
以下の例では、シンプルなButtonコンポーネントがどのようにコンパイルされ、最適化されたJavaScriptコードに変換されるかを示します。
入力:Svelteコンポーネント
コンパイラに渡される元の.svelteファイルです。Svelte 5のRunesシステムを使用したモダンな構文で記述されています。
// 入力: Button.svelte
<script lang="ts">
let { count = $bindable(0), onclick }: {
count?: number;
onclick?: () => void;
} = $props();
</script>
<button {onclick}>
クリック回数: {count}
</button>
<style>
button {
padding: 8px 16px;
background: #ff3e00;
color: white;
}
</style>
出力:コンパイル済みJavaScript
コンパイラが生成する最適化されたJavaScriptコードです。create_fragment関数がコンポーネントのライフサイクルを管理し、効率的なDOM操作を実現します。
// 出力: Button.js(簡略化)
import { SvelteComponent, init, safe_not_equal, element, text, attr, insert, listen, set_data, noop } from "svelte/internal";
class Button extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {
count: 0,
onclick: 1
});
}
}
function create_fragment(ctx) {
let button;
let t0;
let t1;
return {
c() { // create
button = element("button");
t0 = text("クリック回数: ");
t1 = text(ctx[0]); // count
attr(button, "class", "svelte-xyz123");
},
m(target, anchor) { // mount
insert(target, button, anchor);
append(button, t0);
append(button, t1);
if (ctx[1]) listen(button, "click", ctx[1]); // onclick
},
p(ctx, dirty) { // update
if (dirty & 1) set_data(t1, ctx[0]); // count changed
},
d(detaching) { // destroy
if (detaching) detach(button);
}
};
}
コンパイル時の最適化
Svelteコンパイラは、以下のような様々な最適化を自動的に適用し、パフォーマンスを最大化します。
- デッドコード削除: 使用されていないコードを削除
- インライン化: 小さな関数をインライン展開
- リアクティビティの静的解析: 変更検知コードの最小化
- CSSスコープ化: コンポーネント固有のクラス名生成
ビルドフェーズ
ViteによるビルドプロセスでSvelteKitアプリケーションが本番用に最適化されます。
Viteビルドの設定
SvelteKitはViteをビルドツールとして使用します。以下の設定例では、コード分割、ツリーシェイキング、最小化などの最適化を構成しています。
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
build: {
// コード分割の設定
rollupOptions: {
output: {
manualChunks: {
// vendorチャンクの分離
vendor: ['svelte', '@sveltejs/kit'],
// 大きなライブラリを個別チャンクに
charts: ['d3', 'chart.js'],
}
}
},
// ツリーシェイキング
treeShaking: true,
// 最小化
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // console.log削除
drop_debugger: true
}
}
}
});
ビルド成果物の構造
ビルド後に生成されるファイル構造です。clientディレクトリにはブラウザ用のアセット、serverディレクトリにはSSR用のコードが配置されます。
# ビルド後のディレクトリ構造
.svelte-kit/
├── output/
│ ├── client/ # クライアントバンドル
│ │ ├── _app/
│ │ │ ├── immutable/ # 不変アセット(ハッシュ付き)
│ │ │ │ ├── chunks/ # 共通チャンク
│ │ │ │ ├── pages/ # ページ別チャンク
│ │ │ │ └── assets/ # 静的アセット
│ │ │ └── version.json
│ │ └── index.html
│ └── server/ # サーバーバンドル
│ ├── chunks/
│ ├── pages/
│ └── index.js
実行フェーズ
SSR実行の流れ
サーバーサイドレンダリングでは、サーバー上でデータを取得し、HTMLを生成してクライアントに送信します。以下の例では、load関数でデータを取得し、それをHTMLに埋め込むプロセスを示しています。
サーバーサイドのデータ取得
load関数でAPIからデータを取得し、コンポーネントに渡します。このデータはHTMLにシリアライズされて埋め込まれます。
// +page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, fetch }) => {
// 1. サーバーサイドでデータ取得
const response = await fetch(`/api/posts/${params.id}`);
const post = await response.json();
// 2. データをシリアライズして埋め込み
return {
post, // このデータはHTMLに埋め込まれる
timestamp: Date.now()
};
};
生成されるHTML
サーバーが生成する完全なHTMLです。data-sveltekit-data属性にシリアライズされたデータが含まれ、data-svelte-h属性でハイドレーション用のマーカーが付与されています。
<!-- 生成されるHTML -->
<!DOCTYPE html>
<html>
<head>
<script type="application/json" data-sveltekit-data>
{
"post": {"id": 1, "title": "記事タイトル", "content": "..."},
"timestamp": 1699123456789
}
</script>
<link rel="modulepreload" href="/_app/immutable/entry/start.js">
<link rel="modulepreload" href="/_app/immutable/chunks/index.js">
</head>
<body>
<!-- サーバーレンダリング済みHTML -->
<div data-svelte-h="svelte-1abc2de">
<h1>記事タイトル</h1>
<p>記事の内容...</p>
</div>
</body>
</html>
ハイドレーション
ハイドレーションは、サーバーでレンダリングされた静的なHTMLを、クライアント側でインタラクティブなアプリケーションに変換するプロセスです。
ハイドレーションの実装
クライアント側でサーバー生成のHTMLをインタラクティブにするためのコードです。既存のDOM要素を再利用し、イベントリスナーとリアクティビティを追加します。
// クライアント側の初期化コード
import { start } from '$app/start';
start({
target: document.body,
hydrate: true, // ハイドレーションを有効化
paths: {
base: '',
assets: ''
},
trailing_slash: 'never'
});
// コンポーネントのハイドレーション
function hydrateComponent(component: SvelteComponent, target: Element) {
// 1. サーバー生成のDOM要素を探索
const elements = target.querySelectorAll('[data-svelte-h]');
// 2. 既存のDOM要素を再利用
elements.forEach(el => {
const hash = el.getAttribute('data-svelte-h');
if (validateHash(hash)) {
// DOM要素を再利用し、イベントリスナーのみ追加
attachEventListeners(el);
}
});
// 3. リアクティブシステムの初期化
initializeReactivity(component);
}
コード分割とチャンク戦略
動的インポートによるコード分割
大きなライブラリやコンポーネントを必要な時にのみロードすることで、初期バンドルサイズを削減できます。以下の例では、チャートコンポーネントを遅延ロードしています。
// +page.svelte
<script lang="ts">
import { onMount } from 'svelte';
let ChartComponent: any;
onMount(async () => {
// 必要な時にのみチャートライブラリをロード
const module = await import('$lib/components/Chart.svelte');
ChartComponent = module.default;
});
</script>
{#if ChartComponent}
<svelte:component this={ChartComponent} data={chartData} />
{:else}
<div class="skeleton">チャート読み込み中...</div>
{/if}
ルートベースの自動分割
SvelteKitはルートごとに自動的にコードを分割します。各ページが別々のJavaScriptチャンクになり、必要なコードのみがロードされます。
// SvelteKitは自動的にルートごとにコードを分割
// routes/
// ├── +layout.svelte → layout.js
// ├── +page.svelte → page.js
// ├── about/+page.svelte → about.js(別チャンク)
// └── blog/
// └── [id]/+page.svelte → blog-[id].js(動的チャンク)
パフォーマンス測定
レンダリングパイプラインのパフォーマンスを測定し、ボトルネックを特定するための実装例です。Server-Timingヘッダーを使用して、ブラウザの開発者ツールで測定結果を確認できます。
// hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
const start = performance.now();
const response = await resolve(event);
const duration = performance.now() - start;
// Server-Timingヘッダーで測定結果を送信
response.headers.set(
'Server-Timing',
`sveltekit;dur=${duration};desc="Total SvelteKit processing"`
);
return response;
};
まとめ
SvelteKitのレンダリングパイプラインは以下の特徴を持ちます。
- コンパイル時最適化: 実行時オーバーヘッドの最小化
- 効率的なハイドレーション: 既存DOMの再利用
- 自動コード分割: ルートベースの最適化
- プログレッシブエンハンスメント: JavaScriptなしでも動作
これらの仕組みを理解することで、より効率的なSvelteKitアプリケーションを構築できます。
次のステップ
- 実行環境とランタイム - 様々な環境での動作
- ビルド最適化 - さらなるパフォーマンス改善
- 基礎編に戻る - 基本的な使い方を復習