SvelteKitでtype safeなナビゲーションを作ってみる
最近nextjsからsveltekitに乗り換えしていて、すごく気にいっています。
きっかけ
tansack routerのような👇をsvelteでもやりたいと思い、自前で作ったので、書き残しておきたいと思います。

実装
sveltekitの場合はサーバーとクライアントでの間でのtype safetyにかなり力を入れていて、devサーバーを立ち上げると.sveltekit/typesの中にload関数で返したtypeを生成してくれます。
👇のように$typesエリアスからインポートして使うことができます。

同じような形でcodegenしたらいけるんじゃない?と思い、sveltekitのソースコードを漁ったら、create_manifest_dataという関数を見つけました。
トップディレクトリにcodegen.jsファイルを作成し、devサーバーを立ち上げると同時にnode codegen.jsを実行し、常にファイルの変更を待ち受ける状態にしています。
import create_manifest_data from './node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js';
import { load_config } from './node_modules/@sveltejs/kit/src/core/config/index.js';
import fs from 'node:fs';
import { format } from 'prettier';
const specialRouteIdPattern = /(\/(\(|\[).*(\]|\)))?/g;
async function getRoutes() {
/** @type import('@sveltejs/kit').ValidatedConfig */
const config = await load_config();
/** @type import("@sveltejs/kit").ManifestData */
const manifest = await create_manifest_data({ config });
return manifest.routes;
}
async function generateRouteTypeFile(
/** @type import("@sveltejs/kit").RouteData[] */
routes
) {
const ids = routes
.filter(
(route) => Boolean(route.page))
.map((route) => route.id.split('').join('').replace(specialRouteIdPattern, '')).filter(Boolean);
const type = `export type RouteList = ${ids.map((id) => `"${id}"`).join(' | ')}`;
fs.writeFileSync('./src/lib/type.d.ts', await format(type, { parser: 'typescript' }));
return { routes: ids };
}
const { routes } = await generateRouteTypeFile(await getRoutes());
console.log('Routes:');
console.table(routes);
console.log('Route type file Generated ✅');
console.log('Watching for changes...');
fs.watch('./src/routes', { recursive: true }, async (event, filename) => {
console.log(`Change detected in ${filename} `);
if (event === 'rename') {
const routes = await getRoutes();
await generateRouteTypeFile(routes);
console.log('Route type file Generated ✅');
}
});
簡単にコードの説明すると、公開されてないモジュールを使い、ルートを取得し、[...path]``(gourp)といった特有ルートを除外して、tsコードを生成し、ファイルに書き込む、そしてファイルの変更をまち続ける。
Devサーバーを立ち上げるとsrc/lib/type.d.tsファイルが生成され👇のようなtypeが書き込まれます。
export type RouteList = "/" | "/auth" | "/auth/forgot-password" | "/auth/reset-password" | "/auth/sign-in" | "/auth/sign-up"
sveltekitではreact-routerのようなもののかわりとして、$app/navigationが用意されています。
なので、そのラッパーを書きます。
import { goto } from '$app/navigation';
import type { RouteList } from '../routes/type';
type GotoParams = Parameters<typeof goto>;
export const navigate = (url: RouteList | URL, opt?: GotoParams[1]) => goto(url, opt);
そうすると、👇のようにタイプヒントが現れます。

注意
@sveltejs/kitからexportされているモジュールでは無いため、破壊的な変更が入った場合に動か無くなる可能性があります。
Discussion
記事をSvelteのdiscordチャンネルに投げた結果、vite-plugin-kit-routesというのがあることを教えて貰いました。
めっちゃすごいです!使ってみます