@absolutejs/absolute 0.20.0-beta.169 → 0.20.0-beta.170

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 (238) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +232 -0
  3. package/changelog.json +228 -0
  4. package/dist/angular/browser.js +7 -8
  5. package/dist/angular/browser.js.map +6 -5
  6. package/dist/angular/components/core/streamingSlotRegistrar.js +6 -2
  7. package/dist/angular/components/core/streamingSlotRegistry.js +7 -3
  8. package/dist/angular/index.js +555 -156
  9. package/dist/angular/index.js.map +29 -23
  10. package/dist/angular/server.js +555 -156
  11. package/dist/angular/server.js.map +29 -23
  12. package/dist/build/buildWorker.js +14 -8
  13. package/dist/build.js +4949 -1941
  14. package/dist/build.js.map +72 -42
  15. package/dist/cli/{add-7azr0c83.js → add-6a7pjkn9.js} +11 -7
  16. package/dist/cli/{analyze-3cyek6v3.js → analyze-606fshwa.js} +1 -1
  17. package/dist/cli/{build-25rz8v25.js → build-g81xqbb9.js} +1 -1
  18. package/dist/cli/{compile-4jnfxts9.js → compile-9y1w9zpj.js} +3 -3
  19. package/dist/cli/config/server.js +10699 -1538
  20. package/dist/cli/{dev-9tk5hn2e.js → dev-hyeb89hp.js} +5 -1
  21. package/dist/cli/{doctor-4nv2c2j3.js → doctor-h55y9hj1.js} +1 -1
  22. package/dist/cli/{generate-v75p2fct.js → generate-20x1pkf7.js} +4 -4
  23. package/dist/cli/{htmx-427swtdz.js → htmx-6mz2h7k1.js} +2 -2
  24. package/dist/cli/{index-bhbs6d5z.js → index-32ge2w1f.js} +12 -4
  25. package/dist/cli/{index-a4ws2jwd.js → index-3wz3mktm.js} +1 -1
  26. package/dist/cli/{index-b7hjgknf.js → index-9sksd8c8.js} +13 -4
  27. package/dist/cli/{index-eyzwc7gz.js → index-qbrzvmh9.js} +4 -2
  28. package/dist/cli/index-qw98g1p6.js +269 -0
  29. package/dist/cli/{index-434b7j4a.js → index-vtp20gwf.js} +69 -1
  30. package/dist/cli/{index-h7qhz02e.js → index-wk0pyxrg.js} +3 -0
  31. package/dist/cli/{index-k3y8cb6c.js → index-z6f43b20.js} +1 -1
  32. package/dist/cli/index.js +16 -16
  33. package/dist/cli/{islands-8sgky9gh.js → islands-dc54e5y1.js} +1 -1
  34. package/dist/cli/{ls-4m4yqwww.js → ls-b6zz24nt.js} +1 -1
  35. package/dist/cli/{mobile-hcxjr2gf.js → mobile-ajnnvqtv.js} +3 -3
  36. package/dist/cli/prerender-fqpp0m1j.js +10 -0
  37. package/dist/cli/{remove-1ycjx85k.js → remove-t8x1mrxg.js} +4 -4
  38. package/dist/cli/{start-mbaejxhm.js → start-d6p831ny.js} +3 -3
  39. package/dist/cli/{typecheck-dr068pmh.js → typecheck-stbt0qt5.js} +6 -1
  40. package/dist/cli/{workspace-1sycp9g1.js → workspace-61hh6jq1.js} +1 -1
  41. package/dist/client/chunks/index-6b66nzyf.js +381 -0
  42. package/dist/client/chunks/index-6b66nzyf.js.map +11 -0
  43. package/dist/client/chunks/{islands-rc28k5sx.js → islands-s2ta5ajb.js} +5 -2
  44. package/dist/client/chunks/{islands-rc28k5sx.js.map → islands-s2ta5ajb.js.map} +7 -6
  45. package/dist/client/index.js +160 -3
  46. package/dist/client/index.js.map +6 -4
  47. package/dist/client/navigation.js +764 -0
  48. package/dist/client/navigation.js.map +10 -0
  49. package/dist/client/prefetch.js +33 -375
  50. package/dist/client/prefetch.js.map +3 -4
  51. package/dist/core/streamingSlotRegistrar.js +5 -2
  52. package/dist/core/streamingSlotRegistrar.js.map +5 -4
  53. package/dist/core/streamingSlotRegistry.js +5 -2
  54. package/dist/core/streamingSlotRegistry.js.map +5 -4
  55. package/dist/dev/client/cspNonce.ts +28 -0
  56. package/dist/dev/client/handlers/html.ts +4 -3
  57. package/dist/dev/client/handlers/htmx.ts +3 -2
  58. package/dist/dev/client/handlers/svelte.ts +15 -90
  59. package/dist/dev/client/syncDevtools.ts +2 -1
  60. package/dist/dev/serverBootstrap.js +11 -4
  61. package/dist/i18n/index.js +543 -0
  62. package/dist/i18n/index.js.map +15 -0
  63. package/dist/index.js +6339 -2233
  64. package/dist/index.js.map +89 -54
  65. package/dist/islands/index.js +282 -40
  66. package/dist/islands/index.js.map +14 -10
  67. package/dist/mobile/browser.js +7 -2
  68. package/dist/mobile/browser.js.map +5 -4
  69. package/dist/mobile/index.js +7 -2
  70. package/dist/mobile/index.js.map +5 -4
  71. package/dist/mobile/remoteMacAgentEntry.js +1 -1
  72. package/dist/react/components/browser/index.js +94 -4
  73. package/dist/react/components/index.js +150 -10
  74. package/dist/react/components/index.js.map +15 -10
  75. package/dist/react/hooks/index.js +479 -13
  76. package/dist/react/hooks/index.js.map +12 -4
  77. package/dist/react/index.js +769 -166
  78. package/dist/react/index.js.map +32 -21
  79. package/dist/react/router/browser.js +21 -4
  80. package/dist/react/router/browser.js.map +5 -4
  81. package/dist/react/router/index.js +21 -4
  82. package/dist/react/router/index.js.map +5 -4
  83. package/dist/react/server.js +597 -203
  84. package/dist/react/server.js.map +25 -15
  85. package/dist/src/angular/zoneRuntime.d.ts +5 -0
  86. package/dist/src/build/buildReactServerComponents.d.ts +22 -0
  87. package/dist/src/build/downlevelAsyncForZone.d.ts +1 -0
  88. package/dist/src/build/fileRoutes.d.ts +65 -0
  89. package/dist/src/build/fonts/buildFonts.d.ts +25 -0
  90. package/dist/src/build/fonts/fallbackMetrics.d.ts +13 -0
  91. package/dist/src/build/fonts/googleFonts.d.ts +18 -0
  92. package/dist/src/build/fonts/injectFontTags.d.ts +7 -0
  93. package/dist/src/build/fonts/parseFontMetrics.d.ts +4 -0
  94. package/dist/src/build/generateReactIndexes.d.ts +7 -1
  95. package/dist/src/build/htmlIncludes.d.ts +16 -0
  96. package/dist/src/build/inlineSourceMapComment.d.ts +1 -0
  97. package/dist/src/build/scanConventions.d.ts +3 -0
  98. package/dist/src/build/vitals.d.ts +21 -0
  99. package/dist/src/client/formActions.d.ts +29 -0
  100. package/dist/src/client/index.d.ts +2 -0
  101. package/dist/src/client/navigation.d.ts +16 -0
  102. package/dist/src/client/prefetch.d.ts +2 -30
  103. package/dist/src/core/build.d.ts +2 -2
  104. package/dist/src/core/cspNonce.d.ts +12 -0
  105. package/dist/src/core/fileRouteAdapter.d.ts +15 -0
  106. package/dist/src/core/fileRoutes.d.ts +5 -0
  107. package/dist/src/core/formActionFlash.d.ts +25 -0
  108. package/dist/src/core/formActions.d.ts +249 -0
  109. package/dist/src/core/index.d.ts +5 -0
  110. package/dist/src/core/partialPrerender.d.ts +28 -0
  111. package/dist/src/core/partialPrerenderServe.d.ts +24 -0
  112. package/dist/src/core/precompressed.d.ts +11 -1
  113. package/dist/src/core/prerenderNonce.d.ts +9 -0
  114. package/dist/src/core/productionRuntime.d.ts +4 -2
  115. package/dist/src/core/serverOnlyStaticFiles.d.ts +8 -0
  116. package/dist/src/core/staticHtmlNonce.d.ts +2 -0
  117. package/dist/src/core/vitals.d.ts +108 -0
  118. package/dist/src/dev/angular/fastHmrCompiler.d.ts +1 -1
  119. package/dist/src/dev/client/cspNonce.d.ts +2 -0
  120. package/dist/src/dev/fileRouteRefresher.d.ts +5 -0
  121. package/dist/src/dev/importCost/instrument.d.ts +1 -1
  122. package/dist/src/dev/reactServerComponentsRebuild.d.ts +3 -0
  123. package/dist/src/i18n/defineMessages.d.ts +5 -0
  124. package/dist/src/i18n/formatMessage.d.ts +26 -0
  125. package/dist/src/i18n/index.d.ts +6 -0
  126. package/dist/src/i18n/intlCache.d.ts +5 -0
  127. package/dist/src/i18n/routing.d.ts +28 -0
  128. package/dist/src/i18n/translator.d.ts +11 -0
  129. package/dist/src/mobile/androidRelease.d.ts +2 -2
  130. package/dist/src/mobile/iosRelease.d.ts +4 -4
  131. package/dist/src/mobile/materializedBundle.d.ts +1 -1
  132. package/dist/src/mobile/mobileInspect.d.ts +1 -1
  133. package/dist/src/mobile/releaseArtifact.d.ts +2 -2
  134. package/dist/src/mobile/remoteMacProtocol.d.ts +2 -2
  135. package/dist/src/plugins/i18n.d.ts +30 -0
  136. package/dist/src/plugins/index.d.ts +2 -1
  137. package/dist/src/plugins/secureHeaders.d.ts +48 -0
  138. package/dist/src/react/components/Head.d.ts +1 -1
  139. package/dist/src/react/components/StreamSlot.browser.d.ts +1 -0
  140. package/dist/src/react/components/StreamSlot.d.ts +5 -1
  141. package/dist/src/react/components/SuspenseSlot.browser.d.ts +2 -0
  142. package/dist/src/react/components/SuspenseSlot.d.ts +12 -0
  143. package/dist/src/react/fileRoute.d.ts +18 -0
  144. package/dist/src/react/hooks/index.d.ts +4 -0
  145. package/dist/src/react/hooks/useFormAction.d.ts +42 -0
  146. package/dist/src/react/hooks/useI18n.d.ts +15 -0
  147. package/dist/src/react/index.d.ts +2 -0
  148. package/dist/src/react/server.d.ts +2 -0
  149. package/dist/src/react/serverComponents/constants.d.ts +11 -0
  150. package/dist/src/react/serverComponents/flightPayload.d.ts +2 -0
  151. package/dist/src/react/serverComponents/pageHandler.d.ts +14 -0
  152. package/dist/src/svelte/browser.d.ts +1 -0
  153. package/dist/src/svelte/fileRoute.d.ts +15 -0
  154. package/dist/src/svelte/i18n.d.ts +8 -0
  155. package/dist/src/svelte/index.d.ts +2 -0
  156. package/dist/src/svelte/lowerAwaitSlotSyntax.d.ts +5 -1
  157. package/dist/src/svelte/renderToReadableStream.d.ts +10 -2
  158. package/dist/src/svelte/router/browser.d.ts +3 -1
  159. package/dist/src/svelte/router/goto.d.ts +13 -10
  160. package/dist/src/svelte/router/guards.d.ts +43 -0
  161. package/dist/src/svelte/router/index.d.ts +3 -1
  162. package/dist/src/svelte/router/lazy.d.ts +17 -0
  163. package/dist/src/svelte/router/lazyModule.d.ts +29 -0
  164. package/dist/src/svelte/router/navigation.d.ts +55 -0
  165. package/dist/src/svelte/router/pushState.d.ts +4 -2
  166. package/dist/src/svelte/router/scroll.d.ts +34 -0
  167. package/dist/src/svelte/routerSsr.d.ts +9 -0
  168. package/dist/src/svelte/server.d.ts +1 -0
  169. package/dist/src/utils/cspNonceRef.d.ts +8 -0
  170. package/dist/src/utils/fontAssets.d.ts +17 -0
  171. package/dist/src/utils/formActionProtocol.d.ts +10 -0
  172. package/dist/src/utils/generateHeadElement.d.ts +1 -1
  173. package/dist/src/utils/index.d.ts +1 -0
  174. package/dist/src/utils/inlinePageCss.d.ts +18 -0
  175. package/dist/src/utils/loadConfig.d.ts +5 -0
  176. package/dist/src/utils/localeRef.d.ts +10 -0
  177. package/dist/src/utils/resolveConvention.d.ts +6 -1
  178. package/dist/src/utils/streamingSlots.d.ts +5 -0
  179. package/dist/src/vue/browser.d.ts +2 -0
  180. package/dist/src/vue/components/Image.d.ts +2 -2
  181. package/dist/src/vue/index.d.ts +2 -0
  182. package/dist/src/vue/serverDataPayload.d.ts +32 -0
  183. package/dist/src/vue/serverDataState.d.ts +9 -0
  184. package/dist/src/vue/useServerData.browser.d.ts +6 -0
  185. package/dist/src/vue/useServerData.d.ts +24 -0
  186. package/dist/svelte/browser.js +330 -3
  187. package/dist/svelte/browser.js.map +8 -4
  188. package/dist/svelte/components/AwaitSlot.svelte +5 -0
  189. package/dist/svelte/components/Head.svelte +50 -0
  190. package/dist/svelte/components/JsonLd.svelte +9 -1
  191. package/dist/svelte/components/StreamSlot.svelte +5 -0
  192. package/dist/svelte/index.js +1017 -117
  193. package/dist/svelte/index.js.map +38 -24
  194. package/dist/svelte/router/Link.svelte +40 -8
  195. package/dist/svelte/router/Route.svelte +157 -12
  196. package/dist/svelte/router/Route.svelte.d.ts +39 -5
  197. package/dist/svelte/router/Router.svelte +140 -25
  198. package/dist/svelte/router/Router.svelte.d.ts +5 -1
  199. package/dist/svelte/router/browser.ts +10 -0
  200. package/dist/svelte/router/goto.ts +15 -86
  201. package/dist/svelte/router/guards.ts +112 -0
  202. package/dist/svelte/router/index.ts +10 -0
  203. package/dist/svelte/router/lazy.ts +82 -0
  204. package/dist/svelte/router/lazyModule.ts +74 -0
  205. package/dist/svelte/router/navigation.ts +412 -0
  206. package/dist/svelte/router/pushState.ts +7 -4
  207. package/dist/svelte/router/scroll.ts +286 -0
  208. package/dist/svelte/server.js +747 -252
  209. package/dist/svelte/server.js.map +30 -20
  210. package/dist/types/angular.d.ts +2 -0
  211. package/dist/types/build.d.ts +37 -0
  212. package/dist/types/conventions.d.ts +3 -0
  213. package/dist/types/fileRoutes.d.ts +72 -0
  214. package/dist/types/fonts.d.ts +86 -0
  215. package/dist/types/formAction.d.ts +54 -0
  216. package/dist/types/globals.d.ts +10 -0
  217. package/dist/types/i18n.d.ts +185 -0
  218. package/dist/types/index.d.ts +4 -0
  219. package/dist/types/metadata.d.ts +11 -0
  220. package/dist/types/navigation.d.ts +37 -0
  221. package/dist/types/prefetch.d.ts +10 -0
  222. package/dist/types/sitemap.d.ts +4 -0
  223. package/dist/types/svelteRouter.d.ts +89 -2
  224. package/dist/types/svelteRouter.ts +112 -2
  225. package/dist/types/vue.d.ts +46 -1
  226. package/dist/utils/iconVersion.ts +34 -0
  227. package/dist/vue/browser.js +107 -11
  228. package/dist/vue/browser.js.map +7 -4
  229. package/dist/vue/components/index.js +5 -2
  230. package/dist/vue/components/index.js.map +5 -4
  231. package/dist/vue/index.js +712 -158
  232. package/dist/vue/index.js.map +32 -23
  233. package/dist/vue/server.js +310 -79
  234. package/dist/vue/server.js.map +20 -15
  235. package/package.json +9 -1
  236. package/dist/cli/prerender-mfnwsvyt.js +0 -7
  237. package/dist/src/plugins/pageRouter.d.ts +0 -1
  238. /package/dist/cli/{index-j7m8pq48.js → index-rervww4p.js} +0 -0
