@solidjs/router 1.0.0-next.10 → 1.0.0-next.11

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/dist/fs.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { RouteSectionComponent } from "./types.js";
2
+ /** A code-split module ref: delivered as a dynamic import. */
3
+ export interface FileRouteLazyRef<M = Record<string, unknown>> {
4
+ src: string;
5
+ import(): Promise<M>;
6
+ }
7
+ /** An eager module ref: its picked exports are imported statically. */
8
+ export interface FileRouteEagerRef<M = Record<string, unknown>> {
9
+ require(): M;
10
+ }
11
+ /** The shape of a nested route-manifest entry the adapter consumes. */
12
+ export interface FileRouteEntry {
13
+ path: string;
14
+ page?: boolean;
15
+ $component?: FileRouteLazyRef<any> | undefined;
16
+ $$route?: FileRouteEagerRef<any> | undefined;
17
+ children?: readonly FileRouteEntry[] | undefined;
18
+ }
19
+ type RouteConfigOf<E> = E extends {
20
+ $$route: {
21
+ require(): {
22
+ route: infer R;
23
+ };
24
+ };
25
+ } ? R : {};
26
+ /**
27
+ * One manifest entry as a route definition. `children` is a required key on
28
+ * purpose: an optional key infers as `C | undefined`, and that union
29
+ * distributes `RoutePaths` into an uncallable shape.
30
+ */
31
+ export type FileRouteFrom<E> = RouteConfigOf<E> & {
32
+ path: E extends {
33
+ path: infer P extends string;
34
+ } ? P : never;
35
+ component: E extends {
36
+ $component: object;
37
+ } ? RouteSectionComponent : undefined;
38
+ children: E extends {
39
+ children: infer C extends readonly FileRouteEntry[];
40
+ } ? FileRoutesFrom<C> : undefined;
41
+ };
42
+ export type FileRoutesFrom<T extends readonly FileRouteEntry[]> = {
43
+ [K in keyof T]: FileRouteFrom<T[K]>;
44
+ };
45
+ /**
46
+ * Converts the nested page entries of a file-system route manifest into
47
+ * route definitions: `$component` refs become code-split `lazy` components
48
+ * (their `src` doubles as the `moduleUrl` core resolves assets and islands
49
+ * against), and each entry's `route` config export is spread into its
50
+ * definition. Pass the result to `createRouter({ routes })`.
51
+ */
52
+ export declare function fileRoutes<const T extends readonly FileRouteEntry[]>(entries: T): FileRoutesFrom<T>;
53
+ export {};
package/dist/fs.js ADDED
@@ -0,0 +1,30 @@
1
+ import { lazy } from "solid-js";
2
+ /**
3
+ * Converts the nested page entries of a file-system route manifest into
4
+ * route definitions: `$component` refs become code-split `lazy` components
5
+ * (their `src` doubles as the `moduleUrl` core resolves assets and islands
6
+ * against), and each entry's `route` config export is spread into its
7
+ * definition. Pass the result to `createRouter({ routes })`.
8
+ */
9
+ export function fileRoutes(entries) {
10
+ const components = new Map();
11
+ const componentOf = (ref) => {
12
+ let component = components.get(ref.src);
13
+ if (!component) {
14
+ component = lazy(ref.import, ref.src);
15
+ components.set(ref.src, component);
16
+ }
17
+ return component;
18
+ };
19
+ const toRoute = (entry) => {
20
+ const config = (entry.$$route?.require().route ?? {});
21
+ return {
22
+ ...config,
23
+ path: entry.path,
24
+ component: entry.$component ? componentOf(entry.$component) : undefined,
25
+ info: { ...config.info, filesystem: true },
26
+ children: entry.children ? entry.children.map(toRoute) : undefined
27
+ };
28
+ };
29
+ return entries.map(toRoute);
30
+ }
package/dist/index.js CHANGED
@@ -1751,10 +1751,18 @@ const STORAGE_KEY = "solid-router:scroll";
1751
1751
  * scroll time (rather than at exit) stays correct through `useBeforeLeave`
