toiljs 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/create.js +75 -61
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/channel/channel.d.ts +23 -0
- package/build/client/channel/channel.js +94 -0
- package/build/client/head/head.d.ts +26 -0
- package/build/client/head/head.js +87 -0
- package/build/client/index.d.ts +17 -17
- package/build/client/index.js +10 -10
- package/build/client/navigation/Link.d.ts +8 -0
- package/build/client/navigation/Link.js +44 -0
- package/build/client/navigation/NavLink.d.ts +14 -0
- package/build/client/navigation/NavLink.js +37 -0
- package/build/client/navigation/navigation.d.ts +13 -0
- package/build/client/navigation/navigation.js +97 -0
- package/build/client/navigation/prefetch.d.ts +11 -0
- package/build/client/navigation/prefetch.js +100 -0
- package/build/client/navigation/scroll.d.ts +8 -0
- package/build/client/navigation/scroll.js +36 -0
- package/build/client/routing/Router.d.ts +7 -0
- package/build/client/routing/Router.js +55 -0
- package/build/client/routing/error-boundary.d.ts +16 -0
- package/build/client/routing/error-boundary.js +19 -0
- package/build/client/routing/hooks.d.ts +17 -0
- package/build/client/routing/hooks.js +48 -0
- package/build/client/routing/lazy.d.ts +16 -0
- package/build/client/routing/lazy.js +53 -0
- package/build/client/routing/match.d.ts +2 -0
- package/build/client/routing/match.js +32 -0
- package/build/client/routing/mount.d.ts +2 -0
- package/build/client/routing/mount.js +13 -0
- package/build/client/routing/params-context.d.ts +2 -0
- package/build/client/routing/params-context.js +2 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.js +10 -1
- package/build/compiler/generate.js +18 -8
- package/build/compiler/plugin.js +14 -0
- package/examples/basic/client/components/HoneycombBackground.tsx +162 -0
- package/examples/basic/client/layout.tsx +12 -8
- package/examples/basic/client/public/favicon.ico +0 -0
- package/examples/basic/client/public/index.html +2 -1
- package/package.json +2 -2
- package/src/cli/create.ts +100 -73
- package/src/client/index.ts +17 -17
- package/src/client/{NavLink.tsx → navigation/NavLink.tsx} +1 -1
- package/src/client/{prefetch.ts → navigation/prefetch.ts} +2 -2
- package/src/client/{Router.tsx → routing/Router.tsx} +3 -3
- package/src/client/{error-boundary.tsx → routing/error-boundary.tsx} +1 -1
- package/src/client/{hooks.ts → routing/hooks.ts} +2 -2
- package/src/client/{lazy.ts → routing/lazy.ts} +1 -1
- package/src/client/{mount.tsx → routing/mount.tsx} +3 -3
- package/src/compiler/config.ts +11 -2
- package/src/compiler/generate.ts +24 -8
- package/src/compiler/plugin.ts +19 -0
- package/templates/app/client/404.tsx +11 -0
- package/templates/app/client/components/.gitkeep +1 -0
- package/templates/app/client/components/Footer.tsx +8 -0
- package/templates/app/client/components/HoneycombBackground.tsx +162 -0
- package/templates/app/client/layout.tsx +53 -0
- package/templates/app/client/public/favicon.ico +0 -0
- package/templates/app/client/public/images/.gitkeep +1 -0
- package/templates/app/client/public/images/logo.svg +37 -0
- package/templates/app/client/public/index.html +16 -0
- package/templates/app/client/public/robots.txt +2 -0
- package/templates/app/client/routes/about.tsx +11 -0
- package/templates/app/client/routes/blog/[id].tsx +12 -0
- package/templates/app/client/routes/docs/[...slug].tsx +12 -0
- package/templates/app/client/routes/get-started.tsx +84 -0
- package/templates/app/client/routes/index.tsx +80 -0
- package/templates/app/client/routes/io.tsx +24 -0
- package/templates/app/client/styles/main.css +461 -0
- package/templates/app/client/toil.tsx +7 -0
- package/test/channel.test.ts +1 -1
- package/test/head.test.ts +1 -1
- package/test/navlink.test.ts +1 -1
- package/test/routes.test.ts +1 -1
- /package/src/client/{channel.ts → channel/channel.ts} +0 -0
- /package/src/client/{head.ts → head/head.ts} +0 -0
- /package/src/client/{Link.tsx → navigation/Link.tsx} +0 -0
- /package/src/client/{navigation.ts → navigation/navigation.ts} +0 -0
- /package/src/client/{scroll.ts → navigation/scroll.ts} +0 -0
- /package/src/client/{match.ts → routing/match.ts} +0 -0
- /package/src/client/{params-context.ts → routing/params-context.ts} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RouteDef } from '../types.js';
|
|
2
|
+
declare global {
|
|
3
|
+
interface Navigator {
|
|
4
|
+
readonly connection?: {
|
|
5
|
+
readonly saveData?: boolean;
|
|
6
|
+
readonly effectiveType?: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export declare function prefetch(href: string): void;
|
|
11
|
+
export declare function startPrefetcher(routes: RouteDef[]): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { matchRoute } from '../routing/match.js';
|
|
2
|
+
let routeTable = [];
|
|
3
|
+
const warmed = new WeakSet();
|
|
4
|
+
let io = null;
|
|
5
|
+
let mo = null;
|
|
6
|
+
function routeForHref(href) {
|
|
7
|
+
let url;
|
|
8
|
+
try {
|
|
9
|
+
url = new URL(href, window.location.href);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (url.origin !== window.location.origin)
|
|
15
|
+
return null;
|
|
16
|
+
for (const route of routeTable) {
|
|
17
|
+
if (matchRoute(route.pattern, url.pathname))
|
|
18
|
+
return route;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function warm(route) {
|
|
23
|
+
if (warmed.has(route))
|
|
24
|
+
return;
|
|
25
|
+
warmed.add(route);
|
|
26
|
+
void route.load().catch(() => {
|
|
27
|
+
warmed.delete(route);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function prefetch(href) {
|
|
31
|
+
const route = routeForHref(href);
|
|
32
|
+
if (route)
|
|
33
|
+
warm(route);
|
|
34
|
+
}
|
|
35
|
+
function isPrefetchable(a) {
|
|
36
|
+
if (a.target && a.target !== '_self')
|
|
37
|
+
return false;
|
|
38
|
+
if (a.hasAttribute('download'))
|
|
39
|
+
return false;
|
|
40
|
+
if (a.dataset.noPrefetch !== undefined)
|
|
41
|
+
return false;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
function observeAnchor(a) {
|
|
45
|
+
if (!io || !isPrefetchable(a) || !routeForHref(a.href))
|
|
46
|
+
return;
|
|
47
|
+
io.observe(a);
|
|
48
|
+
}
|
|
49
|
+
function scan(root) {
|
|
50
|
+
root.querySelectorAll('a[href]').forEach((el) => {
|
|
51
|
+
if (el instanceof HTMLAnchorElement)
|
|
52
|
+
observeAnchor(el);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function shouldSkipForConnection() {
|
|
56
|
+
const c = navigator.connection;
|
|
57
|
+
if (!c)
|
|
58
|
+
return false;
|
|
59
|
+
return c.saveData === true || c.effectiveType === 'slow-2g' || c.effectiveType === '2g';
|
|
60
|
+
}
|
|
61
|
+
export function startPrefetcher(routes) {
|
|
62
|
+
routeTable = routes;
|
|
63
|
+
if (typeof window === 'undefined' ||
|
|
64
|
+
typeof IntersectionObserver === 'undefined' ||
|
|
65
|
+
typeof MutationObserver === 'undefined' ||
|
|
66
|
+
io) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (shouldSkipForConnection())
|
|
70
|
+
return;
|
|
71
|
+
io = new IntersectionObserver((entries) => {
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isIntersecting)
|
|
74
|
+
continue;
|
|
75
|
+
const a = entry.target;
|
|
76
|
+
if (a instanceof HTMLAnchorElement) {
|
|
77
|
+
io?.unobserve(a);
|
|
78
|
+
prefetch(a.href);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}, { rootMargin: '200px' });
|
|
82
|
+
mo = new MutationObserver((mutations) => {
|
|
83
|
+
for (const m of mutations) {
|
|
84
|
+
for (const node of m.addedNodes) {
|
|
85
|
+
if (node instanceof HTMLAnchorElement)
|
|
86
|
+
observeAnchor(node);
|
|
87
|
+
else if (node instanceof Element)
|
|
88
|
+
scan(node);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const begin = () => {
|
|
93
|
+
scan(document);
|
|
94
|
+
mo?.observe(document.body, { childList: true, subtree: true });
|
|
95
|
+
};
|
|
96
|
+
if (typeof requestIdleCallback === 'function')
|
|
97
|
+
requestIdleCallback(begin);
|
|
98
|
+
else
|
|
99
|
+
setTimeout(begin, 200);
|
|
100
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function enableManualScrollRestoration(): void;
|
|
2
|
+
export declare function rememberScroll(key: string): void;
|
|
3
|
+
export declare function planScroll(opts: {
|
|
4
|
+
restoreKey?: string;
|
|
5
|
+
hash: string;
|
|
6
|
+
toTop: boolean;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function applyScroll(): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const positions = new Map();
|
|
2
|
+
let plan = null;
|
|
3
|
+
export function enableManualScrollRestoration() {
|
|
4
|
+
if (typeof window !== 'undefined' && 'scrollRestoration' in window.history) {
|
|
5
|
+
window.history.scrollRestoration = 'manual';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function rememberScroll(key) {
|
|
9
|
+
positions.set(key, window.scrollY);
|
|
10
|
+
}
|
|
11
|
+
export function planScroll(opts) {
|
|
12
|
+
plan = {
|
|
13
|
+
restore: opts.restoreKey !== undefined ? (positions.get(opts.restoreKey) ?? 0) : null,
|
|
14
|
+
hash: opts.hash,
|
|
15
|
+
toTop: opts.toTop,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function applyScroll() {
|
|
19
|
+
const current = plan;
|
|
20
|
+
plan = null;
|
|
21
|
+
if (!current)
|
|
22
|
+
return;
|
|
23
|
+
if (current.hash) {
|
|
24
|
+
const el = document.getElementById(decodeURIComponent(current.hash.slice(1)));
|
|
25
|
+
if (el) {
|
|
26
|
+
el.scrollIntoView();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (current.restore !== null) {
|
|
31
|
+
window.scrollTo(0, current.restore);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (current.toTop)
|
|
35
|
+
window.scrollTo(0, 0);
|
|
36
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createElement, Suspense, useLayoutEffect } from 'react';
|
|
3
|
+
import { ErrorBoundary } from './error-boundary.js';
|
|
4
|
+
import { useLocation } from './hooks.js';
|
|
5
|
+
import { errorComponent, loadingComponent, nestedLayout, pageComponent, resolveLayout, resolveNotFound, } from './lazy.js';
|
|
6
|
+
import { matchRoute } from './match.js';
|
|
7
|
+
import { ParamsContext } from './params-context.js';
|
|
8
|
+
import { settleNavigation } from '../navigation/navigation.js';
|
|
9
|
+
import { applyScroll } from '../navigation/scroll.js';
|
|
10
|
+
export function Router(props) {
|
|
11
|
+
const { routes, layout = null, notFound = null } = props;
|
|
12
|
+
const pathname = useLocation();
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
applyScroll();
|
|
15
|
+
settleNavigation();
|
|
16
|
+
});
|
|
17
|
+
let matched;
|
|
18
|
+
let params = {};
|
|
19
|
+
for (const route of routes) {
|
|
20
|
+
const result = matchRoute(route.pattern, pathname);
|
|
21
|
+
if (result) {
|
|
22
|
+
matched = route;
|
|
23
|
+
params = result;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
let content;
|
|
28
|
+
if (matched) {
|
|
29
|
+
const Page = pageComponent(matched);
|
|
30
|
+
const fallback = matched.loading
|
|
31
|
+
? createElement(Suspense, { fallback: null }, createElement(loadingComponent(matched.loading)))
|
|
32
|
+
: null;
|
|
33
|
+
content = (_jsx(Suspense, { fallback: fallback, children: _jsx(Page, {}) }));
|
|
34
|
+
const chain = matched.layouts ?? [];
|
|
35
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
36
|
+
const NestedLayout = nestedLayout(chain[i]);
|
|
37
|
+
content = (_jsx(Suspense, { fallback: null, children: _jsx(NestedLayout, { children: content }) }));
|
|
38
|
+
}
|
|
39
|
+
if (matched.errorComponent) {
|
|
40
|
+
content = (_jsx(ErrorBoundary, { fallback: errorComponent(matched.errorComponent), children: content }));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (notFound) {
|
|
44
|
+
const NotFound = resolveNotFound(notFound);
|
|
45
|
+
content = (_jsx(Suspense, { fallback: null, children: _jsx(NotFound, {}) }));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
content = _jsx("div", { style: { padding: 24, fontFamily: 'system-ui' }, children: "404 \u2014 Not found" });
|
|
49
|
+
}
|
|
50
|
+
if (layout) {
|
|
51
|
+
const Layout = resolveLayout(layout);
|
|
52
|
+
content = (_jsx(Suspense, { fallback: null, children: _jsx(Layout, { children: content }) }));
|
|
53
|
+
}
|
|
54
|
+
return _jsx(ParamsContext.Provider, { value: params, children: content });
|
|
55
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Component, type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import type { RouteErrorProps } from '../types.js';
|
|
3
|
+
interface ErrorBoundaryProps {
|
|
4
|
+
readonly fallback: ComponentType<RouteErrorProps>;
|
|
5
|
+
readonly children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
interface ErrorBoundaryState {
|
|
8
|
+
readonly error: Error | null;
|
|
9
|
+
}
|
|
10
|
+
export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
11
|
+
state: ErrorBoundaryState;
|
|
12
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
13
|
+
reset: () => void;
|
|
14
|
+
render(): ReactNode;
|
|
15
|
+
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Component, Suspense } from 'react';
|
|
3
|
+
export class ErrorBoundary extends Component {
|
|
4
|
+
state = { error: null };
|
|
5
|
+
static getDerivedStateFromError(error) {
|
|
6
|
+
return { error };
|
|
7
|
+
}
|
|
8
|
+
reset = () => {
|
|
9
|
+
this.setState({ error: null });
|
|
10
|
+
};
|
|
11
|
+
render() {
|
|
12
|
+
const { error } = this.state;
|
|
13
|
+
if (error) {
|
|
14
|
+
const Fallback = this.props.fallback;
|
|
15
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(Fallback, { error: error, reset: this.reset }) }));
|
|
16
|
+
}
|
|
17
|
+
return this.props.children;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RouteParams } from './match.js';
|
|
2
|
+
import { navigate, type NavigateOptions } from '../navigation/navigation.js';
|
|
3
|
+
export interface RouterInstance {
|
|
4
|
+
push(href: string, options?: NavigateOptions): void;
|
|
5
|
+
replace(href: string): void;
|
|
6
|
+
back(): void;
|
|
7
|
+
forward(): void;
|
|
8
|
+
refresh(): void;
|
|
9
|
+
prefetch(href: string): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function useParams(): RouteParams;
|
|
12
|
+
export declare function useNavigate(): typeof navigate;
|
|
13
|
+
export declare function useRouter(): RouterInstance;
|
|
14
|
+
export declare function useLocation(): string;
|
|
15
|
+
export declare function usePathname(): string;
|
|
16
|
+
export declare function useSearchParams(): URLSearchParams;
|
|
17
|
+
export declare function useNavigationPending(): boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { startTransition, useContext, useEffect, useMemo, useReducer, useSyncExternalStore, } from 'react';
|
|
2
|
+
import { back, forward, isNavigationPending, navigate, refresh, subscribeLocation, subscribePending, } from '../navigation/navigation.js';
|
|
3
|
+
import { ParamsContext } from './params-context.js';
|
|
4
|
+
import { prefetch } from '../navigation/prefetch.js';
|
|
5
|
+
const ROUTER = {
|
|
6
|
+
push: (href, options) => {
|
|
7
|
+
navigate(href, options);
|
|
8
|
+
},
|
|
9
|
+
replace: (href) => {
|
|
10
|
+
navigate(href, { replace: true });
|
|
11
|
+
},
|
|
12
|
+
back,
|
|
13
|
+
forward,
|
|
14
|
+
refresh,
|
|
15
|
+
prefetch,
|
|
16
|
+
};
|
|
17
|
+
export function useParams() {
|
|
18
|
+
return useContext(ParamsContext);
|
|
19
|
+
}
|
|
20
|
+
export function useNavigate() {
|
|
21
|
+
return navigate;
|
|
22
|
+
}
|
|
23
|
+
export function useRouter() {
|
|
24
|
+
return ROUTER;
|
|
25
|
+
}
|
|
26
|
+
function useLocationSubscription() {
|
|
27
|
+
const [, forceUpdate] = useReducer((n) => n + 1, 0);
|
|
28
|
+
useEffect(() => subscribeLocation(() => {
|
|
29
|
+
startTransition(() => {
|
|
30
|
+
forceUpdate();
|
|
31
|
+
});
|
|
32
|
+
}), []);
|
|
33
|
+
}
|
|
34
|
+
export function useLocation() {
|
|
35
|
+
useLocationSubscription();
|
|
36
|
+
return window.location.pathname;
|
|
37
|
+
}
|
|
38
|
+
export function usePathname() {
|
|
39
|
+
return useLocation();
|
|
40
|
+
}
|
|
41
|
+
export function useSearchParams() {
|
|
42
|
+
useLocationSubscription();
|
|
43
|
+
const search = window.location.search;
|
|
44
|
+
return useMemo(() => new URLSearchParams(search), [search]);
|
|
45
|
+
}
|
|
46
|
+
export function useNavigationPending() {
|
|
47
|
+
return useSyncExternalStore(subscribePending, isNavigationPending, () => false);
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
2
|
+
import type { LayoutComponentLoader, LayoutLoader, NotFoundLoader, RouteDef, RouteErrorProps } from '../types.js';
|
|
3
|
+
type Loader<P> = () => Promise<{
|
|
4
|
+
default: ComponentType<P>;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function loadingComponent(loader: Loader<object>): ComponentType<object>;
|
|
7
|
+
export declare function errorComponent(loader: Loader<RouteErrorProps>): ComponentType<RouteErrorProps>;
|
|
8
|
+
export declare function pageComponent(route: RouteDef): ComponentType;
|
|
9
|
+
export declare function resolveLayout(loader: NonNullable<LayoutLoader>): ComponentType<{
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function nestedLayout(loader: LayoutComponentLoader): ComponentType<{
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function resolveNotFound(loader: NonNullable<NotFoundLoader>): ComponentType;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { lazy } from 'react';
|
|
2
|
+
function memoLazy(cache, loader) {
|
|
3
|
+
let component = cache.get(loader);
|
|
4
|
+
if (!component) {
|
|
5
|
+
component = lazy(loader);
|
|
6
|
+
cache.set(loader, component);
|
|
7
|
+
}
|
|
8
|
+
return component;
|
|
9
|
+
}
|
|
10
|
+
const loadingCache = new Map();
|
|
11
|
+
export function loadingComponent(loader) {
|
|
12
|
+
return memoLazy(loadingCache, loader);
|
|
13
|
+
}
|
|
14
|
+
const errorCache = new Map();
|
|
15
|
+
export function errorComponent(loader) {
|
|
16
|
+
return memoLazy(errorCache, loader);
|
|
17
|
+
}
|
|
18
|
+
const pageCache = new Map();
|
|
19
|
+
export function pageComponent(route) {
|
|
20
|
+
let component = pageCache.get(route);
|
|
21
|
+
if (!component) {
|
|
22
|
+
component = lazy(route.load);
|
|
23
|
+
pageCache.set(route, component);
|
|
24
|
+
}
|
|
25
|
+
return component;
|
|
26
|
+
}
|
|
27
|
+
let layoutComponent = null;
|
|
28
|
+
let layoutLoader = null;
|
|
29
|
+
export function resolveLayout(loader) {
|
|
30
|
+
if (layoutLoader !== loader || !layoutComponent) {
|
|
31
|
+
layoutComponent = lazy(loader);
|
|
32
|
+
layoutLoader = loader;
|
|
33
|
+
}
|
|
34
|
+
return layoutComponent;
|
|
35
|
+
}
|
|
36
|
+
const nestedLayoutCache = new Map();
|
|
37
|
+
export function nestedLayout(loader) {
|
|
38
|
+
let component = nestedLayoutCache.get(loader);
|
|
39
|
+
if (!component) {
|
|
40
|
+
component = lazy(loader);
|
|
41
|
+
nestedLayoutCache.set(loader, component);
|
|
42
|
+
}
|
|
43
|
+
return component;
|
|
44
|
+
}
|
|
45
|
+
let notFoundComponent = null;
|
|
46
|
+
let notFoundLoader = null;
|
|
47
|
+
export function resolveNotFound(loader) {
|
|
48
|
+
if (notFoundLoader !== loader || !notFoundComponent) {
|
|
49
|
+
notFoundComponent = lazy(loader);
|
|
50
|
+
notFoundLoader = loader;
|
|
51
|
+
}
|
|
52
|
+
return notFoundComponent;
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function matchRoute(pattern, pathname) {
|
|
2
|
+
const patternSegs = pattern.split('/').filter(Boolean);
|
|
3
|
+
const pathSegs = pathname.split('/').filter(Boolean);
|
|
4
|
+
const params = {};
|
|
5
|
+
for (let i = 0; i < patternSegs.length; i++) {
|
|
6
|
+
const p = patternSegs[i];
|
|
7
|
+
if (p.startsWith('**')) {
|
|
8
|
+
params[p.slice(2)] = pathSegs
|
|
9
|
+
.slice(i)
|
|
10
|
+
.map((s) => decodeURIComponent(s))
|
|
11
|
+
.join('/');
|
|
12
|
+
return params;
|
|
13
|
+
}
|
|
14
|
+
if (p.startsWith('*')) {
|
|
15
|
+
const rest = pathSegs.slice(i);
|
|
16
|
+
if (rest.length === 0)
|
|
17
|
+
return null;
|
|
18
|
+
params[p.slice(1)] = rest.map((s) => decodeURIComponent(s)).join('/');
|
|
19
|
+
return params;
|
|
20
|
+
}
|
|
21
|
+
if (i >= pathSegs.length)
|
|
22
|
+
return null;
|
|
23
|
+
const value = pathSegs[i];
|
|
24
|
+
if (p.startsWith(':')) {
|
|
25
|
+
params[p.slice(1)] = decodeURIComponent(value);
|
|
26
|
+
}
|
|
27
|
+
else if (p !== value) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return patternSegs.length === pathSegs.length ? params : null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { initNavigation } from '../navigation/navigation.js';
|
|
4
|
+
import { startPrefetcher } from '../navigation/prefetch.js';
|
|
5
|
+
import { Router } from './Router.js';
|
|
6
|
+
export function mount(routes, layout = null, notFound = null) {
|
|
7
|
+
const el = document.getElementById('root');
|
|
8
|
+
if (!el)
|
|
9
|
+
throw new Error('toil: #root element not found');
|
|
10
|
+
initNavigation();
|
|
11
|
+
createRoot(el).render(_jsx(Router, { routes: routes, layout: layout, notFound: notFound }));
|
|
12
|
+
startPrefetcher(routes);
|
|
13
|
+
}
|