DEV Community

Max Core
Max Core

Posted on • Edited on

SvelteKit: How to make code-based router, instead of file-based router [December 2022]

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:

292: prevent_conflicts(routes); 
Enter fullscreen mode Exit fullscreen mode

And print:

+ 293: console.log(routes); 
Enter fullscreen mode Exit fullscreen mode

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

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; } 
Enter fullscreen mode Exit fullscreen mode

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:
  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; 
Enter fullscreen mode Exit fullscreen mode

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

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

2) We do not need any magic sorting any more

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

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 
Enter fullscreen mode Exit fullscreen mode

package.json:

{ ... "scripts": { ... "postinstall": "patch-package" // <— add this 
Enter fullscreen mode Exit fullscreen mode

That's all! Easy :D

Top comments (2)

Collapse
 
ktibow profile image
Kendell R

but why
also you sound a bit weird, are you a non-native speaker?

Collapse
 
maxcore profile image
Max Core • Edited

Hi! At least because "me as a developer" (sounds like user story :D ), want to organise my code and name files and folders according to project's code-style etc., which was designed to serve "that current enterprise project" needs. So, let's say I just do not want framework to dictate how to name my files and folders, and how to respect its hierarchy. I need total control and flexibility (which only code can give).
And I know, that I am not the only one
So, just for them) I feel their pain too) Probably it would help someone

Ye, I am not native