@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.d98a8e9d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2154 -861
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +243 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +57 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +128 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +98 -0
- package/skills/testing/client-components.md +121 -0
- package/skills/testing/e2e-parity.md +124 -0
- package/skills/testing/flight.md +89 -0
- package/skills/testing/handles.md +127 -0
- package/skills/testing/loader.md +108 -0
- package/skills/testing/middleware.md +97 -0
- package/skills/testing/render-handler.md +102 -0
- package/skills/testing/response-routes.md +94 -0
- package/skills/testing/reverse-and-types.md +83 -0
- package/skills/testing/server-actions.md +89 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +84 -11
- package/src/browser/navigation-client.ts +104 -68
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +64 -26
- package/src/browser/prefetch/cache.ts +183 -44
- package/src/browser/prefetch/fetch.ts +228 -37
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +32 -1
- package/src/browser/rsc-router.tsx +69 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +8 -1
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +95 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +54 -13
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +96 -205
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -4
- package/src/handle.ts +32 -14
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -21
- package/src/index.rsc.ts +10 -6
- package/src/index.ts +54 -17
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +25 -7
- package/src/loader.ts +16 -9
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +27 -6
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +26 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +54 -6
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +41 -22
- package/src/router/loader-resolution.ts +82 -36
- package/src/router/manifest.ts +41 -19
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +53 -32
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +116 -19
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +27 -6
- package/src/router/segment-resolution/revalidation.ts +147 -106
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +40 -16
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +52 -30
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +28 -69
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +57 -61
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +175 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +67 -51
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +25 -3
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +581 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +326 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +110 -0
- package/src/testing/flight-normalize.ts +38 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +51 -0
- package/src/testing/flight.ts +234 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +304 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +42 -0
- package/src/testing/render-handler.ts +323 -0
- package/src/testing/render-route.tsx +590 -0
- package/src/testing/run-loader.ts +363 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +285 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +35 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +1 -5
- package/src/urls/path-helper-types.ts +41 -7
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +106 -75
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +67 -26
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +496 -486
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +8 -59
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Node ESM loader hook that resolves `cloudflare:*` imports to the same
|
|
2
|
+
// stub ESM the Vite transform produces for rewritten specifiers.
|
|
3
|
+
//
|
|
4
|
+
// Why both? The Vite transform (cloudflare-protocol-stub.ts) catches
|
|
5
|
+
// imports in modules that flow through Vite's plugin pipeline — covers
|
|
6
|
+
// user source and any node_modules package Vite fetches and transforms.
|
|
7
|
+
// But Vite/Rollup externalize certain packages (e.g. `partyserver`,
|
|
8
|
+
// which has `import { DurableObject, env } from "cloudflare:workers"`
|
|
9
|
+
// at its top level, and similar "workerd-native" libraries). Externalized
|
|
10
|
+
// modules bypass the transform: Rollup hands their resolution to Node's
|
|
11
|
+
// native ESM loader, which rejects URL-scheme specifiers. This loader
|
|
12
|
+
// hook registers via `module.register()` from `createTempRscServer` and
|
|
13
|
+
// intercepts `cloudflare:*` at Node's resolve layer — before the default
|
|
14
|
+
// loader throws ERR_UNSUPPORTED_ESM_URL_SCHEME.
|
|
15
|
+
//
|
|
16
|
+
// Lifecycle: the hook runs in a dedicated worker thread (Node ESM loader
|
|
17
|
+
// architecture) with its own globalThis. It cannot see the main thread's
|
|
18
|
+
// `__rango_build_env__` bridge, so the `env` export here is always `{}`.
|
|
19
|
+
// That's fine in practice — externalized libraries don't typically touch
|
|
20
|
+
// `env` at module top level; they read it at request time in workerd
|
|
21
|
+
// where the real module exists. Build-time prerender handlers in user
|
|
22
|
+
// source DO read `env`, but they flow through the Vite transform (which
|
|
23
|
+
// does bridge `env` from `getPlatformProxy()`), not through this loader.
|
|
24
|
+
//
|
|
25
|
+
// Keep STUBS in sync with cloudflare-protocol-stub.ts — both paths need
|
|
26
|
+
// to hand out the same base classes.
|
|
27
|
+
|
|
28
|
+
const CF_PREFIX = "cloudflare:";
|
|
29
|
+
|
|
30
|
+
const STUBS = {
|
|
31
|
+
"cloudflare:workers": `
|
|
32
|
+
export class DurableObject { constructor(_ctx, _env) {} }
|
|
33
|
+
export class WorkerEntrypoint { constructor(_ctx, _env) {} }
|
|
34
|
+
export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
|
|
35
|
+
export class RpcTarget {}
|
|
36
|
+
export const env = {};
|
|
37
|
+
export default {};
|
|
38
|
+
`,
|
|
39
|
+
"cloudflare:email": `
|
|
40
|
+
export class EmailMessage { constructor(_from, _to, _raw) {} }
|
|
41
|
+
export default {};
|
|
42
|
+
`,
|
|
43
|
+
"cloudflare:sockets": `
|
|
44
|
+
export function connect() { return {}; }
|
|
45
|
+
export default {};
|
|
46
|
+
`,
|
|
47
|
+
"cloudflare:workflows": `
|
|
48
|
+
export class NonRetryableError extends Error {
|
|
49
|
+
constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
|
|
50
|
+
}
|
|
51
|
+
export default {};
|
|
52
|
+
`,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Policy: unknown `cloudflare:*` specifiers resolve permissively to an
|
|
56
|
+
// empty default export rather than throwing. Same reasoning as
|
|
57
|
+
// cloudflare-protocol-stub.ts's FALLBACK_STUB — we prioritize
|
|
58
|
+
// dependency-graph resilience over strict validation, because third-party
|
|
59
|
+
// packages can pull `cloudflare:*` modules we haven't curated.
|
|
60
|
+
const FALLBACK_STUB = `export default {};\n`;
|
|
61
|
+
|
|
62
|
+
function dataUrlFor(specifier) {
|
|
63
|
+
const body = STUBS[specifier] ?? FALLBACK_STUB;
|
|
64
|
+
return "data:text/javascript;base64," + Buffer.from(body).toString("base64");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
68
|
+
if (specifier.startsWith(CF_PREFIX)) {
|
|
69
|
+
return {
|
|
70
|
+
shortCircuit: true,
|
|
71
|
+
url: dataUrlFor(specifier),
|
|
72
|
+
format: "module",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return nextResolve(specifier, context);
|
|
76
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
const VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
|
|
4
|
+
const NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
|
|
5
|
+
const CF_PREFIX = "cloudflare:";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `globalThis` key the `cloudflare:workers` stub reads to populate its
|
|
9
|
+
* `env` export. Router discovery sets this to the resolved `buildEnv`
|
|
10
|
+
* proxy (from `wrangler.getPlatformProxy()` when `buildEnv: "auto"` is
|
|
11
|
+
* configured, or a user-supplied object otherwise) before importing the
|
|
12
|
+
* worker entry, and clears it after discovery disposes the proxy. When
|
|
13
|
+
* unset, the stub's `env` falls back to `{}`.
|
|
14
|
+
*
|
|
15
|
+
* Using `globalThis` is the only cross-module bridge that works here:
|
|
16
|
+
* the stub's `load` hook returns source text, not a live closure, but
|
|
17
|
+
* the stub module is evaluated in the same Node process as the
|
|
18
|
+
* discovery plugin — so reading a global at module-evaluation time
|
|
19
|
+
* reaches whatever the plugin assigned there. A symbol key would be
|
|
20
|
+
* cleaner in-process but awkward to name from the stub source.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export const BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
|
|
25
|
+
|
|
26
|
+
const SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
|
|
27
|
+
|
|
28
|
+
const IMPORT_NODE_TYPES = new Set([
|
|
29
|
+
"ImportDeclaration",
|
|
30
|
+
"ImportExpression",
|
|
31
|
+
"ExportNamedDeclaration",
|
|
32
|
+
"ExportAllDeclaration",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Keep in sync with `STUBS` in cloudflare-protocol-loader-hook.mjs —
|
|
36
|
+
// both paths (Vite transform and Node loader) need to hand out the same
|
|
37
|
+
// classes. Unknown `cloudflare:*` modules fall back to an empty default
|
|
38
|
+
// export so third-party packages (e.g. the Cloudflare Agents SDK) can
|
|
39
|
+
// pull them into the graph without crashing discovery. Discovery only
|
|
40
|
+
// evaluates module top-level code — no handlers run — so missing named
|
|
41
|
+
// exports only fail if something does `class X extends Missing {}` at
|
|
42
|
+
// module scope, which is rare outside the already-stubbed classes.
|
|
43
|
+
const STUBS: Record<string, string> = {
|
|
44
|
+
"cloudflare:workers": `
|
|
45
|
+
export class DurableObject { constructor(_ctx, _env) {} }
|
|
46
|
+
export class WorkerEntrypoint { constructor(_ctx, _env) {} }
|
|
47
|
+
export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
|
|
48
|
+
export class RpcTarget {}
|
|
49
|
+
export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
|
|
50
|
+
export default {};
|
|
51
|
+
`,
|
|
52
|
+
"cloudflare:email": `
|
|
53
|
+
export class EmailMessage { constructor(_from, _to, _raw) {} }
|
|
54
|
+
export default {};
|
|
55
|
+
`,
|
|
56
|
+
"cloudflare:sockets": `
|
|
57
|
+
export function connect() { return {}; }
|
|
58
|
+
export default {};
|
|
59
|
+
`,
|
|
60
|
+
"cloudflare:workflows": `
|
|
61
|
+
export class NonRetryableError extends Error {
|
|
62
|
+
constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
|
|
63
|
+
}
|
|
64
|
+
export default {};
|
|
65
|
+
`,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Policy: unknown `cloudflare:*` specifiers resolve permissively (empty
|
|
69
|
+
// default export) rather than throwing. We prioritize dependency-graph
|
|
70
|
+
// resilience over strict validation of user imports because third-party
|
|
71
|
+
// packages can pull `cloudflare:*` modules we haven't curated, and
|
|
72
|
+
// discovery should not fail just because those modules appear in the graph.
|
|
73
|
+
// Tradeoff: unsupported user-authored `cloudflare:*` imports may fail later
|
|
74
|
+
// with a generic JS/module error instead of a tailored rango-branded hint.
|
|
75
|
+
// The test below pins this behavior so dependency compatibility is not
|
|
76
|
+
// regressed accidentally.
|
|
77
|
+
const FALLBACK_STUB = `export default {};\n`;
|
|
78
|
+
|
|
79
|
+
interface AstNode {
|
|
80
|
+
type: string;
|
|
81
|
+
start?: number;
|
|
82
|
+
end?: number;
|
|
83
|
+
source?: AstNode | null;
|
|
84
|
+
value?: unknown;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Stubs `cloudflare:*` imports for the discovery-time Node Vite server.
|
|
90
|
+
*
|
|
91
|
+
* Discovery only evaluates user module top-level code — it never invokes
|
|
92
|
+
* DurableObject / WorkerEntrypoint / Workflow handlers — so empty base
|
|
93
|
+
* classes are enough for `class X extends DurableObject {}` declarations
|
|
94
|
+
* to load in Node, where `cloudflare:*` is otherwise unresolvable.
|
|
95
|
+
*
|
|
96
|
+
* Interception point: a transform hook parses source with Rollup's
|
|
97
|
+
* plugin-context parser (`this.parse`) and rewrites only real import
|
|
98
|
+
* specifier spans (`import ... from "cloudflare:xxx"`,
|
|
99
|
+
* `import("cloudflare:xxx")`, `export ... from "cloudflare:xxx"`) to a
|
|
100
|
+
* plain virtual module name (`virtual:rango-cloudflare-stub-xxx`).
|
|
101
|
+
* This must be done in transform because Vite's module runner routes
|
|
102
|
+
* URL-scheme specifiers straight to Node's native ESM loader without
|
|
103
|
+
* consulting plugin `resolveId` hooks. Using the AST (instead of a
|
|
104
|
+
* text regex or a permissive lexer) guarantees that strings,
|
|
105
|
+
* comments, and template literals that merely contain import-like
|
|
106
|
+
* text are never mutated — the walker only looks at the four import
|
|
107
|
+
* node types.
|
|
108
|
+
*
|
|
109
|
+
* The transform runs on user source AND on compiled node_modules
|
|
110
|
+
* output: real-world CF packages (e.g. the Cloudflare Agents SDK)
|
|
111
|
+
* ship compiled JS that contains `import ... from "cloudflare:email"`
|
|
112
|
+
* and similar, so excluding node_modules would leave those imports
|
|
113
|
+
* unrewritten. Cost is small because the early exit (`code.includes`)
|
|
114
|
+
* skips files with no cloudflare: mention.
|
|
115
|
+
*
|
|
116
|
+
* The plugin intentionally runs at Vite's default ordering (no
|
|
117
|
+
* `enforce: "pre"`) so TS/JSX has already been compiled to plain JS
|
|
118
|
+
* by the time `this.parse` runs — acorn doesn't understand
|
|
119
|
+
* non-standard syntax.
|
|
120
|
+
*
|
|
121
|
+
* `cloudflare:workers`, `cloudflare:email`, `cloudflare:sockets`, and
|
|
122
|
+
* `cloudflare:workflows` each get curated stubs with the well-known
|
|
123
|
+
* symbols that appear in top-level `extends` positions. Any other
|
|
124
|
+
* `cloudflare:*` specifier falls back to an empty default export —
|
|
125
|
+
* discovery never executes the handlers, so an empty module is safe
|
|
126
|
+
* for anything the graph pulls in transitively.
|
|
127
|
+
*
|
|
128
|
+
* Only registered in the discovery temp server, not the user's runtime
|
|
129
|
+
* config.
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
export function createCloudflareProtocolStubPlugin(): Plugin {
|
|
133
|
+
return {
|
|
134
|
+
name: "@rangojs/router:cloudflare-protocol-stub",
|
|
135
|
+
transform(code, id) {
|
|
136
|
+
const cleanId = id.split("?")[0] ?? id;
|
|
137
|
+
if (!SOURCE_EXT_RE.test(cleanId)) return null;
|
|
138
|
+
if (!code.includes(CF_PREFIX)) return null;
|
|
139
|
+
|
|
140
|
+
let ast: AstNode;
|
|
141
|
+
try {
|
|
142
|
+
ast = this.parse(code, { lang: "tsx" }) as unknown as AstNode;
|
|
143
|
+
} catch {
|
|
144
|
+
// Malformed source — let a downstream plugin surface the parse error.
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const hits: Array<{ start: number; end: number; value: string }> = [];
|
|
149
|
+
walk(ast, (node) => {
|
|
150
|
+
if (!IMPORT_NODE_TYPES.has(node.type)) return;
|
|
151
|
+
const source = node.source;
|
|
152
|
+
if (!source || source.type !== "Literal") return;
|
|
153
|
+
if (typeof source.value !== "string") return;
|
|
154
|
+
if (!source.value.startsWith(CF_PREFIX)) return;
|
|
155
|
+
if (typeof source.start !== "number" || typeof source.end !== "number")
|
|
156
|
+
return;
|
|
157
|
+
hits.push({
|
|
158
|
+
start: source.start,
|
|
159
|
+
end: source.end,
|
|
160
|
+
value: source.value,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (hits.length === 0) return null;
|
|
165
|
+
|
|
166
|
+
// Rewrite from last to first so earlier offsets stay valid. `start`/
|
|
167
|
+
// `end` span the full literal including quotes, so we re-emit the
|
|
168
|
+
// same quote character around the new specifier.
|
|
169
|
+
hits.sort((a, b) => b.start - a.start);
|
|
170
|
+
let out = code;
|
|
171
|
+
for (const hit of hits) {
|
|
172
|
+
const submodule = hit.value.slice(CF_PREFIX.length);
|
|
173
|
+
const quote = code[hit.start] === "'" ? "'" : '"';
|
|
174
|
+
out =
|
|
175
|
+
out.slice(0, hit.start) +
|
|
176
|
+
quote +
|
|
177
|
+
VIRTUAL_PREFIX +
|
|
178
|
+
submodule +
|
|
179
|
+
quote +
|
|
180
|
+
out.slice(hit.end);
|
|
181
|
+
}
|
|
182
|
+
return { code: out, map: null };
|
|
183
|
+
},
|
|
184
|
+
resolveId(id) {
|
|
185
|
+
if (id.startsWith(VIRTUAL_PREFIX)) {
|
|
186
|
+
return "\0" + id;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
},
|
|
190
|
+
load(id) {
|
|
191
|
+
if (!id.startsWith(NULL_PREFIX)) return null;
|
|
192
|
+
const submodule = id.slice(NULL_PREFIX.length);
|
|
193
|
+
const specifier = CF_PREFIX + submodule;
|
|
194
|
+
return STUBS[specifier] ?? FALLBACK_STUB;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function walk(node: unknown, visit: (n: AstNode) => void): void {
|
|
200
|
+
if (!node || typeof node !== "object") return;
|
|
201
|
+
if (Array.isArray(node)) {
|
|
202
|
+
for (const child of node) walk(child, visit);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const n = node as AstNode;
|
|
206
|
+
if (typeof n.type !== "string") return;
|
|
207
|
+
visit(n);
|
|
208
|
+
for (const key in n) {
|
|
209
|
+
if (key === "loc" || key === "start" || key === "end" || key === "range") {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
walk(n[key], visit);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -3,6 +3,9 @@ import MagicString from "magic-string";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { normalizePath } from "./expose-id-utils.js";
|
|
6
|
+
import { createRangoDebugger, createCounter, NS } from "../debug.js";
|
|
7
|
+
|
|
8
|
+
const debug = createRangoDebugger(NS.transform);
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Type for the RSC plugin's manager API
|
|
@@ -39,7 +42,7 @@ function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
|
|
|
39
42
|
);
|
|
40
43
|
if (plugin) {
|
|
41
44
|
console.warn(
|
|
42
|
-
`[
|
|
45
|
+
`[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). ` +
|
|
43
46
|
`Consider updating the name lookup if the plugin was renamed.`,
|
|
44
47
|
);
|
|
45
48
|
}
|
|
@@ -254,6 +257,8 @@ export function exposeActionId(): Plugin {
|
|
|
254
257
|
let isBuild = false;
|
|
255
258
|
let hashToFileMap: Map<string, string> | undefined;
|
|
256
259
|
let rscPluginApi: RscPluginApi | undefined;
|
|
260
|
+
const counterTransform = createCounter(debug, "expose-action-id transform");
|
|
261
|
+
const counterRender = createCounter(debug, "expose-action-id renderChunk");
|
|
257
262
|
|
|
258
263
|
return {
|
|
259
264
|
name: "@rangojs/router:expose-action-id",
|
|
@@ -268,6 +273,11 @@ export function exposeActionId(): Plugin {
|
|
|
268
273
|
rscPluginApi = getRscPluginApi(config);
|
|
269
274
|
},
|
|
270
275
|
|
|
276
|
+
buildEnd() {
|
|
277
|
+
counterTransform?.flush();
|
|
278
|
+
counterRender?.flush();
|
|
279
|
+
},
|
|
280
|
+
|
|
271
281
|
buildStart() {
|
|
272
282
|
// Verify RSC plugin is present at build start (after all config hooks have run)
|
|
273
283
|
// This allows rsc-router:rsc-integration to dynamically add the RSC plugin
|
|
@@ -277,7 +287,7 @@ export function exposeActionId(): Plugin {
|
|
|
277
287
|
|
|
278
288
|
if (!rscPluginApi) {
|
|
279
289
|
throw new Error(
|
|
280
|
-
"[
|
|
290
|
+
"[rango] Could not find @vitejs/plugin-rsc. " +
|
|
281
291
|
"@rangojs/router requires the Vite RSC plugin, which is included automatically by rango().",
|
|
282
292
|
);
|
|
283
293
|
}
|
|
@@ -324,40 +334,54 @@ export function exposeActionId(): Plugin {
|
|
|
324
334
|
return;
|
|
325
335
|
}
|
|
326
336
|
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
const start = counterTransform ? performance.now() : 0;
|
|
338
|
+
try {
|
|
339
|
+
// Dev mode: no hash-to-file mapping needed (IDs are already file paths)
|
|
340
|
+
return transformServerReferences(code, id);
|
|
341
|
+
} finally {
|
|
342
|
+
counterTransform?.record(id, performance.now() - start);
|
|
343
|
+
}
|
|
329
344
|
},
|
|
330
345
|
|
|
331
346
|
// Build mode: renderChunk runs after all transforms and bundling complete
|
|
332
347
|
renderChunk(code, chunk) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
code
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
348
|
+
const start = counterRender ? performance.now() : 0;
|
|
349
|
+
try {
|
|
350
|
+
// Only RSC bundle should get file paths for revalidation matching
|
|
351
|
+
// SSR bundle must NOT use file paths because client components run there
|
|
352
|
+
// and need to match the client bundle during hydration (otherwise: error #418)
|
|
353
|
+
const isRscEnv = this.environment?.name === "rsc";
|
|
354
|
+
|
|
355
|
+
// Only use file path mapping for RSC environment
|
|
356
|
+
const effectiveMap = isRscEnv ? hashToFileMap : undefined;
|
|
357
|
+
|
|
358
|
+
// For RSC bundles, both createServerReference and registerServerReference
|
|
359
|
+
// may need transforming. Use a single MagicString for correct sourcemaps.
|
|
360
|
+
if (isRscEnv && hashToFileMap) {
|
|
361
|
+
const s = new MagicString(code);
|
|
362
|
+
const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
|
|
363
|
+
const changed2 = applyRegisterReferenceWrapping(
|
|
364
|
+
code,
|
|
365
|
+
s,
|
|
366
|
+
hashToFileMap,
|
|
367
|
+
);
|
|
368
|
+
if (changed1 || changed2) {
|
|
369
|
+
return {
|
|
370
|
+
code: s.toString(),
|
|
371
|
+
map: s.generateMap({
|
|
372
|
+
source: chunk.fileName,
|
|
373
|
+
includeContent: true,
|
|
374
|
+
}),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return null;
|
|
355
378
|
}
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
379
|
|
|
359
|
-
|
|
360
|
-
|
|
380
|
+
// Non-RSC environments: only transform createServerReference calls
|
|
381
|
+
return transformServerReferences(code, chunk.fileName, effectiveMap);
|
|
382
|
+
} finally {
|
|
383
|
+
counterRender?.record(chunk.fileName, performance.now() - start);
|
|
384
|
+
}
|
|
361
385
|
},
|
|
362
386
|
};
|
|
363
387
|
}
|
|
@@ -32,18 +32,22 @@ export function makeStubId(
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Generate an 8-char hex hash for an inline
|
|
36
|
-
*
|
|
35
|
+
* Generate an 8-char hex hash for an inline handler call site.
|
|
36
|
+
*
|
|
37
|
+
* Keyed on the source-order INDEX of the call (the Nth inline `fnName(...)` in
|
|
38
|
+
* the file), NOT its line number. Line numbers shift between the prerender
|
|
39
|
+
* build context and the production build context (preceding transforms differ,
|
|
40
|
+
* e.g. plugin-react boilerplate), which would desync the prerender manifest key
|
|
41
|
+
* from the runtime handler id and break prerender/static freezing. The
|
|
42
|
+
* source-order index is invariant to line shifts; `fnName` keeps Static and
|
|
43
|
+
* Prerender inline ids from colliding at the same index.
|
|
37
44
|
*/
|
|
38
45
|
export function hashInlineId(
|
|
39
46
|
filePath: string,
|
|
40
|
-
|
|
41
|
-
index
|
|
47
|
+
fnName: string,
|
|
48
|
+
index: number,
|
|
42
49
|
): string {
|
|
43
|
-
const input =
|
|
44
|
-
index !== undefined && index > 0
|
|
45
|
-
? `${filePath}:${lineNumber}:${index}`
|
|
46
|
-
: `${filePath}:${lineNumber}`;
|
|
50
|
+
const input = `${filePath}:${fnName}:${index}`;
|
|
47
51
|
return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
buildExportMap,
|
|
7
7
|
escapeRegExp,
|
|
8
8
|
} from "../expose-id-utils.js";
|
|
9
|
+
import { codeMatchIndices } from "../../../build/route-types/source-scan.js";
|
|
9
10
|
import type { CreateExportBinding } from "./types.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -59,19 +60,57 @@ export function isExportOnlyFile(
|
|
|
59
60
|
return true;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
function createCallPattern(fnNames: string[]): RegExp {
|
|
64
|
+
return new RegExp(
|
|
65
|
+
`\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
|
|
66
|
+
"g",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Counts real create*() call sites, ignoring occurrences inside comments and
|
|
71
|
+
// string literals. Used by the unsupported-shape warning heuristic and the
|
|
72
|
+
// inline-extraction pre-check.
|
|
66
73
|
export function countCreateCallsForNames(
|
|
67
74
|
code: string,
|
|
68
75
|
fnNames: string[],
|
|
69
76
|
): number {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
return codeMatchIndices(code, createCallPattern(fnNames)).length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Convert a 0-based byte offset to a 1-based { line, column }. */
|
|
81
|
+
export function offsetToLineColumn(
|
|
82
|
+
code: string,
|
|
83
|
+
index: number,
|
|
84
|
+
): { line: number; column: number } {
|
|
85
|
+
let line = 1;
|
|
86
|
+
let lineStart = 0;
|
|
87
|
+
const end = Math.min(index, code.length);
|
|
88
|
+
for (let i = 0; i < end; i++) {
|
|
89
|
+
if (code[i] === "\n") {
|
|
90
|
+
line++;
|
|
91
|
+
lineStart = i + 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { line, column: index - lineStart + 1 };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Locate every real create*() call site (comment/string-free) that is NOT one
|
|
99
|
+
* of the supported, id-injectable export bindings, returning each as a 1-based
|
|
100
|
+
* { line, column }. The empty result means every call is in a supported shape.
|
|
101
|
+
* Both binding-collection paths anchor `callExprStart` at the start of the
|
|
102
|
+
* create* identifier — exactly where this pattern matches — so the set
|
|
103
|
+
* difference is exact.
|
|
104
|
+
*/
|
|
105
|
+
export function findUnsupportedCreateCallSites(
|
|
106
|
+
code: string,
|
|
107
|
+
fnNames: string[],
|
|
108
|
+
supportedBindings: CreateExportBinding[],
|
|
109
|
+
): Array<{ line: number; column: number }> {
|
|
110
|
+
const supported = new Set(supportedBindings.map((b) => b.callExprStart));
|
|
111
|
+
return codeMatchIndices(code, createCallPattern(fnNames))
|
|
112
|
+
.filter((index) => !supported.has(index))
|
|
113
|
+
.map((index) => offsetToLineColumn(code, index));
|
|
75
114
|
}
|
|
76
115
|
|
|
77
116
|
export function getImportedFnNames(
|
|
@@ -119,6 +158,28 @@ export function getCalledIdentifierFromCall(callExpr: any): string | null {
|
|
|
119
158
|
return null;
|
|
120
159
|
}
|
|
121
160
|
|
|
161
|
+
/**
|
|
162
|
+
* plugin-react's dev Fast Refresh wraps exports whose function body uses
|
|
163
|
+
* hook-like calls in a signature-registration call. A loader/handle that calls
|
|
164
|
+
* `ctx.use(...)` trips this heuristic, so `export const X = createLoader(...)`
|
|
165
|
+
* becomes `export const X = _s(createLoader(...), "<sig>", true)` — the create*
|
|
166
|
+
* call is the first argument of an unrelated wrapper call. Unwrap a single such
|
|
167
|
+
* layer so ID injection still targets the inner create* call. The `$$id`
|
|
168
|
+
* assignment is appended after the whole statement (against the export local),
|
|
169
|
+
* which is unaffected by the wrapper since `_s(x)` returns `x`.
|
|
170
|
+
*/
|
|
171
|
+
function unwrapSignatureWrappedCall(init: any, fnNameSet: Set<string>): any {
|
|
172
|
+
if (init?.type !== "CallExpression") return init;
|
|
173
|
+
const directId = getCalledIdentifierFromCall(init);
|
|
174
|
+
if (directId && fnNameSet.has(directId)) return init;
|
|
175
|
+
const firstArg = init.arguments?.[0];
|
|
176
|
+
if (firstArg?.type === "CallExpression") {
|
|
177
|
+
const innerId = getCalledIdentifierFromCall(firstArg);
|
|
178
|
+
if (innerId && fnNameSet.has(innerId)) return firstArg;
|
|
179
|
+
}
|
|
180
|
+
return init;
|
|
181
|
+
}
|
|
182
|
+
|
|
122
183
|
export function collectCreateExportBindingsFallback(
|
|
123
184
|
code: string,
|
|
124
185
|
fnNames: string[],
|
|
@@ -196,7 +257,7 @@ export function collectCreateExportBindings(
|
|
|
196
257
|
): CreateExportBinding[] {
|
|
197
258
|
if (!program) {
|
|
198
259
|
try {
|
|
199
|
-
program = parseAst(code, {
|
|
260
|
+
program = parseAst(code, { lang: "tsx" });
|
|
200
261
|
} catch {
|
|
201
262
|
return collectCreateExportBindingsFallback(code, fnNames);
|
|
202
263
|
}
|
|
@@ -212,10 +273,13 @@ export function collectCreateExportBindings(
|
|
|
212
273
|
}
|
|
213
274
|
|
|
214
275
|
for (const decl of varDecl.declarations ?? []) {
|
|
215
|
-
|
|
276
|
+
// Unwrap a Fast Refresh signature wrapper (`_s(createLoader(...), ...)`)
|
|
277
|
+
// so injection targets the inner create* call. Falls back to decl.init.
|
|
278
|
+
const callExpr = unwrapSignatureWrappedCall(decl?.init, fnNameSet);
|
|
279
|
+
const calledIdentifier = getCalledIdentifierFromCall(callExpr);
|
|
216
280
|
if (
|
|
217
281
|
decl?.id?.type !== "Identifier" ||
|
|
218
|
-
|
|
282
|
+
callExpr?.type !== "CallExpression" ||
|
|
219
283
|
!calledIdentifier ||
|
|
220
284
|
!fnNameSet.has(calledIdentifier)
|
|
221
285
|
) {
|
|
@@ -226,9 +290,8 @@ export function collectCreateExportBindings(
|
|
|
226
290
|
const exportNames = exportMap.get(localName) ?? [];
|
|
227
291
|
if (exportNames.length === 0) continue;
|
|
228
292
|
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
const calleeEnd = decl.init.callee.end as number;
|
|
293
|
+
const callEnd = callExpr.end as number;
|
|
294
|
+
const calleeEnd = callExpr.callee.end as number;
|
|
232
295
|
|
|
233
296
|
let openParenPos = -1;
|
|
234
297
|
for (let i = calleeEnd; i < callEnd; i++) {
|
|
@@ -245,10 +308,10 @@ export function collectCreateExportBindings(
|
|
|
245
308
|
bindings.push({
|
|
246
309
|
localName,
|
|
247
310
|
exportNames,
|
|
248
|
-
callExprStart:
|
|
311
|
+
callExprStart: callExpr.start as number,
|
|
249
312
|
callOpenParenPos: openParenPos,
|
|
250
313
|
callCloseParenPos: closeParenPos,
|
|
251
|
-
argCount:
|
|
314
|
+
argCount: callExpr.arguments?.length ?? 0,
|
|
252
315
|
statementEnd,
|
|
253
316
|
});
|
|
254
317
|
}
|
|
@@ -282,9 +345,25 @@ export function collectCreateExportBindings(
|
|
|
282
345
|
export function buildUnsupportedShapeWarning(
|
|
283
346
|
filePath: string,
|
|
284
347
|
fnName: string,
|
|
348
|
+
sites: Array<{ line: number; column: number }> = [],
|
|
285
349
|
): string {
|
|
286
|
-
|
|
287
|
-
|
|
350
|
+
const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
|
|
351
|
+
|
|
352
|
+
// Point at the exact call(s) so the location is clickable in the terminal/IDE
|
|
353
|
+
// (file:line:column) instead of leaving the user to scan the whole file.
|
|
354
|
+
if (sites.length === 1) {
|
|
355
|
+
const s = sites[0];
|
|
356
|
+
lines.push(
|
|
357
|
+
`The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected — it is not in a supported shape.`,
|
|
358
|
+
);
|
|
359
|
+
} else if (sites.length > 1) {
|
|
360
|
+
lines.push(
|
|
361
|
+
`These ${fnName}(...) calls have no stable $$id injected — they are not in a supported shape:`,
|
|
362
|
+
);
|
|
363
|
+
for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
lines.push(
|
|
288
367
|
`Supported shapes are:`,
|
|
289
368
|
` - export const X = ${fnName}(...)`,
|
|
290
369
|
` - const X = ${fnName}(...); export { X }`,
|
|
@@ -292,5 +371,6 @@ export function buildUnsupportedShapeWarning(
|
|
|
292
371
|
`Potentially unsupported forms include:`,
|
|
293
372
|
` - export let/var X = ${fnName}(...)`,
|
|
294
373
|
` - inline ${fnName}(...) calls`,
|
|
295
|
-
|
|
374
|
+
);
|
|
375
|
+
return lines.join("\n");
|
|
296
376
|
}
|