SvelteKit を Cloudflare Workers で動かしてみた

2023.02.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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 のルーティングには多くの便利な機能が備わっています。より詳細な情報はドキュメントをご確認ください

ルーティング • Docs • SvelteKit

SvelteKit のサーバーサイド処理

データ取得を行う +page.tsload関数は初回アクセス時にはSSRとしてサーバーで実行され、ページ内のリンククリックなどクライアントサイドのナビゲーションが行われた場合には、CSRとしてブラウザで実行されます

そのため、 +page.tsload 関数はユニバーサル(ブラウザとサーバー両方で実行できる)な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.tsload 関数で取得したデータが格納されます
  • <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 メモ

Svelte と Sveltekit を触ってみた

Cloudflare WorkersでSSRできるフレームワークを求めて - console.lealog();

kit/packages/adapter-cloudflare-workers at master · sveltejs/kit

Cloudflare Workers + KV + honoで簡単なAPIサーバを作る

はじめてのSvelte - 基礎から応用まで | www.twilio.com

D1 client API · Cloudflare D1 docs