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
Top comments (1)
I think you should mention patch-package upfront. I know you said "its the third time i have made that patch" but its not clear.
patch-package will be a no from me. But thanks for showing this is possible, just not in the way i would like it.