How to make declarative/code-based router instead of file-based router in SvelteKit 2

how-to-make-declarative/code-based-router-instead-of-file-based-router-in-sveltekit-2

Dear dudes. SvelteKit 2 is released. That means that my previous post on that topic is garbage now.

For those, who builds enterprise projects, not just landing-pages, and want to name your files and organise your folders based on domains (DDD), or based on technical convenience, rather than on url structure dictated by “marketing needs”; and get full control and flexibility, like — two different urls match one component etc. — please — welcome.

So. After a full day of research and code I’ve ended up with..

As a first step, let’s check what is going on in SvelteKit’s depths. Open file:
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js Navigate to:

292: prevent_conflicts(routes);

And print:

+ 293: console.log(routes);

So, this complex array of objects it the result of defaults walking through file structure. And that’s exactly what we have to define somehow and somewhere in code.

So, what literally have I done…

I. In root of src/ create routes.js

… where I’ve places both routes array with custom basic rules and router() function that transforms that rules to array of objects that we’ve seen in console.log(routes):

const routes = [
    {id: 'home',    pattern: /^/$/,                              page: 'home.svelte',            layout: 'layout.svelte',         segment: ''},
    {id: 'article', pattern: /^/([^/]+?)/?$/, params: ['slug'], page: 'article/article.svelte', layout: 'article/layout.svelte', parent_layout_segment: ''}
]

export function router(routes_dir) {
    const result = []
    for (const route of routes) {
        const parent = result.find(o => o.segment === route.parent_layout_segment)
        const new_route = {
            parent: parent,
            id: route.id,
            segment: route.segment,
            pattern: route.pattern,
            params: (route.params || []).map((param, index) => ({name: param, matcher: undefined, optional: false, rest: false, chained: false})),
            layout: {
                depth: route.layout_depth || route.depth || 0,
                child_pages: [],
                component:route.layout && routes_dir + route.layout,
                shared: route.layout_js && routes_dir + route.layout_js,
                server: route.layout_server_js && routes_dir + route.layout_server_js,
            },
            error: {
                depth: route.error_depth || route.depth || 0,
                component: route.error && routes_dir + route.error
            },
            leaf: {
                depth: route.leaf_depth || route.depth || 0,
                shared: route.page_js && routes_dir + route.page_js,
                server: route.page_server_js && routes_dir + route.page_server_js,
                component: routes_dir + route.page,
            },
            endpoint: route.server_js && {
                file: routes_dir + route.server_js,
            },
            page: null,  // Have no idea what is it for, but let it be here, just not to forget
        }

        result.push(new_route)
    }
    return result;
}

About params in routes object:

  1. id — just need to be unique. And! It is something that is accessible in $page store from import {page} from '$app/stores';, and can be used as main container css class, etc.
  2. segment — also need to be unique, and it is needed to create nested layouts. So, another rule can use parent_layout_segment to refer parent layout (empty string is also ok).
  3. params — needed in case of dynamic slugs. Each round parentheses in pattern should be presented in params array, like: {pattern: /^/(d+)/(d+)/?$/}, params: ['year', 'month']}
  4. You know, SvelteKit do not have only +page.svelte and +layout.svelte, but also:
  5. +page.js so { ... page_js: 'my_page.js' ... } could be passed in rule;
  6. +page.server.js —> page_server_js;
  7. +server.js —> server_js;
  8. +layout.js —> layout_js;
  9. +layout.server.js —> layout.server_js;
  10. Didn’t notice any changes in playing with some depth param, but, just in case we can pass:
  11. layout_depth;
  12. page_depth;
  13. error_depth;
  14. depth (global);
    Just in case we’ll find one day that it matters)

II. In svelte.config.js

import adapter from '@sveltejs/adapter-auto';
+ import {router} from './routes.js';  // <— add this

/** @type {import('@sveltejs/kit').Config} */
const config = {
+   routes: router(),  // <— add this
    kit: {
        adapter: adapter(),
    }
};
export default config;

III. In svelte.config.js

In already familiar
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js:

1) Replace file system walking result with our code routes

292:    prevent_conflicts(routes);
+ 293:  routes.length = 0;
+ 294:  routes.push(...config.routes);

2) We do not need any magic sorting any more

372:    return {
373:    nodes,
- 374:  routes: sort_routes(routes)
+ 375:  routes: routes
376:    };

IV. Install patch-package, so this changes will be automatically applied in future without manual hacks:

> npm i patch-package
> npx patch-package @sveltejs/kit

package.json:

{
  ...
  "scripts": {
    ...
    "postinstall": "patch-package" // <— add this

That’s all! Easy 😀

Total
1
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
what-is-the-future-of-a-web-dev?-what-is-your destiny?

What is the future of a web dev? What is your destiny?

Next Post
creating-a-telegram-chatbot-with-chatgpt:-a-step-by-step-guide

Creating a Telegram chatbot with ChatGPT: A Step-by-Step Guide

Related Posts