git clone https://github.com/webentlib/router.git
To your root folder (same level as package.json
) to get router/
folder.
svelte.config.js
:Add routes: 'router/'
to config.kit.files
:
const config = {
...
kit: {
files: {
...
routes: 'router/',
...
},
...
}
vite.config.js
:Ensure allow: ['..']
in vite.config.ts
, if not —
Add allow: ['..']
to server.fs
:
export default defineConfig({
...
server: {
fs: {
allow: ['..'], // Allow serving files from one level up to the project root
},
},
}
index.ts
Optionally, re-export Routers' vars via your root's index.ts
.
At the very top of index.ts
add:
// ROUTER
export { routeStore, titleStore, h1Store, Sides } from '/router/';
export type { Pattern, Layout, Error } from '/router/';
urls.ts
Create urls.ts
in the root folder (same level as package.json
)
import type { Pattern, Layout, Error } from '/router/types'; // or from '/all.ts' in case you use root index.ts
export const error: Error = () => import('/src/error.svelte');
const layout: Layout = { page: () => import('/src/base.svelte'), error };
const layouts: Layout[] = [layout]; // for nested layouts — [layout, sublayout, ...]
export const patterns: Pattern[] = [
{re: '', page: () => import('/src/home.svelte'), layouts},
]
Router expects
error
andpatterns
to be exported.
error
, layout
and first route in pattern
.svelte
pages for 'error', 'base layout' and 'home', call them as you wanterror
, layout
and first route in patterns
.Layout
and Pattern
There are 4 params, that are common for both 'layout' and 'pattern' declaration:
page
error
js
side
options
So now we have only:
{re: '', page: () => import('/src/home.svelte'), layouts},
load
function in home.svelte
s' <script module>
, just like it was in the good old Sapper.home.svelte
:
<script module>
export async function load({ url, params, data, fetch, setHeaders, depends, parent, untrack }) {
const response = await fetch(`/api/articles/`);
const articles = await response.json();
return {articles}
}
</script>
load
function in separate file, and specify its' path like this:{re: '', page: () => import('/src/home.svelte'), js: () => import('/src/home.js'), layouts},
load
, add side
param to pattern:import { Sides } from '/router/'; // or from '/all.ts' in case you use root index.ts
{re: '', page: () => import('/src/home.svelte'), page: () => import('/src/home.js'), side: Sides.SERVER, layouts}
There are 'SERVER', 'CLIENT' and 'UNIVERSAL'.
It could be passed via enum:
export enum Sides {
SERVER = 'SERVER',
CLIENT = 'CLIENT',
UNIVERSAL = 'UNIVERSAL',
}
Or just as string, like side: 'UNIVERSAL'
.
Enum is recommended.
options
param:https://svelte.dev/docs/kit/page-options
{re: 'articles/(<id>[0-9]+)', page: () => import('/src/articles/article.svelte'), layouts},
Here we declare group (<id>[0-9]+)
, where id
is the named param, which is available in:
load
function as in incoming param slugs
objects.import { routeStore } from '/router/';
as $routeStore.slugs
.So load
function using slugs
could look like that:
export async function load({ url, params, data, fetch, setHeaders, depends, parent, untrack, slugs }) {
const response = await fetch(`/api/articles/${slugs.id}/`);
const article = await response.json();
return {article}
}
Or using routeStore
:
import {get} from 'svelte/store';
import { routeStore } from '/router/';
export async function load({ url, params, data, fetch, setHeaders, depends, parent, untrack, slugs }) {
const response = await fetch(`/api/articles/${get(routeStore).slugs}/`);
const article = await response.json();
return {article}
}
If you inspect import type { Pattern } from '/router/types';
you'll see that there are much more options to pass to Pattern
.
There are also:
layout?: string, // E.g: 'CUSTOM', 'CUSTOM'
wrapper?: string, // Can be used for css class for <main>
title?: string, // Can be used for <title>
h1?: string, // Can be used for <h1>
name?: string, // 'id' of a route
extras?: string[], // Any additions to use in layout
That's just variables, that could be defined in one place and used in base layout somehow.
They convert to properties of routeStore
, and it's totally up to you how to use them, but here are an examples of a layout and page on steroids:
urls.ts
:
import { Sides } from '/router/enums.ts';
import type { Pattern, Layout, Error } from '/router/types.ts';
// ENUMS
export enum Layouts {
DEFAULT = 'DEFAULT',
CUSTOM = 'CUSTOM',
BLANK = 'BLANK',
}
export enum Wrappers {
WIDE = 'WIDE',
DEFAULT = 'DEFAULT',
NARROW = 'NARROW',
}
export enum Extras {
GO_TOP = 'GO_TOP',
}
// ERRORS
export const error: Error = () => import('/src/error.svelte');
// DEFAULTS
const layout: Layout = {page: () => import('/src/base.svelte'), error: error}
const layouts: Layout[] = [layout];
// SPECIFIC
const task_layout: Layout = {page: () => import('/src/tasks/task.svelte'), error: error}
const task_layouts: Layout[] = [layout, task_layout];
export const patterns: Pattern[] = [
{
re: '',
page: () => import('/src/tasks/tasks.svelte'),
layouts: layouts,
layout: Layouts.DEFAULT,
extras: [Extras.GO_TOP],
wrapper: Wrappers.DEFAULT
},
{
re: 'articles',
page: () => import('/src/tasks/task_form.svelte'),
layouts: layouts,
layout: Layouts.DEFAULT,
extras: [],
wrapper: Wrappers.DEFAULT,
title: 'Articles',
h1: 'Recent Articles',
name: 'ARTICLES',
},
{
re: 'article/(<id>[0-9]+)',
page: () => import('/src/tasks/results.svelte'),
layouts: task_layouts,
layout: Layouts.CUSTOM,
extras: [Extras.GO_TOP],
wrapper: Wrappers.WIDE
},
]
base.svelte
:
<script>
// ROUTER
import {routeStore, titleStore, h1Store, Layouts, Wrappers, Extras, beforeNavigate} from '/all.ts';
let title = $derived($titleStore || $routeStore.title);
let h1 = $derived($h1Store || $routeStore.h1);
let path = $derived($routeStore.url.pathname + $routeStore.url.search);
let layout = $derived($routeStore.layout);
let wrapper = $derived($routeStore.wrapper);
let name = $derived($routeStore.name);
let extras = $derived($routeStore.extras);
beforeNavigate(() => {
$titleStore = null;
$h1Store = null;
});
// CSRF
import '/lib/csrf.ts';
// CUSTOM
import { Nprogress, GoTop } from '/all.ts';
import Header from '/src/Header.svelte';
let { children } = $props();
</script>
<svelte:head>
<title>{title ? title + ' | My Project' : 'My Project'}</title>
</svelte:head>
{#if layout !== Layouts.BLANK}
<Header/>
{/if}
{#key path}
{#if layout === Layouts.DEFAULT}
<main
class={`Wrapper`}
class:_Wide={wrapper === Wrappers.WIDE}
class:_Default={wrapper === Wrappers.DEFAULT}
class:_Narrow={wrapper === Wrappers.NARROW}
class:_Form={wrapper === Wrappers.FORM}
data-name={name}
>
{#if h1}
<div class="Heading">
<h1 class="Title">{h1}</h1>
</div>
{/if}
{@render children?.()}
</main>
{:else}
{@render children?.()}
{/if}
{/key}
<Nprogress/>
{#if extras?.length && extras.includes(Extras.GO_TOP)}
<GoTop/>
{/if}
article.svelte
:
$effect(() => {
$titleStore = article.name
})