SvelteKit: How to make code-based router, instead of file-based router [December 2022]
Tags: svelte, sveltekit
Published: 2022-12-04 02:44:08

Dudes. There are breaking changes in SvelteKit [on December 2022]. 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:
```javascript
292: prevent_conflicts(routes);
```
And print:
```javascript
+ 293: console.log(routes);
```
It'l give us a complex array of objects as 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)`:
```javascript
const routes = [
	{id: '/',       pattern: /^\/$/,                              page: 'home.svelte',            layout: 'layout.svelte',         segment: ''},
	{id: '/[slug]', 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** — need to be unique. Also goes to client manifest.
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:
- `+page.js` so `{ ... page_js: 'my_page.js' ... }` could be passed in rule;
- `+page.server.js` —> `page_server_js`;
- `+server.js` —> `server_js`;
- `+layout.js` —> `layout_js`;
- `+layout.server.js` —> `layout.server_js`;
5. Didn't notice any changes in playing with some `depth` param, but, just in case we can pass:
- `layout_depth`;
- `page_depth`;
- `error_depth`;
- `depth` (global);
Just in case we'll find one day that it matters)

## II. In `svelte.config.js`

```javascript
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 already familiar
`node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js`:

1) Replace file system walking result with our code routes
```javascript
292:    prevent_conflicts(routes);
+ 293:  routes.length = 0;
+ 294:  routes.push(...config.routes);
```

2) We do not need any magic sorting any more
```javascript
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`:
```js
{
  ...
  "scripts": {
    ...
    "postinstall": "patch-package" // <— add this
```

## That's all! Easy :D