@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,287 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize path to forward slashes.
|
|
6
|
+
*/
|
|
7
|
+
export function normalizePath(p: string): string {
|
|
8
|
+
return p.split(path.sep).join("/");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate a short hash for an ID.
|
|
13
|
+
* Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short.
|
|
14
|
+
* Appends export name for easier debugging in production: "abc123#ExportName"
|
|
15
|
+
*/
|
|
16
|
+
export function hashId(filePath: string, exportName: string): string {
|
|
17
|
+
const input = `${filePath}#${exportName}`;
|
|
18
|
+
const hash = crypto.createHash("sha256").update(input).digest("hex");
|
|
19
|
+
return `${hash.slice(0, 8)}#${exportName}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate an 8-char hex hash for an inline static handler call site.
|
|
24
|
+
* Uses file path and line number (plus optional index for same-line collisions).
|
|
25
|
+
*/
|
|
26
|
+
export function hashInlineId(
|
|
27
|
+
filePath: string,
|
|
28
|
+
lineNumber: number,
|
|
29
|
+
index?: number,
|
|
30
|
+
): string {
|
|
31
|
+
const input =
|
|
32
|
+
index !== undefined && index > 0
|
|
33
|
+
? `${filePath}:${lineNumber}:${index}`
|
|
34
|
+
: `${filePath}:${lineNumber}`;
|
|
35
|
+
return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DetectedImports {
|
|
39
|
+
loader: boolean;
|
|
40
|
+
handle: boolean;
|
|
41
|
+
locationState: boolean;
|
|
42
|
+
prerenderHandler: boolean;
|
|
43
|
+
staticHandler: boolean;
|
|
44
|
+
router: boolean;
|
|
45
|
+
any: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build a map from local binding name to exported names by walking
|
|
50
|
+
* ExportNamedDeclaration nodes. Handles `export const X`, `export { X }`,
|
|
51
|
+
* and `export { X as Y }`. Skips re-exports (`export { X } from "..."`).
|
|
52
|
+
*/
|
|
53
|
+
export function buildExportMap(program: any): Map<string, string[]> {
|
|
54
|
+
const exportMap = new Map<string, string[]>();
|
|
55
|
+
|
|
56
|
+
const pushExport = (local: string, exported: string) => {
|
|
57
|
+
const list = exportMap.get(local);
|
|
58
|
+
if (list) {
|
|
59
|
+
if (!list.includes(exported)) list.push(exported);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
exportMap.set(local, [exported]);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (const node of program.body ?? []) {
|
|
66
|
+
if (node?.type !== "ExportNamedDeclaration") continue;
|
|
67
|
+
|
|
68
|
+
if (node.declaration?.type === "VariableDeclaration") {
|
|
69
|
+
for (const decl of node.declaration.declarations ?? []) {
|
|
70
|
+
if (decl?.id?.type === "Identifier") {
|
|
71
|
+
pushExport(decl.id.name, decl.id.name);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!node.source && Array.isArray(node.specifiers)) {
|
|
77
|
+
for (const spec of node.specifiers) {
|
|
78
|
+
if (
|
|
79
|
+
spec?.type === "ExportSpecifier" &&
|
|
80
|
+
spec.local?.type === "Identifier" &&
|
|
81
|
+
spec.exported?.type === "Identifier"
|
|
82
|
+
) {
|
|
83
|
+
pushExport(spec.local.name, spec.exported.name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return exportMap;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Single-pass detection of all create* imports from @rangojs/router.
|
|
94
|
+
* Returns which create functions are imported so we can skip unnecessary transforms.
|
|
95
|
+
*/
|
|
96
|
+
export function detectImports(code: string): DetectedImports {
|
|
97
|
+
// Extract all import declarations from @rangojs/router in one scan
|
|
98
|
+
const importPattern =
|
|
99
|
+
/import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
|
|
100
|
+
|
|
101
|
+
const result: DetectedImports = {
|
|
102
|
+
loader: false,
|
|
103
|
+
handle: false,
|
|
104
|
+
locationState: false,
|
|
105
|
+
prerenderHandler: false,
|
|
106
|
+
staticHandler: false,
|
|
107
|
+
router: false,
|
|
108
|
+
any: false,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let match: RegExpExecArray | null;
|
|
112
|
+
while ((match = importPattern.exec(code)) !== null) {
|
|
113
|
+
const imports = match[1];
|
|
114
|
+
if (/\bcreateLoader\b/.test(imports)) result.loader = true;
|
|
115
|
+
if (/\bcreateHandle\b/.test(imports)) result.handle = true;
|
|
116
|
+
if (/\bcreateLocationState\b/.test(imports)) result.locationState = true;
|
|
117
|
+
if (/\bPrerender\b/.test(imports)) result.prerenderHandler = true;
|
|
118
|
+
if (/\bStatic\b/.test(imports)) result.staticHandler = true;
|
|
119
|
+
if (/\bcreateRouter\b/.test(imports)) result.router = true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// createRouter has a stricter check: only from "@rangojs/router" (not sub-paths).
|
|
123
|
+
// NOTE: This is intentional — detectImports is used as a fast pre-filter in
|
|
124
|
+
// exposeInternalIds (which does NOT handle router transforms). The separate
|
|
125
|
+
// exposeRouterId plugin handles createRouter and DOES accept the /server subpath.
|
|
126
|
+
if (result.router) {
|
|
127
|
+
result.router =
|
|
128
|
+
/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router["']/.test(
|
|
129
|
+
code,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
result.any =
|
|
134
|
+
result.loader ||
|
|
135
|
+
result.handle ||
|
|
136
|
+
result.locationState ||
|
|
137
|
+
result.prerenderHandler ||
|
|
138
|
+
result.staticHandler ||
|
|
139
|
+
result.router;
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Skip past a string literal, template literal, or comment starting at pos.
|
|
146
|
+
* Returns the index after the closing delimiter, or pos if not at a
|
|
147
|
+
* string/comment start. Handles escape sequences and nested ${} in templates.
|
|
148
|
+
*/
|
|
149
|
+
export function skipStringOrComment(code: string, pos: number): number {
|
|
150
|
+
const ch = code[pos];
|
|
151
|
+
|
|
152
|
+
if (ch === '"' || ch === "'") {
|
|
153
|
+
for (let j = pos + 1; j < code.length; j++) {
|
|
154
|
+
if (code[j] === "\\") {
|
|
155
|
+
j++;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (code[j] === ch) return j + 1;
|
|
159
|
+
}
|
|
160
|
+
return code.length;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (ch === "`") {
|
|
164
|
+
let j = pos + 1;
|
|
165
|
+
while (j < code.length) {
|
|
166
|
+
if (code[j] === "\\") {
|
|
167
|
+
j += 2;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (code[j] === "`") return j + 1;
|
|
171
|
+
if (code[j] === "$" && j + 1 < code.length && code[j + 1] === "{") {
|
|
172
|
+
j += 2;
|
|
173
|
+
let braceDepth = 1;
|
|
174
|
+
while (j < code.length && braceDepth > 0) {
|
|
175
|
+
const inner = skipStringOrComment(code, j);
|
|
176
|
+
if (inner > j) {
|
|
177
|
+
j = inner;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (code[j] === "{") braceDepth++;
|
|
181
|
+
else if (code[j] === "}") braceDepth--;
|
|
182
|
+
if (braceDepth > 0) j++;
|
|
183
|
+
}
|
|
184
|
+
if (braceDepth === 0) j++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
j++;
|
|
188
|
+
}
|
|
189
|
+
return j;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (ch === "/" && pos + 1 < code.length) {
|
|
193
|
+
if (code[pos + 1] === "/") {
|
|
194
|
+
const eol = code.indexOf("\n", pos + 2);
|
|
195
|
+
return eol === -1 ? code.length : eol + 1;
|
|
196
|
+
}
|
|
197
|
+
if (code[pos + 1] === "*") {
|
|
198
|
+
const end = code.indexOf("*/", pos + 2);
|
|
199
|
+
return end === -1 ? code.length : end + 2;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return pos;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Find the matching closing paren starting after an already-opened paren.
|
|
208
|
+
* Skips strings, template literals, and comments so parens inside them
|
|
209
|
+
* don't affect depth tracking. Returns the index after the closing paren.
|
|
210
|
+
*/
|
|
211
|
+
export function findMatchingParen(code: string, startPos: number): number {
|
|
212
|
+
let depth = 1;
|
|
213
|
+
let i = startPos;
|
|
214
|
+
while (i < code.length && depth > 0) {
|
|
215
|
+
const skipped = skipStringOrComment(code, i);
|
|
216
|
+
if (skipped > i) {
|
|
217
|
+
i = skipped;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (code[i] === "(") depth++;
|
|
221
|
+
if (code[i] === ")") depth--;
|
|
222
|
+
i++;
|
|
223
|
+
}
|
|
224
|
+
return i;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Count the number of top-level arguments in a function call.
|
|
229
|
+
* Skips nested parens, brackets, braces, strings, and comments.
|
|
230
|
+
*/
|
|
231
|
+
export function countArgs(
|
|
232
|
+
code: string,
|
|
233
|
+
startPos: number,
|
|
234
|
+
endPos: number,
|
|
235
|
+
): number {
|
|
236
|
+
let depth = 0;
|
|
237
|
+
let argCount = 0;
|
|
238
|
+
let hasContent = false;
|
|
239
|
+
let i = startPos;
|
|
240
|
+
|
|
241
|
+
while (i < endPos) {
|
|
242
|
+
const skipped = skipStringOrComment(code, i);
|
|
243
|
+
if (skipped > i) {
|
|
244
|
+
hasContent = true;
|
|
245
|
+
i = skipped;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const char = code[i];
|
|
250
|
+
if (char === "(" || char === "[" || char === "{") {
|
|
251
|
+
depth++;
|
|
252
|
+
hasContent = true;
|
|
253
|
+
} else if (char === ")" || char === "]" || char === "}") {
|
|
254
|
+
depth--;
|
|
255
|
+
} else if (char === "," && depth === 0) {
|
|
256
|
+
argCount++;
|
|
257
|
+
} else if (!/\s/.test(char)) {
|
|
258
|
+
hasContent = true;
|
|
259
|
+
}
|
|
260
|
+
i++;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return hasContent ? argCount + 1 : 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Find the end of a statement: skip whitespace and optional semicolon after
|
|
268
|
+
* a closing paren position.
|
|
269
|
+
*/
|
|
270
|
+
export function findStatementEnd(code: string, pos: number): number {
|
|
271
|
+
let i = pos;
|
|
272
|
+
while (i < code.length && /\s/.test(code[i])) {
|
|
273
|
+
i++;
|
|
274
|
+
}
|
|
275
|
+
if (i < code.length && code[i] === ";") {
|
|
276
|
+
i++;
|
|
277
|
+
}
|
|
278
|
+
return i;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Escape special regex characters in a string so it can be safely
|
|
283
|
+
* interpolated into a RegExp pattern.
|
|
284
|
+
*/
|
|
285
|
+
export function escapeRegExp(input: string): string {
|
|
286
|
+
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
287
|
+
}
|
|
@@ -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
|
+
}
|