@rangojs/router 0.0.0-experimental.fa8a383a → 0.0.0-experimental.fb4fdc18
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 +188 -35
- package/dist/bin/rango.js +130 -47
- package/dist/vite/index.js +1884 -537
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +7 -5
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +8 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +33 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +93 -17
- package/skills/loader/SKILL.md +123 -46
- package/skills/middleware/SKILL.md +36 -3
- package/skills/migrate-nextjs/SKILL.md +562 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/parallel/SKILL.md +133 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/rango/SKILL.md +26 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +75 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/server-actions/SKILL.md +739 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +19 -1
- package/src/__internal.ts +1 -1
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +44 -4
- package/src/browser/navigation-bridge.ts +95 -7
- package/src/browser/navigation-client.ts +128 -53
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/partial-update.ts +93 -12
- package/src/browser/prefetch/cache.ts +129 -21
- package/src/browser/prefetch/fetch.ts +156 -18
- package/src/browser/prefetch/queue.ts +92 -29
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +72 -8
- package/src/browser/react/NavigationProvider.tsx +82 -21
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +17 -4
- package/src/browser/react/use-router.ts +29 -9
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/rsc-router.tsx +60 -9
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +46 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +52 -25
- 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/cf/cf-cache-store.ts +5 -7
- package/src/cache/taint.ts +55 -0
- package/src/client.tsx +84 -230
- package/src/context-var.ts +72 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +6 -1
- package/src/index.ts +49 -6
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +28 -2
- package/src/route-definition/dsl-helpers.ts +210 -35
- package/src/route-definition/helpers-types.ts +73 -20
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +9 -1
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/handler-context.ts +102 -25
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/lazy-includes.ts +6 -6
- package/src/router/loader-resolution.ts +159 -21
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +1 -0
- package/src/router/match-middleware/background-revalidation.ts +12 -1
- package/src/router/match-middleware/cache-lookup.ts +74 -14
- 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 +112 -9
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +20 -33
- package/src/router/middleware.ts +56 -12
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- 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/revalidation.ts +15 -1
- 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 +114 -18
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/revalidation.ts +257 -127
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +55 -7
- package/src/rsc/handler.ts +478 -383
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +20 -1
- package/src/rsc/server-action.ts +12 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +15 -1
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +22 -62
- package/src/server/context.ts +76 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +185 -57
- package/src/ssr/index.tsx +8 -1
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +145 -68
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +12 -1
- package/src/types/segments.ts +18 -1
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +47 -12
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +18 -16
- package/src/use-loader.tsx +77 -5
- package/src/vite/debug.ts +184 -0
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +36 -4
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +175 -74
- package/src/vite/discovery/self-gen-tracking.ts +27 -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/cjs-to-esm.ts +5 -0
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +16 -4
- 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 +52 -28
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +563 -316
- package/src/vite/plugins/performance-tracks.ts +96 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +56 -43
- package/src/vite/plugins/version-injector.ts +37 -11
- package/src/vite/rango.ts +63 -11
- package/src/vite/router-discovery.ts +732 -86
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +38 -5
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
import { createRangoDebugger, createCounter, NS } from "../debug.js";
|
|
18
|
+
|
|
19
|
+
const debug = createRangoDebugger(NS.transform);
|
|
20
|
+
|
|
21
|
+
const RSDW_PATCH_RE =
|
|
22
|
+
/((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
|
|
23
|
+
|
|
24
|
+
function buildPatchReplacement(match: string, debugInfoVar: string): string {
|
|
25
|
+
return `${match}
|
|
26
|
+
if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
|
|
27
|
+
var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
|
|
28
|
+
if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
|
|
29
|
+
${debugInfoVar} = _resolved._debugInfo;
|
|
30
|
+
}
|
|
31
|
+
}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function patchRsdwClientDebugInfoRecovery(code: string): {
|
|
35
|
+
code: string;
|
|
36
|
+
debugInfoVar: string | null;
|
|
37
|
+
} {
|
|
38
|
+
const match = code.match(RSDW_PATCH_RE);
|
|
39
|
+
if (!match) {
|
|
40
|
+
return { code, debugInfoVar: null };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
code: code.replace(match[1]!, buildPatchReplacement(match[1]!, match[2]!)),
|
|
45
|
+
debugInfoVar: match[2]!,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function performanceTracksOptimizeDepsPlugin(): {
|
|
50
|
+
name: string;
|
|
51
|
+
setup(build: any): void;
|
|
52
|
+
} {
|
|
53
|
+
return {
|
|
54
|
+
name: "@rangojs/router:performance-tracks-optimize-deps",
|
|
55
|
+
setup(build: any): void {
|
|
56
|
+
build.onLoad(
|
|
57
|
+
{
|
|
58
|
+
filter:
|
|
59
|
+
/react-server-dom-webpack-client\.browser\.(development|production)\.js$/,
|
|
60
|
+
},
|
|
61
|
+
async (args: { path: string }) => {
|
|
62
|
+
const code = await readFile(args.path, "utf8");
|
|
63
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
64
|
+
return {
|
|
65
|
+
contents: patched.code,
|
|
66
|
+
loader: "js",
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function performanceTracksPlugin(): Plugin {
|
|
75
|
+
const counter = createCounter(debug, "performance-tracks");
|
|
76
|
+
return {
|
|
77
|
+
name: "@rangojs/router:performance-tracks",
|
|
78
|
+
|
|
79
|
+
buildEnd() {
|
|
80
|
+
counter?.flush();
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
transform(code, id) {
|
|
84
|
+
if (!id.includes("react-server-dom") || !id.includes("client")) return;
|
|
85
|
+
const start = counter ? performance.now() : 0;
|
|
86
|
+
try {
|
|
87
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
88
|
+
if (!patched.debugInfoVar) return;
|
|
89
|
+
debug?.("patched RSDW client (var: %s)", patched.debugInfoVar);
|
|
90
|
+
return patched.code;
|
|
91
|
+
} finally {
|
|
92
|
+
counter?.record(id, performance.now() - start);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -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
|
};
|
|
@@ -20,6 +20,9 @@ import type { Plugin } from "vite";
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import MagicString from "magic-string";
|
|
22
22
|
import { normalizePath, hashId } from "./expose-id-utils.js";
|
|
23
|
+
import { createRangoDebugger, createCounter, NS } from "../debug.js";
|
|
24
|
+
|
|
25
|
+
const debug = createRangoDebugger(NS.transform);
|
|
23
26
|
|
|
24
27
|
const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
|
|
25
28
|
|
|
@@ -32,6 +35,7 @@ export function useCacheTransform(): Plugin {
|
|
|
32
35
|
let isBuild = false;
|
|
33
36
|
let rscTransforms: typeof import("@vitejs/plugin-rsc/transforms") | null =
|
|
34
37
|
null;
|
|
38
|
+
const counter = createCounter(debug, "use-cache");
|
|
35
39
|
|
|
36
40
|
return {
|
|
37
41
|
name: "@rangojs/router:use-cache",
|
|
@@ -42,6 +46,10 @@ export function useCacheTransform(): Plugin {
|
|
|
42
46
|
isBuild = config.command === "build";
|
|
43
47
|
},
|
|
44
48
|
|
|
49
|
+
buildEnd() {
|
|
50
|
+
counter?.flush();
|
|
51
|
+
},
|
|
52
|
+
|
|
45
53
|
async transform(code, id) {
|
|
46
54
|
// Only process in RSC environment
|
|
47
55
|
if (this.environment?.name !== "rsc") return;
|
|
@@ -55,63 +63,68 @@ export function useCacheTransform(): Plugin {
|
|
|
55
63
|
// Only JS/TS files
|
|
56
64
|
if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
const start = counter ? performance.now() : 0;
|
|
67
|
+
try {
|
|
68
|
+
// Lazy-load transform helpers
|
|
69
|
+
if (!rscTransforms) {
|
|
70
|
+
try {
|
|
71
|
+
rscTransforms = await import("@vitejs/plugin-rsc/transforms");
|
|
72
|
+
} catch {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const {
|
|
78
|
+
hasDirective,
|
|
79
|
+
transformWrapExport,
|
|
80
|
+
transformHoistInlineDirective,
|
|
81
|
+
} = rscTransforms;
|
|
82
|
+
|
|
83
|
+
// Parse AST
|
|
84
|
+
let ast: any;
|
|
60
85
|
try {
|
|
61
|
-
|
|
86
|
+
const { parseAst } = await import("vite");
|
|
87
|
+
ast = parseAst(code);
|
|
62
88
|
} catch {
|
|
63
89
|
return;
|
|
64
90
|
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const {
|
|
68
|
-
hasDirective,
|
|
69
|
-
transformWrapExport,
|
|
70
|
-
transformHoistInlineDirective,
|
|
71
|
-
} = rscTransforms;
|
|
72
91
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
ast
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
const filePath = normalizePath(path.relative(projectRoot, id));
|
|
93
|
+
const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
|
|
94
|
+
|
|
95
|
+
// Check for file-level "use cache"
|
|
96
|
+
if (hasDirective(ast.body, "use cache")) {
|
|
97
|
+
return transformFileLevelUseCache(
|
|
98
|
+
code,
|
|
99
|
+
ast,
|
|
100
|
+
filePath,
|
|
101
|
+
id,
|
|
102
|
+
isBuild,
|
|
103
|
+
isLayoutOrTemplate,
|
|
104
|
+
transformWrapExport,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
84
107
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
// Check for function-level "use cache" / "use cache: profileName"
|
|
109
|
+
// (only if there's no file-level directive but code still contains the string)
|
|
110
|
+
const functionResult = transformFunctionLevelUseCache(
|
|
88
111
|
code,
|
|
89
112
|
ast,
|
|
90
113
|
filePath,
|
|
91
114
|
id,
|
|
92
115
|
isBuild,
|
|
93
|
-
|
|
94
|
-
transformWrapExport,
|
|
116
|
+
transformHoistInlineDirective,
|
|
95
117
|
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Check for function-level "use cache" / "use cache: profileName"
|
|
99
|
-
// (only if there's no file-level directive but code still contains the string)
|
|
100
|
-
const functionResult = transformFunctionLevelUseCache(
|
|
101
|
-
code,
|
|
102
|
-
ast,
|
|
103
|
-
filePath,
|
|
104
|
-
id,
|
|
105
|
-
isBuild,
|
|
106
|
-
transformHoistInlineDirective,
|
|
107
|
-
);
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
// Always check for near-miss directives, even when valid directives
|
|
120
|
+
// exist. A file may contain both valid and invalid "use cache" directives
|
|
121
|
+
// in different functions — the invalid ones should still warn.
|
|
122
|
+
warnOnNearMissDirectives(ast, id, this.warn.bind(this));
|
|
113
123
|
|
|
114
|
-
|
|
124
|
+
if (functionResult) return functionResult;
|
|
125
|
+
} finally {
|
|
126
|
+
counter?.record(id, performance.now() - start);
|
|
127
|
+
}
|
|
115
128
|
},
|
|
116
129
|
};
|
|
117
130
|
}
|
|
@@ -47,16 +47,26 @@ export function createVersionInjectorPlugin(
|
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
// Always prepend `import "virtual:rsc-router/routes-manifest"` as the
|
|
51
|
+
// first side-effect import. The manifest virtual module's `load()` hook
|
|
52
|
+
// awaits `s.discoveryDone` so that, by the time the rest of the entry
|
|
53
|
+
// including any module-level `router.reverse()` calls under `./router.js`
|
|
54
|
+
// evaluates, runtime discovery has rewritten `router.named-routes.gen.ts`
|
|
55
|
+
// with the full route table.
|
|
56
|
+
//
|
|
57
|
+
// ES module evaluation order matters here: while imports are *parsed*
|
|
58
|
+
// hoisted, side-effect imports are evaluated in source order in the
|
|
59
|
+
// dependency graph. A user-authored `import "virtual:rsc-router/..."`
|
|
60
|
+
// placed after `import "./router.js"` runs too late: the manifest
|
|
61
|
+
// gate fires after router.tsx has already crashed on a stale gen file.
|
|
62
|
+
// We always prepend; ESM dedups any user-written duplicate, so module
|
|
63
|
+
// initialization still runs once.
|
|
64
|
+
const prepend: string[] = [
|
|
65
|
+
`import "virtual:rsc-router/routes-manifest";`,
|
|
66
|
+
];
|
|
58
67
|
|
|
59
68
|
// Auto-inject VERSION if file uses createRSCHandler without version
|
|
69
|
+
let newCode = code;
|
|
60
70
|
const needsVersion =
|
|
61
71
|
code.includes("createRSCHandler") &&
|
|
62
72
|
!code.includes("@rangojs/router:version") &&
|
|
@@ -70,9 +80,25 @@ export function createVersionInjectorPlugin(
|
|
|
70
80
|
);
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
// Insert after any leading `/// <reference ... />` triple-slash
|
|
84
|
+
// directives (and surrounding blank lines). TypeScript requires those
|
|
85
|
+
// directives to precede all other code; putting our imports above
|
|
86
|
+
// them silently demotes the directives to plain comments.
|
|
87
|
+
const lines = newCode.split("\n");
|
|
88
|
+
let insertAt = 0;
|
|
89
|
+
while (insertAt < lines.length) {
|
|
90
|
+
const trimmed = lines[insertAt]!.trim();
|
|
91
|
+
if (trimmed === "" || /^\/\/\/\s*<reference\b/.test(trimmed)) {
|
|
92
|
+
insertAt++;
|
|
93
|
+
} else {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
newCode = [
|
|
98
|
+
...lines.slice(0, insertAt),
|
|
99
|
+
...prepend,
|
|
100
|
+
...lines.slice(insertAt),
|
|
101
|
+
].join("\n");
|
|
76
102
|
|
|
77
103
|
return {
|
|
78
104
|
code: newCode,
|
package/src/vite/rango.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
|
|
|
12
12
|
import {
|
|
13
13
|
getExcludeDeps,
|
|
14
14
|
getPackageAliases,
|
|
15
|
+
getPublishedPackageName,
|
|
16
|
+
getVendorAliases,
|
|
15
17
|
} from "./utils/package-resolution.js";
|
|
16
18
|
import { findRouterFiles } from "../build/generate-route-types.js";
|
|
17
19
|
import { createVersionPlugin } from "./plugins/version-plugin.js";
|
|
@@ -26,6 +28,10 @@ import { printBanner, rangoVersion } from "./utils/banner.js";
|
|
|
26
28
|
import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
|
|
27
29
|
import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
|
|
28
30
|
import { createRouterDiscoveryPlugin } from "./router-discovery.js";
|
|
31
|
+
import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
|
|
32
|
+
import { createRangoDebugger, NS } from "./debug.js";
|
|
33
|
+
|
|
34
|
+
const debugConfig = createRangoDebugger(NS.config);
|
|
29
35
|
|
|
30
36
|
/**
|
|
31
37
|
* Vite plugin for @rangojs/router.
|
|
@@ -52,15 +58,40 @@ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
|
|
|
52
58
|
* ```
|
|
53
59
|
*/
|
|
54
60
|
export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
61
|
+
const rangoStart = performance.now();
|
|
55
62
|
const resolvedOptions: RangoOptions = options ?? { preset: "node" };
|
|
56
63
|
const preset = resolvedOptions.preset ?? "node";
|
|
57
64
|
const showBanner = resolvedOptions.banner ?? true;
|
|
65
|
+
debugConfig?.("rango(%s) setup start", preset);
|
|
58
66
|
|
|
59
67
|
const plugins: PluginOption[] = [];
|
|
60
68
|
|
|
61
|
-
// Get package resolution info (workspace vs npm install)
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
// Get package resolution info (workspace vs npm install).
|
|
70
|
+
// Vendor aliases redirect the bare plugin-rsc vendor specs (which plugin-rsc
|
|
71
|
+
// itself injects into optimizeDeps.include) to absolute paths resolved from
|
|
72
|
+
// this package — so strict-pnpm consumers don't hit "Failed to resolve
|
|
73
|
+
// dependency" warnings when those deps aren't hoisted to their app root.
|
|
74
|
+
const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
|
|
75
|
+
const excludeDeps = [
|
|
76
|
+
...getExcludeDeps(),
|
|
77
|
+
// plugin-rsc itself injects these into the client env's
|
|
78
|
+
// optimizeDeps.include, which overrides exclude for the dep's own
|
|
79
|
+
// pre-bundle entry. What exclude still controls is how *other*
|
|
80
|
+
// pre-bundled deps treat imports of these specs (external vs inlined)
|
|
81
|
+
// via esbuildCjsExternalPlugin. The cjs-to-esm transform in
|
|
82
|
+
// plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
|
|
83
|
+
// where client.browser's bare include fails to resolve and Vite ends up
|
|
84
|
+
// serving the raw CJS file at dev-serve time.
|
|
85
|
+
"@vitejs/plugin-rsc/browser",
|
|
86
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
// Vite supports a nested `A > B` syntax in optimizeDeps.include that resolves
|
|
90
|
+
// B from A's location. We anchor transitive deps (rsc-html-stream,
|
|
91
|
+
// @vitejs/plugin-rsc/vendor/*) to @rangojs/router so pnpm consumers — where
|
|
92
|
+
// these aren't visible at the app root — can still pre-bundle them.
|
|
93
|
+
const pkg = getPublishedPackageName();
|
|
94
|
+
const nested = (spec: string) => `${pkg} > ${spec}`;
|
|
64
95
|
|
|
65
96
|
// Mutable ref for router path (node preset only).
|
|
66
97
|
// Set immediately when user-specified, or populated by the auto-discover
|
|
@@ -116,7 +147,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
116
147
|
// Pre-bundle rsc-html-stream to prevent discovery during first request
|
|
117
148
|
// Exclude rsc-router modules to ensure same Context instance
|
|
118
149
|
optimizeDeps: {
|
|
119
|
-
include: ["rsc-html-stream/client"],
|
|
150
|
+
include: [nested("rsc-html-stream/client")],
|
|
120
151
|
exclude: excludeDeps,
|
|
121
152
|
esbuildOptions: sharedEsbuildOptions,
|
|
122
153
|
},
|
|
@@ -141,8 +172,10 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
141
172
|
"react-dom/static.edge",
|
|
142
173
|
"react/jsx-runtime",
|
|
143
174
|
"react/jsx-dev-runtime",
|
|
144
|
-
"rsc-html-stream/server",
|
|
145
|
-
|
|
175
|
+
nested("rsc-html-stream/server"),
|
|
176
|
+
nested(
|
|
177
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
|
|
178
|
+
),
|
|
146
179
|
],
|
|
147
180
|
exclude: excludeDeps,
|
|
148
181
|
esbuildOptions: sharedEsbuildOptions,
|
|
@@ -157,7 +190,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
157
190
|
"react",
|
|
158
191
|
"react/jsx-runtime",
|
|
159
192
|
"react/jsx-dev-runtime",
|
|
160
|
-
|
|
193
|
+
nested(
|
|
194
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
|
|
195
|
+
),
|
|
161
196
|
],
|
|
162
197
|
exclude: excludeDeps,
|
|
163
198
|
esbuildOptions: sharedEsbuildOptions,
|
|
@@ -182,6 +217,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
182
217
|
|
|
183
218
|
plugins.push(createVirtualEntriesPlugin(finalEntries));
|
|
184
219
|
|
|
220
|
+
// Dev-only: RSDW client patch for React Performance Tracks
|
|
221
|
+
plugins.push(performanceTracksPlugin());
|
|
222
|
+
|
|
185
223
|
// Add RSC plugin with cloudflare-specific options
|
|
186
224
|
// Note: loadModuleDevProxy should NOT be used with childEnvironments
|
|
187
225
|
// since SSR runs in workerd alongside RSC
|
|
@@ -267,7 +305,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
267
305
|
"react-dom",
|
|
268
306
|
"react/jsx-runtime",
|
|
269
307
|
"react/jsx-dev-runtime",
|
|
270
|
-
"rsc-html-stream/client",
|
|
308
|
+
nested("rsc-html-stream/client"),
|
|
271
309
|
],
|
|
272
310
|
exclude: excludeDeps,
|
|
273
311
|
esbuildOptions: sharedEsbuildOptions,
|
|
@@ -284,7 +322,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
284
322
|
"react-dom/static.edge",
|
|
285
323
|
"react/jsx-runtime",
|
|
286
324
|
"react/jsx-dev-runtime",
|
|
287
|
-
|
|
325
|
+
nested(
|
|
326
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
|
|
327
|
+
),
|
|
288
328
|
],
|
|
289
329
|
exclude: excludeDeps,
|
|
290
330
|
esbuildOptions: sharedEsbuildOptions,
|
|
@@ -297,7 +337,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
297
337
|
"react",
|
|
298
338
|
"react/jsx-runtime",
|
|
299
339
|
"react/jsx-dev-runtime",
|
|
300
|
-
|
|
340
|
+
nested(
|
|
341
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
|
|
342
|
+
),
|
|
301
343
|
],
|
|
302
344
|
esbuildOptions: sharedEsbuildOptions,
|
|
303
345
|
},
|
|
@@ -334,6 +376,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
334
376
|
// Add virtual entries plugin (RSC entry generated lazily from routerRef)
|
|
335
377
|
plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
|
|
336
378
|
|
|
379
|
+
// Dev-only: RSDW client patch for React Performance Tracks
|
|
380
|
+
plugins.push(performanceTracksPlugin());
|
|
381
|
+
|
|
337
382
|
plugins.push(
|
|
338
383
|
rsc({
|
|
339
384
|
entries: finalEntries,
|
|
@@ -437,9 +482,16 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
|
|
|
437
482
|
createRouterDiscoveryPlugin(discoveryEntryPath, {
|
|
438
483
|
routerPathRef: discoveryRouterRef,
|
|
439
484
|
enableBuildPrerender: prerenderEnabled,
|
|
440
|
-
|
|
485
|
+
buildEnv: options?.buildEnv,
|
|
486
|
+
preset,
|
|
441
487
|
}),
|
|
442
488
|
);
|
|
443
489
|
|
|
490
|
+
debugConfig?.(
|
|
491
|
+
"rango(%s) setup done: %d plugin(s) (%sms)",
|
|
492
|
+
preset,
|
|
493
|
+
plugins.length,
|
|
494
|
+
(performance.now() - rangoStart).toFixed(1),
|
|
495
|
+
);
|
|
444
496
|
return plugins;
|
|
445
497
|
}
|