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というのがあることを教えて貰いました。
めっちゃすごいです!使ってみます