@mpen/rerouter 0.3.0 → 0.3.1
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/README.md +4 -0
- package/{src → cli}/bin.test.ts +24 -2
- package/{src → cli}/bin.ts +27 -18
- package/cli/tsconfig.json +9 -0
- package/dist/acorn-k7ED_tOl.js +4968 -0
- package/dist/angular--Iqdw9UJ.js +4057 -0
- package/dist/babel-hfWAujRY.js +9878 -0
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +28 -23
- package/dist/estree-C1Zjnvlw.js +7266 -0
- package/dist/flow-BaD9LyIP.js +52912 -0
- package/dist/glimmer-CvCjW_1V.js +7541 -0
- package/dist/graphql-BdtzBuWh.js +1945 -0
- package/dist/html-DkZtUVbo.js +7137 -0
- package/dist/index.d.ts +19 -6
- package/dist/index.js +135 -27
- package/dist/markdown-Z8Vrc69e.js +6876 -0
- package/dist/meriyah-DeO4stuH.js +7590 -0
- package/dist/postcss-BmgGJ0E5.js +6777 -0
- package/dist/prettier-BT_F8kIx.js +15629 -0
- package/dist/typescript-DtIxStjy.js +22936 -0
- package/dist/yaml-CWOPBY0q.js +5281 -0
- package/examples/App.tsx +18 -49
- package/examples/dist/BlogPost-c10d9w2p.js +1 -0
- package/examples/dist/FetchLoading-534mdrgz.js +1 -0
- package/examples/dist/FetchLoading-sbxbdkre.js +1 -0
- package/examples/dist/Home-a1258p25.js +1 -0
- package/examples/dist/KitchenSink-821mjg0h.js +1 -0
- package/examples/dist/Login-wywx6bp7.js +1 -0
- package/examples/dist/Match-1e72jm5w.js +1 -0
- package/examples/dist/NotFound-smxj24jw.js +1 -0
- package/examples/dist/SlowLoading-59xxmbfk.js +1 -0
- package/examples/dist/index-0d4kj0rv.js +2 -0
- package/examples/dist/index-3x197sbt.js +9 -0
- package/examples/dist/index-a2hkfx1n.js +9 -0
- package/examples/dist/index-d21me1mc.js +9 -0
- package/examples/dist/index-ktqdknsn.js +2 -0
- package/examples/dist/index-p53qxxzd.js +2 -0
- package/examples/dist/index.html +67 -0
- package/examples/routes.gen.ts +66 -86
- package/examples/routes.ts +2 -2
- package/examples/server/serve-dist.ts +33 -0
- package/examples/server/tsconfig.json +9 -0
- package/package.json +11 -6
- package/src/components/Link.tsx +8 -6
- package/src/components/Router.test.tsx +183 -0
- package/src/components/Router.tsx +161 -29
- package/src/lib/routes.ts +2 -0
- package/tsconfig.json +3 -2
- package/tsdown.config.ts +3 -4
- package/dist/hooks-Dlwcb0sV.js +0 -20
- package/dist/hooks.d.ts +0 -2
- package/dist/hooks.js +0 -2
- package/dist/index-BYXpNitc.d.ts +0 -5
- /package/{src → cli}/fixtures/bin/kitchen-sink.tsx +0 -0
- /package/{src → cli}/fixtures/bin/optional.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Home.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/KitchenSink.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Login.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Match.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/NotFound.tsx +0 -0
- /package/{src → cli}/fixtures/bin/pages/Optional.tsx +0 -0
- /package/{src → cli}/fixtures/bin/regexp-groups.tsx +0 -0
- /package/{src → cli}/fixtures/bin/simple.tsx +0 -0
- /package/{src → cli}/fixtures/bin/unnamed.tsx +0 -0
- /package/dist/{routes-Hpf6cwcZ.js → routes-PW-bNm8e.js} +0 -0
|
@@ -1,18 +1,141 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { startTransition, Suspense, useEffect, useMemo, useState, type ReactNode } from 'react'
|
|
2
2
|
import { useUrlPath } from '../hooks'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
normalizeRoutes,
|
|
5
|
+
type NormalizedRoute,
|
|
6
|
+
type Route,
|
|
7
|
+
type RouteComponent,
|
|
8
|
+
} from '../lib/routes'
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
const DEFAULT_LOADING_DELAY_MS = 400
|
|
11
|
+
const loadedRouteComponents = new WeakMap<Route['component'], RouteComponent<any>>()
|
|
12
|
+
const loadingRouteComponents = new WeakMap<Route['component'], Promise<RouteComponent<any>>>()
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
type RouteMatch = {
|
|
15
|
+
route: NormalizedRoute
|
|
16
|
+
params: Record<string, string | undefined>
|
|
17
|
+
pathname: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type RenderedRoute = RouteMatch & {
|
|
21
|
+
Component: RouteComponent<any>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadRouteComponent(component: Route['component']): Promise<RouteComponent<any>> {
|
|
25
|
+
const loaded = loadedRouteComponents.get(component)
|
|
26
|
+
if (loaded) return Promise.resolve(loaded)
|
|
27
|
+
|
|
28
|
+
let loading = loadingRouteComponents.get(component)
|
|
29
|
+
if (!loading) {
|
|
30
|
+
loading = component().then((module) => {
|
|
31
|
+
loadedRouteComponents.set(component, module.default)
|
|
32
|
+
loadingRouteComponents.delete(component)
|
|
33
|
+
return module.default
|
|
34
|
+
})
|
|
35
|
+
loadingRouteComponents.set(component, loading)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return loading
|
|
39
|
+
}
|
|
8
40
|
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
41
|
+
function findRouteMatch(routes: readonly NormalizedRoute[], pathname: string): RouteMatch | null {
|
|
42
|
+
for (const route of routes) {
|
|
43
|
+
const params = route.matches(pathname)
|
|
44
|
+
if (!params) continue
|
|
45
|
+
return { route, params, pathname }
|
|
14
46
|
}
|
|
15
|
-
|
|
47
|
+
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getLoadedRoute(match: RouteMatch | null): RenderedRoute | null {
|
|
52
|
+
if (!match) return null
|
|
53
|
+
|
|
54
|
+
const Component = loadedRouteComponents.get(match.route.component)
|
|
55
|
+
if (!Component) return null
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...match,
|
|
59
|
+
Component,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function scheduleTransition(cb: () => void): void {
|
|
64
|
+
queueMicrotask(() => {
|
|
65
|
+
startTransition(cb)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function useRenderedRoute(
|
|
70
|
+
match: RouteMatch | null,
|
|
71
|
+
loadingDelayMs: number,
|
|
72
|
+
): { renderedRoute: RenderedRoute | null; showLoading: boolean } {
|
|
73
|
+
const [renderedRoute, setRenderedRoute] = useState(() => getLoadedRoute(match))
|
|
74
|
+
const [showLoading, setShowLoading] = useState(false)
|
|
75
|
+
const [loadError, setLoadError] = useState<unknown>(null)
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!match) {
|
|
79
|
+
scheduleTransition(() => {
|
|
80
|
+
setRenderedRoute(null)
|
|
81
|
+
setShowLoading(false)
|
|
82
|
+
setLoadError(null)
|
|
83
|
+
})
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const loaded = getLoadedRoute(match)
|
|
88
|
+
if (loaded) {
|
|
89
|
+
scheduleTransition(() => {
|
|
90
|
+
setRenderedRoute(loaded)
|
|
91
|
+
setShowLoading(false)
|
|
92
|
+
setLoadError(null)
|
|
93
|
+
})
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let active = true
|
|
98
|
+
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
99
|
+
|
|
100
|
+
scheduleTransition(() => {
|
|
101
|
+
setShowLoading(false)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (loadingDelayMs <= 0) {
|
|
105
|
+
timeout = setTimeout(() => {
|
|
106
|
+
if (active) setShowLoading(true)
|
|
107
|
+
}, 0)
|
|
108
|
+
} else {
|
|
109
|
+
timeout = setTimeout(() => {
|
|
110
|
+
if (active) setShowLoading(true)
|
|
111
|
+
}, loadingDelayMs)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
loadRouteComponent(match.route.component)
|
|
115
|
+
.then((Component) => {
|
|
116
|
+
if (!active) return
|
|
117
|
+
if (timeout) clearTimeout(timeout)
|
|
118
|
+
|
|
119
|
+
startTransition(() => {
|
|
120
|
+
setRenderedRoute({ ...match, Component })
|
|
121
|
+
setShowLoading(false)
|
|
122
|
+
setLoadError(null)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
.catch((error: unknown) => {
|
|
126
|
+
if (!active) return
|
|
127
|
+
setLoadError(error)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return () => {
|
|
131
|
+
active = false
|
|
132
|
+
if (timeout) clearTimeout(timeout)
|
|
133
|
+
}
|
|
134
|
+
}, [loadingDelayMs, match])
|
|
135
|
+
|
|
136
|
+
if (loadError) throw loadError
|
|
137
|
+
|
|
138
|
+
return { renderedRoute, showLoading }
|
|
16
139
|
}
|
|
17
140
|
|
|
18
141
|
/**
|
|
@@ -32,6 +155,14 @@ export interface RouterProps {
|
|
|
32
155
|
* Optional fallback rendered while a matched route component module is loading.
|
|
33
156
|
*/
|
|
34
157
|
loading?: ReactNode
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Delay before rendering [`RouterProps.loading`]{@link RouterProps#loading} for a suspended
|
|
161
|
+
* route, in milliseconds.
|
|
162
|
+
*
|
|
163
|
+
* @defaultValue `400`
|
|
164
|
+
*/
|
|
165
|
+
loadingDelayMs?: number
|
|
35
166
|
}
|
|
36
167
|
|
|
37
168
|
/**
|
|
@@ -45,31 +176,32 @@ export interface RouterProps {
|
|
|
45
176
|
* import routes from './routes'
|
|
46
177
|
*
|
|
47
178
|
* function App() {
|
|
48
|
-
* return <Router routes={routes} loading={<div>Loading...</div>} />
|
|
179
|
+
* return <Router routes={routes} loading={<div>Loading...</div>} loadingDelayMs={400} />
|
|
49
180
|
* }
|
|
50
181
|
* ```
|
|
51
182
|
*/
|
|
52
|
-
export function Router({
|
|
183
|
+
export function Router({
|
|
184
|
+
routes,
|
|
185
|
+
loading = null,
|
|
186
|
+
loadingDelayMs = DEFAULT_LOADING_DELAY_MS,
|
|
187
|
+
}: RouterProps) {
|
|
53
188
|
const pathname = useUrlPath()
|
|
54
189
|
|
|
55
|
-
const normalizedRoutes = useMemo(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Component: getLazyRouteComponent(route.component),
|
|
60
|
-
})),
|
|
61
|
-
[routes],
|
|
190
|
+
const normalizedRoutes = useMemo(() => normalizeRoutes(routes), [routes])
|
|
191
|
+
const match = useMemo(
|
|
192
|
+
() => findRouteMatch(normalizedRoutes, pathname),
|
|
193
|
+
[normalizedRoutes, pathname],
|
|
62
194
|
)
|
|
195
|
+
const { renderedRoute, showLoading } = useRenderedRoute(match, loadingDelayMs)
|
|
63
196
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (!params) continue
|
|
67
|
-
return (
|
|
68
|
-
<Suspense fallback={loading}>
|
|
69
|
-
<Component {...(params as any)} />
|
|
70
|
-
</Suspense>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
197
|
+
if (showLoading) return loading
|
|
198
|
+
if (!renderedRoute) return null
|
|
73
199
|
|
|
74
|
-
|
|
200
|
+
const { Component, params } = renderedRoute
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<Suspense fallback={loading}>
|
|
204
|
+
<Component {...(params as any)} />
|
|
205
|
+
</Suspense>
|
|
206
|
+
)
|
|
75
207
|
}
|
package/src/lib/routes.ts
CHANGED
package/tsconfig.json
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"jsx": "react-jsx",
|
|
5
5
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
6
|
-
"types": ["
|
|
6
|
+
"types": ["react", "react-dom"]
|
|
7
7
|
},
|
|
8
|
-
"include": ["src/**/*", "examples
|
|
8
|
+
"include": ["src/**/*", "examples/**/*.ts", "examples/**/*.tsx"],
|
|
9
|
+
"exclude": ["**/*.test.*", "cli/**/*", "examples/dist/**/*", "examples/server/**/*"]
|
|
9
10
|
}
|
package/tsdown.config.ts
CHANGED
|
@@ -4,18 +4,17 @@ import { defineConfig } from 'tsdown'
|
|
|
4
4
|
export default defineConfig({
|
|
5
5
|
entry: {
|
|
6
6
|
index: 'src/index.ts',
|
|
7
|
-
|
|
8
|
-
bin: 'src/bin.ts',
|
|
7
|
+
bin: 'cli/bin.ts',
|
|
9
8
|
},
|
|
10
9
|
format: 'esm',
|
|
11
|
-
platform: '
|
|
10
|
+
platform: 'neutral',
|
|
12
11
|
deps: {
|
|
13
12
|
neverBundle: [/^(node|bun):/, 'react'],
|
|
14
13
|
},
|
|
15
14
|
exports: {
|
|
16
15
|
legacy: true,
|
|
17
16
|
bin: {
|
|
18
|
-
rerouter: './
|
|
17
|
+
rerouter: './cli/bin.ts',
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
dts: true, // The client must use "moduleResolution": "bundler", "node16" or "nodenext". "node" will not resolve the types properly.
|
package/dist/hooks-Dlwcb0sV.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useMemo, useSyncExternalStore } from "react";
|
|
2
|
-
//#region src/hooks/useUrl.ts
|
|
3
|
-
const getPathname = () => window.location.pathname;
|
|
4
|
-
const getSearch = () => window.location.search;
|
|
5
|
-
function subscribe(cb) {
|
|
6
|
-
const handler = () => cb();
|
|
7
|
-
window.addEventListener("popstate", handler);
|
|
8
|
-
return () => {
|
|
9
|
-
window.removeEventListener("popstate", handler);
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
function useUrlPath() {
|
|
13
|
-
return useSyncExternalStore(subscribe, getPathname, getPathname);
|
|
14
|
-
}
|
|
15
|
-
function useUrlSearchParams() {
|
|
16
|
-
const search = useSyncExternalStore(subscribe, getSearch, getSearch);
|
|
17
|
-
return useMemo(() => new URLSearchParams(search), [search]);
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
|
-
export { useUrlSearchParams as n, useUrlPath as t };
|
package/dist/hooks.d.ts
DELETED
package/dist/hooks.js
DELETED
package/dist/index-BYXpNitc.d.ts
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|