Trying Out Type Safe Navigation in SvelteKit

qaynam

qaynam

Posted on January 11, 2024

Trying Out Type Safe Navigation in SvelteKit

Recently Switched from Next.js to SvelteKit and Loving It

Motivation

I wanted to implement tansack-router typing in Svelte like thisπŸ‘‡

nextjs-typesafe-route

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 πŸ‘‡

load-type

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
    };
}


Enter fullscreen mode Exit fullscreen mode

github link

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 βœ…');
    }
});




Enter fullscreen mode Exit fullscreen mode

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"



Enter fullscreen mode Exit fullscreen mode

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);



Enter fullscreen mode Exit fullscreen mode

Now I get type hints like:

sample

Warning

This module is not exported from @sveltejs/kit, so it may stop working if destructive changes are made.

πŸ’– πŸ’ͺ πŸ™… 🚩
qaynam
qaynam

Posted on January 11, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related