@rangojs/router 0.0.0-experimental.fa8a383a → 0.0.0-experimental.fad716ff
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 +76 -18
- package/dist/bin/rango.js +130 -47
- package/dist/vite/index.js +526 -168
- package/dist/vite/index.js.bak +5448 -0
- package/package.json +2 -2
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +8 -0
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +53 -43
- package/skills/middleware/SKILL.md +2 -0
- package/skills/parallel/SKILL.md +67 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/route/SKILL.md +31 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/typesafety/SKILL.md +10 -0
- package/src/__internal.ts +1 -1
- package/src/browser/app-version.ts +14 -0
- package/src/browser/navigation-bridge.ts +16 -3
- package/src/browser/navigation-client.ts +64 -40
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +37 -4
- package/src/browser/prefetch/fetch.ts +8 -2
- package/src/browser/prefetch/queue.ts +61 -29
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +44 -8
- package/src/browser/react/NavigationProvider.tsx +13 -4
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +26 -3
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +27 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +211 -72
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +46 -5
- package/src/cache/taint.ts +55 -0
- package/src/client.tsx +2 -56
- package/src/context-var.ts +72 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +12 -0
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +22 -1
- package/src/route-definition/dsl-helpers.ts +42 -19
- package/src/route-definition/helpers-types.ts +10 -6
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +9 -1
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-types.ts +11 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/handler-context.ts +79 -23
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/loader-resolution.ts +156 -21
- package/src/router/match-api.ts +124 -189
- package/src/router/match-middleware/background-revalidation.ts +12 -1
- package/src/router/match-middleware/cache-lookup.ts +72 -13
- package/src/router/match-middleware/cache-store.ts +21 -4
- package/src/router/match-middleware/segment-resolution.ts +53 -0
- package/src/router/match-result.ts +11 -5
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +6 -8
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +101 -18
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/revalidation.ts +122 -26
- package/src/router/types.ts +1 -0
- package/src/router.ts +54 -5
- package/src/rsc/handler.ts +464 -377
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +14 -2
- package/src/rsc/rsc-rendering.ts +10 -1
- package/src/rsc/server-action.ts +8 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- package/src/server/context.ts +50 -1
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +175 -15
- package/src/ssr/index.tsx +3 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +137 -33
- package/src/types/loader-types.ts +36 -9
- package/src/types/route-entry.ts +1 -1
- package/src/urls/path-helper-types.ts +9 -2
- package/src/urls/path-helper.ts +47 -12
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +16 -6
- package/src/use-loader.tsx +73 -4
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/discovery/prerender-collection.ts +14 -1
- package/src/vite/discovery/state.ts +13 -4
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +60 -5
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +118 -39
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +19 -2
- package/src/vite/router-discovery.ts +178 -37
- package/src/vite/utils/prerender-utils.ts +18 -0
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getImportedFnNames,
|
|
24
24
|
collectCreateExportBindings,
|
|
25
25
|
buildUnsupportedShapeWarning,
|
|
26
|
+
isExportOnlyFile,
|
|
26
27
|
} from "./expose-ids/export-analysis.js";
|
|
27
28
|
import {
|
|
28
29
|
hasCreateLoaderImport,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
transformLocationState,
|
|
35
36
|
generateWholeFileStubs,
|
|
36
37
|
generateExprStubs,
|
|
38
|
+
stubHandlerExprs,
|
|
37
39
|
transformHandlerIds,
|
|
38
40
|
} from "./expose-ids/handler-transform.js";
|
|
39
41
|
|
|
@@ -385,7 +387,9 @@ ${lazyImports.join(",\n")}
|
|
|
385
387
|
if (stubResult) return stubResult;
|
|
386
388
|
}
|
|
387
389
|
|
|
388
|
-
// --- PrerenderHandler: non-RSC stub replacement ---
|
|
390
|
+
// --- PrerenderHandler: non-RSC whole-file stub replacement ---
|
|
391
|
+
// When ALL exports are Prerender() calls, replace the entire file.
|
|
392
|
+
// Mixed-export files are handled in the unified pipeline below.
|
|
389
393
|
if (hasPrerenderHandlerCode && !isRscEnv) {
|
|
390
394
|
const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
|
|
391
395
|
const bindings = getBindings(code, fnNames);
|
|
@@ -397,16 +401,6 @@ ${lazyImports.join(",\n")}
|
|
|
397
401
|
isBuild,
|
|
398
402
|
);
|
|
399
403
|
if (wholeFile) return wholeFile;
|
|
400
|
-
|
|
401
|
-
const exprStubs = generateExprStubs(
|
|
402
|
-
PRERENDER_CONFIG,
|
|
403
|
-
bindings,
|
|
404
|
-
code,
|
|
405
|
-
filePath,
|
|
406
|
-
id,
|
|
407
|
-
isBuild,
|
|
408
|
-
);
|
|
409
|
-
if (exprStubs) return exprStubs;
|
|
410
404
|
}
|
|
411
405
|
|
|
412
406
|
// --- PrerenderHandler: RSC build module tracking ---
|
|
@@ -467,7 +461,8 @@ ${lazyImports.join(",\n")}
|
|
|
467
461
|
}
|
|
468
462
|
}
|
|
469
463
|
|
|
470
|
-
// --- StaticHandler: non-RSC stub replacement ---
|
|
464
|
+
// --- StaticHandler: non-RSC whole-file stub replacement ---
|
|
465
|
+
// When ALL exports are Static() calls, replace the entire file.
|
|
471
466
|
if (hasStaticHandlerCode && !isRscEnv) {
|
|
472
467
|
const fnNames = getFnNames(STATIC_CONFIG.fnName);
|
|
473
468
|
const bindings = getBindings(code, fnNames);
|
|
@@ -479,16 +474,79 @@ ${lazyImports.join(",\n")}
|
|
|
479
474
|
isBuild,
|
|
480
475
|
);
|
|
481
476
|
if (wholeFile) return wholeFile;
|
|
477
|
+
}
|
|
482
478
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (
|
|
479
|
+
// --- Mixed-type whole-file stub replacement (non-RSC) ---
|
|
480
|
+
// When the individual whole-file checks above fail (each only checks
|
|
481
|
+
// one type), the file has mixed exports (e.g. createLoader + Prerender).
|
|
482
|
+
// Gather ALL recognized bindings and check if they cover every export.
|
|
483
|
+
// If yes, replace the entire file with stubs — this strips server-only
|
|
484
|
+
// imports (node:fs, DB clients, etc.) that would crash in the browser.
|
|
485
|
+
if (!isRscEnv) {
|
|
486
|
+
const allBindings: CreateExportBinding[] = [];
|
|
487
|
+
if (hasLoaderCode) {
|
|
488
|
+
allBindings.push(...getBindings(code, getFnNames("createLoader")));
|
|
489
|
+
}
|
|
490
|
+
if (hasHandleCode) {
|
|
491
|
+
allBindings.push(...getBindings(code, getFnNames("createHandle")));
|
|
492
|
+
}
|
|
493
|
+
if (hasLocationStateCode) {
|
|
494
|
+
allBindings.push(
|
|
495
|
+
...getBindings(code, getFnNames("createLocationState")),
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
if (hasPrerenderHandlerCode) {
|
|
499
|
+
allBindings.push(
|
|
500
|
+
...getBindings(code, getFnNames(PRERENDER_CONFIG.fnName)),
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (hasStaticHandlerCode) {
|
|
504
|
+
allBindings.push(
|
|
505
|
+
...getBindings(code, getFnNames(STATIC_CONFIG.fnName)),
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
if (allBindings.length > 0 && isExportOnlyFile(code, allBindings)) {
|
|
509
|
+
const stubs: string[] = [];
|
|
510
|
+
for (const binding of allBindings) {
|
|
511
|
+
const name = binding.exportNames[0];
|
|
512
|
+
const stubId = isBuild
|
|
513
|
+
? hashId(filePath, name)
|
|
514
|
+
: `${filePath}#${name}`;
|
|
515
|
+
// Determine brand from which type this binding belongs to
|
|
516
|
+
const fnCall = code.slice(
|
|
517
|
+
binding.callExprStart,
|
|
518
|
+
binding.callOpenParenPos + 1,
|
|
519
|
+
);
|
|
520
|
+
let brand = "loader";
|
|
521
|
+
if (
|
|
522
|
+
hasPrerenderHandlerCode &&
|
|
523
|
+
getFnNames(PRERENDER_CONFIG.fnName).some((n) =>
|
|
524
|
+
fnCall.includes(n),
|
|
525
|
+
)
|
|
526
|
+
) {
|
|
527
|
+
brand = PRERENDER_CONFIG.brand;
|
|
528
|
+
} else if (
|
|
529
|
+
hasStaticHandlerCode &&
|
|
530
|
+
getFnNames(STATIC_CONFIG.fnName).some((n) => fnCall.includes(n))
|
|
531
|
+
) {
|
|
532
|
+
brand = STATIC_CONFIG.brand;
|
|
533
|
+
} else if (
|
|
534
|
+
hasHandleCode &&
|
|
535
|
+
getFnNames("createHandle").some((n) => fnCall.includes(n))
|
|
536
|
+
) {
|
|
537
|
+
brand = "handle";
|
|
538
|
+
} else if (
|
|
539
|
+
hasLocationStateCode &&
|
|
540
|
+
getFnNames("createLocationState").some((n) => fnCall.includes(n))
|
|
541
|
+
) {
|
|
542
|
+
brand = "locationState";
|
|
543
|
+
}
|
|
544
|
+
stubs.push(
|
|
545
|
+
`export const ${name} = { __brand: "${brand}", $$id: "${stubId}" };`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return { code: stubs.join("\n") + "\n", map: null };
|
|
549
|
+
}
|
|
492
550
|
}
|
|
493
551
|
|
|
494
552
|
// --- StaticHandler: RSC build module tracking ---
|
|
@@ -535,27 +593,48 @@ ${lazyImports.join(",\n")}
|
|
|
535
593
|
isBuild,
|
|
536
594
|
) || changed;
|
|
537
595
|
}
|
|
538
|
-
if (hasPrerenderHandlerCode
|
|
596
|
+
if (hasPrerenderHandlerCode) {
|
|
539
597
|
const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
598
|
+
const bindings = getBindings(code, fnNames);
|
|
599
|
+
if (isRscEnv) {
|
|
600
|
+
changed =
|
|
601
|
+
transformHandlerIds(
|
|
602
|
+
PRERENDER_CONFIG,
|
|
603
|
+
bindings,
|
|
604
|
+
s,
|
|
605
|
+
filePath,
|
|
606
|
+
isBuild,
|
|
607
|
+
) || changed;
|
|
608
|
+
} else {
|
|
609
|
+
// Non-RSC mixed-export file: replace Prerender() calls with stubs
|
|
610
|
+
// on the shared MagicString so sourcemaps stay accurate.
|
|
611
|
+
changed =
|
|
612
|
+
stubHandlerExprs(
|
|
613
|
+
PRERENDER_CONFIG,
|
|
614
|
+
bindings,
|
|
615
|
+
s,
|
|
616
|
+
filePath,
|
|
617
|
+
isBuild,
|
|
618
|
+
) || changed;
|
|
619
|
+
}
|
|
548
620
|
}
|
|
549
|
-
if (hasStaticHandlerCode
|
|
621
|
+
if (hasStaticHandlerCode) {
|
|
550
622
|
const fnNames = getFnNames(STATIC_CONFIG.fnName);
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
623
|
+
const bindings = getBindings(code, fnNames);
|
|
624
|
+
if (isRscEnv) {
|
|
625
|
+
changed =
|
|
626
|
+
transformHandlerIds(
|
|
627
|
+
STATIC_CONFIG,
|
|
628
|
+
bindings,
|
|
629
|
+
s,
|
|
630
|
+
filePath,
|
|
631
|
+
isBuild,
|
|
632
|
+
) || changed;
|
|
633
|
+
} else {
|
|
634
|
+
changed =
|
|
635
|
+
stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) ||
|
|
636
|
+
changed;
|
|
637
|
+
}
|
|
559
638
|
}
|
|
560
639
|
|
|
561
640
|
if (!changed) return;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Performance Tracks — RSDW client patch
|
|
3
|
+
*
|
|
4
|
+
* Patches the RSDW client so _debugInfo recovery works for plain-object
|
|
5
|
+
* payloads (our RscPayload shape). Without this, the Server Components
|
|
6
|
+
* track in Chrome DevTools stays empty.
|
|
7
|
+
*
|
|
8
|
+
* React's flushComponentPerformance uses splice(0) to empty _debugInfo
|
|
9
|
+
* after resolution, then recovers it from the resolved value — but only
|
|
10
|
+
* for arrays, async iterables, React elements, and lazy types. Since our
|
|
11
|
+
* RscPayload is a plain object, _debugInfo is lost. This patch relaxes
|
|
12
|
+
* the check so _debugInfo is recovered from any object.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Plugin } from "vite";
|
|
16
|
+
import { readFile } from "node:fs/promises";
|
|
17
|
+
|
|
18
|
+
const RSDW_PATCH_RE =
|
|
19
|
+
/((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
|
|
20
|
+
|
|
21
|
+
function buildPatchReplacement(match: string, debugInfoVar: string): string {
|
|
22
|
+
return `${match}
|
|
23
|
+
if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
|
|
24
|
+
var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
|
|
25
|
+
if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
|
|
26
|
+
${debugInfoVar} = _resolved._debugInfo;
|
|
27
|
+
}
|
|
28
|
+
}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function patchRsdwClientDebugInfoRecovery(code: string): {
|
|
32
|
+
code: string;
|
|
33
|
+
debugInfoVar: string | null;
|
|
34
|
+
} {
|
|
35
|
+
const match = code.match(RSDW_PATCH_RE);
|
|
36
|
+
if (!match) {
|
|
37
|
+
return { code, debugInfoVar: null };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
code: code.replace(match[1]!, buildPatchReplacement(match[1]!, match[2]!)),
|
|
42
|
+
debugInfoVar: match[2]!,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function performanceTracksOptimizeDepsPlugin(): {
|
|
47
|
+
name: string;
|
|
48
|
+
setup(build: any): void;
|
|
49
|
+
} {
|
|
50
|
+
return {
|
|
51
|
+
name: "@rangojs/router:performance-tracks-optimize-deps",
|
|
52
|
+
setup(build: any): void {
|
|
53
|
+
build.onLoad(
|
|
54
|
+
{
|
|
55
|
+
filter:
|
|
56
|
+
/react-server-dom-webpack-client\.browser\.(development|production)\.js$/,
|
|
57
|
+
},
|
|
58
|
+
async (args: { path: string }) => {
|
|
59
|
+
const code = await readFile(args.path, "utf8");
|
|
60
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
61
|
+
return {
|
|
62
|
+
contents: patched.code,
|
|
63
|
+
loader: "js",
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function performanceTracksPlugin(): Plugin {
|
|
72
|
+
return {
|
|
73
|
+
name: "@rangojs/router:performance-tracks",
|
|
74
|
+
|
|
75
|
+
transform(code, id) {
|
|
76
|
+
if (!id.includes("react-server-dom") || !id.includes("client")) return;
|
|
77
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
78
|
+
if (!patched.debugInfoVar) return;
|
|
79
|
+
if (process.env.INTERNAL_RANGO_DEBUG)
|
|
80
|
+
console.log(
|
|
81
|
+
"[perf-tracks] patched RSDW client (var:",
|
|
82
|
+
patched.debugInfoVar,
|
|
83
|
+
")",
|
|
84
|
+
);
|
|
85
|
+
return patched.code;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Vite plugin that triggers a full browser reload
|
|
5
|
-
*
|
|
4
|
+
* Vite plugin that triggers a full browser reload from terminal input.
|
|
5
|
+
*
|
|
6
|
+
* This plugin is intentionally passive:
|
|
7
|
+
* - it never enables raw mode on stdin
|
|
8
|
+
* - it never restores terminal state
|
|
9
|
+
* - it reacts to Ctrl+R when that raw byte reaches the process
|
|
10
|
+
* - it also supports safe line-based fallbacks like "e" + Enter
|
|
6
11
|
*
|
|
7
12
|
* Usage:
|
|
8
13
|
* ```ts
|
|
@@ -20,35 +25,95 @@ export function poke(): Plugin {
|
|
|
20
25
|
|
|
21
26
|
configureServer(server) {
|
|
22
27
|
const stdin = process.stdin;
|
|
28
|
+
const debug = process.env.RANGO_POKE_DEBUG === "1";
|
|
29
|
+
|
|
30
|
+
const triggerReload = (source: string) => {
|
|
31
|
+
server.hot.send({ type: "full-reload", path: "*" });
|
|
32
|
+
server.config.logger.info(` browser reload (${source})`, {
|
|
33
|
+
timestamp: true,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const toBuffer = (chunk: string | Buffer): Buffer => {
|
|
38
|
+
return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const formatChunk = (chunk: string | Buffer): string => {
|
|
42
|
+
const data = toBuffer(chunk);
|
|
43
|
+
const hex = Array.from(data)
|
|
44
|
+
.map((byte) => `0x${byte.toString(16).padStart(2, "0")}`)
|
|
45
|
+
.join(" ");
|
|
46
|
+
const ascii = Array.from(data)
|
|
47
|
+
.map((byte) => {
|
|
48
|
+
if (byte >= 0x20 && byte <= 0x7e) return String.fromCharCode(byte);
|
|
49
|
+
if (byte === 0x0a) return "\\n";
|
|
50
|
+
if (byte === 0x0d) return "\\r";
|
|
51
|
+
if (byte === 0x09) return "\\t";
|
|
52
|
+
return ".";
|
|
53
|
+
})
|
|
54
|
+
.join("");
|
|
55
|
+
return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const readCtrlR = (chunk: string | Buffer): boolean => {
|
|
59
|
+
const data =
|
|
60
|
+
typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
61
|
+
return data.length === 1 && data[0] === 0x12;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const readSubmittedCommands = (chunk: string | Buffer): string[] => {
|
|
65
|
+
const text = toBuffer(chunk)
|
|
66
|
+
.toString("utf8")
|
|
67
|
+
.replace(/\r\n/g, "\n")
|
|
68
|
+
.replace(/\r/g, "\n");
|
|
69
|
+
|
|
70
|
+
if (!text.includes("\n")) return [];
|
|
71
|
+
|
|
72
|
+
const lines = text.split("\n");
|
|
73
|
+
lines.pop();
|
|
74
|
+
return lines;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (debug) {
|
|
78
|
+
server.config.logger.info(
|
|
79
|
+
` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? (stdin.isRaw ? "yes" : "no") : "n/a"})`,
|
|
80
|
+
{ timestamp: true },
|
|
81
|
+
);
|
|
82
|
+
}
|
|
23
83
|
|
|
24
|
-
// Raw mode delivers individual keystrokes as immediate single-byte
|
|
25
|
-
// events instead of waiting for Enter (cooked/line-buffered mode).
|
|
26
|
-
// Without it, Ctrl+R (0x12) is never delivered as a discrete byte.
|
|
27
|
-
// When stdin is a pipe (CI, spawned process) setRawMode is unavailable
|
|
28
|
-
// but data already arrives unbuffered, so the isTTY guard suffices.
|
|
29
|
-
const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
|
|
30
84
|
if (stdin.isTTY) {
|
|
31
|
-
|
|
85
|
+
server.config.logger.info(
|
|
86
|
+
" poke ready: press e + enter to reload browser (ctrl+r also works when available)",
|
|
87
|
+
{ timestamp: true },
|
|
88
|
+
);
|
|
32
89
|
}
|
|
33
90
|
|
|
34
|
-
const onData = (data: Buffer) => {
|
|
35
|
-
if (
|
|
91
|
+
const onData = (data: string | Buffer) => {
|
|
92
|
+
if (debug) {
|
|
93
|
+
server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
|
|
94
|
+
timestamp: true,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
36
97
|
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
if (data
|
|
42
|
-
|
|
98
|
+
// Only react to the exact Ctrl+R byte when some host terminal or
|
|
99
|
+
// wrapper already delivers it to this process. We intentionally do
|
|
100
|
+
// not enable raw mode here because that can steal Vite shortcuts
|
|
101
|
+
// like "r" / "q" and interfere with terminal-level controls.
|
|
102
|
+
if (readCtrlR(data)) {
|
|
103
|
+
triggerReload("ctrl+r");
|
|
43
104
|
return;
|
|
44
105
|
}
|
|
45
106
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
107
|
+
for (const command of readSubmittedCommands(data)) {
|
|
108
|
+
if (command === "e") {
|
|
109
|
+
triggerReload("e+enter");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (command === "\u001br") {
|
|
114
|
+
triggerReload("option+r+enter");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
52
117
|
}
|
|
53
118
|
};
|
|
54
119
|
|
|
@@ -56,9 +121,6 @@ export function poke(): Plugin {
|
|
|
56
121
|
|
|
57
122
|
server.httpServer?.on("close", () => {
|
|
58
123
|
stdin.off("data", onData);
|
|
59
|
-
if (stdin.isTTY && previousRawMode !== null) {
|
|
60
|
-
stdin.setRawMode(previousRawMode);
|
|
61
|
-
}
|
|
62
124
|
});
|
|
63
125
|
},
|
|
64
126
|
};
|
package/src/vite/rango.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { printBanner, rangoVersion } from "./utils/banner.js";
|
|
|
26
26
|
import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
|
|
27
27
|
import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
|
|
28
28
|
import { createRouterDiscoveryPlugin } from "./router-discovery.js";
|
|
29
|
+
import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Vite plugin for @rangojs/router.
|
|
@@ -60,7 +61,16 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
60
61
|
|
|
61
62
|
// Get package resolution info (workspace vs npm install)
|
|
62
63
|
const rangoAliases = getPackageAliases();
|
|
63
|
-
const excludeDeps =
|
|
64
|
+
const excludeDeps = [
|
|
65
|
+
...getExcludeDeps(),
|
|
66
|
+
// The public browser entry re-exports the RSDW browser client.
|
|
67
|
+
// Excluding both keeps Vite from freezing the unpatched bundle into
|
|
68
|
+
// .vite/deps before our source transforms run.
|
|
69
|
+
"@vitejs/plugin-rsc/browser",
|
|
70
|
+
// Keep the browser RSDW client out of Vite's dep optimizer so our
|
|
71
|
+
// cjs-to-esm transform can patch the real file.
|
|
72
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
|
|
73
|
+
];
|
|
64
74
|
|
|
65
75
|
// Mutable ref for router path (node preset only).
|
|
66
76
|
// Set immediately when user-specified, or populated by the auto-discover
|
|
@@ -182,6 +192,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
182
192
|
|
|
183
193
|
plugins.push(createVirtualEntriesPlugin(finalEntries));
|
|
184
194
|
|
|
195
|
+
// Dev-only: RSDW client patch for React Performance Tracks
|
|
196
|
+
plugins.push(performanceTracksPlugin());
|
|
197
|
+
|
|
185
198
|
// Add RSC plugin with cloudflare-specific options
|
|
186
199
|
// Note: loadModuleDevProxy should NOT be used with childEnvironments
|
|
187
200
|
// since SSR runs in workerd alongside RSC
|
|
@@ -334,6 +347,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
334
347
|
// Add virtual entries plugin (RSC entry generated lazily from routerRef)
|
|
335
348
|
plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
|
|
336
349
|
|
|
350
|
+
// Dev-only: RSDW client patch for React Performance Tracks
|
|
351
|
+
plugins.push(performanceTracksPlugin());
|
|
352
|
+
|
|
337
353
|
plugins.push(
|
|
338
354
|
rsc({
|
|
339
355
|
entries: finalEntries,
|
|
@@ -437,7 +453,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
437
453
|
createRouterDiscoveryPlugin(discoveryEntryPath, {
|
|
438
454
|
routerPathRef: discoveryRouterRef,
|
|
439
455
|
enableBuildPrerender: prerenderEnabled,
|
|
440
|
-
|
|
456
|
+
buildEnv: options?.buildEnv,
|
|
457
|
+
preset,
|
|
441
458
|
}),
|
|
442
459
|
);
|
|
443
460
|
|