@rangojs/router 0.0.0-experimental.0f44aca1
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 +5 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1601 -0
- package/dist/vite/index.js +5214 -0
- package/package.json +176 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +220 -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 +645 -0
- package/src/browser/navigation-client.ts +215 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +550 -0
- package/src/browser/prefetch/cache.ts +146 -0
- package/src/browser/prefetch/fetch.ts +135 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +360 -0
- package/src/browser/react/NavigationProvider.tsx +386 -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 +431 -0
- package/src/browser/scroll-restoration.ts +400 -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 +538 -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 +469 -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 +540 -0
- package/src/cache/cf/index.ts +25 -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 +43 -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 +275 -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 +158 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +395 -0
- package/src/router/lazy-includes.ts +234 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +248 -0
- package/src/router/manifest.ts +267 -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 +192 -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 +748 -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 +316 -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 +1239 -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 +289 -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 +1002 -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 +235 -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 +914 -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 +102 -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 +110 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +131 -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 +365 -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 +254 -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 +510 -0
- package/src/vite/router-discovery.ts +785 -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,110 @@
|
|
|
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
|
+
include?: string[];
|
|
17
|
+
exclude?: string[];
|
|
18
|
+
// Mutable ref for deferred auto-discovery (node preset).
|
|
19
|
+
// The auto-discover config() hook populates this before configResolved.
|
|
20
|
+
routerPathRef?: { path?: string };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PrecomputedEntry {
|
|
24
|
+
staticPrefix: string;
|
|
25
|
+
routes: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ChunkInfo {
|
|
29
|
+
fileName: string;
|
|
30
|
+
exports: Array<{ name: string; handlerId: string; passthrough: boolean }>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface PerRouterManifestEntry {
|
|
34
|
+
id: string;
|
|
35
|
+
routeManifest: Record<string, string>;
|
|
36
|
+
routeSearchSchemas?: Record<string, Record<string, string>>;
|
|
37
|
+
sourceFile?: string;
|
|
38
|
+
factoryOnlyPrefixes?: Set<string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DiscoveryState {
|
|
42
|
+
resolvedEntryPath: string | undefined;
|
|
43
|
+
projectRoot: string;
|
|
44
|
+
isBuildMode: boolean;
|
|
45
|
+
userResolveAlias: any;
|
|
46
|
+
scanFilter: ScanFilter | undefined;
|
|
47
|
+
cachedRouterFiles: string[] | undefined;
|
|
48
|
+
opts: PluginOptions | undefined;
|
|
49
|
+
|
|
50
|
+
mergedRouteManifest: Record<string, string> | null;
|
|
51
|
+
perRouterManifests: PerRouterManifestEntry[];
|
|
52
|
+
mergedPrecomputedEntries: PrecomputedEntry[] | null;
|
|
53
|
+
mergedRouteTrie: any;
|
|
54
|
+
|
|
55
|
+
perRouterTrieMap: Map<string, any>;
|
|
56
|
+
perRouterPrecomputedMap: Map<string, PrecomputedEntry[]>;
|
|
57
|
+
perRouterManifestDataMap: Map<string, Record<string, string>>;
|
|
58
|
+
|
|
59
|
+
prerenderManifestEntries: Record<string, string> | null;
|
|
60
|
+
staticManifestEntries: Record<string, string> | null;
|
|
61
|
+
handlerChunkInfo: ChunkInfo | null;
|
|
62
|
+
staticHandlerChunkInfo: ChunkInfo | null;
|
|
63
|
+
rscEntryFileName: string | null;
|
|
64
|
+
resolvedPrerenderModules: Map<string, string[]> | undefined;
|
|
65
|
+
resolvedStaticModules: Map<string, string[]> | undefined;
|
|
66
|
+
|
|
67
|
+
discoveryDone: Promise<void> | null;
|
|
68
|
+
devServerOrigin: string | null;
|
|
69
|
+
devServer: any;
|
|
70
|
+
selfWrittenGenFiles: Map<string, { at: number; hash: string }>;
|
|
71
|
+
SELF_WRITE_WINDOW_MS: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createDiscoveryState(
|
|
75
|
+
entryPath: string | undefined,
|
|
76
|
+
opts: PluginOptions | undefined,
|
|
77
|
+
): DiscoveryState {
|
|
78
|
+
return {
|
|
79
|
+
resolvedEntryPath: entryPath,
|
|
80
|
+
projectRoot: "",
|
|
81
|
+
isBuildMode: false,
|
|
82
|
+
userResolveAlias: undefined,
|
|
83
|
+
scanFilter: undefined,
|
|
84
|
+
cachedRouterFiles: undefined,
|
|
85
|
+
opts,
|
|
86
|
+
|
|
87
|
+
mergedRouteManifest: null,
|
|
88
|
+
perRouterManifests: [],
|
|
89
|
+
mergedPrecomputedEntries: null,
|
|
90
|
+
mergedRouteTrie: null,
|
|
91
|
+
|
|
92
|
+
perRouterTrieMap: new Map(),
|
|
93
|
+
perRouterPrecomputedMap: new Map(),
|
|
94
|
+
perRouterManifestDataMap: new Map(),
|
|
95
|
+
|
|
96
|
+
prerenderManifestEntries: null,
|
|
97
|
+
staticManifestEntries: null,
|
|
98
|
+
handlerChunkInfo: null,
|
|
99
|
+
staticHandlerChunkInfo: null,
|
|
100
|
+
rscEntryFileName: null,
|
|
101
|
+
resolvedPrerenderModules: undefined,
|
|
102
|
+
resolvedStaticModules: undefined,
|
|
103
|
+
|
|
104
|
+
discoveryDone: null,
|
|
105
|
+
devServerOrigin: null,
|
|
106
|
+
devServer: null,
|
|
107
|
+
selfWrittenGenFiles: new Map(),
|
|
108
|
+
SELF_WRITE_WINDOW_MS: 5_000,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -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,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RSC plugin entry points configuration.
|
|
3
|
+
* All entries use virtual modules by default. Specify a path to use a custom entry file.
|
|
4
|
+
*/
|
|
5
|
+
export interface RscEntries {
|
|
6
|
+
/**
|
|
7
|
+
* Path to a custom browser/client entry file.
|
|
8
|
+
* If not specified, a default virtual entry is used.
|
|
9
|
+
*/
|
|
10
|
+
client?: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Path to a custom SSR entry file.
|
|
14
|
+
* If not specified, a default virtual entry is used.
|
|
15
|
+
*/
|
|
16
|
+
ssr?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Path to a custom RSC entry file.
|
|
20
|
+
* If not specified, a default virtual entry is used that imports the router from the `entry` option.
|
|
21
|
+
*/
|
|
22
|
+
rsc?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options for @vitejs/plugin-rsc integration
|
|
27
|
+
*/
|
|
28
|
+
export interface RscPluginOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Entry points for client, ssr, and rsc environments.
|
|
31
|
+
* All entries use virtual modules by default.
|
|
32
|
+
* Specify paths only when you need custom entry files.
|
|
33
|
+
*/
|
|
34
|
+
entries?: RscEntries;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Base options shared by all presets
|
|
39
|
+
*/
|
|
40
|
+
interface RangoBaseOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Show startup banner. Set to false to disable.
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
banner?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate named-routes.gen.ts by parsing url modules at startup.
|
|
49
|
+
* Provides type-safe Handler<"name"> and href() without executing router code.
|
|
50
|
+
* Set to `false` to disable (run `npx rango extract-names` manually instead).
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
53
|
+
staticRouteTypesGeneration?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Glob patterns for files to include in route type scanning.
|
|
57
|
+
* Only files matching at least one pattern will be scanned.
|
|
58
|
+
* Patterns are relative to the project root.
|
|
59
|
+
* When unset, all .ts/.tsx files are scanned.
|
|
60
|
+
*/
|
|
61
|
+
include?: string[];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Glob patterns for files to exclude from route type scanning.
|
|
65
|
+
* Takes precedence over `include`. Patterns are relative to the project root.
|
|
66
|
+
* Defaults to common test/build directories.
|
|
67
|
+
*/
|
|
68
|
+
exclude?: string[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Options for Node.js deployment (default)
|
|
73
|
+
*/
|
|
74
|
+
export interface RangoNodeOptions extends RangoBaseOptions {
|
|
75
|
+
/**
|
|
76
|
+
* Deployment preset. Defaults to 'node' when not specified.
|
|
77
|
+
*/
|
|
78
|
+
preset?: "node";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Path to your router configuration file that exports the route tree.
|
|
82
|
+
* This file must export a `router` object created with `createRouter()`.
|
|
83
|
+
*
|
|
84
|
+
* When omitted, auto-discovers the router by scanning for files containing
|
|
85
|
+
* `createRouter`. If exactly one is found, it is used automatically.
|
|
86
|
+
* If multiple are found, an error is thrown with the list of candidates.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* rango({ router: './src/router.tsx' })
|
|
91
|
+
* // or simply:
|
|
92
|
+
* rango()
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
router?: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* RSC plugin configuration. By default, rsc-router includes @vitejs/plugin-rsc
|
|
99
|
+
* with sensible defaults.
|
|
100
|
+
*
|
|
101
|
+
* Entry files (browser, ssr, rsc) are optional - if they don't exist,
|
|
102
|
+
* virtual defaults are used.
|
|
103
|
+
*
|
|
104
|
+
* - Omit or pass `true`/`{}` to use defaults (recommended)
|
|
105
|
+
* - Pass `{ entries: {...} }` to customize entry paths
|
|
106
|
+
* - Pass `false` to disable (for manual @vitejs/plugin-rsc configuration)
|
|
107
|
+
*
|
|
108
|
+
* @default true
|
|
109
|
+
*/
|
|
110
|
+
rsc?: boolean | RscPluginOptions;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Options for Cloudflare Workers deployment
|
|
115
|
+
*/
|
|
116
|
+
export interface RangoCloudflareOptions extends RangoBaseOptions {
|
|
117
|
+
/**
|
|
118
|
+
* Deployment preset for Cloudflare Workers.
|
|
119
|
+
* When using cloudflare preset:
|
|
120
|
+
* - @vitejs/plugin-rsc is NOT added (cloudflare plugin adds it)
|
|
121
|
+
* - Your worker entry (e.g., worker.rsc.tsx) imports the router directly
|
|
122
|
+
* - Browser and SSR use virtual entries
|
|
123
|
+
* - Build-time manifest generation is auto-detected from the resolved RSC environment config
|
|
124
|
+
*/
|
|
125
|
+
preset: "cloudflare";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Options for rango() Vite plugin
|
|
130
|
+
*/
|
|
131
|
+
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
|
+
}
|