SvelteKit を Cloudflare Workers で動かしてみた
西田@CX事業本部です
今回は1.0が発表された(SvelteKit 1.0 発表) SvelteKit を Cloudflare Workers で動かしてみたいと思います
SvelteKit は Svelte と Vite を使用しており、最低限のコンフィグで開発が始められるWEBアプリケーションフレームワークです
SPA, MPA, SSR, SSG 全てに対応しており、アダプタと呼ばれる機能でCloudflare、Vercel などの多くのサービスに簡単にデプロイすることができます
Svelte について
Svelte の特徴の一つには純粋なコンパイラであることが挙げられます。
React や Vue と違い、Vitural DOMを実行時に構築せずに、あらかじめvanila JS(ピュアなJS)とCSS にコンパイルしておき、これらをデプロイし実行します
SvelteKit について
Svelte に SSR の機能や、デプロイのためのアダブターという機能を提供します。 React に詳しい方なら、Svelte の Next.js のような位置付けのものと考えるとわかりやすいかもしれません
SvelteKit のアダプタ
SvelteKit のアダプタは、作成したアプリケーションをデプロイする方法を提供しています
今回は Cloudflare Workers にデプロイする アダプターを使用します
SvelteKit のルーティング
SvelteKit では routes/
ディレクトリ配下のディレクトリ構造がそのままルーティングされるパスになります
+
から始まるファイルでそのパスにアクセスされた時のアクションを定義します
+page.svelte
⇒ ページ表示+page.server.ts
=> データ取得、Fromの Actionの処理+server.ts
⇒ API(Endpoint)
+page.svelte
ファイルを置くそのパスにアクセスされた時にページをレンダリングします。例えば /routes/todo/+page.svelte
とファイルを配置すると /todo
にアクセスした際にその +page.svelte
の内容がレンダリングされます
ページのレンダリングに必要なデータを取得するには同一ディレクトリに .ts
(or js) ファイルを配置し、その中で load
関数を定義し、 export
することでデータを取得し、 +page.svelte
ファイルに渡すことができます
純粋なAPI(Endpoint)を提供したい場合は +servet.ts
ファイルを置きます
また、レイアウトを指定したい場合は +layout.svelte
ファイルを配置します。配置されたディレクトリ配下にレイアウトが適用されます。
- routes/
|-- +layout.svelte (全体に適用されるレイアウト)
|-- todo/
|-- +page.svelte (/todo にアクセスされた時にレンダリングされる)
|-- +page.ts (/todo にアクセスされた時に呼ばれるデータ取得処理)
|-- api/
|-- todo/
|-- +server.ts (/api/todo にアクセスされたときに実行されるAPI)
SvelteKit のルーティングには多くの便利な機能が備わっています。より詳細な情報はドキュメントをご確認ください
SvelteKit のサーバーサイド処理
データ取得を行う +page.ts
の load
関数は初回アクセス時にはSSRとしてサーバーで実行され、ページ内のリンククリックなどクライアントサイドのナビゲーションが行われた場合には、CSRとしてブラウザで実行されます
そのため、 +page.ts
の load
関数はユニバーサル(ブラウザとサーバー両方で実行できる)なJavaScriptのコードであることが期待されます
ただし、データベースへのアクセスなど、どうしてもサーバーサイドで実行したい場合があります。その場合は+page.server.ts
とファイル名をリネームすることで実現が可能です。 +page.server.ts
内の処理はサーバーサイドで実行されることが保証されます
今回作成したアプリ
今回は Todo アプリを作成ました
作成したアプリは Cloudflare Workers にデプロイし、データは Cloudflare D1 に保存しています
なお、今回作成したアプリのソースコードは Github に Push してありますのでご参考ください
事前準備
この記事は以下の内容が既に完了している前提です
- Node.js がインストールされてる
- Cloudflare のアカウント作成済み
- wlangler コマンドがインストールされてる
また、VSCode をお使いの方は以下の Extension が便利だったのでお勧めです
プロジェクトの作成
SvelteKit のプロジェクトを作成します
mkdir svelte-cloudflare-example
npm create svelte@latest .
作成時のオプションは以下を選びました
✔ Where should we create your project?
(leave blank to use current directory) …
✔ Directory not empty. Continue? … yes
✔ Which Svelte app template? › SvelteKit demo app
✔ Add type checking with TypeScript? › Yes, using TypeScript syntax
✔ Add ESLint for code linting? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Playwright for browser testing? … No / Yes
✔ Add Vitest for unit testing? … No / Yes
依存関係のパッケージをインストールし、ローカルDEVサーバーを起動します
npm install
npm run dev -- --open
アダプターの追加
Cloudflare Workers にデプロイするためのアダプタを導入します
npm i -D @sveltejs/adapter-cloudflare-workers
wrangler.toml
を作成し、以下の内容を記述します
name = "<your-service-name>"
account_id = "<your-account-id>"
compatibility_flags = []
workers_dev = true
compatibility_date = "2023-01-28"
main = "./.cloudflare/worker.js"
site.bucket = "./.cloudflare/public"
build.command = "npm run build"
<your-service-name>
は任意の名前を使用できます
<your-account-id>
は Cloudflare の Workers ページの右側のパネルに記載されています(執筆時点)
Cloudflare D1の作成
TODOデータ保存用の Cloudflare D1 データベース を作成します。
今回はローカルで開発中もCloudflare D1に接続して開発を行うので、本番運用と開発用二つのデータベースが必要です
npx wrangler d1 create todo
npx wrangler d1 create todo_preview
コマンド実行後の出力に従って、 wrangler.toml にD1をバインドするための設定を追加します
[[ d1_databases ]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "todo"
database_id = "データベースID"
preview_database_id = "開発用(Preview)のデータベースID"
次にtodoデータを格納するテーブルを作成するSQLを用意します
sql/schema.sql
DROP TABLE IF EXISTS Todos;
CREATE TABLE Todos (id INTEGER PRIMARY KEY AUTOINCREMENT, TITLE TEXT, created TIMESTAMP DEFAULT (datetime(CURRENT_TIMESTAMP,'localtime')));
wrangler コマンドで作成したSQLを実行します
wrangler d1 execute todo --file=./sql/schema.sql
開発サーバーを起動
開発サーバーを起動します。
D1等の Cloudflare のサービスは wrangler の dev サーバー経由でないと使えないので、ローカルでテストする際は、 wrangler dev
を利用していください
SvelteKit のdevサーバーのデフォルトポートは 5173 ですが、wrangler の dev サーバーは 8787 ポートで待ち受けるので注意してください
wrangler dev
SvelteKit から Cloudflare D1 を呼び出す
src/app.d.ts を編集し Cloudflare D1 を使用するための設定を追加します
wrangler.toml に追加したD1 の binding の名前 app.d.ts の Platform に定義し、 D1Databse
の型を指定します。
下記の例なら TODO_LIST
です
wrangler.toml
[[ d1_databases ]]
binding = "TODO_LIST"
app.d.ts
/// <reference types="@sveltejs/kit" />
/// <reference types="@sveltejs/adapter-cloudflare-workers" />
declare namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
interface Platform {
env: {
TODO_LIST: D1Database;
};
}
}
上記の設定を行うと、 load 関数に渡される platform 引数の env フィールドから Cloudflare D1 を操作できるオブジェクトにアクセスできます
次のコードは load 関数の処理の中で D1 にアクセスし SELECT
分でデータを取得する例です。 all
関数で取得したデータはオブジェクトの配列が返却されます
import type { Todo } from '$lib/types';
export const load = (async ({ platform }) => {
const res = await platform.env.TODO_LIST.prepare(
'SELECT * FROM Todos ORDER BY created'
).all<Todo>();
return {
todoList: res.results
};
}) satisfies PageServerLoad;
SvelteKitでページを作成する
.svelte
ファイルにページの内容を記述していきます
Svelte ではファイルロード時の処理などは <script>
タグ内に記述します。TypeScript で記述する場合は lang="ts"
属性を指定します
CSSは <style>
タグ内に記述します。 <style>
タグ内に書かれたCSSの適用範囲はその .svelte
ファイル内に限定されます
下記は今回作成したページの一部を抜粋したものになります
<script lang="ts">
export let data: PageData;
</script>
<div>
<ul>
{#each data.todoList as todo}
<li>
<CheckBox label={todo.title} />
</li>
{/each}
</ul>
</div>
<style>
li {
padding: 6px 0;
}
</style>
主に以下の処理を行なっています
<script>
タグ内でデータを取得しています。data
変数をexport let
で宣言することで、+page.ts
のload
関数で取得したデータが格納されます<div>
タグ内でHTMLを記述します。<script>
タグで取得したdata
を使用し、#each
を使って複数行に渡るTodoリストを生成しています<style>
タグ内でCSSを記述します。この中で記述されたスタイルは、この svelte ファイル内に限定されるため、li
タグに対してスタイル指定を行なっています
SvelteKitでデータ取得を行う
今回は D1 からデータ取得を行うので、サーバーサイド(Cloudflare Workers上)でデータを取得する必要があります。
そのため、 +page.ts ではなく +page.server.ts に処理を記述しています
下記は今回作成したデータの処理の一部を抜粋したソースです
import type { Todo } from '$lib/types';
import type { Actions, PageServerLoad } from './$types';
export const load = (async ({ platform }) => {
const res = await platform!.env.TODO_LIST.prepare(
'SELECT * FROM Todos ORDER BY created'
).all<Todo>();
const data = res.results ?? [];
return {
todoList: data
};
}) satisfies PageServerLoad;
主に以下の処理をしてます
load
関数に渡される platform からD1の操作オブジェクトを取得し、そのオブジェクトを使ってデータベースから Todo データを取得します- 取得したデータを画面をレンダーする処理に渡します
SvelteKit で API を作成する
ブラウザ上で実行されるJavaScriptから fetch で呼べるAPIを作成します。
SvelteKit では +server.ts
という名前のファイルに、GET、POST、PUTなど、HTTP のメソッドに応じた関数を export
することで API を作成することができます
以下は今回作成したAPIのソースになります
src/routes/api/todo/[id]/status/+server.ts
import type { RequestHandler } from '../$types';
type RequestBody = {
newStatus: string;
};
export const PUT = (async ({ params, platform, request }) => {
const { id } = params;
const requestBody = await request.json<RequestBody>();
await platform?.env?.TODO_LIST.prepare('UPDATE todos SET status = ? WHERE id = ?')
.bind(requestBody.newStatus === 'done' ? 'done' : 'new', id)
.run();
return new Response(JSON.stringify({ status: 'OK' }));
}) satisfies RequestHandler;
画面からTodoのステータス更新を行なっています
分解すると以下の処理を行なっています
- パスパラメーターから todo の id を取得する
- リクエストボディからアップデートしたい todo のステータスを取得します
- 渡されたパラメーターの情報を使って D1 のデータベースを更新します
Cloudflare workers にデプロイする
最後に Cloudflare Workers に デプロイします
本番用の D1 の初期処理を行い、 wrangler publish
でデプロイ完了です
wrangler d1 execute todo --file=./schemas/schema.sql
wrangler publish
最後に
今回は SvelteKit で作成したアプリを Cloudflare Workers にデプロイしてみました 実際に動作するソースコードをGithubにPushしています
こちらもご参考いただけたら幸いです
参考
SvelteKit + Cloudflare Worekr メモ
Cloudflare WorkersでSSRできるフレームワークを求めて - console.lealog();
kit/packages/adapter-cloudflare-workers at master · sveltejs/kit
Cloudflare Workers + KV + honoで簡単なAPIサーバを作る