@@ -1,93 +1,22 @@
1
- import type { GotoOptions, RouterMode } from '../../types/svelteRouter';
2
- import { buildHashHref } from './hashMode';
3
- import { setPage } from './page.js';
4
- import { withViewTransition } from './viewTransitions';
1
+ import type { GotoOptions } from '../../types/svelteRouter';
2
+ import { navigate } from './navigation';
5
3
 
6
- let activeMode: RouterMode = 'history';
4
+ export { setRouterMode } from './navigation';
7
5
 
8
6
  /**
9
- * Internal — called by Router.svelte on mount so navigation primitives
10
- * know which URL strategy to use. Hash mode rewrites the URL bar to
11
- * `#/path` instead of `/path`.
12
- */
13
- export const setRouterMode = (mode: RouterMode) => {
14
- activeMode = mode;
15
- };
16
-
17
- const resolveAbsoluteUrl = (target: string) => {
18
- if (typeof window === 'undefined') {
19
- // Programmatic goto on the server is rare but we tolerate it as a
20
- // way for tests to drive the router without a real DOM.
21
- return new URL(target, 'http://localhost/');
22
- }
23
-
24
- return new URL(target, window.location.href);
25
- };
26
-
27
- const isExternal = (target: URL) => {
28
- if (typeof window === 'undefined') return false;
29
-
30
- return target.origin !== window.location.origin;
31
- };
32
-
33
- const writeHistory = (target: URL, options: GotoOptions) => {
34
- if (typeof window === 'undefined') return;
35
-
36
- const href =
37
- activeMode === 'hash'
38
- ? `${window.location.pathname}${window.location.search}${buildHashHref(target.pathname + target.search)}`
39
- : `${target.pathname}${target.search}${target.hash}`;
40
-
41
- const method = options.replaceState
42
- ? window.history.replaceState
43
- : window.history.pushState;
44
- method.call(window.history, options.state ?? null, '', href);
45
- };
46
-
47
- const applyScrollAndFocus = (options: GotoOptions) => {
48
- if (typeof window === 'undefined') return;
49
-
50
- if (!options.noScroll) window.scrollTo({ left: 0, top: 0 });
51
- if (!options.keepFocus && document.activeElement instanceof HTMLElement) {
52
- document.activeElement.blur();
53
- }
54
- };
55
-
56
- /**
57
- * Programmatically navigate to a URL. Updates `page.url`, writes history,
58
- * and (when supported) wraps the swap in `document.startViewTransition`.
7
+ * Programmatically navigate to a URL. Runs the navigation guards
8
+ * (`beforeNavigate`, `<Router guard>`, `<Route guard>`), then updates
9
+ * `page.url`, writes history, swaps the route (inside
10
+ * `document.startViewTransition` when supported) and scrolls: to the
11
+ * `#hash` target when the URL has one, otherwise to the top — unless
12
+ * `noScroll` is set.
13
+ *
14
+ * Resolves to what happened: `'committed'`, `'cancelled'` (a guard said
15
+ * no), `'redirected'`, `'superseded'` (a newer navigation started while a
16
+ * guard was pending) or `'external'` (another origin; the browser loads it).
59
17
  *
60
18
  * Mirrors SvelteKit's `goto` from `$app/navigation` — same name, same
61
19
  * options shape, so a SvelteKit user finds the primitive familiar.
62
20
  */
