@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
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 +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
package/src/use-loader.tsx
CHANGED
|
@@ -12,18 +12,26 @@ import {
|
|
|
12
12
|
type ReactNode,
|
|
13
13
|
} from "react";
|
|
14
14
|
import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
15
|
+
import { loaderStore, type LoaderEntry } from "./loader-store.js";
|
|
15
16
|
import type { LoaderDefinition, LoadOptions } from "./types.js";
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
function isShareableGet(options: LoadOptions | undefined): boolean {
|
|
19
|
+
if (!options) return true;
|
|
20
|
+
if (options.method && options.method !== "GET") return false;
|
|
21
|
+
if ("body" in options && (options as { body?: unknown }).body !== undefined) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isPlainRefetch(options: LoadOptions | undefined): boolean {
|
|
28
|
+
if (!isShareableGet(options)) return false;
|
|
29
|
+
if (options?.params && Object.keys(options.params).length > 0) return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let privateGroupBucketSeq = 0;
|
|
34
|
+
|
|
27
35
|
const NOT_FOUND = Symbol("not-found");
|
|
28
36
|
|
|
29
37
|
function extractContentLoaderData(
|
|
@@ -39,10 +47,6 @@ function extractContentLoaderData(
|
|
|
39
47
|
return props.loaderData[loaderId];
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
// LoaderBoundary: loaderIds + loaderDataPromise (already resolved array).
|
|
43
|
-
// When the segment has loading(), loaderData is resolved inside
|
|
44
|
-
// LoaderBoundary via use(). If the promise was pre-awaited (forceAwait
|
|
45
|
-
// or isAction), the prop is a raw array we can index into.
|
|
46
50
|
if (
|
|
47
51
|
props.loaderIds &&
|
|
48
52
|
Array.isArray(props.loaderIds) &&
|
|
@@ -52,7 +56,6 @@ function extractContentLoaderData(
|
|
|
52
56
|
const idx = (props.loaderIds as string[]).indexOf(loaderId);
|
|
53
57
|
if (idx !== -1) {
|
|
54
58
|
const data = (props.loaderDataPromise as any[])[idx];
|
|
55
|
-
// loaderDataPromise entries may be { ok, data } result objects
|
|
56
59
|
if (data && typeof data === "object" && "ok" in data) {
|
|
57
60
|
return data.ok ? data.data : NOT_FOUND;
|
|
58
61
|
}
|
|
@@ -60,150 +63,265 @@ function extractContentLoaderData(
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
// Traverse into wrapper elements (MountContextProvider, ViewTransition,
|
|
64
|
-
// Suspense wrappers, etc.)
|
|
65
66
|
if (props.children) return extractContentLoaderData(props.children, loaderId);
|
|
66
67
|
return NOT_FOUND;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
/**
|
|
70
|
-
* Payload returned by loader RSC requests
|
|
71
|
-
*/
|
|
72
70
|
interface LoaderRscPayload<T = unknown> {
|
|
73
71
|
loaderResult: T;
|
|
74
72
|
loaderError?: { message: string; name: string };
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
/**
|
|
78
|
-
* Load function type for fetching loader data from the client
|
|
79
|
-
*/
|
|
80
75
|
export type LoadFunction<T> = (options?: LoadOptions) => Promise<T>;
|
|
81
76
|
|
|
82
|
-
/**
|
|
83
|
-
* Result type for useLoader hook (strict - data is required)
|
|
84
|
-
*/
|
|
85
77
|
export interface UseLoaderResult<T> {
|
|
86
|
-
/** The loaded data - guaranteed to exist when loader is registered on route */
|
|
87
78
|
data: T;
|
|
88
|
-
/** True while a load() is in progress */
|
|
89
79
|
isLoading: boolean;
|
|
90
|
-
/** Error from the most recent load attempt, null if successful */
|
|
91
80
|
error: Error | null;
|
|
92
|
-
/** Function to trigger a fetch (only works if loader is fetchable) */
|
|
93
81
|
load: LoadFunction<T>;
|
|
94
|
-
/** Alias for load */
|
|
95
82
|
refetch: LoadFunction<T>;
|
|
96
83
|
}
|
|
97
84
|
|
|
98
|
-
/**
|
|
99
|
-
* Result type for useFetchLoader hook (flexible - data is optional)
|
|
100
|
-
*/
|
|
101
85
|
export interface UseFetchLoaderResult<T> {
|
|
102
|
-
/** The loaded data - may be undefined if not yet fetched or not in context */
|
|
103
86
|
data: T | undefined;
|
|
104
|
-
/** True while a load() is in progress */
|
|
105
87
|
isLoading: boolean;
|
|
106
|
-
/** Error from the most recent load attempt, null if successful */
|
|
107
88
|
error: Error | null;
|
|
108
|
-
/** Function to trigger a fetch (only works if loader is fetchable) */
|
|
109
89
|
load: LoadFunction<T>;
|
|
110
|
-
/** Alias for load */
|
|
111
90
|
refetch: LoadFunction<T>;
|
|
112
91
|
}
|
|
113
92
|
|
|
114
|
-
/**
|
|
115
|
-
* Options for useLoader hook
|
|
116
|
-
*/
|
|
117
93
|
export interface UseLoaderOptions {
|
|
118
|
-
/**
|
|
119
|
-
* If true (default), errors from load() will be thrown to the nearest error boundary.
|
|
120
|
-
* If false, errors are only captured in the `error` state.
|
|
121
|
-
* @default true
|
|
122
|
-
*/
|
|
123
94
|
throwOnError?: boolean;
|
|
95
|
+
key?: string;
|
|
96
|
+
refreshGroup?: string | string[];
|
|
124
97
|
}
|
|
125
98
|
|
|
126
|
-
/**
|
|
127
|
-
* Internal hook implementation shared by useLoader and useFetchLoader
|
|
128
|
-
*/
|
|
129
99
|
function useLoaderInternal<T>(
|
|
130
100
|
loader: LoaderDefinition<T>,
|
|
131
101
|
options?: UseLoaderOptions,
|
|
132
102
|
): UseFetchLoaderResult<T> {
|
|
133
103
|
const context = useContext(OutletContext);
|
|
134
104
|
|
|
135
|
-
|
|
136
|
-
|
|
105
|
+
const { contextData, hasContextData } = useMemo((): {
|
|
106
|
+
contextData: T | undefined;
|
|
107
|
+
hasContextData: boolean;
|
|
108
|
+
} => {
|
|
137
109
|
let current: OutletContextValue | null | undefined = context;
|
|
138
110
|
while (current) {
|
|
139
111
|
if (current.loaderData && loader.$$id in current.loaderData) {
|
|
140
|
-
return
|
|
112
|
+
return {
|
|
113
|
+
contextData: current.loaderData[loader.$$id] as T,
|
|
114
|
+
hasContextData: true,
|
|
115
|
+
};
|
|
141
116
|
}
|
|
142
|
-
// Check content element — the route's OutletProvider is rendered as
|
|
143
|
-
// <Outlet /> content (a child), so its loaderData isn't in the parent
|
|
144
|
-
// chain. Parallel slots need to reach into it to find route-level loaders.
|
|
145
117
|
const contentData = extractContentLoaderData(
|
|
146
118
|
current.content,
|
|
147
119
|
loader.$$id,
|
|
148
120
|
);
|
|
149
121
|
if (contentData !== NOT_FOUND) {
|
|
150
|
-
return contentData as T;
|
|
122
|
+
return { contextData: contentData as T, hasContextData: true };
|
|
151
123
|
}
|
|
152
124
|
current = current.parent;
|
|
153
125
|
}
|
|
154
|
-
return undefined;
|
|
126
|
+
return { contextData: undefined, hasContextData: false };
|
|
155
127
|
}, [context, loader.$$id]);
|
|
156
128
|
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
129
|
+
const loaderId = loader.$$id;
|
|
130
|
+
const key = options?.key;
|
|
131
|
+
const refreshGroupOption = options?.refreshGroup;
|
|
132
|
+
const groupKey =
|
|
133
|
+
refreshGroupOption === undefined
|
|
134
|
+
? ""
|
|
135
|
+
: JSON.stringify(
|
|
136
|
+
typeof refreshGroupOption === "string"
|
|
137
|
+
? [refreshGroupOption]
|
|
138
|
+
: [...new Set(refreshGroupOption)].sort(),
|
|
139
|
+
);
|
|
140
|
+
const groupList = useMemo<string[]>(
|
|
141
|
+
() => (groupKey === "" ? [] : (JSON.parse(groupKey) as string[])),
|
|
142
|
+
[groupKey],
|
|
143
|
+
);
|
|
144
|
+
const hasGroups = groupList.length > 0;
|
|
145
|
+
const privateBucketIdRef = useRef<string | null>(null);
|
|
146
|
+
if (hasGroups && key === undefined && privateBucketIdRef.current === null) {
|
|
147
|
+
privateBucketIdRef.current = `__rg${privateGroupBucketSeq++}`;
|
|
148
|
+
}
|
|
149
|
+
const effectiveKey =
|
|
150
|
+
key ?? (hasGroups ? privateBucketIdRef.current! : undefined);
|
|
151
|
+
const bucketKey =
|
|
152
|
+
effectiveKey === undefined ? loaderId : `${loaderId}::${effectiveKey}`;
|
|
153
|
+
|
|
154
|
+
const groupRefetch = useCallback(async (): Promise<void> => {
|
|
155
|
+
if (!loaderId) return;
|
|
156
|
+
const requestId = loaderStore.reserveRequestId(bucketKey);
|
|
157
|
+
loaderStore.beginRequest(bucketKey, requestId);
|
|
158
|
+
try {
|
|
159
|
+
const url = new URL(window.location.href);
|
|
160
|
+
url.searchParams.set("_rsc_loader", loaderId);
|
|
161
|
+
const response = fetch(url.toString(), {
|
|
162
|
+
method: "GET",
|
|
163
|
+
headers: { Accept: "text/x-component" },
|
|
164
|
+
});
|
|
165
|
+
const { createFromFetch } = await import("./deps/browser.js");
|
|
166
|
+
const payload = await createFromFetch<LoaderRscPayload<T>>(response);
|
|
167
|
+
if (payload.loaderError) {
|
|
168
|
+
throw new Error(payload.loaderError.message);
|
|
169
|
+
}
|
|
170
|
+
loaderStore.finishData(bucketKey, requestId, payload.loaderResult);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
173
|
+
loaderStore.finishError(bucketKey, requestId, err);
|
|
174
|
+
throw err;
|
|
175
|
+
} finally {
|
|
176
|
+
loaderStore.setLoading(bucketKey, requestId, false);
|
|
177
|
+
}
|
|
178
|
+
}, [loaderId, bucketKey]);
|
|
179
|
+
|
|
180
|
+
const [sharedState, setSharedState] = useState<{
|
|
181
|
+
bucketKey: string;
|
|
182
|
+
snapshot: LoaderEntry;
|
|
183
|
+
}>(() => ({
|
|
184
|
+
bucketKey,
|
|
185
|
+
snapshot: loaderStore.getSnapshot(bucketKey),
|
|
186
|
+
}));
|
|
187
|
+
const sharedSnapshot =
|
|
188
|
+
sharedState.bucketKey === bucketKey
|
|
189
|
+
? sharedState.snapshot
|
|
190
|
+
: loaderStore.getSnapshot(bucketKey);
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
const initial = loaderStore.getSnapshot(bucketKey);
|
|
193
|
+
if (initial !== sharedSnapshot) {
|
|
194
|
+
startTransition(() => {
|
|
195
|
+
setSharedState({ bucketKey, snapshot: initial });
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// ephemeral: a reader with no route context has no route-context reset
|
|
199
|
+
// trigger, so its keyed bucket is reference-counted by the store. A
|
|
200
|
+
// route-registered reader makes the bucket sticky (reset via clearFamily).
|
|
201
|
+
return loaderStore.subscribe(
|
|
202
|
+
bucketKey,
|
|
203
|
+
() => {
|
|
204
|
+
const next = loaderStore.getSnapshot(bucketKey);
|
|
205
|
+
startTransition(() => {
|
|
206
|
+
setSharedState({ bucketKey, snapshot: next });
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
loaderId,
|
|
211
|
+
ephemeral: !hasContextData,
|
|
212
|
+
group: hasGroups ? groupList : undefined,
|
|
213
|
+
refetch: hasGroups ? groupRefetch : undefined,
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional:
|
|
217
|
+
// sharedSnapshot is captured for the one-shot init sync; we don't want
|
|
218
|
+
// to re-subscribe on every snapshot change. bucketKey, hasContextData,
|
|
219
|
+
// groupKey, and groupRefetch are the only inputs that require a fresh
|
|
220
|
+
// subscription (groupList is memoized on groupKey; groupRefetch is stable
|
|
221
|
+
// per bucketKey).
|
|
222
|
+
}, [bucketKey, hasContextData, groupKey, groupRefetch]);
|
|
223
|
+
|
|
224
|
+
// Local state holds the result of:
|
|
225
|
+
// - parameterized / mutation `load()` calls (load({ params }), POST,
|
|
226
|
+
// etc.) — stay scoped so concurrent same-loader different-params
|
|
227
|
+
// fetches don't clobber each other through the shared store;
|
|
228
|
+
// - any `load()` made by hooks that are NOT in route context (i.e.
|
|
229
|
+
// useFetchLoader of an unregistered loader) — keeping those local
|
|
230
|
+
// prevents two unrelated components from accidentally sharing data
|
|
231
|
+
// through the global store just because they reference the same
|
|
232
|
+
// loader id.
|
|
233
|
+
// `has` distinguishes a committed local result (including `null`/`undefined`)
|
|
234
|
+
// from "no local load yet", so a load() that resolves to a falsy value is not
|
|
235
|
+
// discarded in favor of the shared snapshot or the seeded context.
|
|
236
|
+
const [localFetchedData, setLocalFetchedData] = useState<{
|
|
237
|
+
has: boolean;
|
|
238
|
+
value: T | undefined;
|
|
239
|
+
}>({ has: false, value: undefined });
|
|
240
|
+
const [localIsLoading, setLocalIsLoading] = useState(false);
|
|
241
|
+
const [localError, setLocalError] = useState<Error | null>(null);
|
|
242
|
+
|
|
243
|
+
// Local request id, mirrors the per-hook gating the previous
|
|
244
|
+
// implementation provided. Two quick parameterized loads from the same
|
|
245
|
+
// hook (e.g. load({ params: { q: "a" } }) then load({ params: { q: "b" } }))
|
|
246
|
+
// can resolve out of order — only the latest must commit.
|
|
247
|
+
const localRequestIdRef = useRef(0);
|
|
248
|
+
|
|
249
|
+
// Tracks the request id of the most recent SHARED load() this hook
|
|
250
|
+
// initiated. The render-throw rule below uses it to scope the throw
|
|
251
|
+
// to the originating hook only — sibling readers see the error in
|
|
252
|
+
// `error` but don't blow up their own boundaries.
|
|
253
|
+
const lastSharedRequestIdRef = useRef<number | null>(null);
|
|
254
|
+
|
|
255
|
+
// Reset on navigation. clear() bumps the entry's latest request id so
|
|
256
|
+
// any pre-navigation load() promise that resolves later fails its gate
|
|
257
|
+
// and is dropped — fixes the race where a stale fetch overwrites the
|
|
258
|
+
// new route's context.
|
|
164
259
|
const prevContextDataRef = useRef(contextData);
|
|
165
260
|
useEffect(() => {
|
|
166
261
|
if (prevContextDataRef.current !== contextData) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
262
|
+
setLocalFetchedData({ has: false, value: undefined });
|
|
263
|
+
setLocalIsLoading(false);
|
|
264
|
+
setLocalError(null);
|
|
265
|
+
lastSharedRequestIdRef.current = null;
|
|
266
|
+
// Reset every sticky bucket of this loader (keyed or not). Ephemeral
|
|
267
|
+
// (unregistered keyed) buckets are left to their refcount lifecycle.
|
|
268
|
+
loaderStore.clearFamily(loaderId);
|
|
170
269
|
prevContextDataRef.current = contextData;
|
|
171
270
|
}
|
|
172
|
-
}, [contextData]);
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
|
|
271
|
+
}, [contextData, loaderId]);
|
|
272
|
+
|
|
273
|
+
// Read priority: a committed parameterized load() result overrides the shared
|
|
274
|
+
// snapshot; a committed shared snapshot overrides the server-seeded context.
|
|
275
|
+
// `has`/`hasValue` gate each level so a committed falsy value is not skipped.
|
|
276
|
+
const data = localFetchedData.has
|
|
277
|
+
? localFetchedData.value
|
|
278
|
+
: sharedSnapshot.hasValue
|
|
279
|
+
? (sharedSnapshot.value as T | undefined)
|
|
280
|
+
: contextData;
|
|
281
|
+
const isLoading = localIsLoading || sharedSnapshot.isLoading;
|
|
282
|
+
const error = localError ?? sharedSnapshot.error;
|
|
176
283
|
|
|
177
284
|
const throwOnError = options?.throwOnError ?? true;
|
|
178
285
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const loaderIdRef = useRef(loader.$$id);
|
|
184
|
-
loaderIdRef.current = loader.$$id;
|
|
286
|
+
const loaderIdRef = useRef(loaderId);
|
|
287
|
+
loaderIdRef.current = loaderId;
|
|
288
|
+
const bucketKeyRef = useRef(bucketKey);
|
|
289
|
+
bucketKeyRef.current = bucketKey;
|
|
185
290
|
const dataRef = useRef(data);
|
|
186
291
|
dataRef.current = data;
|
|
292
|
+
const hasContextDataRef = useRef(hasContextData);
|
|
293
|
+
hasContextDataRef.current = hasContextData;
|
|
187
294
|
|
|
188
|
-
// Load function for fetching data via the ?_rsc_loader endpoint.
|
|
189
|
-
// Supports GET (data fetching) and POST/PUT/PATCH/DELETE (mutations).
|
|
190
295
|
const load = useCallback(
|
|
191
296
|
async (loadOptions?: LoadOptions): Promise<T> => {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
// Verify the loader has $$id
|
|
195
|
-
if (!loaderId) {
|
|
297
|
+
const id = loaderIdRef.current;
|
|
298
|
+
if (!id) {
|
|
196
299
|
throw new Error(
|
|
197
300
|
`Loader is missing $$id. Make sure the exposeLoaderId Vite plugin is enabled.`,
|
|
198
301
|
);
|
|
199
302
|
}
|
|
200
303
|
|
|
201
|
-
|
|
202
|
-
|
|
304
|
+
const bucket = bucketKeyRef.current;
|
|
305
|
+
const hasDedicatedBucket = bucket !== id;
|
|
306
|
+
|
|
307
|
+
const shared = hasDedicatedBucket
|
|
308
|
+
? isShareableGet(loadOptions)
|
|
309
|
+
: isPlainRefetch(loadOptions) && hasContextDataRef.current;
|
|
310
|
+
let sharedRequestId = -1;
|
|
311
|
+
let localRequestId = -1;
|
|
312
|
+
if (shared) {
|
|
313
|
+
sharedRequestId = loaderStore.reserveRequestId(bucket);
|
|
314
|
+
lastSharedRequestIdRef.current = sharedRequestId;
|
|
315
|
+
loaderStore.beginRequest(bucket, sharedRequestId);
|
|
316
|
+
} else {
|
|
317
|
+
localRequestId = ++localRequestIdRef.current;
|
|
318
|
+
setLocalIsLoading(true);
|
|
319
|
+
setLocalError(null);
|
|
320
|
+
}
|
|
203
321
|
|
|
204
322
|
try {
|
|
205
323
|
const url = new URL(window.location.href);
|
|
206
|
-
url.searchParams.set("_rsc_loader",
|
|
324
|
+
url.searchParams.set("_rsc_loader", id);
|
|
207
325
|
|
|
208
326
|
const method = loadOptions?.method ?? "GET";
|
|
209
327
|
const isBodyMethod = method !== "GET";
|
|
@@ -219,8 +337,6 @@ function useLoaderInternal<T>(
|
|
|
219
337
|
loadOptions?.params && Object.keys(loadOptions.params).length > 0;
|
|
220
338
|
|
|
221
339
|
if (bodyValue instanceof FormData) {
|
|
222
|
-
// FormData body — send as multipart/form-data (preserves File objects).
|
|
223
|
-
// Params are appended as a JSON string in a special field.
|
|
224
340
|
if (hasParams) {
|
|
225
341
|
bodyValue.set(
|
|
226
342
|
"_rsc_loader_params",
|
|
@@ -233,7 +349,6 @@ function useLoaderInternal<T>(
|
|
|
233
349
|
body: bodyValue,
|
|
234
350
|
};
|
|
235
351
|
} else {
|
|
236
|
-
// JSON body — send params and body as JSON
|
|
237
352
|
const bodyPayload: {
|
|
238
353
|
params?: Record<string, string>;
|
|
239
354
|
body?: unknown;
|
|
@@ -255,7 +370,6 @@ function useLoaderInternal<T>(
|
|
|
255
370
|
};
|
|
256
371
|
}
|
|
257
372
|
} else {
|
|
258
|
-
// GET - send params in query string
|
|
259
373
|
if (
|
|
260
374
|
loadOptions?.params &&
|
|
261
375
|
Object.keys(loadOptions.params).length > 0
|
|
@@ -284,36 +398,45 @@ function useLoaderInternal<T>(
|
|
|
284
398
|
}
|
|
285
399
|
|
|
286
400
|
const result = payload.loaderResult;
|
|
287
|
-
if (
|
|
401
|
+
if (shared) {
|
|
402
|
+
loaderStore.finishData(bucket, sharedRequestId, result);
|
|
403
|
+
} else if (localRequestId === localRequestIdRef.current) {
|
|
288
404
|
startTransition(() => {
|
|
289
|
-
|
|
405
|
+
setLocalFetchedData({ has: true, value: result });
|
|
406
|
+
setLocalIsLoading(false);
|
|
290
407
|
});
|
|
291
408
|
}
|
|
292
409
|
return result;
|
|
293
410
|
} catch (e) {
|
|
294
411
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
295
|
-
if (
|
|
296
|
-
|
|
412
|
+
if (shared) {
|
|
413
|
+
loaderStore.finishError(bucket, sharedRequestId, err);
|
|
414
|
+
} else if (localRequestId === localRequestIdRef.current) {
|
|
415
|
+
setLocalError(err);
|
|
416
|
+
setLocalIsLoading(false);
|
|
297
417
|
}
|
|
298
418
|
if (throwOnError) {
|
|
299
419
|
throw err;
|
|
300
420
|
}
|
|
301
|
-
// When throwOnError is false, return the latest data snapshot (previous
|
|
302
|
-
// successful value or undefined). Caller should check error state.
|
|
303
421
|
return dataRef.current as T;
|
|
304
422
|
} finally {
|
|
305
|
-
if (
|
|
306
|
-
|
|
423
|
+
if (shared) {
|
|
424
|
+
loaderStore.setLoading(bucket, sharedRequestId, false);
|
|
307
425
|
}
|
|
308
426
|
}
|
|
309
427
|
},
|
|
310
428
|
[throwOnError],
|
|
311
429
|
);
|
|
312
430
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
431
|
+
if (throwOnError) {
|
|
432
|
+
if (localError) throw localError;
|
|
433
|
+
if (
|
|
434
|
+
sharedSnapshot.error &&
|
|
435
|
+
lastSharedRequestIdRef.current !== null &&
|
|
436
|
+
sharedSnapshot.requestId === lastSharedRequestIdRef.current
|
|
437
|
+
) {
|
|
438
|
+
throw sharedSnapshot.error;
|
|
439
|
+
}
|
|
317
440
|
}
|
|
318
441
|
|
|
319
442
|
return {
|
|
@@ -357,7 +480,7 @@ function useLoaderInternal<T>(
|
|
|
357
480
|
export function useLoader<T>(
|
|
358
481
|
loader: LoaderDefinition<T>,
|
|
359
482
|
options?: UseLoaderOptions,
|
|
360
|
-
): UseLoaderResult<T
|
|
483
|
+
): UseLoaderResult<Rango.FlightSerialize<T>> {
|
|
361
484
|
const result = useLoaderInternal(loader, options);
|
|
362
485
|
|
|
363
486
|
// Strict mode: throw if data is not in context
|
|
@@ -369,7 +492,7 @@ export function useLoader<T>(
|
|
|
369
492
|
);
|
|
370
493
|
}
|
|
371
494
|
|
|
372
|
-
return result as UseLoaderResult<T
|
|
495
|
+
return result as UseLoaderResult<Rango.FlightSerialize<T>>;
|
|
373
496
|
}
|
|
374
497
|
|
|
375
498
|
/**
|
|
@@ -421,6 +544,68 @@ export function useLoader<T>(
|
|
|
421
544
|
export function useFetchLoader<T>(
|
|
422
545
|
loader: LoaderDefinition<T>,
|
|
423
546
|
options?: UseLoaderOptions,
|
|
424
|
-
): UseFetchLoaderResult<T
|
|
425
|
-
return useLoaderInternal(loader, options)
|
|
547
|
+
): UseFetchLoaderResult<Rango.FlightSerialize<T>> {
|
|
548
|
+
return useLoaderInternal(loader, options) as UseFetchLoaderResult<
|
|
549
|
+
Rango.FlightSerialize<T>
|
|
550
|
+
>;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Get a stable function that refreshes loaders by cross-loader group tag.
|
|
555
|
+
*
|
|
556
|
+
* The returned `refresh(groups)` takes one group name or an array of names and
|
|
557
|
+
* re-runs every currently-mounted read tagged with ANY of them, with a plain GET
|
|
558
|
+
* against the current route URL. This is the cross-loader counterpart to the
|
|
559
|
+
* single-loader `key`: use it to refresh a set of DIFFERENT loaders together
|
|
560
|
+
* (e.g. profile + orders after an account switch). Members are tagged via
|
|
561
|
+
* `useLoader(Loader, { refreshGroup })` / `useFetchLoader(Loader, { refreshGroup })`,
|
|
562
|
+
* where `refreshGroup` is one name or several.
|
|
563
|
+
*
|
|
564
|
+
* Passing the group(s) to the returned function rather than to the hook lets a
|
|
565
|
+
* single `useRefreshLoaders()` instance refresh different groups depending on
|
|
566
|
+
* context, and lets one call refresh several groups at once — their members are
|
|
567
|
+
* unioned and deduped, so a loader tagged into two of the named groups is fetched
|
|
568
|
+
* exactly once.
|
|
569
|
+
*
|
|
570
|
+
* Group refresh never render-throws: a failing member surfaces its error via
|
|
571
|
+
* that read's `error` state, and the returned promise rejects with an
|
|
572
|
+
* `AggregateError` of the failures so the caller can handle them at the await
|
|
573
|
+
* site. Each loader is refreshed in place — no params, no body, no mutations.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```tsx
|
|
577
|
+
* "use client";
|
|
578
|
+
* import { useLoader, useRefreshLoaders } from "rsc-router/client";
|
|
579
|
+
*
|
|
580
|
+
* function Profile() {
|
|
581
|
+
* const { data } = useLoader(ProfileLoader, { key: userId, refreshGroup: "account" });
|
|
582
|
+
* return <span>{data.name}</span>;
|
|
583
|
+
* }
|
|
584
|
+
* function Orders() {
|
|
585
|
+
* // Tagged into two groups: refreshed by "account" (the whole set) or "orders".
|
|
586
|
+
* const { data } = useLoader(OrdersLoader, {
|
|
587
|
+
* key: userId,
|
|
588
|
+
* refreshGroup: ["account", "orders"],
|
|
589
|
+
* });
|
|
590
|
+
* return <span>{data.count} orders</span>;
|
|
591
|
+
* }
|
|
592
|
+
* function RefreshButtons() {
|
|
593
|
+
* const refresh = useRefreshLoaders();
|
|
594
|
+
* return (
|
|
595
|
+
* <>
|
|
596
|
+
* <button onClick={() => refresh("account")}>Refresh account</button>
|
|
597
|
+
* <button onClick={() => refresh("orders")}>Refresh orders</button>
|
|
598
|
+
* <button onClick={() => refresh(["account", "orders"])}>Refresh both</button>
|
|
599
|
+
* </>
|
|
600
|
+
* );
|
|
601
|
+
* }
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
export function useRefreshLoaders(): (
|
|
605
|
+
groups: string | string[],
|
|
606
|
+
) => Promise<void> {
|
|
607
|
+
return useCallback(
|
|
608
|
+
(groups: string | string[]) => loaderStore.refreshGroups(groups),
|
|
609
|
+
[],
|
|
610
|
+
);
|
|
426
611
|
}
|
package/src/vite/debug.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { resolve } from "node:path";
|
|
|
9
9
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
10
10
|
import { evictHandlerCode } from "../utils/bundle-analysis.js";
|
|
11
11
|
import { copyStagedBuildAssets } from "../utils/prerender-utils.js";
|
|
12
|
+
import { jsonParseExpression } from "../utils/manifest-utils.js";
|
|
12
13
|
import type { DiscoveryState } from "./state.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -71,12 +72,12 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
71
72
|
writeFileSync(chunkPath, result.code);
|
|
72
73
|
const savedKB = (result.savedBytes / 1024).toFixed(1);
|
|
73
74
|
console.log(
|
|
74
|
-
`[
|
|
75
|
+
`[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
|
|
75
76
|
);
|
|
76
77
|
}
|
|
77
78
|
} catch (replaceErr: any) {
|
|
78
79
|
console.warn(
|
|
79
|
-
`[
|
|
80
|
+
`[rango] Failed to evict ${target.label}: ${replaceErr.message}`,
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
}
|
|
@@ -104,7 +105,7 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
const manifestCode = [
|
|
107
|
-
`const m
|
|
108
|
+
`const m=${jsonParseExpression(manifestMap)};`,
|
|
108
109
|
`export function loadPrerenderAsset(s){return import(s)}`,
|
|
109
110
|
`export default m;`,
|
|
110
111
|
"",
|
|
@@ -121,11 +122,11 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
121
122
|
|
|
122
123
|
const totalKB = (totalBytes / 1024).toFixed(1);
|
|
123
124
|
console.log(
|
|
124
|
-
`[
|
|
125
|
+
`[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
|
|
125
126
|
);
|
|
126
127
|
} catch (err: any) {
|
|
127
128
|
throw new Error(
|
|
128
|
-
`[
|
|
129
|
+
`[rango] Failed to write prerender assets: ${err.message}`,
|
|
129
130
|
);
|
|
130
131
|
}
|
|
131
132
|
}
|
|
@@ -169,11 +170,11 @@ export function postprocessBundle(state: DiscoveryState): void {
|
|
|
169
170
|
|
|
170
171
|
const totalKB = (totalBytes / 1024).toFixed(1);
|
|
171
172
|
console.log(
|
|
172
|
-
`[
|
|
173
|
+
`[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
|
|
173
174
|
);
|
|
174
175
|
} catch (err: any) {
|
|
175
176
|
throw new Error(
|
|
176
|
-
`[
|
|
177
|
+
`[rango] Failed to write static assets: ${err.message}`,
|
|
177
178
|
);
|
|
178
179
|
}
|
|
179
180
|
}
|