Recently Switched from Next.js to SvelteKit and Loving It
Motivation
I wanted to implement tansack-router typing in Svelte like this👇
So I built my own and wanted to document it here.
Implementation
SvelteKit puts a lot of emphasis on type safety between server and client. When you start the dev server, it generates various types for data returned by load functions in .sveltekit/types
.
You can import and use them from the $types
alias like this 👇
https://kit.svelte.dev/docs/load#page-data
I thought I could do something similar with code generation. Looking through the SvelteKit source code, I found the create_manifest_data
function
export default function create_manifest_data({
config,
fallback = `${runtime_directory}/components`,
cwd = process.cwd()
}) {
const assets = create_assets(config);
const matchers = create_matchers(config, cwd);
const { nodes, routes } = create_routes_and_nodes(cwd, config, fallback);
for (const route of routes) {
for (const param of route.params) {
if (param.matcher && !matchers[param.matcher]) {
throw new Error(`No matcher found for parameter '${param.matcher}' in route ${route.id}`);
}
}
}
return {
assets,
matchers,
nodes,
routes
};
}
I created a codegen.js
file in the project root. It runs node codegen.js
when starting dev server to continually watch for file changes.
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('Generated route type file ✅');
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('Generated route type file ✅');
}
});
To briefly explain the code, we use a module that is not publicly available, get a route, exclude specific routes such as [.... .path] (gourp)
, generate the ts code, write it to the file, and keep waiting for the file to change.
When dev server starts, it generates src/lib/type.d.ts
with route ids like:
export type RouteList = "/" | "/auth" | "/auth/forgot-password" | "/auth/reset-password" | "/auth/sign-in" | "/auth/sign-up"
Since SvelteKit has $app/navigation
instead of react-router, I wrapped it in lib/navigation.ts
file like this👇
import { goto } from '$app/navigation';
import type { RouteList } from '../routes/type';
export const navigate = (url: RouteList | URL, opt?: GotoParams[1]) => goto(url, opt);
Now I get type hints like:
Warning
This module is not exported from @sveltejs/kit, so it may stop working if destructive changes are made.
Top comments (3)
Did / will / would you ever turn this into an npm package?
I posted on svelte's discord server and got a reply that there is already a package called vite-plugin-kit-routes.
I haven't tried it myself yet, but it looks pretty good.
You have got to be kidding me. I spent so many hours yesterday basically trying to write the exact same plugin. Thanks for sharing man. I love having more type-safety and auto-completion everywhere.