63
- export const goto = async (target: string, options: GotoOptions = {}) => {
64
- const url = resolveAbsoluteUrl(target);
65
-
66
- if (isExternal(url)) {
67
- // External URLs go through the browser — we don't try to SPA them.
68
- if (typeof window !== 'undefined') {
69
- window.location.href = url.href;
70
- }
71
-
72
- return;
73
- }
74
-
75
- // `goto` performs no network fetch of its own: the swap is a client-side
76
- // route match against the registered <Route>s. What `<Link prefetch>`
77
- // buys the click is therefore already in place by now — the target's
78
- // HTML sits in the HTTP cache (content-hash ETag → 304 on a later real
79
- // navigation), its client modules are in the modulepreload map and, when
80
- // `prerender` was set, a speculation rule has the page rendered in a
81
- // hidden tab. Nothing to consume here.
82
- const mutate = () => {
83
- writeHistory(url, options);
84
- setPage({
85
- params: {},
86
- state: options.state ?? null,
87
- url
88
- });
89
- applyScrollAndFocus(options);
90
- };
91
-
92
- await withViewTransition(mutate);
93
- };
21
+ export const goto = (target: string, options: GotoOptions = {}) =>
22
+ navigate(target, options, 'goto');
@@ -0,0 +1,112 @@
1
+ import type {
2
+ NavigationContext,
3
+ NavigationRedirectTarget,
4
+ RouteGuard
5
+ } from '../../types/svelteRouter';
6
+
7
+ /* Guard evaluation shared by the browser navigation path and the server
8
+ * render: run a chain of guards in order and reduce their answers to one
9
+ * decision. Pure apart from awaiting the guards themselves. */
10
+
11
+ export type GuardEntry = {
12
+ guard: RouteGuard;
13
+ params: Record<string, string | undefined>;
14
+ };
15
+
16
+ export type GuardDecision =
17
+ | { kind: 'allow' }
18
+ | { kind: 'cancel' }
19
+ | { kind: 'redirect'; location: string; replace: boolean }
20
+ | { kind: 'stale' };
21
+
22
+ export type GuardRunContext = Omit<NavigationContext, 'params'>;
23
+
24
+ const ALLOW: GuardDecision = { kind: 'allow' };
25
+ const CANCEL: GuardDecision = { kind: 'cancel' };
26
+ const STALE: GuardDecision = { kind: 'stale' };
27
+
28
+ /** Thrown (or returned) by `redirect()`. An `Error` so a throw from deep
29
+ * inside a helper keeps a useful stack; recognised structurally, so a
30
+ * copy of the router bundled twice still understands it. */
31
+ export class NavigationRedirect
32
+ extends Error
33
+ implements NavigationRedirectTarget
34
+ {
35
+ redirect: string;
36
+ replace: boolean;
37
+
38
+ constructor(location: string, replace: boolean) {
39
+ super(`Redirect to ${location}`);
40
+ this.name = 'NavigationRedirect';
41
+ this.redirect = location;
42
+ this.replace = replace;
43
+ }
44
+ }
45
+
46
+ const isRedirectTarget = (value: unknown): value is NavigationRedirectTarget =>
47
+ typeof value === 'object' &&
48
+ value !== null &&
49
+ 'redirect' in value &&
50
+ typeof value.redirect === 'string';
51
+
52
+ const toDecision = (result: unknown): GuardDecision => {
53
+ if (result === false) return CANCEL;
54
+ if (typeof result === 'string') {
55
+ return { kind: 'redirect', location: result, replace: false };
56
+ }
57
+ if (!isRedirectTarget(result)) return ALLOW;
58
+ const replace = 'replace' in result && result.replace === true;
59
+
60
+ return { kind: 'redirect', location: result.redirect, replace };
61
+ };
62
+
63
+ const evaluateGuard = async (entry: GuardEntry, context: GuardRunContext) => {
64
+ try {
65
+ return toDecision(
66
+ await entry.guard({ ...context, params: entry.params })
67
+ );
68
+ } catch (error) {
69
+ if (isRedirectTarget(error)) return toDecision(error);
70
+ throw error;
71
+ }
72
+ };
73
+
74
+ const runFrom = async (
75
+ entries: GuardEntry[],
76
+ index: number,
77
+ context: GuardRunContext,
78
+ isStale: () => boolean
79
+ ): Promise<GuardDecision> => {
80
+ const entry = entries[index];
81
+ if (!entry) return ALLOW;
82
+ const decision = await evaluateGuard(entry, context);
83
+ if (isStale()) return STALE;
84
+ if (decision.kind !== 'allow') return decision;
85
+
86
+ return runFrom(entries, index + 1, context, isStale);
87
+ };
88
+
89
+ /**
90
+ * Redirect from inside a guard. Return it, or throw it from a helper the
91
+ * guard calls — both short-circuit the guard chain the same way:
92
+ *
93
+ * guard={({ request }) => {
94
+ * if (!isSignedIn(request)) throw redirect('/login');
95
+ * }}
96
+ *
97
+ * In the browser the redirect adds a history entry unless
98
+ * `{ replace: true }`; on the server it is a 302.
99
+ */
100
+ export const redirect = (
101
+ location: string,
102
+ options: { replace?: boolean } = {}
103
+ ) => new NavigationRedirect(location, options.replace === true);
104
+
105
+ /** Run `entries` one after another; the first guard that cancels or
106
+ * redirects decides. `isStale` is checked after every guard so a newer
107
+ * navigation can abandon this one mid-chain. */
108
+ export const runGuardChain = (
109
+ entries: GuardEntry[],
110
+ context: GuardRunContext,
111
+ isStale: () => boolean = () => false
112
+ ) => runFrom(entries, 0, context, isStale);
@@ -11,13 +11,23 @@
11
11
  // AbsoluteJS's Svelte compile pipeline.
