@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,296 @@
|
|
|
1
|
+
import { parseAst } from "vite";
|
|
2
|
+
import {
|
|
3
|
+
findMatchingParen,
|
|
4
|
+
countArgs,
|
|
5
|
+
findStatementEnd,
|
|
6
|
+
buildExportMap,
|
|
7
|
+
escapeRegExp,
|
|
8
|
+
} from "../expose-id-utils.js";
|
|
9
|
+
import type { CreateExportBinding } from "./types.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check whether every non-type export in `code` is accounted for by the given
|
|
13
|
+
* bindings. Returns false if any export exists that is not one of the known
|
|
14
|
+
* create* call locals/exports, allowing callers to bail out for mixed-export
|
|
15
|
+
* files.
|
|
16
|
+
*/
|
|
17
|
+
export function isExportOnlyFile(
|
|
18
|
+
code: string,
|
|
19
|
+
bindings: CreateExportBinding[],
|
|
20
|
+
): boolean {
|
|
21
|
+
if (bindings.length === 0) return false;
|
|
22
|
+
|
|
23
|
+
const knownLocals = new Set<string>();
|
|
24
|
+
const knownExports = new Set<string>();
|
|
25
|
+
for (const b of bindings) {
|
|
26
|
+
knownLocals.add(b.localName);
|
|
27
|
+
for (const e of b.exportNames) knownExports.add(e);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Bail on star re-exports (unknown exports)
|
|
31
|
+
if (/export\s*\*/.test(code)) return false;
|
|
32
|
+
|
|
33
|
+
// Check `export const/let/var/function/class/default X` declarations
|
|
34
|
+
const declExportPattern =
|
|
35
|
+
/export\s+(const|let|var|function|class|default)\s+(\w+)/g;
|
|
36
|
+
let match: RegExpExecArray | null;
|
|
37
|
+
while ((match = declExportPattern.exec(code)) !== null) {
|
|
38
|
+
if (!knownExports.has(match[2])) return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check `export { X }` and `export { X as Y }` specifiers: the local name
|
|
42
|
+
// must reference a known create* binding.
|
|
43
|
+
const specExportPattern = /export\s*\{([^}]+)\}/g;
|
|
44
|
+
while ((match = specExportPattern.exec(code)) !== null) {
|
|
45
|
+
const specifiers = match[1]
|
|
46
|
+
.split(",")
|
|
47
|
+
.map((s) => s.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
for (const spec of specifiers) {
|
|
50
|
+
const m = spec.match(
|
|
51
|
+
/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/,
|
|
52
|
+
);
|
|
53
|
+
if (!m) continue;
|
|
54
|
+
const local = m[1];
|
|
55
|
+
if (!knownLocals.has(local)) return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// NOTE: This regex may over-count when the fn name appears inside strings or
|
|
63
|
+
// comments, but it's only used for the warning heuristic (totalCalls >
|
|
64
|
+
// supportedBindings) and the inline-extraction pre-check, so over-counting
|
|
65
|
+
// triggers a harmless extra AST parse rather than affecting correctness.
|
|
66
|
+
export function countCreateCallsForNames(
|
|
67
|
+
code: string,
|
|
68
|
+
fnNames: string[],
|
|
69
|
+
): number {
|
|
70
|
+
const pattern = new RegExp(
|
|
71
|
+
`\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
|
|
72
|
+
"g",
|
|
73
|
+
);
|
|
74
|
+
return (code.match(pattern) || []).length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getImportedFnNames(
|
|
78
|
+
code: string,
|
|
79
|
+
importedName: string,
|
|
80
|
+
): string[] {
|
|
81
|
+
const importPattern =
|
|
82
|
+
/import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
|
|
83
|
+
|
|
84
|
+
const localNames = new Set<string>();
|
|
85
|
+
let match: RegExpExecArray | null;
|
|
86
|
+
|
|
87
|
+
while ((match = importPattern.exec(code)) !== null) {
|
|
88
|
+
const specList = match[1]
|
|
89
|
+
.split(",")
|
|
90
|
+
.map((s) => s.trim())
|
|
91
|
+
.filter(Boolean);
|
|
92
|
+
|
|
93
|
+
for (const spec of specList) {
|
|
94
|
+
const m = spec.match(
|
|
95
|
+
/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/,
|
|
96
|
+
);
|
|
97
|
+
if (!m) continue;
|
|
98
|
+
const imported = m[1];
|
|
99
|
+
const local = m[2] || imported;
|
|
100
|
+
if (imported === importedName) {
|
|
101
|
+
localNames.add(local);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const names = Array.from(localNames);
|
|
107
|
+
return names.length > 0 ? names : [importedName];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getCalledIdentifierFromCall(callExpr: any): string | null {
|
|
111
|
+
const callee = callExpr?.callee;
|
|
112
|
+
if (callee?.type === "Identifier") return callee.name;
|
|
113
|
+
if (
|
|
114
|
+
callee?.type === "TSInstantiationExpression" &&
|
|
115
|
+
callee.expression?.type === "Identifier"
|
|
116
|
+
) {
|
|
117
|
+
return callee.expression.name;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function collectCreateExportBindingsFallback(
|
|
123
|
+
code: string,
|
|
124
|
+
fnNames: string[],
|
|
125
|
+
): CreateExportBinding[] {
|
|
126
|
+
const alternation = fnNames.map(escapeRegExp).join("|");
|
|
127
|
+
const exportConstPattern = new RegExp(
|
|
128
|
+
`export\\s+const\\s+(\\w+)\\s*=\\s*(?:${alternation})\\s*(?:<[^>]*>)?\\s*\\(`,
|
|
129
|
+
"g",
|
|
130
|
+
);
|
|
131
|
+
const localDeclPattern = new RegExp(
|
|
132
|
+
`\\bconst\\s+(\\w+)\\s*=\\s*((?:${alternation})\\s*(?:<[^>]*>)?\\s*\\()`,
|
|
133
|
+
"g",
|
|
134
|
+
);
|
|
135
|
+
const exportSpecPattern = /export\s*\{([^}]+)\}/g;
|
|
136
|
+
|
|
137
|
+
const exportMap = new Map<string, string[]>();
|
|
138
|
+
const pushExport = (local: string, exported: string) => {
|
|
139
|
+
const list = exportMap.get(local);
|
|
140
|
+
if (list) {
|
|
141
|
+
if (!list.includes(exported)) list.push(exported);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
exportMap.set(local, [exported]);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
let match: RegExpExecArray | null;
|
|
148
|
+
while ((match = exportConstPattern.exec(code)) !== null) {
|
|
149
|
+
pushExport(match[1], match[1]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
while ((match = exportSpecPattern.exec(code)) !== null) {
|
|
153
|
+
const specifiers = match[1]
|
|
154
|
+
.split(",")
|
|
155
|
+
.map((s) => s.trim())
|
|
156
|
+
.filter(Boolean);
|
|
157
|
+
for (const specifier of specifiers) {
|
|
158
|
+
const specMatch = specifier.match(
|
|
159
|
+
/^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/,
|
|
160
|
+
);
|
|
161
|
+
if (!specMatch) continue;
|
|
162
|
+
const local = specMatch[1];
|
|
163
|
+
const exported = specMatch[2] || local;
|
|
164
|
+
pushExport(local, exported);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const bindings: CreateExportBinding[] = [];
|
|
169
|
+
while ((match = localDeclPattern.exec(code)) !== null) {
|
|
170
|
+
const localName = match[1];
|
|
171
|
+
const exportNames = exportMap.get(localName) ?? [];
|
|
172
|
+
if (exportNames.length === 0) continue;
|
|
173
|
+
|
|
174
|
+
const openParenPos = match.index + match[0].length - 1;
|
|
175
|
+
const closeParenPos = findMatchingParen(code, openParenPos + 1) - 1;
|
|
176
|
+
if (closeParenPos <= openParenPos) continue;
|
|
177
|
+
|
|
178
|
+
bindings.push({
|
|
179
|
+
localName,
|
|
180
|
+
exportNames,
|
|
181
|
+
callExprStart: match.index + match[0].length - match[2].length,
|
|
182
|
+
callOpenParenPos: openParenPos,
|
|
183
|
+
callCloseParenPos: closeParenPos,
|
|
184
|
+
argCount: countArgs(code, openParenPos + 1, closeParenPos),
|
|
185
|
+
statementEnd: findStatementEnd(code, closeParenPos + 1),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return bindings;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function collectCreateExportBindings(
|
|
193
|
+
code: string,
|
|
194
|
+
fnNames: string[],
|
|
195
|
+
program?: any,
|
|
196
|
+
): CreateExportBinding[] {
|
|
197
|
+
if (!program) {
|
|
198
|
+
try {
|
|
199
|
+
program = parseAst(code, { jsx: true });
|
|
200
|
+
} catch {
|
|
201
|
+
return collectCreateExportBindingsFallback(code, fnNames);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const exportMap = buildExportMap(program);
|
|
206
|
+
const fnNameSet = new Set(fnNames);
|
|
207
|
+
const bindings: CreateExportBinding[] = [];
|
|
208
|
+
|
|
209
|
+
const collectFromVarDecl = (varDecl: any, statementEnd: number) => {
|
|
210
|
+
if (varDecl?.type !== "VariableDeclaration" || varDecl.kind !== "const") {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const decl of varDecl.declarations ?? []) {
|
|
215
|
+
const calledIdentifier = getCalledIdentifierFromCall(decl?.init);
|
|
216
|
+
if (
|
|
217
|
+
decl?.id?.type !== "Identifier" ||
|
|
218
|
+
decl?.init?.type !== "CallExpression" ||
|
|
219
|
+
!calledIdentifier ||
|
|
220
|
+
!fnNameSet.has(calledIdentifier)
|
|
221
|
+
) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const localName = decl.id.name;
|
|
226
|
+
const exportNames = exportMap.get(localName) ?? [];
|
|
227
|
+
if (exportNames.length === 0) continue;
|
|
228
|
+
|
|
229
|
+
const callStart = decl.init.start as number;
|
|
230
|
+
const callEnd = decl.init.end as number;
|
|
231
|
+
const calleeEnd = decl.init.callee.end as number;
|
|
232
|
+
|
|
233
|
+
let openParenPos = -1;
|
|
234
|
+
for (let i = calleeEnd; i < callEnd; i++) {
|
|
235
|
+
if (code[i] === "(") {
|
|
236
|
+
openParenPos = i;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (openParenPos === -1) continue;
|
|
241
|
+
|
|
242
|
+
const closeParenPos = findMatchingParen(code, openParenPos + 1) - 1;
|
|
243
|
+
if (closeParenPos <= openParenPos) continue;
|
|
244
|
+
|
|
245
|
+
bindings.push({
|
|
246
|
+
localName,
|
|
247
|
+
exportNames,
|
|
248
|
+
callExprStart: decl.init.start as number,
|
|
249
|
+
callOpenParenPos: openParenPos,
|
|
250
|
+
callCloseParenPos: closeParenPos,
|
|
251
|
+
argCount: decl.init.arguments?.length ?? 0,
|
|
252
|
+
statementEnd,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
for (const node of program.body ?? []) {
|
|
258
|
+
if (node?.type === "VariableDeclaration") {
|
|
259
|
+
collectFromVarDecl(node, node.end as number);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (
|
|
264
|
+
node?.type === "ExportNamedDeclaration" &&
|
|
265
|
+
node.declaration?.type === "VariableDeclaration"
|
|
266
|
+
) {
|
|
267
|
+
collectFromVarDecl(node.declaration, node.end as number);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// When the JS parser misidentifies TypeScript generics (e.g.,
|
|
272
|
+
// createLocationState<{ text: string }>({...})) as binary expressions,
|
|
273
|
+
// the AST path finds 0 bindings even though calls exist. Fall back to
|
|
274
|
+
// regex-based detection which handles generics via <[^>]*> matching.
|
|
275
|
+
if (bindings.length === 0) {
|
|
276
|
+
return collectCreateExportBindingsFallback(code, fnNames);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return bindings;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function buildUnsupportedShapeWarning(
|
|
283
|
+
filePath: string,
|
|
284
|
+
fnName: string,
|
|
285
|
+
): string {
|
|
286
|
+
return [
|
|
287
|
+
`[rsc-router] Unsupported ${fnName} shape in "${filePath}".`,
|
|
288
|
+
`Supported shapes are:`,
|
|
289
|
+
` - export const X = ${fnName}(...)`,
|
|
290
|
+
` - const X = ${fnName}(...); export { X }`,
|
|
291
|
+
` - const X = ${fnName}(...); export { X as Y }`,
|
|
292
|
+
`Potentially unsupported forms include:`,
|
|
293
|
+
` - export let/var X = ${fnName}(...)`,
|
|
294
|
+
` - inline ${fnName}(...) calls`,
|
|
295
|
+
].join("\n");
|
|
296
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import MagicString from "magic-string";
|
|
2
|
+
import { hashId } from "../expose-id-utils.js";
|
|
3
|
+
import type { HandlerTransformConfig, CreateExportBinding } from "./types.js";
|
|
4
|
+
import { isExportOnlyFile } from "./export-analysis.js";
|
|
5
|
+
|
|
6
|
+
function analyzeCreateHandleArgs(
|
|
7
|
+
code: string,
|
|
8
|
+
startPos: number,
|
|
9
|
+
endPos: number,
|
|
10
|
+
): { hasArgs: boolean } {
|
|
11
|
+
const content = code.slice(startPos, endPos).trim();
|
|
12
|
+
return { hasArgs: content.length > 0 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function transformHandles(
|
|
16
|
+
bindings: CreateExportBinding[],
|
|
17
|
+
s: MagicString,
|
|
18
|
+
code: string,
|
|
19
|
+
filePath: string,
|
|
20
|
+
isBuild: boolean,
|
|
21
|
+
): boolean {
|
|
22
|
+
let hasChanges = false;
|
|
23
|
+
for (const binding of bindings) {
|
|
24
|
+
const exportName = binding.exportNames[0];
|
|
25
|
+
const args = analyzeCreateHandleArgs(
|
|
26
|
+
code,
|
|
27
|
+
binding.callOpenParenPos + 1,
|
|
28
|
+
binding.callCloseParenPos,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const handleId = isBuild
|
|
32
|
+
? hashId(filePath, exportName)
|
|
33
|
+
: `${filePath}#${exportName}`;
|
|
34
|
+
|
|
35
|
+
let paramInjection: string;
|
|
36
|
+
if (!args.hasArgs) {
|
|
37
|
+
paramInjection = `undefined, "${handleId}"`;
|
|
38
|
+
} else {
|
|
39
|
+
paramInjection = `, "${handleId}"`;
|
|
40
|
+
}
|
|
41
|
+
s.appendLeft(binding.callCloseParenPos, paramInjection);
|
|
42
|
+
|
|
43
|
+
const propInjection = `\n${binding.localName}.$$id = "${handleId}";`;
|
|
44
|
+
s.appendRight(binding.statementEnd, propInjection);
|
|
45
|
+
hasChanges = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return hasChanges;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function transformLocationState(
|
|
52
|
+
bindings: CreateExportBinding[],
|
|
53
|
+
s: MagicString,
|
|
54
|
+
filePath: string,
|
|
55
|
+
isBuild: boolean,
|
|
56
|
+
): boolean {
|
|
57
|
+
let hasChanges = false;
|
|
58
|
+
for (const binding of bindings) {
|
|
59
|
+
const exportName = binding.exportNames[0];
|
|
60
|
+
|
|
61
|
+
const stateKey = isBuild
|
|
62
|
+
? hashId(filePath, exportName)
|
|
63
|
+
: `${filePath}#${exportName}`;
|
|
64
|
+
|
|
65
|
+
// Key is injected as a property assignment (not as a function argument).
|
|
66
|
+
// This allows createLocationState to accept options like { flash: true }
|
|
67
|
+
// without conflicting with key injection.
|
|
68
|
+
const propInjection = `\n${binding.localName}.__rsc_ls_key = "__rsc_ls_${stateKey}";`;
|
|
69
|
+
s.appendRight(binding.statementEnd, propInjection);
|
|
70
|
+
hasChanges = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return hasChanges;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Replace the entire file with lightweight stubs when ALL non-type exports are
|
|
78
|
+
* handler calls of the given type. Returns null for files with mixed exports.
|
|
79
|
+
*/
|
|
80
|
+
export function generateWholeFileStubs(
|
|
81
|
+
cfg: HandlerTransformConfig,
|
|
82
|
+
bindings: CreateExportBinding[],
|
|
83
|
+
code: string,
|
|
84
|
+
filePath: string,
|
|
85
|
+
isBuild: boolean,
|
|
86
|
+
): { code: string; map: null } | null {
|
|
87
|
+
if (!isExportOnlyFile(code, bindings)) return null;
|
|
88
|
+
|
|
89
|
+
const exportNames = bindings.flatMap((b) => b.exportNames);
|
|
90
|
+
const stubs = exportNames.map((name) => {
|
|
91
|
+
const handlerId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
|
|
92
|
+
return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return { code: stubs.join("\n") + "\n", map: null };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Replace handler call expressions with lightweight stub objects in non-RSC
|
|
100
|
+
* environments. Other exports, imports, and module-level code remain untouched.
|
|
101
|
+
*/
|
|
102
|
+
export function generateExprStubs(
|
|
103
|
+
cfg: HandlerTransformConfig,
|
|
104
|
+
bindings: CreateExportBinding[],
|
|
105
|
+
code: string,
|
|
106
|
+
filePath: string,
|
|
107
|
+
sourceId: string,
|
|
108
|
+
isBuild: boolean,
|
|
109
|
+
): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
|
|
110
|
+
if (bindings.length === 0) return null;
|
|
111
|
+
|
|
112
|
+
const s = new MagicString(code);
|
|
113
|
+
let hasChanges = false;
|
|
114
|
+
|
|
115
|
+
for (const binding of bindings) {
|
|
116
|
+
const exportName = binding.exportNames[0];
|
|
117
|
+
const handlerId = isBuild
|
|
118
|
+
? hashId(filePath, exportName)
|
|
119
|
+
: `${filePath}#${exportName}`;
|
|
120
|
+
|
|
121
|
+
s.overwrite(
|
|
122
|
+
binding.callExprStart,
|
|
123
|
+
binding.callCloseParenPos + 1,
|
|
124
|
+
`{ __brand: "${cfg.brand}", $$id: "${handlerId}" }`,
|
|
125
|
+
);
|
|
126
|
+
hasChanges = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!hasChanges) return null;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
code: s.toString(),
|
|
133
|
+
map: s.generateMap({
|
|
134
|
+
source: sourceId,
|
|
135
|
+
includeContent: true,
|
|
136
|
+
hires: "boundary",
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Inject $$id into export const handler calls in RSC environments.
|
|
143
|
+
*/
|
|
144
|
+
export function transformHandlerIds(
|
|
145
|
+
cfg: HandlerTransformConfig,
|
|
146
|
+
bindings: CreateExportBinding[],
|
|
147
|
+
s: MagicString,
|
|
148
|
+
filePath: string,
|
|
149
|
+
isBuild: boolean,
|
|
150
|
+
): boolean {
|
|
151
|
+
let hasChanges = false;
|
|
152
|
+
for (const binding of bindings) {
|
|
153
|
+
const exportName = binding.exportNames[0];
|
|
154
|
+
|
|
155
|
+
const handlerId = isBuild
|
|
156
|
+
? hashId(filePath, exportName)
|
|
157
|
+
: `${filePath}#${exportName}`;
|
|
158
|
+
|
|
159
|
+
// Injection strategy matches the runtime overload signatures:
|
|
160
|
+
// 0 args -> inject undefined, "id"
|
|
161
|
+
// 1 arg (handler) -> inject , undefined, "id"
|
|
162
|
+
// 2+ args -> inject , "id"
|
|
163
|
+
let paramInjection: string;
|
|
164
|
+
if (binding.argCount === 0) {
|
|
165
|
+
paramInjection = `undefined, "${handlerId}"`;
|
|
166
|
+
} else if (binding.argCount === 1) {
|
|
167
|
+
paramInjection = `, undefined, "${handlerId}"`;
|
|
168
|
+
} else {
|
|
169
|
+
paramInjection = `, "${handlerId}"`;
|
|
170
|
+
}
|
|
171
|
+
s.appendLeft(binding.callCloseParenPos, paramInjection);
|
|
172
|
+
|
|
173
|
+
const propInjection = `\n${binding.localName}.$$id = "${handlerId}";`;
|
|
174
|
+
s.appendRight(binding.statementEnd, propInjection);
|
|
175
|
+
hasChanges = true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return hasChanges;
|
|
179
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type MagicString from "magic-string";
|
|
2
|
+
import { hashId } from "../expose-id-utils.js";
|
|
3
|
+
import type { CreateExportBinding } from "./types.js";
|
|
4
|
+
import { isExportOnlyFile } from "./export-analysis.js";
|
|
5
|
+
|
|
6
|
+
export function hasCreateLoaderImport(code: string): boolean {
|
|
7
|
+
return /import\s*\{[^}]*\bcreateLoader\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
|
|
8
|
+
code,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate lightweight client stubs for loader files.
|
|
14
|
+
*
|
|
15
|
+
* When a loader file is imported from a client component (e.g., for useLoader()),
|
|
16
|
+
* the client only needs { __brand: "loader", $$id: "..." } objects.
|
|
17
|
+
* This function replaces the entire file contents with just those stub exports,
|
|
18
|
+
* preventing server-only data (constants, DB queries, etc.) from leaking into
|
|
19
|
+
* the client bundle.
|
|
20
|
+
*
|
|
21
|
+
* Only applies when ALL named exports are createLoader() calls (plus type exports
|
|
22
|
+
* which are erased at compile time). Files with mixed exports are left untouched.
|
|
23
|
+
*/
|
|
24
|
+
export function generateClientLoaderStubs(
|
|
25
|
+
bindings: CreateExportBinding[],
|
|
26
|
+
code: string,
|
|
27
|
+
filePath: string,
|
|
28
|
+
isBuild: boolean,
|
|
29
|
+
): { code: string; map?: undefined } | null {
|
|
30
|
+
if (!isExportOnlyFile(code, bindings)) return null;
|
|
31
|
+
|
|
32
|
+
const lines: string[] = [];
|
|
33
|
+
|
|
34
|
+
for (const binding of bindings) {
|
|
35
|
+
for (const name of binding.exportNames) {
|
|
36
|
+
const loaderId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
|
|
37
|
+
lines.push(
|
|
38
|
+
`export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { code: lines.join("\n") + "\n" };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function transformLoaders(
|
|
47
|
+
bindings: CreateExportBinding[],
|
|
48
|
+
s: MagicString,
|
|
49
|
+
filePath: string,
|
|
50
|
+
isBuild: boolean,
|
|
51
|
+
): boolean {
|
|
52
|
+
let hasChanges = false;
|
|
53
|
+
|
|
54
|
+
for (const binding of bindings) {
|
|
55
|
+
const exportName = binding.exportNames[0];
|
|
56
|
+
|
|
57
|
+
const loaderId = isBuild
|
|
58
|
+
? hashId(filePath, exportName)
|
|
59
|
+
: `${filePath}#${exportName}`;
|
|
60
|
+
|
|
61
|
+
// Inject $$id as hidden third parameter.
|
|
62
|
+
// createLoader(fn) -> createLoader(fn, undefined, "id")
|
|
63
|
+
// createLoader(fn, true) -> createLoader(fn, true, "id")
|
|
64
|
+
const paramInjection =
|
|
65
|
+
binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
|
|
66
|
+
s.appendLeft(binding.callCloseParenPos, paramInjection);
|
|
67
|
+
|
|
68
|
+
const propInjection = `\n${binding.localName}.$$id = "${loaderId}";`;
|
|
69
|
+
s.appendRight(binding.statementEnd, propInjection);
|
|
70
|
+
hasChanges = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return hasChanges;
|
|
74
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { normalizePath, findMatchingParen } from "../expose-id-utils.js";
|
|
6
|
+
import { getImportedFnNames } from "./export-analysis.js";
|
|
7
|
+
|
|
8
|
+
export function transformRouter(
|
|
9
|
+
code: string,
|
|
10
|
+
filePath: string,
|
|
11
|
+
routerFnNames: string[],
|
|
12
|
+
absolutePath?: string,
|
|
13
|
+
): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
|
|
14
|
+
const pat = new RegExp(
|
|
15
|
+
`\\b(?:${routerFnNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*(?:<[^>]*>)?\\s*\\(`,
|
|
16
|
+
"g",
|
|
17
|
+
);
|
|
18
|
+
let match: RegExpExecArray | null;
|
|
19
|
+
const s = new MagicString(code);
|
|
20
|
+
let changed = false;
|
|
21
|
+
|
|
22
|
+
// Compute the import path for the generated route names file.
|
|
23
|
+
// filePath is relative to project root (e.g., "src/router.tsx")
|
|
24
|
+
const basename = path.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
|
|
25
|
+
const routeNamesImport = `./${basename}.named-routes.gen.js`;
|
|
26
|
+
const routeNamesVar = `__rsc_rn`;
|
|
27
|
+
|
|
28
|
+
while ((match = pat.exec(code)) !== null) {
|
|
29
|
+
const callStart = match.index;
|
|
30
|
+
const parenPos = match.index + match[0].length - 1;
|
|
31
|
+
|
|
32
|
+
// Scope the $$id check to within this call's arguments only,
|
|
33
|
+
// not the entire remaining file.
|
|
34
|
+
const closeParen = findMatchingParen(code, parenPos + 1);
|
|
35
|
+
const callArgs = code.slice(parenPos + 1, closeParen);
|
|
36
|
+
|
|
37
|
+
// Skip if $$id is already present in this call
|
|
38
|
+
if (callArgs.includes("$$id")) continue;
|
|
39
|
+
|
|
40
|
+
// Compute line number for this call
|
|
41
|
+
const lineNumber = code.slice(0, callStart).split("\n").length;
|
|
42
|
+
const hash = createHash("sha256")
|
|
43
|
+
.update(`${filePath}:${lineNumber}`)
|
|
44
|
+
.digest("hex")
|
|
45
|
+
.slice(0, 8);
|
|
46
|
+
|
|
47
|
+
changed = true;
|
|
48
|
+
// $$sourceFile uses the absolute path so that downstream consumers
|
|
49
|
+
// (virtual-module-codegen, runtime-discovery) can resolve gen file
|
|
50
|
+
// imports correctly via path.dirname / path.join.
|
|
51
|
+
const sourceFilePath = absolutePath ?? filePath;
|
|
52
|
+
const injected = ` $$id: "${hash}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
|
|
53
|
+
|
|
54
|
+
const afterParen = callArgs.trimStart();
|
|
55
|
+
if (afterParen.startsWith("{")) {
|
|
56
|
+
const bracePos = code.indexOf("{", parenPos + 1);
|
|
57
|
+
s.appendRight(bracePos + 1, injected);
|
|
58
|
+
} else if (afterParen.startsWith(")")) {
|
|
59
|
+
s.appendRight(parenPos + 1, `{${injected} }`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!changed) return null;
|
|
64
|
+
|
|
65
|
+
// Prepend the static import as the first line. MagicString tracks the
|
|
66
|
+
// offset so all downstream source maps remain correct.
|
|
67
|
+
s.prepend(
|
|
68
|
+
`import { NamedRoutes as ${routeNamesVar} } from "${routeNamesImport}";\n`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
code: s.toString(),
|
|
73
|
+
map: s.generateMap({ hires: true }),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Inject stable $$id into createRouter() calls at compile time.
|
|
79
|
+
* This must be a separate plugin without enforce:"post" because running
|
|
80
|
+
* at "post" priority changes Vite's dep optimization timing and can cause
|
|
81
|
+
* ERR_OUTDATED_OPTIMIZED_DEP / React dual-instance issues.
|
|
82
|
+
*/
|
|
83
|
+
export function exposeRouterId(): Plugin {
|
|
84
|
+
let projectRoot = "";
|
|
85
|
+
return {
|
|
86
|
+
name: "@rangojs/router:expose-router-id",
|
|
87
|
+
configResolved(config) {
|
|
88
|
+
projectRoot = config.root;
|
|
89
|
+
},
|
|
90
|
+
transform(code, id) {
|
|
91
|
+
if (!code.includes("createRouter")) return null;
|
|
92
|
+
// Accepts both @rangojs/router and @rangojs/router/server subpath.
|
|
93
|
+
// NOTE: detectImports in expose-id-utils has a stricter check that
|
|
94
|
+
// excludes /server for its router flag -- that's intentional since
|
|
95
|
+
// detectImports is only used in exposeInternalIds, not here.
|
|
96
|
+
if (
|
|
97
|
+
!/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
|
|
98
|
+
code,
|
|
99
|
+
)
|
|
100
|
+
) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (id.includes("node_modules")) return null;
|
|
104
|
+
|
|
105
|
+
const filePath = normalizePath(path.relative(projectRoot, id));
|
|
106
|
+
const routerFnNames = getImportedFnNames(code, "createRouter");
|
|
107
|
+
return transformRouter(code, filePath, routerFnNames, normalizePath(id));
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface HandlerTransformConfig {
|
|
2
|
+
fnName: string;
|
|
3
|
+
brand: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CreateExportBinding {
|
|
7
|
+
localName: string;
|
|
8
|
+
exportNames: string[];
|
|
9
|
+
callExprStart: number;
|
|
10
|
+
callOpenParenPos: number;
|
|
11
|
+
callCloseParenPos: number;
|
|
12
|
+
argCount: number;
|
|
13
|
+
statementEnd: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface StrictCreateTransformConfig {
|
|
17
|
+
fnName: "createLoader" | "createHandle" | "createLocationState";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const PRERENDER_CONFIG: HandlerTransformConfig = {
|
|
21
|
+
fnName: "Prerender",
|
|
22
|
+
brand: "prerenderHandler",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const STATIC_CONFIG: HandlerTransformConfig = {
|
|
26
|
+
fnName: "Static",
|
|
27
|
+
brand: "staticHandler",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const STRICT_CREATE_CONFIGS: StrictCreateTransformConfig[] = [
|
|
31
|
+
{ fnName: "createLoader" },
|
|
32
|
+
{ fnName: "createHandle" },
|
|
33
|
+
{ fnName: "createLocationState" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export interface ExposeInternalIdsApi {
|
|
37
|
+
/** Tracks absolute module IDs that contain prerender handler exports.
|
|
38
|
+
* key: absolute module ID (filesystem path)
|
|
39
|
+
* value: array of export names (e.g., ["ArticlesIndex", "ArticleDetail"]) */
|
|
40
|
+
prerenderHandlerModules: Map<string, string[]>;
|
|
41
|
+
/** Tracks absolute module IDs that contain static handler exports.
|
|
42
|
+
* key: absolute module ID (filesystem path)
|
|
43
|
+
* value: array of export names (e.g., ["DocsNav", "DocShell"]) */
|
|
44
|
+
staticHandlerModules: Map<string, string[]>;
|
|
45
|
+
}
|