@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.dacec167
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 +2151 -846
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- 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 +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- 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 +778 -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 +117 -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 +76 -28
- 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 +129 -21
- package/src/browser/prefetch/fetch.ts +148 -16
- 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 +25 -0
- package/src/browser/rsc-router.tsx +64 -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 +2 -0
- package/src/build/route-trie.ts +52 -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 +92 -182
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +26 -13
- 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 -20
- package/src/index.rsc.ts +9 -4
- package/src/index.ts +53 -15
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +21 -6
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +4 -4
- 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/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +8 -8
- package/src/router/loader-resolution.ts +19 -2
- package/src/router/manifest.ts +22 -13
- 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 +101 -17
- 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 +18 -13
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +38 -23
- 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/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-route-handler.ts +46 -53
- 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/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 +143 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +20 -42
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +1 -1
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -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 +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +320 -0
- package/src/testing/flight.entry.ts +39 -0
- package/src/testing/flight.ts +197 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +331 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +341 -0
- package/src/testing/run-middleware.ts +188 -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 +270 -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 +5 -6
- 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 +0 -3
- 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 +26 -116
- 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 +101 -51
- 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 +21 -5
- 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
|
@@ -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
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import MagicString from "magic-string";
|
|
2
|
-
import {
|
|
2
|
+
import { makeStubId } from "../expose-id-utils.js";
|
|
3
3
|
import type { HandlerTransformConfig, CreateExportBinding } from "./types.js";
|
|
4
4
|
import { isExportOnlyFile } from "./export-analysis.js";
|
|
5
5
|
|
|
@@ -28,9 +28,7 @@ export function transformHandles(
|
|
|
28
28
|
binding.callCloseParenPos,
|
|
29
29
|
);
|
|
30
30
|
|
|
31
|
-
const handleId = isBuild
|
|
32
|
-
? hashId(filePath, exportName)
|
|
33
|
-
: `${filePath}#${exportName}`;
|
|
31
|
+
const handleId = makeStubId(filePath, exportName, isBuild);
|
|
34
32
|
|
|
35
33
|
let paramInjection: string;
|
|
36
34
|
if (!args.hasArgs) {
|
|
@@ -58,9 +56,7 @@ export function transformLocationState(
|
|
|
58
56
|
for (const binding of bindings) {
|
|
59
57
|
const exportName = binding.exportNames[0];
|
|
60
58
|
|
|
61
|
-
const stateKey = isBuild
|
|
62
|
-
? hashId(filePath, exportName)
|
|
63
|
-
: `${filePath}#${exportName}`;
|
|
59
|
+
const stateKey = makeStubId(filePath, exportName, isBuild);
|
|
64
60
|
|
|
65
61
|
// Key is injected as a property assignment (not as a function argument).
|
|
66
62
|
// This allows createLocationState to accept options like { flash: true }
|
|
@@ -88,7 +84,7 @@ export function generateWholeFileStubs(
|
|
|
88
84
|
|
|
89
85
|
const exportNames = bindings.flatMap((b) => b.exportNames);
|
|
90
86
|
const stubs = exportNames.map((name) => {
|
|
91
|
-
const handlerId =
|
|
87
|
+
const handlerId = makeStubId(filePath, name, isBuild);
|
|
92
88
|
return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
|
|
93
89
|
});
|
|
94
90
|
|
|
@@ -96,53 +92,8 @@ export function generateWholeFileStubs(
|
|
|
96
92
|
}
|
|
97
93
|
|
|
98
94
|
/**
|
|
99
|
-
* Replace handler call expressions with lightweight stub objects
|
|
100
|
-
*
|
|
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
|
-
* Replace handler call expressions with lightweight stub objects on an
|
|
143
|
-
* existing MagicString. Unlike generateExprStubs (which creates its own
|
|
144
|
-
* MagicString and returns the full result), this integrates into the
|
|
145
|
-
* unified transform pipeline so all transforms share one sourcemap.
|
|
95
|
+
* Replace handler call expressions with lightweight stub objects on the shared
|
|
96
|
+
* unified-pipeline MagicString so all transforms share one sourcemap.
|
|
146
97
|
*/
|
|
147
98
|
export function stubHandlerExprs(
|
|
148
99
|
cfg: HandlerTransformConfig,
|
|
@@ -154,9 +105,7 @@ export function stubHandlerExprs(
|
|
|
154
105
|
let hasChanges = false;
|
|
155
106
|
for (const binding of bindings) {
|
|
156
107
|
const exportName = binding.exportNames[0];
|
|
157
|
-
const handlerId = isBuild
|
|
158
|
-
? hashId(filePath, exportName)
|
|
159
|
-
: `${filePath}#${exportName}`;
|
|
108
|
+
const handlerId = makeStubId(filePath, exportName, isBuild);
|
|
160
109
|
|
|
161
110
|
s.overwrite(
|
|
162
111
|
binding.callExprStart,
|
|
@@ -182,9 +131,7 @@ export function transformHandlerIds(
|
|
|
182
131
|
for (const binding of bindings) {
|
|
183
132
|
const exportName = binding.exportNames[0];
|
|
184
133
|
|
|
185
|
-
const handlerId = isBuild
|
|
186
|
-
? hashId(filePath, exportName)
|
|
187
|
-
: `${filePath}#${exportName}`;
|
|
134
|
+
const handlerId = makeStubId(filePath, exportName, isBuild);
|
|
188
135
|
|
|
189
136
|
// Injection strategy matches the runtime overload signatures:
|
|
190
137
|
// 0 args -> inject undefined, "id"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type MagicString from "magic-string";
|
|
2
|
-
import {
|
|
2
|
+
import { makeStubId } from "../expose-id-utils.js";
|
|
3
3
|
import type { CreateExportBinding } from "./types.js";
|
|
4
4
|
import { isExportOnlyFile } from "./export-analysis.js";
|
|
5
5
|
|
|
@@ -33,7 +33,7 @@ export function generateClientLoaderStubs(
|
|
|
33
33
|
|
|
34
34
|
for (const binding of bindings) {
|
|
35
35
|
for (const name of binding.exportNames) {
|
|
36
|
-
const loaderId =
|
|
36
|
+
const loaderId = makeStubId(filePath, name, isBuild);
|
|
37
37
|
lines.push(
|
|
38
38
|
`export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`,
|
|
39
39
|
);
|
|
@@ -54,9 +54,7 @@ export function transformLoaders(
|
|
|
54
54
|
for (const binding of bindings) {
|
|
55
55
|
const exportName = binding.exportNames[0];
|
|
56
56
|
|
|
57
|
-
const loaderId = isBuild
|
|
58
|
-
? hashId(filePath, exportName)
|
|
59
|
-
: `${filePath}#${exportName}`;
|
|
57
|
+
const loaderId = makeStubId(filePath, exportName, isBuild);
|
|
60
58
|
|
|
61
59
|
// Inject $$id as hidden third parameter.
|
|
62
60
|
// createLoader(fn) -> createLoader(fn, undefined, "id")
|
|
@@ -4,6 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { normalizePath, findMatchingParen } from "../expose-id-utils.js";
|
|
6
6
|
import { getImportedFnNames } from "./export-analysis.js";
|
|
7
|
+
import { createRangoDebugger, createCounter, NS } from "../../debug.js";
|
|
8
|
+
|
|
9
|
+
const debug = createRangoDebugger(NS.transform);
|
|
7
10
|
|
|
8
11
|
export function transformRouter(
|
|
9
12
|
code: string,
|
|
@@ -82,11 +85,15 @@ export function transformRouter(
|
|
|
82
85
|
*/
|
|
83
86
|
export function exposeRouterId(): Plugin {
|
|
84
87
|
let projectRoot = "";
|
|
88
|
+
const counter = createCounter(debug, "expose-router-id");
|
|
85
89
|
return {
|
|
86
90
|
name: "@rangojs/router:expose-router-id",
|
|
87
91
|
configResolved(config) {
|
|
88
92
|
projectRoot = config.root;
|
|
89
93
|
},
|
|
94
|
+
buildEnd() {
|
|
95
|
+
counter?.flush();
|
|
96
|
+
},
|
|
90
97
|
transform(code, id) {
|
|
91
98
|
if (!code.includes("createRouter")) return null;
|
|
92
99
|
// Accepts both @rangojs/router and @rangojs/router/server subpath.
|
|
@@ -102,9 +109,19 @@ export function exposeRouterId(): Plugin {
|
|
|
102
109
|
}
|
|
103
110
|
if (id.includes("node_modules")) return null;
|
|
104
111
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
const start = counter ? performance.now() : 0;
|
|
113
|
+
try {
|
|
114
|
+
const filePath = normalizePath(path.relative(projectRoot, id));
|
|
115
|
+
const routerFnNames = getImportedFnNames(code, "createRouter");
|
|
116
|
+
return transformRouter(
|
|
117
|
+
code,
|
|
118
|
+
filePath,
|
|
119
|
+
routerFnNames,
|
|
120
|
+
normalizePath(id),
|
|
121
|
+
);
|
|
122
|
+
} finally {
|
|
123
|
+
counter?.record(id, performance.now() - start);
|
|
124
|
+
}
|
|
108
125
|
},
|
|
109
126
|
};
|
|
110
127
|
}
|