12
12
 
13
13
  export { goto } from './goto';
14
+ export { NavigationRedirect, redirect } from './guards';
15
+ export { beforeNavigate, preloadRoute } from './navigation';
14
16
  export { page } from './page.js';
15
17
  export { pushState, replaceState } from './pushState';
16
18
 
17
19
  export type {
18
20
  ExtractRouteParams,
19
21
  GotoOptions,
22
+ LazyRouteLoader,
23
+ LazyRouteModule,
20
24
  LinkPrefetchMode,
25
+ NavigationContext,
26
+ NavigationOutcome,
27
+ NavigationRedirectTarget,
28
+ NavigationType,
21
29
  PageState,
30
+ RouteGuard,
31
+ RouteGuardResult,
22
32
  RouterMode
23
33
  } from '../../types/svelteRouter';
@@ -0,0 +1,82 @@
1
+ import type { LazyRouteLoader } from '../../types/svelteRouter';
2
+ import {
3
+ type LazyComponent,
4
+ readSeededLazy,
5
+ toLazyComponent
6
+ } from './lazyModule';
7
+
8
+ /* Browser-side cache of lazy route components. A `<Route loader>` usually
9
+ * passes an inline arrow, so a fresh function arrives with every Route
10
+ * instance; the cache is keyed by the route pattern plus the loader's
11
+ * source text instead, which is stable for the life of the bundle. A
12
+ * route visited (or prefetched) once therefore renders synchronously the
13
+ * next time it becomes active — no pending flash on revisits. */
14
+
15
+ const resolvedComponents = new Map<string, LazyComponent>();
16
+ const inflightLoads = new Map<string, Promise<LazyComponent>>();
17
+ const LAZY_SETTLE_TIMEOUT_MS = 3_000;
18
+
19
+ /** Forget a failed (or stale) load so the next attempt calls the loader
20
+ * again. */
21
+ export const forgetLazy = (key: string) => {
22
+ resolvedComponents.delete(key);
23
+ inflightLoads.delete(key);
24
+ };
25
+
26
+ export const hasPendingLazyLoads = () => inflightLoads.size > 0;
27
+
28
+ export const lazyCacheKey = (pattern: string, loader: LazyRouteLoader) =>
29
+ `${pattern}\u0000${String(loader)}`;
30
+
31
+ /** Load (once) and cache the component behind `loader`. */
32
+ export const loadLazy = (key: string, loader: LazyRouteLoader) => {
33
+ const cached = resolvedComponents.get(key);
34
+ if (cached) return Promise.resolve(cached);
35
+ const inflight = inflightLoads.get(key);
36
+ if (inflight) return inflight;
37
+
38
+ const load = loader()
39
+ .then((value) => {
40
+ const component = toLazyComponent(value);
41
+ if (!component) {
42
+ throw new Error(
43
+ 'A lazy <Route> loader must resolve to a Svelte component or to a module whose default export is one, e.g. `() => import("./Page.svelte")`.'
44
+ );
45
+ }
46
+ resolvedComponents.set(key, component);
47
+
48
+ return component;
49
+ })
50
+ .finally(() => {
51
+ inflightLoads.delete(key);
52
+ });
53
+ inflightLoads.set(key, load);
54
+
55
+ return load;
56
+ };
57
+
58
+ /** The component for `key` if it has already loaded — or, during the
59
+ * first hydration, the module the hydration entry loaded ahead of time
60
+ * for this route pattern. */
61
+ export const peekLazy = (key: string, pattern: string) => {
62
+ const cached = resolvedComponents.get(key);
63
+ if (cached) return cached;
64
+ const seeded = readSeededLazy(pattern);
65
+ if (seeded) resolvedComponents.set(key, seeded);
66
+
67
+ return seeded;
68
+ };
69
+
70
+ /** Resolves once every lazy route load in flight has settled, or after a
71
+ * few seconds — scroll restoration waits on this so it doesn't restore a
72
+ * position into content that hasn't rendered yet. */
73
+ export const whenLazySettled = async () => {
74
+ if (inflightLoads.size === 0) return;
75
+ const settled = Promise.allSettled(Array.from(inflightLoads.values()));
76
+ let timer: ReturnType<typeof setTimeout> | undefined;
77
+ const timeout = new Promise<void>((resolve) => {
78
+ timer = setTimeout(resolve, LAZY_SETTLE_TIMEOUT_MS);
79
+ });
80
+ await Promise.race([settled, timeout]);
81
+ clearTimeout(timer);
82
+ };
@@ -0,0 +1,74 @@
1
+ import type { Component } from 'svelte';
2
+
3
+ /* Pure helpers shared by the router (browser + server render) and the
4
+ * Svelte page handler: turn whatever a lazy route's loader resolved to
5
+ * into a component, and read the source id the AbsoluteJS Svelte compiler
6
+ * stamps on every compiled component module. No module state lives here,
7
+ * so it is safe to bundle more than once. */
8
+
9
+ export type LazyComponent = Component<Record<string, unknown>>;
10
+
11
+ /** `globalThis` record of `route pattern → module` that the hydration
12
+ * entry fills before it hydrates a page whose server render included lazy
13
+ * routes. Compiled client modules fill a sibling registry,
14
+ * `Symbol.for('absolutejs.svelte.lazy-modules')`, of `source id →
15
+ * () => import(chunk)` for every `.svelte` file they import dynamically
16
+ * (see {@link lazyModuleRegistration}). */
17
+ export const LAZY_RESOLVED_KEY = Symbol.for('absolutejs.svelte.lazy-resolved');
18
+
19
+ /** Export (and component property) the Svelte compiler adds to every
20
+ * compiled component: the component's source path, relative to the
21
+ * project root. The server records it for each lazy route it rendered so
22
+ * the browser can load the same module before hydrating. */
23
+ export const LAZY_SOURCE_EXPORT = '__ABSOLUTE_SVELTE_SOURCE__';
24
+
25
+ const isRecord = (value: unknown): value is Record<string | symbol, unknown> =>
26
+ typeof value === 'object' && value !== null;
27
+
28
+ const isComponent = (value: unknown): value is LazyComponent =>
29
+ typeof value === 'function';
30
+
31
+ /** The component a loader resolved to: a module namespace's `default`, or
32
+ * the value itself when the loader already unwrapped it. */
33
+ export const toLazyComponent = (value: unknown) => {
34
+ if (isComponent(value)) return value;
35
+ if (!isRecord(value)) return undefined;
36
+ const fallback = value.default;
37
+
38
+ return isComponent(fallback) ? fallback : undefined;
39
+ };
40
+
41
+ const readSourceFrom = (value: unknown) => {
42
+ if (!isRecord(value) && !isComponent(value)) return undefined;
43
+ const source: unknown = Reflect.get(value, LAZY_SOURCE_EXPORT);
44
+
45
+ return typeof source === 'string' ? source : undefined;
46
+ };
47
+
48
+ /** The statement a compiled client module appends for each `.svelte` file
49
+ * it imports dynamically, registering `source id → () => import(chunk)`,
50
+ * so the hydration entry can load the module a lazy route rendered on the
51
+ * server before it hydrates. Used by the build (compileSvelte) and the dev
52
+ * module server. */
53
+ export const lazyModuleRegistration = (sourceId: string, specifier: string) =>
54
+ `\n;(globalThis[Symbol.for("absolutejs.svelte.lazy-modules")] ??= new Map()).set(${JSON.stringify(sourceId)}, () => import(${JSON.stringify(specifier)}));`;
55
+
56
+ /** The compiler-stamped source id of a loader result, from the module
57
+ * namespace or from the component itself. */
58
+ export const readLazySource = (value: unknown) => {
59
+ const fromValue = readSourceFrom(value);
60
+ if (fromValue !== undefined) return fromValue;
61
+ if (!isRecord(value)) return undefined;
62
+
63
+ return readSourceFrom(value.default);
64
+ };
65
+
66
+ /** The module the hydration entry loaded for `pattern` before hydrating,
67
+ * if any. */
68
+ export const readSeededLazy = (pattern: string) => {
69
+ if (typeof window === 'undefined') return undefined;
70
+ const seeded: unknown = Reflect.get(globalThis, LAZY_RESOLVED_KEY);
71
+ if (!isRecord(seeded)) return undefined;
72
+
73
+ return toLazyComponent(seeded[pattern]);
74
+ };