toiljs 0.0.14 → 0.0.16
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/.babelrc +13 -13
- package/.gitattributes +2 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
- package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -90
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- package/.github/PULL_REQUEST_TEMPLATE.md +43 -43
- package/.github/changelog-config.json +45 -45
- package/.github/dependabot.yml +27 -27
- package/.github/workflows/ci.yml +191 -191
- package/.prettierrc.json +11 -11
- package/.vscode/settings.json +9 -9
- package/CHANGELOG.md +5 -5
- package/LICENSE +187 -187
- package/README.md +339 -315
- package/as-pect.asconfig.json +34 -34
- package/as-pect.config.js +65 -65
- package/assets/logo.svg +36 -36
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +2926 -191
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/devtools.d.ts +6 -0
- package/build/client/dev/devtools.js +442 -0
- package/build/client/dev/error-overlay.d.ts +9 -0
- package/build/client/dev/error-overlay.js +19 -4
- package/build/client/head/metadata.d.ts +3 -1
- package/build/client/head/metadata.js +8 -0
- package/build/client/index.d.ts +4 -4
- package/build/client/index.js +2 -2
- package/build/client/navigation/navigation.d.ts +2 -0
- package/build/client/navigation/navigation.js +9 -1
- package/build/client/navigation/prefetch.d.ts +1 -0
- package/build/client/navigation/prefetch.js +35 -0
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/hooks.js +6 -2
- package/build/client/routing/loader.d.ts +25 -0
- package/build/client/routing/loader.js +53 -7
- package/build/client/routing/mount.js +4 -3
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/config.d.ts +18 -0
- package/build/compiler/config.js +8 -0
- package/build/compiler/docs.js +16 -16
- package/build/compiler/generate.js +3 -0
- package/build/compiler/index.d.ts +2 -2
- package/build/compiler/index.js +3 -1
- package/build/compiler/plugin.js +156 -0
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +2 -1
- package/build/compiler/seo.d.ts +2 -2
- package/build/compiler/seo.js +8 -6
- package/build/compiler/ssg.d.ts +5 -0
- package/build/compiler/ssg.js +121 -0
- package/build/io/.tsbuildinfo +1 -1
- package/build/logger/.tsbuildinfo +1 -1
- package/build/shared/.tsbuildinfo +1 -1
- package/eslint.config.js +48 -48
- package/examples/basic/client/404.tsx +11 -11
- package/examples/basic/client/components/.gitkeep +1 -1
- package/examples/basic/client/global-error.tsx +13 -13
- package/examples/basic/client/layout.tsx +25 -25
- package/examples/basic/client/public/images/.gitkeep +1 -1
- package/examples/basic/client/public/images/logo.svg +36 -36
- package/examples/basic/client/public/robots.txt +2 -2
- package/examples/basic/client/routes/docs/[...slug].tsx +12 -12
- package/examples/basic/client/routes/features/error/error.tsx +16 -16
- package/examples/basic/client/routes/features/template/b.tsx +14 -14
- package/examples/basic/client/routes/files/[[...slug]].tsx +21 -21
- package/examples/basic/client/routes/gallery/layout.tsx +13 -13
- package/examples/basic/client/routes/io.tsx +24 -24
- package/examples/basic/client/routes/loader-demo/loading.tsx +13 -13
- package/examples/basic/client/routes/search.tsx +61 -61
- package/examples/basic/client/toil.tsx +5 -5
- package/package.json +155 -147
- package/presets/eslint.js +88 -88
- package/presets/no-uint8array-tostring.js +200 -200
- package/presets/prettier.json +18 -18
- package/presets/tsconfig.json +37 -37
- package/src/backend/index.ts +160 -160
- package/src/cli/proc.ts +50 -50
- package/src/cli/updates.ts +69 -69
- package/src/cli/validate.ts +31 -31
- package/src/client/channel/channel.ts +146 -146
- package/src/client/components/Form.tsx +65 -65
- package/src/client/components/Script.tsx +113 -113
- package/src/client/components/Slot.tsx +21 -21
- package/src/client/dev/devtools.tsx +973 -0
- package/src/client/dev/error-overlay.tsx +30 -4
- package/src/client/head/head.ts +167 -167
- package/src/client/head/metadata.ts +19 -1
- package/src/client/index.ts +19 -9
- package/src/client/navigation/NavLink.tsx +86 -86
- package/src/client/navigation/navigation.ts +25 -5
- package/src/client/navigation/prefetch.ts +169 -130
- package/src/client/navigation/scroll.ts +53 -53
- package/src/client/routing/Router.tsx +8 -2
- package/src/client/routing/action.ts +122 -122
- package/src/client/routing/error-boundary.tsx +43 -43
- package/src/client/routing/hooks.ts +21 -6
- package/src/client/routing/loader.ts +325 -225
- package/src/client/routing/match.ts +47 -47
- package/src/client/routing/mount.tsx +54 -52
- package/src/client/routing/params-context.ts +10 -10
- package/src/client/routing/slot-context.ts +7 -7
- package/src/client/search/search.ts +189 -189
- package/src/client/search/use-page-search.ts +73 -73
- package/src/client/types.ts +73 -73
- package/src/compiler/config.ts +47 -1
- package/src/compiler/docs.ts +228 -228
- package/src/compiler/generate.ts +394 -391
- package/src/compiler/index.ts +64 -54
- package/src/compiler/pages.ts +70 -70
- package/src/compiler/plugin.ts +170 -2
- package/src/compiler/prerender.ts +5 -1
- package/src/compiler/seo.ts +23 -7
- package/src/compiler/ssg.ts +162 -0
- package/src/io/BinaryReader.ts +340 -340
- package/src/io/BinaryWriter.ts +385 -385
- package/src/io/FastMap.ts +127 -127
- package/src/io/index.ts +11 -11
- package/src/io/lengths.ts +14 -14
- package/src/io/types.ts +18 -18
- package/src/logger/index.ts +22 -22
- package/src/server/index.ts +10 -10
- package/src/server/main.ts +13 -13
- package/src/server/tsconfig.json +4 -4
- package/src/shared/index.ts +10 -10
- package/std/client/index.d.ts +15 -15
- package/std/client/package.json +3 -3
- package/test/assembly/example.spec.ts +7 -7
- package/test/channel.test.ts +21 -21
- package/test/dom/Link.test.tsx +47 -47
- package/test/dom/NavLink.test.tsx +37 -37
- package/test/dom/error-overlay.test.tsx +44 -44
- package/test/dom/loader.test.tsx +121 -121
- package/test/dom/navigation.test.ts +59 -59
- package/test/dom/revalidate.test.tsx +38 -38
- package/test/dom/route-head.test.tsx +78 -78
- package/test/dom/router-loading.test.tsx +44 -44
- package/test/dom/scroll.test.ts +56 -56
- package/test/dom/use-metadata.test.tsx +58 -0
- package/test/io.test.ts +93 -93
- package/test/navlink.test.ts +28 -28
- package/test/placeholder.test.ts +9 -9
- package/test/routes.test.ts +76 -76
- package/test/seo.test.ts +175 -164
- package/test/slot-layouts.test.ts +69 -69
- package/test/ssg.test.ts +36 -0
- package/test/update.test.ts +44 -44
- package/test/validate.test.ts +42 -42
- package/toil-routes.d.ts +7 -0
- package/toilconfig.json +30 -30
- package/tsconfig.backend.json +13 -13
- package/tsconfig.base.json +35 -35
- package/tsconfig.cli.json +13 -13
- package/tsconfig.client.json +14 -14
- package/tsconfig.compiler.json +13 -13
- package/tsconfig.io.json +12 -12
- package/tsconfig.json +22 -22
- package/tsconfig.logger.json +12 -12
- package/tsconfig.server.json +10 -10
- package/tsconfig.shared.json +12 -12
- package/vitest.config.ts +26 -26
- package/.idea/codeStyles/Project.xml +0 -54
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/prettier.xml +0 -7
- package/.idea/toiljs.iml +0 -8
- package/.idea/vcs.xml +0 -6
- package/.toil/entry.tsx +0 -9
- package/.toil/index.html +0 -12
- package/.toil/routes.ts +0 -9
- package/build/cli/configure.d.ts +0 -16
- package/build/cli/configure.js +0 -272
- package/build/cli/create.d.ts +0 -16
- package/build/cli/create.js +0 -420
- package/build/cli/diagnostics.d.ts +0 -55
- package/build/cli/diagnostics.js +0 -333
- package/build/cli/doctor.d.ts +0 -6
- package/build/cli/doctor.js +0 -249
- package/build/cli/features.d.ts +0 -25
- package/build/cli/features.js +0 -107
- package/build/cli/index.d.ts +0 -2
- package/build/cli/proc.d.ts +0 -6
- package/build/cli/proc.js +0 -31
- package/build/cli/ui.d.ts +0 -9
- package/build/cli/ui.js +0 -75
- package/build/cli/update.d.ts +0 -7
- package/build/cli/update.js +0 -117
- package/build/cli/updates.d.ts +0 -10
- package/build/cli/updates.js +0 -45
- package/build/cli/validate.d.ts +0 -4
- package/build/cli/validate.js +0 -19
- package/build/client/Link.d.ts +0 -8
- package/build/client/Link.js +0 -44
- package/build/client/NavLink.d.ts +0 -14
- package/build/client/NavLink.js +0 -37
- package/build/client/Router.d.ts +0 -7
- package/build/client/Router.js +0 -55
- package/build/client/channel.d.ts +0 -23
- package/build/client/channel.js +0 -94
- package/build/client/error-boundary.d.ts +0 -16
- package/build/client/error-boundary.js +0 -19
- package/build/client/head.d.ts +0 -26
- package/build/client/head.js +0 -87
- package/build/client/hooks.d.ts +0 -17
- package/build/client/hooks.js +0 -48
- package/build/client/lazy.d.ts +0 -16
- package/build/client/lazy.js +0 -53
- package/build/client/match.d.ts +0 -2
- package/build/client/match.js +0 -32
- package/build/client/mount.d.ts +0 -2
- package/build/client/mount.js +0 -13
- package/build/client/navigation.d.ts +0 -13
- package/build/client/navigation.js +0 -97
- package/build/client/params-context.d.ts +0 -2
- package/build/client/params-context.js +0 -2
- package/build/client/prefetch.d.ts +0 -11
- package/build/client/prefetch.js +0 -100
- package/build/client/runtime.d.ts +0 -31
- package/build/client/runtime.js +0 -112
- package/build/client/scroll.d.ts +0 -8
- package/build/client/scroll.js +0 -36
- package/toil-env.d.ts +0 -16
|
@@ -2,12 +2,17 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Component, useSyncExternalStore, } from 'react';
|
|
3
3
|
let current = null;
|
|
4
4
|
const listeners = new Set();
|
|
5
|
+
let errorLog = [];
|
|
6
|
+
const MAX_LOG = 50;
|
|
5
7
|
function emit() {
|
|
6
8
|
for (const listener of listeners)
|
|
7
9
|
listener();
|
|
8
10
|
}
|
|
9
11
|
function setDevError(next) {
|
|
10
12
|
current = next;
|
|
13
|
+
if (next) {
|
|
14
|
+
errorLog = [...errorLog, next].slice(-MAX_LOG);
|
|
15
|
+
}
|
|
11
16
|
emit();
|
|
12
17
|
}
|
|
13
18
|
function subscribe(listener) {
|
|
@@ -16,6 +21,10 @@ function subscribe(listener) {
|
|
|
16
21
|
listeners.delete(listener);
|
|
17
22
|
};
|
|
18
23
|
}
|
|
24
|
+
export function getErrorLog() {
|
|
25
|
+
return errorLog;
|
|
26
|
+
}
|
|
27
|
+
export const subscribeErrors = subscribe;
|
|
19
28
|
export function isDevMode() {
|
|
20
29
|
try {
|
|
21
30
|
return Boolean(import.meta.env?.DEV);
|
|
@@ -30,13 +39,14 @@ export function initDevErrorOverlay() {
|
|
|
30
39
|
return;
|
|
31
40
|
windowBound = true;
|
|
32
41
|
window.addEventListener('error', (event) => {
|
|
33
|
-
if (event.error instanceof Error)
|
|
34
|
-
setDevError({ error: event.error, source: 'window' });
|
|
42
|
+
if (event.error instanceof Error) {
|
|
43
|
+
setDevError({ error: event.error, source: 'window', time: Date.now() });
|
|
44
|
+
}
|
|
35
45
|
});
|
|
36
46
|
window.addEventListener('unhandledrejection', (event) => {
|
|
37
47
|
const reason = event.reason;
|
|
38
48
|
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
39
|
-
setDevError({ error, source: 'unhandledrejection' });
|
|
49
|
+
setDevError({ error, source: 'unhandledrejection', time: Date.now() });
|
|
40
50
|
});
|
|
41
51
|
}
|
|
42
52
|
export class DevErrorBoundary extends Component {
|
|
@@ -46,7 +56,12 @@ export class DevErrorBoundary extends Component {
|
|
|
46
56
|
return { crashed: true };
|
|
47
57
|
}
|
|
48
58
|
componentDidCatch(error, info) {
|
|
49
|
-
setDevError({
|
|
59
|
+
setDevError({
|
|
60
|
+
error,
|
|
61
|
+
componentStack: info.componentStack ?? undefined,
|
|
62
|
+
source: 'render',
|
|
63
|
+
time: Date.now(),
|
|
64
|
+
});
|
|
50
65
|
}
|
|
51
66
|
componentDidMount() {
|
|
52
67
|
this.unsubscribe = subscribe(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HeadSpec, type LinkTag, type MetaTag } from './head.js';
|
|
2
2
|
import type { RouteParams } from '../routing/match.js';
|
|
3
3
|
export interface OpenGraph {
|
|
4
4
|
readonly title?: string;
|
|
@@ -27,3 +27,5 @@ export interface GenerateMetadataArgs<T = unknown> {
|
|
|
27
27
|
}
|
|
28
28
|
export type GenerateMetadata<T = unknown> = (args: GenerateMetadataArgs<T>) => Metadata | Promise<Metadata>;
|
|
29
29
|
export declare function resolveMetadata(metadata: Metadata): HeadSpec;
|
|
30
|
+
export declare function useMetadata(metadata: Metadata): void;
|
|
31
|
+
export declare function Metadata(props: Metadata): null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useHead } from './head.js';
|
|
1
2
|
export function resolveMetadata(metadata) {
|
|
2
3
|
const meta = [];
|
|
3
4
|
if (metadata.description !== undefined) {
|
|
@@ -38,3 +39,10 @@ export function resolveMetadata(metadata) {
|
|
|
38
39
|
link.push(...metadata.link);
|
|
39
40
|
return { title: metadata.title, titleTemplate: metadata.titleTemplate, meta, link };
|
|
40
41
|
}
|
|
42
|
+
export function useMetadata(metadata) {
|
|
43
|
+
useHead(resolveMetadata(metadata));
|
|
44
|
+
}
|
|
45
|
+
export function Metadata(props) {
|
|
46
|
+
useMetadata(props);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
package/build/client/index.d.ts
CHANGED
|
@@ -4,12 +4,12 @@ export { Link } from './navigation/Link.js';
|
|
|
4
4
|
export type { LinkProps } from './navigation/Link.js';
|
|
5
5
|
export { NavLink, matchActive } from './navigation/NavLink.js';
|
|
6
6
|
export type { NavLinkProps, NavLinkState } from './navigation/NavLink.js';
|
|
7
|
-
export { navigate, back, forward, refresh, setViewTransitions } from './navigation/navigation.js';
|
|
7
|
+
export { navigate, back, forward, refresh, setViewTransitions, setTransitions, href, } from './navigation/navigation.js';
|
|
8
8
|
export type { NavigateOptions } from './navigation/navigation.js';
|
|
9
9
|
export { useParams, useNavigate, useLocation, usePathname, useSearchParams, useRouter, useNavigationPending, } from './routing/hooks.js';
|
|
10
10
|
export type { RouterInstance } from './routing/hooks.js';
|
|
11
11
|
export { useLoaderData, revalidate, invalidateLoaderData } from './routing/loader.js';
|
|
12
|
-
export type { LoaderArgs, LoaderFunction, LoaderData, Revalidate } from './routing/loader.js';
|
|
12
|
+
export type { LoaderArgs, LoaderFunction, LoaderData, Revalidate, StaticParams, GenerateStaticParams, } from './routing/loader.js';
|
|
13
13
|
export { useAction } from './routing/action.js';
|
|
14
14
|
export type { UseActionOptions, ActionState, ActionHandle, RevalidateTarget, } from './routing/action.js';
|
|
15
15
|
export { prefetch } from './navigation/prefetch.js';
|
|
@@ -20,8 +20,8 @@ export { connectChannel, useChannel, resolveChannelUrl } from './channel/channel
|
|
|
20
20
|
export type { Channel, ChannelOptions, ChannelHook, ChannelData } from './channel/channel.js';
|
|
21
21
|
export { useHead, useTitle, Head, mergeHead } from './head/head.js';
|
|
22
22
|
export type { HeadSpec, MetaTag, LinkTag, ResolvedHead } from './head/head.js';
|
|
23
|
-
export { resolveMetadata } from './head/metadata.js';
|
|
24
|
-
export type {
|
|
23
|
+
export { resolveMetadata, useMetadata, Metadata } from './head/metadata.js';
|
|
24
|
+
export type { GenerateMetadata, GenerateMetadataArgs, OpenGraph } from './head/metadata.js';
|
|
25
25
|
export { searchPages, registerPages, getPages, pagePath } from './search/search.js';
|
|
26
26
|
export type { PageMeta, PageSearchResult, PageSearchOptions, SearchField, SearchHints, } from './search/search.js';
|
|
27
27
|
export { usePageSearch } from './search/use-page-search.js';
|
package/build/client/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { mount } from './routing/mount.js';
|
|
|
2
2
|
export { Router } from './routing/Router.js';
|
|
3
3
|
export { Link } from './navigation/Link.js';
|
|
4
4
|
export { NavLink, matchActive } from './navigation/NavLink.js';
|
|
5
|
-
export { navigate, back, forward, refresh, setViewTransitions } from './navigation/navigation.js';
|
|
5
|
+
export { navigate, back, forward, refresh, setViewTransitions, setTransitions, href, } from './navigation/navigation.js';
|
|
6
6
|
export { useParams, useNavigate, useLocation, usePathname, useSearchParams, useRouter, useNavigationPending, } from './routing/hooks.js';
|
|
7
7
|
export { useLoaderData, revalidate, invalidateLoaderData } from './routing/loader.js';
|
|
8
8
|
export { useAction } from './routing/action.js';
|
|
@@ -10,7 +10,7 @@ export { prefetch } from './navigation/prefetch.js';
|
|
|
10
10
|
export { matchRoute } from './routing/match.js';
|
|
11
11
|
export { connectChannel, useChannel, resolveChannelUrl } from './channel/channel.js';
|
|
12
12
|
export { useHead, useTitle, Head, mergeHead } from './head/head.js';
|
|
13
|
-
export { resolveMetadata } from './head/metadata.js';
|
|
13
|
+
export { resolveMetadata, useMetadata, Metadata } from './head/metadata.js';
|
|
14
14
|
export { searchPages, registerPages, getPages, pagePath } from './search/search.js';
|
|
15
15
|
export { usePageSearch } from './search/use-page-search.js';
|
|
16
16
|
export { Image } from './components/Image.js';
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Href } from '../types.js';
|
|
2
|
+
export declare const href: (path: string) => Href;
|
|
2
3
|
export declare function setViewTransitions(enabled: boolean): void;
|
|
4
|
+
export declare function setTransitions(enabled: boolean): void;
|
|
3
5
|
export declare function isSoftNavigation(): boolean;
|
|
4
6
|
export declare function previousPathname(): string;
|
|
5
7
|
export declare function settleNavigation(): void;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { startTransition } from 'react';
|
|
2
2
|
import { flushSync } from 'react-dom';
|
|
3
3
|
import { enableManualScrollRestoration, planScroll, rememberScroll } from './scroll.js';
|
|
4
|
+
export const href = (path) => path;
|
|
4
5
|
const listeners = new Set();
|
|
5
6
|
let popstateBound = false;
|
|
6
7
|
let viewTransitions = false;
|
|
7
8
|
export function setViewTransitions(enabled) {
|
|
8
9
|
viewTransitions = enabled;
|
|
9
10
|
}
|
|
11
|
+
let navTransitions = false;
|
|
12
|
+
export function setTransitions(enabled) {
|
|
13
|
+
navTransitions = enabled;
|
|
14
|
+
}
|
|
10
15
|
function shouldViewTransition() {
|
|
11
16
|
if (!viewTransitions || typeof document === 'undefined' || typeof window === 'undefined') {
|
|
12
17
|
return false;
|
|
@@ -31,9 +36,12 @@ function notify() {
|
|
|
31
36
|
flushSync(runListeners);
|
|
32
37
|
});
|
|
33
38
|
}
|
|
34
|
-
else {
|
|
39
|
+
else if (navTransitions) {
|
|
35
40
|
startTransition(runListeners);
|
|
36
41
|
}
|
|
42
|
+
else {
|
|
43
|
+
runListeners();
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
let softNav = false;
|
|
39
47
|
let currentPath = typeof window === 'undefined' ? '/' : window.location.pathname;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { prefetchRouteData } from '../routing/loader.js';
|
|
1
2
|
import { matchRoute } from '../routing/match.js';
|
|
2
3
|
let routeTable = [];
|
|
3
4
|
const warmed = new WeakSet();
|
|
5
|
+
const dataWarmed = new Set();
|
|
4
6
|
let io = null;
|
|
5
7
|
let mo = null;
|
|
6
8
|
function routeForHref(href) {
|
|
@@ -32,6 +34,29 @@ export function prefetch(href) {
|
|
|
32
34
|
if (route)
|
|
33
35
|
warm(route);
|
|
34
36
|
}
|
|
37
|
+
export function prefetchData(href) {
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(href, window.location.href);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (url.origin !== window.location.origin)
|
|
46
|
+
return;
|
|
47
|
+
const key = url.pathname + url.search;
|
|
48
|
+
if (dataWarmed.has(key))
|
|
49
|
+
return;
|
|
50
|
+
for (const route of routeTable) {
|
|
51
|
+
const params = matchRoute(route.pattern, url.pathname);
|
|
52
|
+
if (params) {
|
|
53
|
+
dataWarmed.add(key);
|
|
54
|
+
warm(route);
|
|
55
|
+
prefetchRouteData(route, params, url.pathname, url.search);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
35
60
|
function isPrefetchable(a) {
|
|
36
61
|
if (a.target && a.target !== '_self')
|
|
37
62
|
return false;
|
|
@@ -89,6 +114,16 @@ export function startPrefetcher(routes) {
|
|
|
89
114
|
}
|
|
90
115
|
}
|
|
91
116
|
});
|
|
117
|
+
const onIntent = (event) => {
|
|
118
|
+
const target = event.target;
|
|
119
|
+
if (!(target instanceof Element))
|
|
120
|
+
return;
|
|
121
|
+
const a = target.closest('a[href]');
|
|
122
|
+
if (a instanceof HTMLAnchorElement && isPrefetchable(a) && a.href)
|
|
123
|
+
prefetchData(a.href);
|
|
124
|
+
};
|
|
125
|
+
document.addEventListener('pointerover', onIntent, { passive: true });
|
|
126
|
+
document.addEventListener('focusin', onIntent);
|
|
92
127
|
const begin = () => {
|
|
93
128
|
scan(document);
|
|
94
129
|
mo?.observe(document.body, { childList: true, subtree: true });
|
|
@@ -21,7 +21,7 @@ function renderMatched(matched, params, pathname, epoch, keyPrefix) {
|
|
|
21
21
|
const fallback = matched.loading
|
|
22
22
|
? createElement(Suspense, { fallback: null }, createElement(loadingComponent(matched.loading)))
|
|
23
23
|
: null;
|
|
24
|
-
let content = (_jsx(Suspense, { fallback: fallback, children: _jsx(RoutePage, { route: matched, params: params, dataKey: dataKey, epoch: epoch }) }, matched.loading ? `${dataKey}:${String(epoch)}` : undefined));
|
|
24
|
+
let content = (_jsx(Suspense, { fallback: fallback, children: _jsx(RoutePage, { route: matched, params: params, dataKey: dataKey, epoch: epoch }, dataKey) }, matched.loading ? `${dataKey}:${String(epoch)}` : undefined));
|
|
25
25
|
const templates = matched.templates ?? [];
|
|
26
26
|
for (let i = templates.length - 1; i >= 0; i--) {
|
|
27
27
|
const Template = nestedLayout(templates[i]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useContext, useEffect, useMemo, useReducer, useSyncExternalStore } from 'react';
|
|
1
|
+
import { startTransition, useContext, useEffect, useMemo, useReducer, useSyncExternalStore, } from 'react';
|
|
2
2
|
import { back, forward, isNavigationPending, navigate, refresh, subscribeLocation, subscribePending, } from '../navigation/navigation.js';
|
|
3
3
|
import { clearLoaderData, revalidate as revalidateData } from './loader.js';
|
|
4
4
|
import { ParamsContext } from './params-context.js';
|
|
@@ -32,7 +32,11 @@ export function useRouter() {
|
|
|
32
32
|
}
|
|
33
33
|
function useLocationSubscription() {
|
|
34
34
|
const [, forceUpdate] = useReducer((n) => n + 1, 0);
|
|
35
|
-
useEffect(() => subscribeLocation(
|
|
35
|
+
useEffect(() => subscribeLocation(() => {
|
|
36
|
+
startTransition(() => {
|
|
37
|
+
forceUpdate();
|
|
38
|
+
});
|
|
39
|
+
}), []);
|
|
36
40
|
}
|
|
37
41
|
export function useLocation() {
|
|
38
42
|
useLocationSubscription();
|
|
@@ -7,6 +7,8 @@ export interface LoaderArgs {
|
|
|
7
7
|
readonly searchParams: URLSearchParams;
|
|
8
8
|
}
|
|
9
9
|
export type LoaderFunction<T = unknown> = (args: LoaderArgs) => T | Promise<T>;
|
|
10
|
+
export type StaticParams = Record<string, string | string[]>;
|
|
11
|
+
export type GenerateStaticParams = () => StaticParams[] | Promise<StaticParams[]>;
|
|
10
12
|
export type Revalidate = number | false;
|
|
11
13
|
export type LoaderData<T> = T extends (...args: never[]) => infer R ? Awaited<R> : T;
|
|
12
14
|
interface RouteData {
|
|
@@ -14,7 +16,30 @@ interface RouteData {
|
|
|
14
16
|
data: unknown;
|
|
15
17
|
head?: HeadSpec;
|
|
16
18
|
}
|
|
19
|
+
interface Entry {
|
|
20
|
+
status: 'pending' | 'done' | 'error';
|
|
21
|
+
promise: Promise<void>;
|
|
22
|
+
value?: RouteData;
|
|
23
|
+
error?: unknown;
|
|
24
|
+
loadedAt: number;
|
|
25
|
+
revalidate: Revalidate;
|
|
26
|
+
epoch: number;
|
|
27
|
+
hasLoader: boolean;
|
|
28
|
+
prefetched: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function subscribeLoaderCache(listener: () => void): () => void;
|
|
31
|
+
export interface LoaderCacheSnapshot {
|
|
32
|
+
readonly key: string;
|
|
33
|
+
readonly status: Entry['status'];
|
|
34
|
+
readonly hasLoader: boolean;
|
|
35
|
+
readonly revalidate: Revalidate;
|
|
36
|
+
readonly loadedAt: number;
|
|
37
|
+
readonly epoch: number;
|
|
38
|
+
readonly data: unknown;
|
|
39
|
+
}
|
|
40
|
+
export declare function inspectLoaderCache(): LoaderCacheSnapshot[];
|
|
17
41
|
export declare function loaderKey(pathname: string, search: string): string;
|
|
42
|
+
export declare function prefetchRouteData(route: RouteDef, params: RouteParams, pathname: string, search: string): void;
|
|
18
43
|
export declare function readRouteData(route: RouteDef, params: RouteParams, key: string, epoch: number): RouteData;
|
|
19
44
|
export declare function clearLoaderData(): void;
|
|
20
45
|
export declare function invalidateLoaderData(href?: string): void;
|
|
@@ -1,14 +1,45 @@
|
|
|
1
1
|
import { createContext, useContext } from 'react';
|
|
2
2
|
import { resolveMetadata } from '../head/metadata.js';
|
|
3
|
-
import { refresh as rerender } from '../navigation/navigation.js';
|
|
3
|
+
import { navigationEpoch, refresh as rerender } from '../navigation/navigation.js';
|
|
4
4
|
const cache = new Map();
|
|
5
5
|
const MAX_ENTRIES = 32;
|
|
6
|
+
const cacheListeners = new Set();
|
|
7
|
+
let cacheSnapshot = [];
|
|
8
|
+
function emitCache() {
|
|
9
|
+
cacheSnapshot = [...cache.entries()].map(([key, e]) => ({
|
|
10
|
+
key,
|
|
11
|
+
status: e.status,
|
|
12
|
+
hasLoader: e.hasLoader,
|
|
13
|
+
revalidate: e.revalidate,
|
|
14
|
+
loadedAt: e.loadedAt,
|
|
15
|
+
epoch: e.epoch,
|
|
16
|
+
data: e.value?.data,
|
|
17
|
+
}));
|
|
18
|
+
for (const l of cacheListeners)
|
|
19
|
+
l();
|
|
20
|
+
}
|
|
21
|
+
export function subscribeLoaderCache(listener) {
|
|
22
|
+
cacheListeners.add(listener);
|
|
23
|
+
return () => {
|
|
24
|
+
cacheListeners.delete(listener);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function inspectLoaderCache() {
|
|
28
|
+
return cacheSnapshot;
|
|
29
|
+
}
|
|
6
30
|
export function loaderKey(pathname, search) {
|
|
7
31
|
return `${pathname}${search}`;
|
|
8
32
|
}
|
|
9
|
-
|
|
33
|
+
export function prefetchRouteData(route, params, pathname, search) {
|
|
34
|
+
const key = loaderKey(pathname, search);
|
|
35
|
+
const existing = cache.get(key);
|
|
36
|
+
if (existing && existing.status !== 'error')
|
|
37
|
+
return;
|
|
38
|
+
startFetch(route, params, key, navigationEpoch(), search, true);
|
|
39
|
+
}
|
|
40
|
+
async function loadRoute(route, params, search) {
|
|
10
41
|
const mod = await route.load();
|
|
11
|
-
const searchParams = new URLSearchParams(
|
|
42
|
+
const searchParams = new URLSearchParams(search);
|
|
12
43
|
const data = mod.loader ? await mod.loader({ params, searchParams }) : undefined;
|
|
13
44
|
let head;
|
|
14
45
|
if (mod.generateMetadata) {
|
|
@@ -30,11 +61,14 @@ function isStale(entry, epoch) {
|
|
|
30
61
|
return false;
|
|
31
62
|
if (entry.revalidate === false)
|
|
32
63
|
return false;
|
|
33
|
-
if (entry.revalidate === 0)
|
|
64
|
+
if (entry.revalidate === 0) {
|
|
65
|
+
if (entry.prefetched)
|
|
66
|
+
return false;
|
|
34
67
|
return entry.epoch !== epoch;
|
|
68
|
+
}
|
|
35
69
|
return Date.now() - entry.loadedAt >= entry.revalidate * 1000;
|
|
36
70
|
}
|
|
37
|
-
function startFetch(route, params, key, epoch) {
|
|
71
|
+
function startFetch(route, params, key, epoch, search, prefetched = false) {
|
|
38
72
|
const created = {
|
|
39
73
|
status: 'pending',
|
|
40
74
|
promise: Promise.resolve(),
|
|
@@ -42,17 +76,20 @@ function startFetch(route, params, key, epoch) {
|
|
|
42
76
|
revalidate: 0,
|
|
43
77
|
epoch,
|
|
44
78
|
hasLoader: false,
|
|
79
|
+
prefetched,
|
|
45
80
|
};
|
|
46
|
-
created.promise = loadRoute(route, params).then((result) => {
|
|
81
|
+
created.promise = loadRoute(route, params, search).then((result) => {
|
|
47
82
|
created.value = result.data;
|
|
48
83
|
created.revalidate = result.revalidate;
|
|
49
84
|
created.hasLoader = result.hasLoader;
|
|
50
85
|
created.loadedAt = Date.now();
|
|
51
86
|
created.status = 'done';
|
|
87
|
+
emitCache();
|
|
52
88
|
}, (error) => {
|
|
53
89
|
created.error = error;
|
|
54
90
|
created.loadedAt = Date.now();
|
|
55
91
|
created.status = 'error';
|
|
92
|
+
emitCache();
|
|
56
93
|
});
|
|
57
94
|
cache.set(key, created);
|
|
58
95
|
while (cache.size > MAX_ENTRIES) {
|
|
@@ -61,24 +98,31 @@ function startFetch(route, params, key, epoch) {
|
|
|
61
98
|
break;
|
|
62
99
|
cache.delete(oldest);
|
|
63
100
|
}
|
|
101
|
+
emitCache();
|
|
64
102
|
return created;
|
|
65
103
|
}
|
|
66
104
|
export function readRouteData(route, params, key, epoch) {
|
|
105
|
+
const search = typeof window === 'undefined' ? '' : window.location.search;
|
|
67
106
|
let entry = cache.get(key);
|
|
68
107
|
if (entry && entry.status !== 'pending' && isStale(entry, epoch)) {
|
|
69
108
|
entry = undefined;
|
|
70
109
|
}
|
|
71
|
-
entry ??= startFetch(route, params, key, epoch);
|
|
110
|
+
entry ??= startFetch(route, params, key, epoch, search);
|
|
72
111
|
if (entry.status === 'pending')
|
|
73
112
|
throw entry.promise;
|
|
74
113
|
if (entry.status === 'error')
|
|
75
114
|
throw entry.error;
|
|
76
115
|
if (!entry.value)
|
|
77
116
|
throw entry.promise;
|
|
117
|
+
if (entry.prefetched) {
|
|
118
|
+
entry.prefetched = false;
|
|
119
|
+
entry.epoch = epoch;
|
|
120
|
+
}
|
|
78
121
|
return entry.value;
|
|
79
122
|
}
|
|
80
123
|
export function clearLoaderData() {
|
|
81
124
|
cache.clear();
|
|
125
|
+
emitCache();
|
|
82
126
|
}
|
|
83
127
|
function keyForHref(href) {
|
|
84
128
|
if (typeof window === 'undefined')
|
|
@@ -94,11 +138,13 @@ function keyForHref(href) {
|
|
|
94
138
|
export function invalidateLoaderData(href) {
|
|
95
139
|
if (href === undefined) {
|
|
96
140
|
cache.clear();
|
|
141
|
+
emitCache();
|
|
97
142
|
return;
|
|
98
143
|
}
|
|
99
144
|
const key = keyForHref(href);
|
|
100
145
|
if (key !== undefined)
|
|
101
146
|
cache.delete(key);
|
|
147
|
+
emitCache();
|
|
102
148
|
}
|
|
103
149
|
export function revalidate(href) {
|
|
104
150
|
invalidateLoaderData(href);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import {
|
|
3
|
+
import { DevToolbar } from '../dev/devtools.js';
|
|
4
|
+
import { DevErrorBoundary, DevErrorOverlay, initDevErrorOverlay, } from '../dev/error-overlay.js';
|
|
4
5
|
import { initNavigation } from '../navigation/navigation.js';
|
|
5
6
|
import { startPrefetcher } from '../navigation/prefetch.js';
|
|
6
7
|
import { Router } from './Router.js';
|
|
@@ -10,9 +11,9 @@ export function mount(routes, layout = null, notFound = null, globalError = null
|
|
|
10
11
|
throw new Error('toil: #root element not found');
|
|
11
12
|
initNavigation();
|
|
12
13
|
const app = (_jsx(Router, { routes: routes, layout: layout, notFound: notFound, globalError: globalError, slots: slots }));
|
|
13
|
-
if (
|
|
14
|
+
if (import.meta.env.DEV) {
|
|
14
15
|
initDevErrorOverlay();
|
|
15
|
-
createRoot(el).render(_jsxs(_Fragment, { children: [_jsx(DevErrorBoundary, { children: app }), _jsx(DevErrorOverlay, {})] }));
|
|
16
|
+
createRoot(el).render(_jsxs(_Fragment, { children: [_jsx(DevErrorBoundary, { children: app }), _jsx(DevErrorOverlay, {}), _jsx(DevToolbar, { routes: routes, slots: slots })] }));
|
|
16
17
|
}
|
|
17
18
|
else {
|
|
18
19
|
createRoot(el).render(app);
|