@rangojs/router 0.0.0-experimental.002d056c
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/AGENTS.md +9 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +464 -0
- package/src/browser/scroll-restoration.ts +397 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +547 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +982 -0
- package/src/cache/cf/index.ts +29 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +44 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +281 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +193 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +749 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +320 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1006 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +920 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +363 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery State
|
|
3
|
+
*
|
|
4
|
+
* Shared mutable state for the router discovery plugin.
|
|
5
|
+
* Created once by createRouterDiscoveryPlugin() and passed
|
|
6
|
+
* to all extracted helper functions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ScanFilter } from "../../build/generate-route-types.js";
|
|
10
|
+
|
|
11
|
+
export const VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
|
|
12
|
+
|
|
13
|
+
export interface PluginOptions {
|
|
14
|
+
enableBuildPrerender?: boolean;
|
|
15
|
+
staticRouteTypesGeneration?: boolean;
|
|
16
|
+
// Mutable ref for deferred auto-discovery (node preset).
|
|
17
|
+
// The auto-discover config() hook populates this before configResolved.
|
|
18
|
+
routerPathRef?: { path?: string };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PrecomputedEntry {
|
|
22
|
+
staticPrefix: string;
|
|
23
|
+
routes: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ChunkInfo {
|
|
27
|
+
fileName: string;
|
|
28
|
+
exports: Array<{ name: string; handlerId: string; passthrough: boolean }>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PerRouterManifestEntry {
|
|
32
|
+
id: string;
|
|
33
|
+
routeManifest: Record<string, string>;
|
|
34
|
+
routeSearchSchemas?: Record<string, Record<string, string>>;
|
|
35
|
+
sourceFile?: string;
|
|
36
|
+
factoryOnlyPrefixes?: Set<string>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DiscoveryState {
|
|
40
|
+
resolvedEntryPath: string | undefined;
|
|
41
|
+
projectRoot: string;
|
|
42
|
+
isBuildMode: boolean;
|
|
43
|
+
userResolveAlias: any;
|
|
44
|
+
scanFilter: ScanFilter | undefined;
|
|
45
|
+
cachedRouterFiles: string[] | undefined;
|
|
46
|
+
opts: PluginOptions | undefined;
|
|
47
|
+
|
|
48
|
+
mergedRouteManifest: Record<string, string> | null;
|
|
49
|
+
perRouterManifests: PerRouterManifestEntry[];
|
|
50
|
+
mergedPrecomputedEntries: PrecomputedEntry[] | null;
|
|
51
|
+
mergedRouteTrie: any;
|
|
52
|
+
|
|
53
|
+
perRouterTrieMap: Map<string, any>;
|
|
54
|
+
perRouterPrecomputedMap: Map<string, PrecomputedEntry[]>;
|
|
55
|
+
perRouterManifestDataMap: Map<string, Record<string, string>>;
|
|
56
|
+
|
|
57
|
+
prerenderManifestEntries: Record<string, string> | null;
|
|
58
|
+
staticManifestEntries: Record<string, string> | null;
|
|
59
|
+
handlerChunkInfo: ChunkInfo | null;
|
|
60
|
+
staticHandlerChunkInfo: ChunkInfo | null;
|
|
61
|
+
rscEntryFileName: string | null;
|
|
62
|
+
resolvedPrerenderModules: Map<string, string[]> | undefined;
|
|
63
|
+
resolvedStaticModules: Map<string, string[]> | undefined;
|
|
64
|
+
|
|
65
|
+
discoveryDone: Promise<void> | null;
|
|
66
|
+
devServerOrigin: string | null;
|
|
67
|
+
devServer: any;
|
|
68
|
+
selfWrittenGenFiles: Map<string, { at: number; hash: string }>;
|
|
69
|
+
SELF_WRITE_WINDOW_MS: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createDiscoveryState(
|
|
73
|
+
entryPath: string | undefined,
|
|
74
|
+
opts: PluginOptions | undefined,
|
|
75
|
+
): DiscoveryState {
|
|
76
|
+
return {
|
|
77
|
+
resolvedEntryPath: entryPath,
|
|
78
|
+
projectRoot: "",
|
|
79
|
+
isBuildMode: false,
|
|
80
|
+
userResolveAlias: undefined,
|
|
81
|
+
scanFilter: undefined,
|
|
82
|
+
cachedRouterFiles: undefined,
|
|
83
|
+
opts,
|
|
84
|
+
|
|
85
|
+
mergedRouteManifest: null,
|
|
86
|
+
perRouterManifests: [],
|
|
87
|
+
mergedPrecomputedEntries: null,
|
|
88
|
+
mergedRouteTrie: null,
|
|
89
|
+
|
|
90
|
+
perRouterTrieMap: new Map(),
|
|
91
|
+
perRouterPrecomputedMap: new Map(),
|
|
92
|
+
perRouterManifestDataMap: new Map(),
|
|
93
|
+
|
|
94
|
+
prerenderManifestEntries: null,
|
|
95
|
+
staticManifestEntries: null,
|
|
96
|
+
handlerChunkInfo: null,
|
|
97
|
+
staticHandlerChunkInfo: null,
|
|
98
|
+
rscEntryFileName: null,
|
|
99
|
+
resolvedPrerenderModules: undefined,
|
|
100
|
+
resolvedStaticModules: undefined,
|
|
101
|
+
|
|
102
|
+
discoveryDone: null,
|
|
103
|
+
devServerOrigin: null,
|
|
104
|
+
devServer: null,
|
|
105
|
+
selfWrittenGenFiles: new Map(),
|
|
106
|
+
SELF_WRITE_WINDOW_MS: 5_000,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Virtual Module Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates the code for virtual:rsc-router/routes-manifest and
|
|
5
|
+
* per-router virtual modules used by the load() hook.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { dirname, basename, join } from "node:path";
|
|
9
|
+
import { jsonParseExpression } from "../utils/manifest-utils.js";
|
|
10
|
+
import { VIRTUAL_ROUTES_MANIFEST_ID } from "./state.js";
|
|
11
|
+
import type { DiscoveryState } from "./state.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generate the code for the main virtual:rsc-router/routes-manifest module.
|
|
15
|
+
*/
|
|
16
|
+
export function generateRoutesManifestModule(state: DiscoveryState): string {
|
|
17
|
+
const hasManifest =
|
|
18
|
+
state.mergedRouteManifest &&
|
|
19
|
+
Object.keys(state.mergedRouteManifest).length > 0;
|
|
20
|
+
|
|
21
|
+
if (hasManifest) {
|
|
22
|
+
// Build gen file import statements for each router with a sourceFile.
|
|
23
|
+
// This creates a dependency in Vite's module graph: when the gen file
|
|
24
|
+
// changes (e.g. after HMR route edits), Vite invalidates this virtual
|
|
25
|
+
// module and re-evaluates it on the next request, calling
|
|
26
|
+
// setCachedManifest() with fresh data. No manual sync needed.
|
|
27
|
+
const genFileImports: string[] = [];
|
|
28
|
+
const genFileVars: string[] = [];
|
|
29
|
+
const routersWithoutGenFile: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
manifest: Record<string, string>;
|
|
32
|
+
}> = [];
|
|
33
|
+
let varIdx = 0;
|
|
34
|
+
|
|
35
|
+
for (const entry of state.perRouterManifests) {
|
|
36
|
+
if (entry.sourceFile) {
|
|
37
|
+
const routerDir = dirname(entry.sourceFile);
|
|
38
|
+
const routerBasename = basename(entry.sourceFile).replace(
|
|
39
|
+
/\.(tsx?|jsx?)$/,
|
|
40
|
+
"",
|
|
41
|
+
);
|
|
42
|
+
const genPath = join(
|
|
43
|
+
routerDir,
|
|
44
|
+
`${routerBasename}.named-routes.gen.js`,
|
|
45
|
+
).replaceAll("\\", "/");
|
|
46
|
+
const varName = `_r${varIdx++}`;
|
|
47
|
+
genFileImports.push(
|
|
48
|
+
`import { NamedRoutes as ${varName} } from ${JSON.stringify(genPath)};`,
|
|
49
|
+
);
|
|
50
|
+
genFileVars.push(varName);
|
|
51
|
+
} else {
|
|
52
|
+
// Routers without sourceFile: inline their manifest data directly
|
|
53
|
+
routersWithoutGenFile.push({
|
|
54
|
+
id: entry.id,
|
|
55
|
+
manifest: entry.routeManifest,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const lines = [
|
|
61
|
+
`import { setCachedManifest, setPrecomputedEntries, setRouteTrie, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
|
|
62
|
+
...genFileImports,
|
|
63
|
+
// Clear stale per-router cached data (manifest, trie, precomputed entries)
|
|
64
|
+
// before re-populating. In Cloudflare dev mode, program reloads re-evaluate
|
|
65
|
+
// this virtual module but the route-map-builder singleton retains old data
|
|
66
|
+
// because it's not in the HMR invalidation chain. Without this clear, the
|
|
67
|
+
// handler finds stale trie data and never rebuilds from updated urlpatterns.
|
|
68
|
+
`clearAllRouterData();`,
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// Flatten NamedRoutes entries: search schema objects -> plain string paths
|
|
72
|
+
if (genFileVars.length > 0) {
|
|
73
|
+
lines.push(
|
|
74
|
+
`function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Build the merged manifest from gen file imports + inlined data
|
|
79
|
+
if (genFileVars.length === 1 && routersWithoutGenFile.length === 0) {
|
|
80
|
+
lines.push(`setCachedManifest(__flat(${genFileVars[0]}));`);
|
|
81
|
+
} else {
|
|
82
|
+
const parts: string[] = [];
|
|
83
|
+
for (const v of genFileVars) parts.push(`...__flat(${v})`);
|
|
84
|
+
for (const { manifest } of routersWithoutGenFile)
|
|
85
|
+
parts.push(`...${jsonParseExpression(manifest)}`);
|
|
86
|
+
lines.push(`setCachedManifest({ ${parts.join(", ")} });`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Set per-router manifests
|
|
90
|
+
let genVarIdx = 0;
|
|
91
|
+
for (const entry of state.perRouterManifests) {
|
|
92
|
+
if (entry.sourceFile) {
|
|
93
|
+
const varName = genFileVars[genVarIdx++];
|
|
94
|
+
lines.push(
|
|
95
|
+
`setRouterManifest(${JSON.stringify(entry.id)}, __flat(${varName}));`,
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
lines.push(
|
|
99
|
+
`setRouterManifest(${JSON.stringify(entry.id)}, ${jsonParseExpression(entry.routeManifest)});`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// In dev mode, skip trie and precomputed entries injection. These are
|
|
105
|
+
// computed once during initial discovery and become stale after route
|
|
106
|
+
// changes. A stale trie would incorrectly match removed routes. The
|
|
107
|
+
// handler falls back to Phase 2 regex matching against the live
|
|
108
|
+
// router.urlpatterns, which is always correct after a program reload.
|
|
109
|
+
// In build mode, the trie is always fresh (built from the final route
|
|
110
|
+
// tree) so it's safe to inject.
|
|
111
|
+
if (state.isBuildMode) {
|
|
112
|
+
if (
|
|
113
|
+
state.mergedPrecomputedEntries &&
|
|
114
|
+
state.mergedPrecomputedEntries.length > 0
|
|
115
|
+
) {
|
|
116
|
+
lines.push(
|
|
117
|
+
`setPrecomputedEntries(${jsonParseExpression(state.mergedPrecomputedEntries)});`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (state.mergedRouteTrie) {
|
|
121
|
+
lines.push(
|
|
122
|
+
`setRouteTrie(${jsonParseExpression(state.mergedRouteTrie)});`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Register lazy loaders for per-router manifest modules.
|
|
128
|
+
// Each import() uses a static string literal so Rollup creates separate chunks.
|
|
129
|
+
for (const routerId of state.perRouterManifestDataMap.keys()) {
|
|
130
|
+
lines.push(
|
|
131
|
+
`registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (!state.isBuildMode && state.devServerOrigin) {
|
|
135
|
+
lines.push(
|
|
136
|
+
`globalThis.__PRERENDER_DEV_URL = ${JSON.stringify(state.devServerOrigin)};`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// No manifest: either discovery hasn't completed or no runner (Cloudflare dev).
|
|
143
|
+
// Still inject __PRERENDER_DEV_URL so the prerender store can fetch on-demand.
|
|
144
|
+
// Re-resolve origin now since the server is listening by module load time.
|
|
145
|
+
if (!state.isBuildMode) {
|
|
146
|
+
const origin =
|
|
147
|
+
state.devServerOrigin ||
|
|
148
|
+
state.devServer?.resolvedUrls?.local?.[0]?.replace(/\/$/, "") ||
|
|
149
|
+
(state.devServer &&
|
|
150
|
+
`http://localhost:${state.devServer.config.server.port || 5173}`);
|
|
151
|
+
if (origin) {
|
|
152
|
+
state.devServerOrigin = origin;
|
|
153
|
+
return `globalThis.__PRERENDER_DEV_URL = ${JSON.stringify(origin)};`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return `// Route manifest will be populated at runtime`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generate the code for a per-router virtual module.
|
|
161
|
+
*/
|
|
162
|
+
export function generatePerRouterModule(
|
|
163
|
+
state: DiscoveryState,
|
|
164
|
+
routerId: string,
|
|
165
|
+
): string {
|
|
166
|
+
// Find the per-router entry to get the gen file path
|
|
167
|
+
const routerEntry = state.perRouterManifests.find((e) => e.id === routerId);
|
|
168
|
+
const trie = state.perRouterTrieMap.get(routerId);
|
|
169
|
+
const entries = state.perRouterPrecomputedMap.get(routerId);
|
|
170
|
+
const lines: string[] = [];
|
|
171
|
+
|
|
172
|
+
if (routerEntry?.sourceFile) {
|
|
173
|
+
// Import manifest from the gen file so HMR auto-propagates
|
|
174
|
+
const routerDir = dirname(routerEntry.sourceFile);
|
|
175
|
+
const routerBasename = basename(routerEntry.sourceFile).replace(
|
|
176
|
+
/\.(tsx?|jsx?)$/,
|
|
177
|
+
"",
|
|
178
|
+
);
|
|
179
|
+
const genPath = join(
|
|
180
|
+
routerDir,
|
|
181
|
+
`${routerBasename}.named-routes.gen.js`,
|
|
182
|
+
).replaceAll("\\", "/");
|
|
183
|
+
lines.push(`import { NamedRoutes as _r } from ${JSON.stringify(genPath)};`);
|
|
184
|
+
lines.push(
|
|
185
|
+
`function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`,
|
|
186
|
+
);
|
|
187
|
+
lines.push(`export const manifest = __flat(_r);`);
|
|
188
|
+
} else {
|
|
189
|
+
const manifest = state.perRouterManifestDataMap.get(routerId);
|
|
190
|
+
if (manifest) {
|
|
191
|
+
lines.push(`export const manifest = ${jsonParseExpression(manifest)};`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (trie) {
|
|
195
|
+
lines.push(`export const trie = ${jsonParseExpression(trie)};`);
|
|
196
|
+
}
|
|
197
|
+
if (entries && entries.length > 0) {
|
|
198
|
+
lines.push(
|
|
199
|
+
`export const precomputedEntries = ${jsonParseExpression(entries)};`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return lines.join("\n") || "// empty router manifest";
|
|
203
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for @rangojs/router/vite
|
|
3
|
+
*
|
|
4
|
+
* Exports: rango() plugin factory, poke() dev utility plugin,
|
|
5
|
+
* and related option types. All other utilities are internal implementation
|
|
6
|
+
* details consumed via direct imports within the package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { rango } from "./rango.js";
|
|
10
|
+
export { poke } from "./plugins/refresh-cmd.js";
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
RangoNodeOptions,
|
|
14
|
+
RangoCloudflareOptions,
|
|
15
|
+
RangoOptions,
|
|
16
|
+
} from "./plugin-types.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base options shared by all presets
|
|
3
|
+
*/
|
|
4
|
+
interface RangoBaseOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Show startup banner. Set to false to disable.
|
|
7
|
+
* @default true
|
|
8
|
+
*/
|
|
9
|
+
banner?: boolean;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate named-routes.gen.ts by parsing url modules at startup.
|
|
13
|
+
* Provides type-safe Handler<"name"> and href() without executing router code.
|
|
14
|
+
* Set to `false` to disable (run `npx rango extract-names` manually instead).
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
staticRouteTypesGeneration?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for Node.js deployment (default)
|
|
22
|
+
*/
|
|
23
|
+
export interface RangoNodeOptions extends RangoBaseOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Deployment preset. Defaults to 'node' when not specified.
|
|
26
|
+
*/
|
|
27
|
+
preset?: "node";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Options for Cloudflare Workers deployment
|
|
32
|
+
*/
|
|
33
|
+
export interface RangoCloudflareOptions extends RangoBaseOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Deployment preset for Cloudflare Workers.
|
|
36
|
+
* When using cloudflare preset:
|
|
37
|
+
* - @vitejs/plugin-rsc is NOT added (cloudflare plugin adds it)
|
|
38
|
+
* - Your worker entry (e.g., worker.rsc.tsx) imports the router directly
|
|
39
|
+
* - Browser and SSR use virtual entries
|
|
40
|
+
* - Build-time manifest generation is auto-detected from the resolved RSC environment config
|
|
41
|
+
*/
|
|
42
|
+
preset: "cloudflare";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options for rango() Vite plugin
|
|
47
|
+
*/
|
|
48
|
+
export type RangoOptions = RangoNodeOptions | RangoCloudflareOptions;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transform CJS vendor files from @vitejs/plugin-rsc to ESM for browser compatibility.
|
|
5
|
+
* The react-server-dom vendor files are shipped as CJS which doesn't work in browsers.
|
|
6
|
+
*/
|
|
7
|
+
export function createCjsToEsmPlugin(): Plugin {
|
|
8
|
+
return {
|
|
9
|
+
name: "@rangojs/router:cjs-to-esm",
|
|
10
|
+
enforce: "pre",
|
|
11
|
+
transform(code, id) {
|
|
12
|
+
const cleanId = id.split("?")[0];
|
|
13
|
+
|
|
14
|
+
// Transform the client.browser.js entry point to re-export from CJS
|
|
15
|
+
if (
|
|
16
|
+
cleanId.includes("vendor/react-server-dom/client.browser.js") ||
|
|
17
|
+
cleanId.includes("vendor\\react-server-dom\\client.browser.js")
|
|
18
|
+
) {
|
|
19
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
20
|
+
const cjsFile = isProd
|
|
21
|
+
? "./cjs/react-server-dom-webpack-client.browser.production.js"
|
|
22
|
+
: "./cjs/react-server-dom-webpack-client.browser.development.js";
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
code: `export * from "${cjsFile}";`,
|
|
26
|
+
map: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Transform the actual CJS files to ESM
|
|
31
|
+
if (
|
|
32
|
+
(cleanId.includes("vendor/react-server-dom/cjs/") ||
|
|
33
|
+
cleanId.includes("vendor\\react-server-dom\\cjs\\")) &&
|
|
34
|
+
cleanId.includes("client.browser")
|
|
35
|
+
) {
|
|
36
|
+
let transformed = code;
|
|
37
|
+
|
|
38
|
+
// Extract the license comment to preserve it
|
|
39
|
+
const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
|
|
40
|
+
const license = licenseMatch ? licenseMatch[0] : "";
|
|
41
|
+
if (license) {
|
|
42
|
+
transformed = transformed.slice(license.length);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Remove "use strict" (both dev and prod have this)
|
|
46
|
+
transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
|
|
47
|
+
|
|
48
|
+
// Remove the conditional IIFE wrapper (development only)
|
|
49
|
+
transformed = transformed.replace(
|
|
50
|
+
/^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
|
|
51
|
+
"",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Remove the closing of the conditional IIFE at the end (development only)
|
|
55
|
+
transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
|
|
56
|
+
|
|
57
|
+
// Replace require('react') and require('react-dom') with imports (development)
|
|
58
|
+
transformed = transformed.replace(
|
|
59
|
+
/var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
60
|
+
'import React from "react";\nimport ReactDOM from "react-dom";\nvar ',
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Replace require('react-dom') only (production - doesn't import React)
|
|
64
|
+
transformed = transformed.replace(
|
|
65
|
+
/var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
66
|
+
'import ReactDOM from "react-dom";\nvar ',
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Transform exports.xyz = function() to export function xyz()
|
|
70
|
+
transformed = transformed.replace(
|
|
71
|
+
/exports\.(\w+)\s*=\s*function\s*\(/g,
|
|
72
|
+
"export function $1(",
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Transform exports.xyz = value to export const xyz = value
|
|
76
|
+
transformed = transformed.replace(
|
|
77
|
+
/exports\.(\w+)\s*=/g,
|
|
78
|
+
"export const $1 =",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Reconstruct with license at the top
|
|
82
|
+
transformed = license + "\n" + transformed;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
code: transformed,
|
|
86
|
+
map: null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
|
+
|
|
3
|
+
const CLIENT_IN_SERVER_PROXY_PREFIX =
|
|
4
|
+
"virtual:vite-rsc/client-in-server-package-proxy/";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract the bare package name from an absolute node_modules path.
|
|
8
|
+
* Handles scoped packages (@org/name) and nested node_modules.
|
|
9
|
+
* Returns null if the path doesn't contain a valid package reference.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: This is a lossy transformation. It maps a specific submodule path
|
|
12
|
+
* (e.g., pkg/internal/context.js) to the package root (pkg). The load()
|
|
13
|
+
* hook then re-exports via the bare specifier, which resolves to the
|
|
14
|
+
* package entry point. This works for packages that barrel-export their
|
|
15
|
+
* "use client" symbols from the root, which covers the common case
|
|
16
|
+
* (component libraries like @mantine/core, @chakra-ui/react, etc.).
|
|
17
|
+
* Packages whose client symbols are only available from deep subpaths
|
|
18
|
+
* (not re-exported from the root) would lose those symbols after the
|
|
19
|
+
* rewrite. A more precise approach would resolve through the package's
|
|
20
|
+
* exports map to find the correct entry point, but that adds significant
|
|
21
|
+
* complexity for a rare edge case.
|
|
22
|
+
* See: https://github.com/cloudflare/vinext/pull/413
|
|
23
|
+
*/
|
|
24
|
+
export function extractPackageName(absolutePath: string): string | null {
|
|
25
|
+
// Find the last /node_modules/ segment (handles nested node_modules)
|
|
26
|
+
const marker = "/node_modules/";
|
|
27
|
+
const idx = absolutePath.lastIndexOf(marker);
|
|
28
|
+
if (idx === -1) return null;
|
|
29
|
+
|
|
30
|
+
const afterModules = absolutePath.slice(idx + marker.length);
|
|
31
|
+
|
|
32
|
+
if (afterModules.startsWith("@")) {
|
|
33
|
+
// Scoped package: @org/name
|
|
34
|
+
const parts = afterModules.split("/");
|
|
35
|
+
if (parts.length < 2 || !parts[1]) return null;
|
|
36
|
+
return `${parts[0]}/${parts[1]}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Unscoped package: name
|
|
40
|
+
const name = afterModules.split("/")[0];
|
|
41
|
+
return name || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Vite plugin that deduplicates client references from third-party packages
|
|
46
|
+
* in dev mode.
|
|
47
|
+
*
|
|
48
|
+
* When @vitejs/plugin-rsc encounters a "use client" submodule inside a
|
|
49
|
+
* package imported from a server component, it creates a
|
|
50
|
+
* client-in-server-package-proxy virtual module that re-exports from the
|
|
51
|
+
* absolute file path. In the client environment, this absolute path bypasses
|
|
52
|
+
* Vite's pre-bundling, while direct client imports of the same package go
|
|
53
|
+
* through .vite/deps/. Two separate module instances are created, breaking
|
|
54
|
+
* React contexts (createContext runs twice, provider/consumer mismatch).
|
|
55
|
+
*
|
|
56
|
+
* This plugin intercepts absolute node_modules imports from proxy modules
|
|
57
|
+
* in the client environment and rewrites them to bare specifier imports
|
|
58
|
+
* that go through pre-bundling, ensuring a single module instance.
|
|
59
|
+
*
|
|
60
|
+
* Dev-only: production builds use the SSR manifest which handles module
|
|
61
|
+
* identity correctly.
|
|
62
|
+
*/
|
|
63
|
+
export function clientRefDedup(): Plugin {
|
|
64
|
+
let clientExclude: string[] = [];
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
name: "@rangojs/router:client-ref-dedup",
|
|
68
|
+
enforce: "pre",
|
|
69
|
+
apply: "serve",
|
|
70
|
+
|
|
71
|
+
configResolved(config: ResolvedConfig) {
|
|
72
|
+
// Respect user's optimizeDeps.exclude — if a package is explicitly
|
|
73
|
+
// excluded from pre-bundling, we shouldn't redirect it there.
|
|
74
|
+
const clientEnv = config.environments?.["client"];
|
|
75
|
+
clientExclude =
|
|
76
|
+
clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
resolveId(source, importer, options) {
|
|
80
|
+
// Only intercept in the client environment
|
|
81
|
+
if (this.environment?.name !== "client") return;
|
|
82
|
+
|
|
83
|
+
// Only handle imports from client-in-server-package-proxy virtual modules
|
|
84
|
+
if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
|
|
85
|
+
|
|
86
|
+
// Only handle absolute node_modules paths
|
|
87
|
+
if (!source.includes("/node_modules/")) return;
|
|
88
|
+
|
|
89
|
+
// Must have an importer
|
|
90
|
+
if (!importer) return;
|
|
91
|
+
|
|
92
|
+
const packageName = extractPackageName(source);
|
|
93
|
+
if (!packageName) return;
|
|
94
|
+
|
|
95
|
+
// Don't redirect packages that are excluded from optimization
|
|
96
|
+
if (clientExclude.includes(packageName)) return;
|
|
97
|
+
|
|
98
|
+
// Return a virtual module that re-exports via bare specifier
|
|
99
|
+
return `\0rango:dedup/${packageName}`;
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
load(id) {
|
|
103
|
+
if (!id.startsWith("\0rango:dedup/")) return;
|
|
104
|
+
|
|
105
|
+
const packageName = id.slice("\0rango:dedup/".length);
|
|
106
|
+
|
|
107
|
+
// Re-export via bare specifier so Vite routes through pre-bundling
|
|
108
|
+
return [
|
|
109
|
+
`export * from ${JSON.stringify(packageName)};`,
|
|
110
|
+
`import * as __all__ from ${JSON.stringify(packageName)};`,
|
|
111
|
+
`export default __all__.default;`,
|
|
112
|
+
].join("\n");
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
// Dev-mode client-reference key prefixes emitted by @vitejs/plugin-rsc
|
|
6
|
+
const CLIENT_PKG_PROXY_PREFIX =
|
|
7
|
+
"/@id/__x00__virtual:vite-rsc/client-package-proxy/";
|
|
8
|
+
const CLIENT_IN_SERVER_PKG_PROXY_PREFIX =
|
|
9
|
+
"/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
|
|
10
|
+
const FS_PREFIX = "/@fs/";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compute the production SHA-256 hash for a dev-mode client reference key.
|
|
14
|
+
* Mirrors the hashing logic in @vitejs/plugin-rsc's build mode:
|
|
15
|
+
* - Local files: hashString(toRelativeId(id)) where toRelativeId = relative(root, id)
|
|
16
|
+
* - Package proxies: hashString(packageSource)
|
|
17
|
+
* - client-in-server-package proxies: hashString(relative(root, decodedAbsPath))
|
|
18
|
+
*
|
|
19
|
+
* Returns the input unchanged if it doesn't match a known dev-mode pattern
|
|
20
|
+
* (e.g., already a production hash).
|
|
21
|
+
*/
|
|
22
|
+
export function computeProductionHash(
|
|
23
|
+
projectRoot: string,
|
|
24
|
+
refKey: string,
|
|
25
|
+
): string {
|
|
26
|
+
let toHash: string;
|
|
27
|
+
|
|
28
|
+
if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
|
|
29
|
+
// /@id/__x00__virtual:vite-rsc/client-package-proxy/<pkg> -> hash("<pkg>")
|
|
30
|
+
toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
|
|
31
|
+
} else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
|
|
32
|
+
// /@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/<encodedAbsPath>
|
|
33
|
+
const absPath = decodeURIComponent(
|
|
34
|
+
refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length),
|
|
35
|
+
);
|
|
36
|
+
toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
|
|
37
|
+
} else if (refKey.startsWith(FS_PREFIX)) {
|
|
38
|
+
// /@fs/abs/path.tsx -> hash(relative(root, "/abs/path.tsx"))
|
|
39
|
+
const absPath = refKey.slice(FS_PREFIX.length - 1); // keep leading /
|
|
40
|
+
toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
|
|
41
|
+
} else if (refKey.startsWith("/")) {
|
|
42
|
+
// /src/Button.tsx -> hash("src/Button.tsx")
|
|
43
|
+
toHash = refKey.slice(1);
|
|
44
|
+
} else {
|
|
45
|
+
// Already hashed or unknown format — return unchanged
|
|
46
|
+
return refKey;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return createHash("sha256").update(toHash).digest("hex").slice(0, 12);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Regex to match registerClientReference() calls as emitted by @vitejs/plugin-rsc.
|
|
53
|
+
// Captures the reference key (second argument) from the call.
|
|
54
|
+
// Handles two proxy forms: parenthesized expression `(expr)` and arrow-throw `() => { ... }`.
|
|
55
|
+
const REGISTER_CLIENT_REF_RE =
|
|
56
|
+
/registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Transform source code by replacing dev-mode client reference keys with
|
|
60
|
+
* production hashes. Exported for testing; used internally by hashClientRefs.
|
|
61
|
+
* Returns null if no replacements were made.
|
|
62
|
+
*/
|
|
63
|
+
export function transformClientRefs(
|
|
64
|
+
code: string,
|
|
65
|
+
projectRoot: string,
|
|
66
|
+
): string | null {
|
|
67
|
+
if (!code.includes("registerClientReference")) return null;
|
|
68
|
+
|
|
69
|
+
let hasReplacement = false;
|
|
70
|
+
const result = code.replace(
|
|
71
|
+
REGISTER_CLIENT_REF_RE,
|
|
72
|
+
(match, refKey: string) => {
|
|
73
|
+
const hash = computeProductionHash(projectRoot, refKey);
|
|
74
|
+
if (hash === refKey) return match;
|
|
75
|
+
hasReplacement = true;
|
|
76
|
+
return match.replace(`"${refKey}"`, `"${hash}"`);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return hasReplacement ? result : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Vite plugin that rewrites registerClientReference() calls in the RSC
|
|
85
|
+
* environment, replacing dev-mode reference keys with production hashes.
|
|
86
|
+
*
|
|
87
|
+
* This runs AFTER the RSC plugin's transform so the Flight serializer
|
|
88
|
+
* naturally emits production IDs, eliminating the need for post-build
|
|
89
|
+
* regex replacement of Flight payloads.
|
|
90
|
+
*/
|
|
91
|
+
export function hashClientRefs(projectRoot: string): Plugin {
|
|
92
|
+
return {
|
|
93
|
+
name: "@rangojs/router:hash-client-refs",
|
|
94
|
+
// Run after the RSC plugin's transform (default enforce is normal)
|
|
95
|
+
enforce: "post",
|
|
96
|
+
applyToEnvironment(env) {
|
|
97
|
+
return env.name === "rsc";
|
|
98
|
+
},
|
|
99
|
+
transform(code, _id) {
|
|
100
|
+
const result = transformClientRefs(code, projectRoot);
|
|
101
|
+
if (result === null) return;
|
|
102
|
+
return { code: result, map: null };
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|