@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.
Files changed (66) hide show
  1. package/README.md +4 -0
  2. package/{src → cli}/bin.test.ts +24 -2
  3. package/{src → cli}/bin.ts +27 -18
  4. package/cli/tsconfig.json +9 -0
  5. package/dist/acorn-k7ED_tOl.js +4968 -0
  6. package/dist/angular--Iqdw9UJ.js +4057 -0
  7. package/dist/babel-hfWAujRY.js +9878 -0
  8. package/dist/bin.d.ts +1 -1
  9. package/dist/bin.js +28 -23
  10. package/dist/estree-C1Zjnvlw.js +7266 -0
  11. package/dist/flow-BaD9LyIP.js +52912 -0
  12. package/dist/glimmer-CvCjW_1V.js +7541 -0
  13. package/dist/graphql-BdtzBuWh.js +1945 -0
  14. package/dist/html-DkZtUVbo.js +7137 -0
  15. package/dist/index.d.ts +19 -6
  16. package/dist/index.js +135 -27
  17. package/dist/markdown-Z8Vrc69e.js +6876 -0
  18. package/dist/meriyah-DeO4stuH.js +7590 -0
  19. package/dist/postcss-BmgGJ0E5.js +6777 -0
  20. package/dist/prettier-BT_F8kIx.js +15629 -0
  21. package/dist/typescript-DtIxStjy.js +22936 -0
  22. package/dist/yaml-CWOPBY0q.js +5281 -0
  23. package/examples/App.tsx +18 -49
  24. package/examples/dist/BlogPost-c10d9w2p.js +1 -0
  25. package/examples/dist/FetchLoading-534mdrgz.js +1 -0
  26. package/examples/dist/FetchLoading-sbxbdkre.js +1 -0
  27. package/examples/dist/Home-a1258p25.js +1 -0
  28. package/examples/dist/KitchenSink-821mjg0h.js +1 -0
  29. package/examples/dist/Login-wywx6bp7.js +1 -0
  30. package/examples/dist/Match-1e72jm5w.js +1 -0
  31. package/examples/dist/NotFound-smxj24jw.js +1 -0
  32. package/examples/dist/SlowLoading-59xxmbfk.js +1 -0
  33. package/examples/dist/index-0d4kj0rv.js +2 -0
  34. package/examples/dist/index-3x197sbt.js +9 -0
  35. package/examples/dist/index-a2hkfx1n.js +9 -0
  36. package/examples/dist/index-d21me1mc.js +9 -0
  37. package/examples/dist/index-ktqdknsn.js +2 -0
  38. package/examples/dist/index-p53qxxzd.js +2 -0
  39. package/examples/dist/index.html +67 -0
  40. package/examples/routes.gen.ts +66 -86
  41. package/examples/routes.ts +2 -2
  42. package/examples/server/serve-dist.ts +33 -0
  43. package/examples/server/tsconfig.json +9 -0
  44. package/package.json +11 -6
  45. package/src/components/Link.tsx +8 -6
  46. package/src/components/Router.test.tsx +183 -0
  47. package/src/components/Router.tsx +161 -29
  48. package/src/lib/routes.ts +2 -0
  49. package/tsconfig.json +3 -2
  50. package/tsdown.config.ts +3 -4
  51. package/dist/hooks-Dlwcb0sV.js +0 -20
  52. package/dist/hooks.d.ts +0 -2
  53. package/dist/hooks.js +0 -2
  54. package/dist/index-BYXpNitc.d.ts +0 -5
  55. /package/{src → cli}/fixtures/bin/kitchen-sink.tsx +0 -0
  56. /package/{src → cli}/fixtures/bin/optional.tsx +0 -0
  57. /package/{src → cli}/fixtures/bin/pages/Home.tsx +0 -0
  58. /package/{src → cli}/fixtures/bin/pages/KitchenSink.tsx +0 -0
  59. /package/{src → cli}/fixtures/bin/pages/Login.tsx +0 -0
  60. /package/{src → cli}/fixtures/bin/pages/Match.tsx +0 -0
  61. /package/{src → cli}/fixtures/bin/pages/NotFound.tsx +0 -0
  62. /package/{src → cli}/fixtures/bin/pages/Optional.tsx +0 -0
  63. /package/{src → cli}/fixtures/bin/regexp-groups.tsx +0 -0
  64. /package/{src → cli}/fixtures/bin/simple.tsx +0 -0
  65. /package/{src → cli}/fixtures/bin/unnamed.tsx +0 -0
  66. /package/dist/{routes-Hpf6cwcZ.js → routes-PW-bNm8e.js} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { n as useUrlSearchParams, t as useUrlPath } from "./index-BYXpNitc.js";
