SvelteKitのソースコードを読んでForm Actionsの処理を浅く把握する
モチベーション
SvelteKitのサーバーで何をやっているのかを知りたくなり軽く調べてみたことをまとめておく。
Severクラス。これをインスタンス化する。
例えば、vite dev
コマンドを実行した場合はこちらのプログラム経由でserverインスタンスが起動する
さらに遡ると vite
コマンドを実行すると 下記が呼び出され vite と sveltekit のconfigをロードし、さらに dev
によって上記のファイルが呼び出されている
vite dev
でServerを起動するときに SvelteKitのRouterのパス群を探索し、manifestの routesプロパティにセットする。
標準的な処理としては、svelteの拡張子 .svelte
と モジュールの拡張子 .ts | .js
ファイルを検出し、さらに +page.svelte
, +layout.svelte
, +error.svelte
および +server.ts(js)
, +page.server.ts(js)
, +page.ts(js)
を検出する。
その上で、.svelte
は component route として収集、.ts | .js
は server route または universal route として収集する
探索するパスのルートディレクトリは、svelte.config.js
の kit.files.routes で設定する。
未設定の場合のデフォルトは src/routes
が指定される
route情報の登録では まずComponentかどうか(.svelte ページ)を判定する。Componentは名称で error / layout / leaf (pageのこと?) に分類し自分の階層、相対PATHなどの情報をもつ。layout や leaf の場合は親となる layoutのidなども登録する
componentではなくモジュールの場合(.ts|.js) も同様で layout / leaf / endpoint に分離し、相対PATHを管理する
この処理を routesディレクトリ内のディレクトリとファイルに対して再帰的に実施する。
全てのファイルの設定が終わったあと、 layoutモジュールとerrorモジュールについては対応する componentのパスを登録する (pageモジュールについては行っていない)
また leafについては pageと判定し、関連するlayoutの情報を格納する
この後、vite/dev/index.js に戻った後、rootの情報をベースに component, server, universal の区分を元にファイルをロードして Node情報として Manifestに追加する
リクエストを受信すると、severインスタンスのrepond関数が呼ばれる。その中でさらに repond関数を呼ぶ
respond関数では リクエストパスを元にRequest Eventと route情報を取得し、resolve関数を呼ぶ。
resolve関数では、routeが pageなら、pageモジュールの render_page関数を実行する
routeがendpointなら endpointモジュールの render_endpoint関数を実行する
以後は、render_page関数を確認する
上記以外に リクエストパスの最後が /__data.json
の場合に呼び出される render_data関数があるのだが、このようなリクエストが実行される条件が不明なのだが、処理としては +page.ts の load
関数を呼び出しているようにも見える。
また、 render_page関数は 同 actions
関数を呼び出して処理をしているようにも見えるので、リクエストを呼び出した時点で 何らかの振り分けが行われている可能性がある
render_page関数ではまず、request メソッドが POST の場合URLに対応する action処理を実行し、ActionResultを取得する。
画面を表示するだけの時のメソッドは GETになるのでアクション処理は呼ばれず、次のデータロードが実行される
データロードでは、まずリクエストのパスの最後に /__data.json
をつけ、load_server_data関数と load_data関数を実行し画面の表示に必要なデータを取得する。
どちらの関数でデータを取得するかは モジュールが sever か universal かで異なる。
この後、render_response で、取得したデータおよび action実行時は actionResultも含めページのレンダリングを実行する
アクション処理で返される情報(actionResult)はrender_responseの中で、form_valueとしてdeseriarizeして埋め込まれている模様
render関数の戻り値は、Response型で content-typeは "text/html" なのでhtmlが返される。
これが正しいとするなら、クライアント側でJavascriptが有効で use:enhanceを設定していると レスポンスのhtmlをそのまま使わずに何らかの解釈をしてページを書き換えているような動きだと推測する。
ActionResultは、Response の text (bodyの内容をtext形式で表現したもの)を JSON形式でparseしている
export function deserialize(result) {
const parsed = JSON.parse(result);
if (parsed.data) {
parsed.data = devalue.parse(parsed.data);
}
return parsed;
}
formsモジュールの enhance関数を見てみる
formタグに use:enhance
をつけている場合、submitイベントをこの関数で実行するようになっている。
この関数内では、actionに対してfetchで次のようなリクエストを送信している
const response = await fetch(action, {
method: 'POST',
headers: {
accept: 'application/json',
'x-sveltekit-action': 'true'
},
cache: 'no-store',
body: form_data,
signal: controller.signal
});
headerに accept: application/json
がついているため、 server側の page.indexでは、is_action_json_request
と判定され、handle_action_json_request
関数でJsonオブジェクトを返すだけでレンダリングは行われていない模様
このときに返すJSONは
return action_json({
type: 'success',
status: data ? 200 : 204,
// @ts-expect-error see comment above
data: stringify_action_response(data, /** @type {string} */ (event.route.id))
});
stringify_action_response()
では、Jsonオブジェクトである dataに対して devalue.strigify(data)
を実施している。
SvelteKit-Superforms の enhance は 前述の$apps/formsのenhanceを呼び出す時に 第2引数の submit関数に独自の処理を追加し、そのコールバック関数として validationResponse
関数を呼び出してイベント処理と applyActionを実行している