1752
1752
  * blocked/reverted traversals. The map persists to sessionStorage on pagehide
1753
1753
  * so restoration survives reloads, which `scrollRestoration = "manual"`
1754
- * otherwise disables. Restoration runs once routing settles; if the document
1755
- * is still shorter than the target (a boundary below the fold hasn't
1756
- * resolved), a ResizeObserver retries as content grows, cancelled by the
1757
- * first user scroll.
1754
+ * otherwise disables.
1755
+ *
1756
+ * Restoration is a single scroll once routing settles — the same strategy
1757
+ * SvelteKit, TanStack Router and React Router use. Settling after the
1758
+ * transition commits is what makes the offset reachable; chasing a still-
1759
+ * growing document afterwards (a ResizeObserver re-asserting the offset as
1760
+ * content arrives) was tried and removed: no peer router does it, an
1761
+ * unbounded observer re-clamps the viewport to the bottom when the target is
1762
+ * never reachable (a list that is genuinely shorter now), and scroll-induced
1763
+ * layout changes can feed it back into itself. Content that commits after the
1764
+ * transition settles — an image without reserved space, a boundary below the
1765
+ * fold — keeps whatever offset the document can hold.
1758
1766
  */
1759
1767
  function createScrollRestoration() {
1760
1768
  window.history.scrollRestoration = "manual";
@@ -1768,19 +1776,11 @@ function createScrollRestoration() {
1768
1776
  const depth = () => window.history.state && window.history.state._depth;
1769
1777
  let programmatic = false;
1770
1778
  let pending;
1771
- let disconnect;
1772
- const cancelGuard = () => {
1773
- disconnect && disconnect();
1774
- disconnect = undefined;
1775
- };
1776
1779
  const unbind = [bindEvent(window, "scroll", () => {
1777
1780
  const d = depth();
1778
1781
  if (d != null) positions[d] = window.scrollY;
1779
- if (!programmatic) {
1780
- // the user took over — a pending or chasing restore would yank them
1781
- pending = undefined;
1782
- cancelGuard();
1783
- }
1782
+ // the user took over — a pending restore would yank them
1783
+ if (!programmatic) pending = undefined;
1784
1784
  }), bindEvent(window, "pagehide", () => {
1785
1785
  try {
1786
1786
  sessionStorage.setItem(STORAGE_KEY, JSON.stringify(positions));
@@ -1791,19 +1791,11 @@ function createScrollRestoration() {
1791
1791
  const y = positions[pending];
1792
1792
  pending = undefined;
1793
1793
  if (y == null) return;
1794
- cancelGuard();
1795
- const attempt = () => {
1796
- programmatic = true;
1797
- window.scrollTo(0, y);
1798
- programmatic = false;
1799
- // reachable once the document is tall enough to hold the offset
1800
- return document.documentElement.scrollHeight - window.innerHeight >= y;
1801
- };
1802
- if (!attempt() && typeof ResizeObserver !== "undefined") {
1803
- const observer = new ResizeObserver(() => attempt() && cancelGuard());
1804
- observer.observe(document.documentElement);
1805
- disconnect = () => observer.disconnect();
1806
- }
1794
+ // flagged so the resulting scroll event is not mistaken for the user
1795
+ // taking over (which cancels a pending restore)
1796
+ programmatic = true;
1797
+ window.scrollTo(0, y);
1798
+ programmatic = false;
1807
1799
  };
1808
1800
  return {
1809
1801
  /** When the adapter notifies a traversal: mark the target for restoration. */
@@ -1821,16 +1813,24 @@ function createScrollRestoration() {
1821
1813
  // isRouting, which reports in-flight transitions — native pops
1822
1814
  // included — and holds the restore until they commit. restore() no-ops
1823
1815
  // unless a traversal marked a target, so push navigations are inert.
1816
+ // `transparent` keeps the effect invisible to the hydration id scheme —
1817
+ // same reasoning as the link-claims effect (claims.ts): this setup is
1818
+ // client-only, so an id-consuming node here has no server counterpart
1819
+ // and every hydration id allocated after it shifts by one child slot.
1820
+ // The visible failure is any <Loading> content that settled before the
1821
+ // shell flush (a cache hit, a preloaded query): its serialized value and
1822
+ // inlined markup are keyed under the server's ids, the shifted client
1823
+ // misses both, recomputes, and re-renders the route fresh — duplicating
1824
+ // the server DOM and leaving it inert.
1824
1825
  createEffect(() => ({
1825
1826
  url: router.location.pathname + router.location.search + router.location.hash,
1826
1827
  routing: router.isRouting()
1827
1828
  }), current => {
1828
1829
  if (!current.routing) restore();
1830
+ }, {
1831
+ transparent: true
1829
1832
  });
1830
- onCleanup(() => {
1831
- unbind.forEach(u => u());
1832
- cancelGuard();
1833
- });
1833
+ onCleanup(() => unbind.forEach(u => u()));
1834
1834
  // reload/back_forward document loads land on an existing entry (a fresh
1835
1835
  // navigation starts a new one and belongs at the top); the effect's
1836
1836
  // initial run performs the restore after first render
@@ -11,10 +11,18 @@ import type { RouterHistory } from "./history.js";
11
11
  * scroll time (rather than at exit) stays correct through `useBeforeLeave`
12
12
  * blocked/reverted traversals. The map persists to sessionStorage on pagehide
13
13
  * so restoration survives reloads, which `scrollRestoration = "manual"`
14
- * otherwise disables. Restoration runs once routing settles; if the document
15
- * is still shorter than the target (a boundary below the fold hasn't
16
- * resolved), a ResizeObserver retries as content grows, cancelled by the
17
- * first user scroll.
14
+ * otherwise disables.
15
+ *
16
+ * Restoration is a single scroll once routing settles — the same strategy
17
+ * SvelteKit, TanStack Router and React Router use. Settling after the
18
+ * transition commits is what makes the offset reachable; chasing a still-
19
+ * growing document afterwards (a ResizeObserver re-asserting the offset as
20
+ * content arrives) was tried and removed: no peer router does it, an
21
+ * unbounded observer re-clamps the viewport to the bottom when the target is
22
+ * never reachable (a list that is genuinely shorter now), and scroll-induced
23
+ * layout changes can feed it back into itself. Content that commits after the
24
+ * transition settles — an image without reserved space, a boundary below the
25
+ * fold — keeps whatever offset the document can hold.
18
26
  */
19
27
  export declare function createScrollRestoration(): {
20
28
  /** When the adapter notifies a traversal: mark the target for restoration. */
@@ -12,10 +12,18 @@ const STORAGE_KEY = "solid-router:scroll";
12
12
  * scroll time (rather than at exit) stays correct through `useBeforeLeave`
13
13
  * blocked/reverted traversals. The map persists to sessionStorage on pagehide
14
14
  * so restoration survives reloads, which `scrollRestoration = "manual"`
15
- * otherwise disables. Restoration runs once routing settles; if the document
16
- * is still shorter than the target (a boundary below the fold hasn't
17
- * resolved), a ResizeObserver retries as content grows, cancelled by the
18
- * first user scroll.
15
+ * otherwise disables.
16
+ *
17
+ * Restoration is a single scroll once routing settles — the same strategy
18
+ * SvelteKit, TanStack Router and React Router use. Settling after the
19
+ * transition commits is what makes the offset reachable; chasing a still-
20
+ * growing document afterwards (a ResizeObserver re-asserting the offset as
21
+ * content arrives) was tried and removed: no peer router does it, an
22
+ * unbounded observer re-clamps the viewport to the bottom when the target is
23
+ * never reachable (a list that is genuinely shorter now), and scroll-induced
24
+ * layout changes can feed it back into itself. Content that commits after the
25
+ * transition settles — an image without reserved space, a boundary below the
26
+ * fold — keeps whatever offset the document can hold.
19
27
  */
20
28
  export function createScrollRestoration() {
21
29
  window.history.scrollRestoration = "manual";
@@ -30,21 +38,14 @@ export function createScrollRestoration() {
30
38
  const depth = () => window.history.state && window.history.state._depth;
31
39
  let programmatic = false;
32
40
  let pending;
33
- let disconnect;
34
- const cancelGuard = () => {
35
- disconnect && disconnect();
36
- disconnect = undefined;
37
- };
38
41
  const unbind = [
39
42
  bindEvent(window, "scroll", () => {
40
43
  const d = depth();
41
44
  if (d != null)
42
45
  positions[d] = window.scrollY;
43
- if (!programmatic) {
44
- // the user took over — a pending or chasing restore would yank them
46
+ // the user took over — a pending restore would yank them
47
+ if (!programmatic)
45
48
  pending = undefined;
46
- cancelGuard();
47
- }
48
49
  }),
49
50
  bindEvent(window, "pagehide", () => {
50
51
  try {
@@ -60,19 +61,11 @@ export function createScrollRestoration() {
60
61
  pending = undefined;
61
62
  if (y == null)
62
63
  return;
63
- cancelGuard();
64
- const attempt = () => {
65
- programmatic = true;
66
- window.scrollTo(0, y);
67
- programmatic = false;
68
- // reachable once the document is tall enough to hold the offset
69
- return document.documentElement.scrollHeight - window.innerHeight >= y;
70
- };
71
- if (!attempt() && typeof ResizeObserver !== "undefined") {
72
- const observer = new ResizeObserver(() => attempt() && cancelGuard());
73
- observer.observe(document.documentElement);
74
- disconnect = () => observer.disconnect();
75
- }
64
+ // flagged so the resulting scroll event is not mistaken for the user
65
+ // taking over (which cancels a pending restore)
66
+ programmatic = true;
67
+ window.scrollTo(0, y);
68
+ programmatic = false;
76
69
  };
77
70
  return {
78
71
  /** When the adapter notifies a traversal: mark the target for restoration. */
@@ -92,17 +85,23 @@ export function createScrollRestoration() {
92
85
  // isRouting, which reports in-flight transitions — native pops
93
86
  // included — and holds the restore until they commit. restore() no-ops
94
87
  // unless a traversal marked a target, so push navigations are inert.
88
+ // `transparent` keeps the effect invisible to the hydration id scheme —
89
+ // same reasoning as the link-claims effect (claims.ts): this setup is
90
+ // client-only, so an id-consuming node here has no server counterpart
91
+ // and every hydration id allocated after it shifts by one child slot.
92
+ // The visible failure is any <Loading> content that settled before the
93
+ // shell flush (a cache hit, a preloaded query): its serialized value and
94
+ // inlined markup are keyed under the server's ids, the shifted client
95
+ // misses both, recomputes, and re-renders the route fresh — duplicating
96
+ // the server DOM and leaving it inert.
95
97
  createEffect(() => ({
96
98
  url: router.location.pathname + router.location.search + router.location.hash,
97
99
  routing: router.isRouting()
98
100
  }), current => {
99
101
  if (!current.routing)
100
102
  restore();
101
- });
102
- onCleanup(() => {
103
- unbind.forEach(u => u());
104
- cancelGuard();
105
- });
103
+ }, { transparent: true });
104
+ onCleanup(() => unbind.forEach(u => u()));
106
105
  // reload/back_forward document loads land on an existing entry (a fresh
107
106
  // navigation starts a new one and belongs at the top); the effect's
108
107
  // initial run performs the restore after first render
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "1.0.0-next.10",
9
+ "version": "1.0.0-next.11",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -23,7 +23,8 @@
23
23
  "solid": "./dist/index.jsx",
24
24
  "default": "./dist/index.js"
25
25
  },
26
- "./server": "./dist/server.js"
26
+ "./server": "./dist/server.js",
27
+ "./fs": "./dist/fs.js"
27
28
  },
28
29
  "files": [
29
30
  "dist"