2
1
  import { ClassValue } from "@mpen/classcat";
3
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as _$react from "react";
4
3
  import { ComponentType, ReactNode } from "react";
4
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
5
  import { OverrideProps } from "@mpen/ts-types/react";
6
6
  import { Override } from "@mpen/ts-types";
7
7
 
@@ -168,6 +168,7 @@ type RouteObject = {
168
168
  pattern: string | URLPattern;
169
169
  component: RouteComponentLoader<any>;
170
170
  };
171
+ type Routes = RouteObject[];
171
172
  /**
172
173
  * Route definition consumed by [`Router`]{@link Router}.
173
174
  *
@@ -237,6 +238,13 @@ interface RouterProps {
237
238
  * Optional fallback rendered while a matched route component module is loading.
238
239
  */
239
240
  loading?: ReactNode;
241
+ /**
242
+ * Delay before rendering [`RouterProps.loading`]{@link RouterProps#loading} for a suspended
243
+ * route, in milliseconds.
244
+ *
245
+ * @defaultValue `400`
246
+ */
247
+ loadingDelayMs?: number;
240
248
  }
241
249
  /**
242
250
  * Renders the first route that matches the current URL pathname.
@@ -249,17 +257,22 @@ interface RouterProps {
249
257
  * import routes from './routes'
250
258
  *
251
259
  * function App() {
252
- * return <Router routes={routes} loading={<div>Loading...</div>} />
260
+ * return <Router routes={routes} loading={<div>Loading...</div>} loadingDelayMs={400} />
253
261
  * }
254
262
  * ```
255
263
  */
256
264
  declare function Router({
257
265
  routes,
258
- loading
259
- }: RouterProps): _$react_jsx_runtime0.JSX.Element | null;
266
+ loading,
267
+ loadingDelayMs
268
+ }: RouterProps): string | number | bigint | boolean | _$react_jsx_runtime0.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | _$react.ReactPortal | _$react.ReactElement<unknown, string | _$react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null;
260
269
  //#endregion
261
270
  //#region src/lib/url.d.ts
262
271
  declare function pushUrl(next: string, state?: unknown): void;
263
272
  declare function replaceUrl(next: string, state?: unknown): void;
264
273
  //#endregion
265
- export { Link, LinkProps, NavLink, NavLinkMatch, NavLinkProps, NormalizedRoute, Route, RouteComponent, RouteComponentLoader, RouteComponentModule, RouteObject, RouteParams, Router, RouterProps, SearchParamsInit, normalizeLegacyPathToRegexpSyntax, normalizeRoutes, pushUrl, replaceUrl, useUrlPath, useUrlSearchParams };
274
+ //#region src/hooks/useUrl.d.ts
275
+ declare function useUrlPath(): string;
276
+ declare function useUrlSearchParams(): URLSearchParams;
277
+ //#endregion
278
+ export { Link, LinkProps, NavLink, NavLinkMatch, NavLinkProps, NormalizedRoute, Route, RouteComponent, RouteComponentLoader, RouteComponentModule, RouteObject, RouteParams, Router, RouterProps, Routes, SearchParamsInit, normalizeLegacyPathToRegexpSyntax, normalizeRoutes, pushUrl, replaceUrl, useUrlPath, useUrlSearchParams };
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
- import { n as useUrlSearchParams, t as useUrlPath } from "./hooks-Dlwcb0sV.js";
2
- import { n as normalizeRoutes, t as normalizeLegacyPathToRegexpSyntax } from "./routes-Hpf6cwcZ.js";
1
+ import { n as normalizeRoutes, t as normalizeLegacyPathToRegexpSyntax } from "./routes-PW-bNm8e.js";
3
2
  import { cc } from "@mpen/classcat";
