SvelteKit: How to make code-based router, instead of file-based router [February 2023]
Max Core
Posted on February 24, 2023
Dear dudes.
It's the third time I've made that patch.
For those who build enterprises and need total control over files/folders and their names/structure. If it's what you've been searching — welcome reading)
In result, there is some urls.js
file with routes declaration:
const layouts = {
'marketing': {
component: 'src/marketing.svelte',
pages: {
'/about': {pattern: /^\/about\/?$/, component: 'src/about.svelte'},
},
},
'app': {
component: 'src/layout.svelte',
layouts: {
'settings': {
component: 'src/settings.svelte',
pages: {
'/privacy': {pattern: /^\/privacy\/?$/, component: 'src/privacy.svelte'},
'/profile': {pattern: /^\/profile\/?$/, component: 'src/profile.svelte'},
},
}
},
pages: {
'/': {pattern: /^\/\/?$/, component: 'src/home.svelte'},
'/[username]': {pattern: /^\/([^/]+?)\/?$/, component: 'src/user.svelte'},
'/post/[slug]': {pattern: /^\/post\/([^/]+?)\/?$/, component: 'src/post.svelte'},
},
},
}
Here we have 2 independent layouts: marketing
and app
.
Each layout has its pages
collection, and app
layout even has nested layout — settings
.
Guess you've mentioned that component
s paths are already nice.
More of that — order is respected. Not only between patterns, but between layout and pages: /privacy
and /profile
matches first, and only then its /[username]
turn.
Along with component
, all other params could be passed:
{
...
universal: 'src/anyname.js'// aka +xxx.js
server: 'src/anyname.js' // aka +xxx.server.js
endpoint: 'src/anyname.js' // aka +server.js
error: 'src/anyname.svelte' // aka +error.svelte
// In case you need full control of params
params: [{name: slug, matcher: undefined, optional: false, rest: false, chained: false},]
}
If it looks nice to you, lets dive to the implementation:
SvelteKit's magic is happening there:
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js
Firstly, some walk()
function creates some routes
object based on file system. Then validates and enriches it.
You can print and see that routes
object.
293: prevent_conflicts(routes);
+ 294: console.log(routes);
You'll see something like:
[
{
id: '/',
segment: '',
pattern: /^\/$/,
params: [],
layout: {
depth: 0,
child_pages: [],
component: 'src/routes/+layout.svelte'
},
error: null,
leaf: null,
page: null,
endpoint: null
},
{
id: '/home',
segment: 'home',
pattern: /^\/home\/?$/,
params: [],
layout: null,
error: null,
leaf: { depth: 1, component: 'src/routes/home/+page.svelte' },
page: null,
endpoint: null
}
]
So, the goal is to build that routes
object somehow ourselves
So, what exactly has to be done?
1) Create urls.js
somewhere (guess in root will be nice) with contents (const layouts
is just for an example):
const layouts = {
'marketing': {
component: 'src/marketing.svelte',
pages: {
'/about': {pattern: /^\/about\/?$/, component: 'src/about.svelte'},
},
},
'app': {
component: 'src/layout.svelte',
layouts: {
'settings': {
component: 'src/settings.svelte',
pages: {
'/privacy': {pattern: /^\/privacy\/?$/, component: 'src/privacy.svelte'},
'/profile': {pattern: /^\/profile\/?$/, component: 'src/profile.svelte'},
},
}
},
pages: {
'/': {pattern: /^\/\/?$/, component: 'src/home.svelte'},
'/[username]': {pattern: /^\/([^/]+?)\/?$/, component: 'src/user.svelte'},
'/post/[slug]': {pattern: /^\/post\/([^/]+?)\/?$/, component: 'src/post.svelte'},
},
},
}
export function routes() {
const result = []
function run(depth, items, parent) {
for (const [id, item] of Object.entries(items)) {
const route = {
id: id,
segment: id.split('/')[1] || id,
pattern: item.pattern,
params: item.params || [],
error: item.error ? {depth: depth, component: item.error} : null,
endpoint: item.endpoint ? { file: item.endpoint } : null,
page: null,
layout: null,
leaf: null,
parent: parent,
}
const details = {
depth: depth,
child_pages: [],
universal: item.universal,
server: item.server,
component: item.component,
}
if (!id.startsWith('/')) { // means — if layout
route.layout = details;
} else {
route.leaf = details;
}
if ((!route.params || !route.params.length)) {
const matches = id.match(/\[([^\]]*)]/g) || [];
for (const match of matches) {
route.params.push({
name: match.replace('[', '').replace(']', ''),
matcher: undefined,
optional: false,
rest: false,
chained: false
})
}
}
result.push(route)
// Do it like this to save order
for (const [field, object] of Object.entries(item)) {
const layouts = (field === 'layouts') ? object : null;
const pages = (field === 'pages') ? object : null;
if (layouts && Object.keys(layouts).length) {
run(depth + 1, layouts, route)
}
if (pages && Object.keys(pages).length) {
run(depth + 1, pages, route)
}
}
}
}
run(0, layouts, null);
return result;
}
2) In svelte.config.js
:
...
import {routes} from './urls.js';
const config = {
routes: routes(),
...
};
3) Open:
node_modules/@sveltejs/kit/src/core/sync/create_manifest_data/index.js
a) Find:
293: prevent_conflicts(routes);
294:
295: const root = routes[0];
Paste between that two lines (right in 294 line):
routes.length = 0;
routes.push(...config.routes);
b) Find:
375: routes: sort_routes(routes)
Replace with just:
routes: routes
No need for additional magic sorting, everything is under control.
Minor notes
1) Layouts names may be anything but unique, and must not start with "/";
2) Pages names must match pattern and start with "/";
If you want, you could 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
Hope it helps!
Posted on February 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 14, 2024