@rangojs/router 0.0.0-experimental.fb4fdc18 → 0.0.0-experimental.fce7fbd1
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 +9 -9
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +914 -485
- package/package.json +55 -11
- 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 +3 -1
- package/skills/hooks/SKILL.md +214 -18
- package/skills/host-router/SKILL.md +45 -20
- package/skills/intercept/SKILL.md +26 -4
- package/skills/layout/SKILL.md +6 -7
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +149 -6
- package/skills/middleware/SKILL.md +13 -9
- package/skills/migrate-nextjs/SKILL.md +1 -1
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +5 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +242 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +58 -9
- package/skills/route/SKILL.md +13 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +53 -41
- package/skills/testing/SKILL.md +599 -0
- package/skills/typesafety/SKILL.md +310 -26
- 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/event-controller.ts +42 -66
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +6 -6
- package/src/browser/navigation-client.ts +12 -15
- package/src/browser/navigation-store.ts +7 -8
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +9 -19
- package/src/browser/react/NavigationProvider.tsx +29 -40
- 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-params.ts +3 -4
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +14 -1
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +30 -16
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +2 -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-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 +49 -6
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +10 -8
- 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 +6 -4
- package/src/index.ts +13 -6
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +238 -263
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +37 -14
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +19 -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 +4 -42
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +2 -2
- package/src/router/loader-resolution.ts +16 -2
- package/src/router/match-handlers.ts +62 -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 +32 -30
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +1 -1
- package/src/router/middleware.ts +46 -78
- 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 +43 -1
- 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 +19 -6
- package/src/router/segment-resolution/revalidation.ts +19 -6
- 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/types.ts +8 -0
- package/src/router.ts +37 -21
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +20 -65
- package/src/rsc/helpers.ts +22 -2
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/response-route-handler.ts +32 -52
- package/src/rsc/rsc-rendering.ts +27 -53
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +13 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +2 -2
- package/src/search-params.ts +4 -4
- package/src/segment-system.tsx +121 -65
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +118 -51
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +10 -0
- 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 +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +193 -0
- package/src/testing/render-route.tsx +536 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +170 -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 +183 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +56 -11
- package/src/types/index.ts +1 -0
- package/src/types/segments.ts +18 -1
- package/src/urls/include-helper.ts +10 -53
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +11 -3
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- 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 +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +70 -48
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +19 -25
- package/src/vite/discovery/route-types-writer.ts +40 -84
- 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 +3 -7
- package/src/vite/plugins/client-ref-hashing.ts +12 -1
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
- package/src/vite/plugins/expose-action-id.ts +2 -2
- 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-internal-ids.ts +47 -67
- package/src/vite/plugins/performance-tracks.ts +12 -16
- package/src/vite/plugins/use-cache-transform.ts +13 -11
- package/src/vite/plugins/version-injector.ts +2 -12
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +67 -15
- package/src/vite/router-discovery.ts +208 -63
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- 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/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -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")
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
getImportedFnNames,
|
|
29
29
|
collectCreateExportBindings,
|
|
30
30
|
buildUnsupportedShapeWarning,
|
|
31
|
+
findUnsupportedCreateCallSites,
|
|
31
32
|
isExportOnlyFile,
|
|
32
33
|
} from "./expose-ids/export-analysis.js";
|
|
33
34
|
import {
|
|
@@ -39,7 +40,6 @@ import {
|
|
|
39
40
|
transformHandles,
|
|
40
41
|
transformLocationState,
|
|
41
42
|
generateWholeFileStubs,
|
|
42
|
-
generateExprStubs,
|
|
43
43
|
stubHandlerExprs,
|
|
44
44
|
transformHandlerIds,
|
|
45
45
|
} from "./expose-ids/handler-transform.js";
|
|
@@ -335,7 +335,7 @@ ${lazyImports.join(",\n")}
|
|
|
335
335
|
}
|
|
336
336
|
if (_cachedAst !== undefined || _astParseFailed) return _cachedAst;
|
|
337
337
|
try {
|
|
338
|
-
_cachedAst = parseAst(code, {
|
|
338
|
+
_cachedAst = parseAst(code, { lang: "tsx" });
|
|
339
339
|
} catch {
|
|
340
340
|
_astParseFailed = true;
|
|
341
341
|
}
|
|
@@ -371,14 +371,21 @@ ${lazyImports.join(",\n")}
|
|
|
371
371
|
if (!hasCode) continue;
|
|
372
372
|
|
|
373
373
|
const fnNames = getFnNames(cfg.fnName);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
374
|
+
// Locate the real (comment/string-free) create* calls not covered by
|
|
375
|
+
// a supported, id-injectable export shape. Empty means every call is
|
|
376
|
+
// fine — in particular, a create*() token in a comment or string no
|
|
377
|
+
// longer trips a spurious warning.
|
|
378
|
+
const sites = findUnsupportedCreateCallSites(
|
|
379
|
+
code,
|
|
380
|
+
fnNames,
|
|
381
|
+
getBindings(code, fnNames),
|
|
382
|
+
);
|
|
383
|
+
if (sites.length === 0) continue;
|
|
377
384
|
|
|
378
385
|
const warnKey = `${id}::${cfg.fnName}`;
|
|
379
386
|
if (unsupportedShapeWarnings.has(warnKey)) continue;
|
|
380
387
|
unsupportedShapeWarnings.add(warnKey);
|
|
381
|
-
this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
|
|
388
|
+
this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName, sites));
|
|
382
389
|
}
|
|
383
390
|
|
|
384
391
|
// --- Loader: track for manifest (RSC env only) ---
|
|
@@ -424,17 +431,6 @@ ${lazyImports.join(",\n")}
|
|
|
424
431
|
if (wholeFile) return wholeFile;
|
|
425
432
|
}
|
|
426
433
|
|
|
427
|
-
// --- PrerenderHandler: RSC build module tracking ---
|
|
428
|
-
if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
|
|
429
|
-
const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
|
|
430
|
-
const exportNames = getBindings(code, fnNames).map(
|
|
431
|
-
(b) => b.exportNames[0],
|
|
432
|
-
);
|
|
433
|
-
if (exportNames.length > 0) {
|
|
434
|
-
prerenderHandlerModules.set(id, exportNames);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
434
|
// --- Inline handler extraction to virtual modules ---
|
|
439
435
|
// Runs before stubs/tracking so inline calls become imports, then
|
|
440
436
|
// the existing regex fast path handles both the original file's
|
|
@@ -710,14 +706,27 @@ ${lazyImports.join(",\n")}
|
|
|
710
706
|
}
|
|
711
707
|
}
|
|
712
708
|
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
709
|
+
// RSC build module tracking (prerender + static), consumed via the
|
|
710
|
+
// plugin API for prerender freezing. Export-binding sets are invariant
|
|
711
|
+
// across the inline-extraction loop, so tracking both here is equivalent
|
|
712
|
+
// to the pre-extraction prerender tracking this replaces.
|
|
713
|
+
if (isRscEnv && isBuild) {
|
|
714
|
+
const trackTypes: Array<
|
|
715
|
+
[boolean, HandlerTransformConfig, Map<string, string[]>]
|
|
716
|
+
> = [
|
|
717
|
+
[
|
|
718
|
+
hasPrerenderHandlerCode,
|
|
719
|
+
PRERENDER_CONFIG,
|
|
720
|
+
prerenderHandlerModules,
|
|
721
|
+
],
|
|
722
|
+
[hasStaticHandlerCode, STATIC_CONFIG, staticHandlerModules],
|
|
723
|
+
];
|
|
724
|
+
for (const [has, cfg, trackMap] of trackTypes) {
|
|
725
|
+
if (!has) continue;
|
|
726
|
+
const exportNames = getBindings(code, getFnNames(cfg.fnName)).map(
|
|
727
|
+
(b) => b.exportNames[0],
|
|
728
|
+
);
|
|
729
|
+
if (exportNames.length > 0) trackMap.set(id, exportNames);
|
|
721
730
|
}
|
|
722
731
|
}
|
|
723
732
|
|
|
@@ -758,48 +767,19 @@ ${lazyImports.join(",\n")}
|
|
|
758
767
|
isBuild,
|
|
759
768
|
) || changed;
|
|
760
769
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
) ||
|
|
773
|
-
|
|
774
|
-
// Non-RSC mixed-export file: replace Prerender() calls with stubs
|
|
775
|
-
// on the shared MagicString so sourcemaps stay accurate.
|
|
776
|
-
changed =
|
|
777
|
-
stubHandlerExprs(
|
|
778
|
-
PRERENDER_CONFIG,
|
|
779
|
-
bindings,
|
|
780
|
-
s,
|
|
781
|
-
filePath,
|
|
782
|
-
isBuild,
|
|
783
|
-
) || changed;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
if (hasStaticHandlerCode) {
|
|
787
|
-
const fnNames = getFnNames(STATIC_CONFIG.fnName);
|
|
788
|
-
const bindings = getBindings(code, fnNames);
|
|
789
|
-
if (isRscEnv) {
|
|
790
|
-
changed =
|
|
791
|
-
transformHandlerIds(
|
|
792
|
-
STATIC_CONFIG,
|
|
793
|
-
bindings,
|
|
794
|
-
s,
|
|
795
|
-
filePath,
|
|
796
|
-
isBuild,
|
|
797
|
-
) || changed;
|
|
798
|
-
} else {
|
|
799
|
-
changed =
|
|
800
|
-
stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) ||
|
|
801
|
-
changed;
|
|
802
|
-
}
|
|
770
|
+
// Prerender + Static share the RSC inject-id vs non-RSC stub dispatch.
|
|
771
|
+
// Call sites are disjoint (distinct fnNames), so loop order is irrelevant.
|
|
772
|
+
const finalHandlerConfigs = [
|
|
773
|
+
hasPrerenderHandlerCode && PRERENDER_CONFIG,
|
|
774
|
+
hasStaticHandlerCode && STATIC_CONFIG,
|
|
775
|
+
].filter((c): c is HandlerTransformConfig => !!c);
|
|
776
|
+
for (const cfg of finalHandlerConfigs) {
|
|
777
|
+
const bindings = getBindings(code, getFnNames(cfg.fnName));
|
|
778
|
+
changed =
|
|
779
|
+
(isRscEnv
|
|
780
|
+
? transformHandlerIds(cfg, bindings, s, filePath, isBuild)
|
|
781
|
+
: stubHandlerExprs(cfg, bindings, s, filePath, isBuild)) ||
|
|
782
|
+
changed;
|
|
803
783
|
}
|
|
804
784
|
|
|
805
785
|
if (!changed) return;
|
|
@@ -48,25 +48,21 @@ export function patchRsdwClientDebugInfoRecovery(code: string): {
|
|
|
48
48
|
|
|
49
49
|
export function performanceTracksOptimizeDepsPlugin(): {
|
|
50
50
|
name: string;
|
|
51
|
-
|
|
51
|
+
load(id: string): Promise<{ code: string } | null>;
|
|
52
52
|
} {
|
|
53
|
+
const RSDW_CLIENT_RE =
|
|
54
|
+
/react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
|
|
53
55
|
return {
|
|
54
56
|
name: "@rangojs/router:performance-tracks-optimize-deps",
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
contents: patched.code,
|
|
66
|
-
loader: "js",
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
);
|
|
57
|
+
// Vite 8 optimizes deps with Rolldown (Rollup-style plugin pipeline), so the
|
|
58
|
+
// pre-bundled RSDW client is patched via load() rather than esbuild's onLoad.
|
|
59
|
+
// Returning code overrides Rolldown's default filesystem read for the module.
|
|
60
|
+
async load(id: string): Promise<{ code: string } | null> {
|
|
61
|
+
const cleanId = id.split("?")[0] ?? id;
|
|
62
|
+
if (!RSDW_CLIENT_RE.test(cleanId)) return null;
|
|
63
|
+
const code = await readFile(cleanId, "utf8");
|
|
64
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
65
|
+
return { code: patched.code };
|
|
70
66
|
},
|
|
71
67
|
};
|
|
72
68
|
}
|
|
@@ -30,6 +30,13 @@ const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
|
|
|
30
30
|
// and should not be wrapped (children can't be cache-keyed).
|
|
31
31
|
const LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Grammar for a valid function-level directive: `use cache` optionally followed
|
|
35
|
+
* by `: <profile-name>`. The single source of truth for both the transform and
|
|
36
|
+
* the near-miss validator below.
|
|
37
|
+
*/
|
|
38
|
+
export const USE_CACHE_DIRECTIVE_RE: RegExp = /^use cache(:\s*[\w-]+)?$/;
|
|
39
|
+
|
|
33
40
|
export function useCacheTransform(): Plugin {
|
|
34
41
|
let projectRoot = "";
|
|
35
42
|
let isBuild = false;
|
|
@@ -84,7 +91,7 @@ export function useCacheTransform(): Plugin {
|
|
|
84
91
|
let ast: any;
|
|
85
92
|
try {
|
|
86
93
|
const { parseAst } = await import("vite");
|
|
87
|
-
ast = parseAst(code);
|
|
94
|
+
ast = parseAst(code, { lang: "tsx" });
|
|
88
95
|
} catch {
|
|
89
96
|
return;
|
|
90
97
|
}
|
|
@@ -116,9 +123,9 @@ export function useCacheTransform(): Plugin {
|
|
|
116
123
|
transformHoistInlineDirective,
|
|
117
124
|
);
|
|
118
125
|
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
126
|
+
// Check for near-miss directives on the function-level path. The
|
|
127
|
+
// file-level branch above returns earlier (it wraps every export
|
|
128
|
+
// regardless), so this runs only when there is no file-level directive.
|
|
122
129
|
warnOnNearMissDirectives(ast, id, this.warn.bind(this));
|
|
123
130
|
|
|
124
131
|
if (functionResult) return functionResult;
|
|
@@ -219,7 +226,7 @@ function transformFunctionLevelUseCache(
|
|
|
219
226
|
) {
|
|
220
227
|
try {
|
|
221
228
|
const { output, names } = transformHoistInlineDirective(code, ast, {
|
|
222
|
-
directive:
|
|
229
|
+
directive: USE_CACHE_DIRECTIVE_RE,
|
|
223
230
|
runtime: (
|
|
224
231
|
value: string,
|
|
225
232
|
name: string,
|
|
@@ -273,11 +280,6 @@ function findFileLevelDirective(
|
|
|
273
280
|
return null;
|
|
274
281
|
}
|
|
275
282
|
|
|
276
|
-
/**
|
|
277
|
-
* The valid directive regex (must stay in sync with transformFunctionLevelUseCache).
|
|
278
|
-
*/
|
|
279
|
-
const VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
|
|
280
|
-
|
|
281
283
|
/**
|
|
282
284
|
* Regex for near-miss: starts with "use cache:" but has invalid tokens.
|
|
283
285
|
*/
|
|
@@ -307,7 +309,7 @@ function warnOnNearMissDirectives(
|
|
|
307
309
|
if (
|
|
308
310
|
value.startsWith("use cache") &&
|
|
309
311
|
NEAR_MISS_RE.test(value) &&
|
|
310
|
-
!
|
|
312
|
+
!USE_CACHE_DIRECTIVE_RE.test(value)
|
|
311
313
|
) {
|
|
312
314
|
const profilePart = value.slice("use cache:".length).trim();
|
|
313
315
|
warn(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import * as Vite from "vite";
|
|
4
|
+
import { resolveRscEntryFromConfig } from "../utils/shared-utils.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Plugin that auto-injects VERSION and routes-manifest into custom entry.rsc files.
|
|
@@ -20,18 +21,7 @@ export function createVersionInjectorPlugin(
|
|
|
20
21
|
|
|
21
22
|
configResolved(config) {
|
|
22
23
|
let entryPath = rscEntryPath;
|
|
23
|
-
|
|
24
|
-
// The @cloudflare/vite-plugin reads wrangler config (toml/json/jsonc)
|
|
25
|
-
// and sets optimizeDeps.entries on the RSC environment.
|
|
26
|
-
if (!entryPath) {
|
|
27
|
-
const rscEnvConfig = (config.environments as any)?.["rsc"];
|
|
28
|
-
const entries = rscEnvConfig?.optimizeDeps?.entries;
|
|
29
|
-
if (typeof entries === "string") {
|
|
30
|
-
entryPath = entries;
|
|
31
|
-
} else if (Array.isArray(entries) && entries.length > 0) {
|
|
32
|
-
entryPath = entries[0];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
24
|
+
if (!entryPath) entryPath = resolveRscEntryFromConfig(config);
|
|
35
25
|
if (entryPath) {
|
|
36
26
|
resolvedEntryPath = resolve(config.root, entryPath);
|
|
37
27
|
}
|