3
+ import { Suspense, startTransition, useEffect, useMemo, useState, useSyncExternalStore } from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
- import { Suspense, lazy, useMemo } from "react";
6
5
  //#region src/lib/url.ts
7
6
  function pushUrl(next, state) {
8
7
  window.history.pushState(state, "", next);
@@ -49,8 +48,10 @@ function Link({ to, search, children, className, replace, ...rest }) {
49
48
  if (ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return;
50
49
  if (ev.button !== 0) return;
51
50
  ev.preventDefault();
52
- if (replace) replaceUrl(href);
53
- else pushUrl(href);
51
+ startTransition(() => {
52
+ if (replace) replaceUrl(href);
53
+ else pushUrl(href);
54
+ });
54
55
  };
55
56
  return /* @__PURE__ */ jsx("a", {
56
57
  ...rest,
@@ -61,6 +62,24 @@ function Link({ to, search, children, className, replace, ...rest }) {
61
62
  });
62
63
  }
63
64
  //#endregion
65
+ //#region src/hooks/useUrl.ts
66
+ const getPathname = () => window.location.pathname;
67
+ const getSearch = () => window.location.search;
68
+ function subscribe(cb) {
69
+ const handler = () => cb();
70
+ window.addEventListener("popstate", handler);
71
+ return () => {
72
+ window.removeEventListener("popstate", handler);
73
+ };
74
+ }
75
+ function useUrlPath() {
76
+ return useSyncExternalStore(subscribe, getPathname, getPathname);
77
+ }
78
+ function useUrlSearchParams() {
79
+ const search = useSyncExternalStore(subscribe, getSearch, getSearch);
80
+ return useMemo(() => new URLSearchParams(search), [search]);
81
+ }
82
+ //#endregion
64
83
  //#region src/components/NavLink.tsx
65
84
  function isActivePath(pathname, targetPathname, match) {
66
85
  if (pathname === targetPathname) return true;
@@ -95,14 +114,107 @@ function NavLink({ activeClass, className, inactiveClass, match = "exact", to, .
95
114
  }
96
115
  //#endregion
97
116
  //#region src/components/Router.tsx
98
- const lazyRouteComponents = /* @__PURE__ */ new WeakMap();
99
- function getLazyRouteComponent(component) {
100
- let LazyComponent = lazyRouteComponents.get(component);
101
- if (!LazyComponent) {
102
- LazyComponent = lazy(component);
103
- lazyRouteComponents.set(component, LazyComponent);
117
+ const DEFAULT_LOADING_DELAY_MS = 400;
118
+ const loadedRouteComponents = /* @__PURE__ */ new WeakMap();
119
+ const loadingRouteComponents = /* @__PURE__ */ new WeakMap();
120
+ function loadRouteComponent(component) {
121
+ const loaded = loadedRouteComponents.get(component);
122
+ if (loaded) return Promise.resolve(loaded);
123
+ let loading = loadingRouteComponents.get(component);
124
+ if (!loading) {
125
+ loading = component().then((module) => {
126
+ loadedRouteComponents.set(component, module.default);
127
+ loadingRouteComponents.delete(component);
128
+ return module.default;
129
+ });
130
+ loadingRouteComponents.set(component, loading);
131
+ }
132
+ return loading;
133
+ }
134
+ function findRouteMatch(routes, pathname) {
135
+ for (const route of routes) {
136
+ const params = route.matches(pathname);
137
+ if (!params) continue;
138
+ return {
139
+ route,
140
+ params,
141
+ pathname
142
+ };
104
143
  }
105
- return LazyComponent;
144
+ return null;
145
+ }
146
+ function getLoadedRoute(match) {
147
+ if (!match) return null;
148
+ const Component = loadedRouteComponents.get(match.route.component);
149
+ if (!Component) return null;
150
+ return {
151
+ ...match,
152
+ Component
153
+ };
154
+ }
155
+ function scheduleTransition(cb) {
156
+ queueMicrotask(() => {
157
+ startTransition(cb);
158
+ });
159
+ }
160
+ function useRenderedRoute(match, loadingDelayMs) {
161
+ const [renderedRoute, setRenderedRoute] = useState(() => getLoadedRoute(match));
162
+ const [showLoading, setShowLoading] = useState(false);
163
+ const [loadError, setLoadError] = useState(null);
164
+ useEffect(() => {
165
+ if (!match) {
166
+ scheduleTransition(() => {
167
+ setRenderedRoute(null);
168
+ setShowLoading(false);
169
+ setLoadError(null);
170
+ });
171
+ return;
172
+ }
173
+ const loaded = getLoadedRoute(match);
174
+ if (loaded) {
175
+ scheduleTransition(() => {
176
+ setRenderedRoute(loaded);
177
+ setShowLoading(false);
178
+ setLoadError(null);
179
+ });
180
+ return;
181
+ }
182
+ let active = true;
183
+ let timeout;
184
+ scheduleTransition(() => {
185
+ setShowLoading(false);
186
+ });
187
+ if (loadingDelayMs <= 0) timeout = setTimeout(() => {
188
+ if (active) setShowLoading(true);
189
+ }, 0);
190
+ else timeout = setTimeout(() => {
191
+ if (active) setShowLoading(true);
192
+ }, loadingDelayMs);
193
+ loadRouteComponent(match.route.component).then((Component) => {
194
+ if (!active) return;
195
+ if (timeout) clearTimeout(timeout);
196
+ startTransition(() => {
197
+ setRenderedRoute({
198
+ ...match,
199
+ Component
200
+ });
201
+ setShowLoading(false);
202
+ setLoadError(null);
203
+ });
204
+ }).catch((error) => {
205
+ if (!active) return;
206
+ setLoadError(error);
207
+ });
208
+ return () => {
209
+ active = false;
210
+ if (timeout) clearTimeout(timeout);
211
+ };
212
+ }, [loadingDelayMs, match]);
213
+ if (loadError) throw loadError;
214
+ return {
215
+ renderedRoute,
216
+ showLoading
217
+ };
106
218
  }
107
219
  /**
108
220
  * Renders the first route that matches the current URL pathname.
@@ -115,25 +227,21 @@ function getLazyRouteComponent(component) {
115
227
  * import routes from './routes'
116
228
  *
117
229
  * function App() {
118
- * return <Router routes={routes} loading={<div>Loading...</div>} />
230
+ * return <Router routes={routes} loading={<div>Loading...</div>} loadingDelayMs={400} />
119
231
  * }
120
232
  * ```
121
233
  */
122
- function Router({ routes, loading = null }) {
234
+ function Router({ routes, loading = null, loadingDelayMs = DEFAULT_LOADING_DELAY_MS }) {
123
235
  const pathname = useUrlPath();
124
- const normalizedRoutes = useMemo(() => normalizeRoutes(routes).map((route) => ({
125
- ...route,
126
- Component: getLazyRouteComponent(route.component)
127
- })), [routes]);
128
- for (const { matches, Component } of normalizedRoutes) {
129
- const params = matches(pathname);
130
- if (!params) continue;
131
- return /* @__PURE__ */ jsx(Suspense, {
132
- fallback: loading,
133
- children: /* @__PURE__ */ jsx(Component, { ...params })
134
- });
135
- }
136
- return null;
236
+ const normalizedRoutes = useMemo(() => normalizeRoutes(routes), [routes]);
237
+ const { renderedRoute, showLoading } = useRenderedRoute(useMemo(() => findRouteMatch(normalizedRoutes, pathname), [normalizedRoutes, pathname]), loadingDelayMs);
238
+ if (showLoading) return loading;
239
+ if (!renderedRoute) return null;
240
+ const { Component, params } = renderedRoute;
241
+ return /* @__PURE__ */ jsx(Suspense, {
242
+ fallback: loading,
243
+ children: /* @__PURE__ */ jsx(Component, { ...params })
244
+ });
137
245
  }
138
246
  //#endregion
139
247
  export { Link, NavLink, Router, normalizeLegacyPathToRegexpSyntax, normalizeRoutes, pushUrl, replaceUrl, useUrlPath, useUrlSearchParams };