@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bf1b128c
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 +126 -38
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +2018 -732
- 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 +45 -4
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +28 -20
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +91 -17
- package/skills/loader/SKILL.md +119 -45
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +192 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +55 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +13 -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 +5 -0
- package/src/browser/navigation-bridge.ts +88 -9
- package/src/browser/navigation-client.ts +167 -59
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +109 -15
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +89 -14
- package/src/browser/react/context.ts +7 -2
- 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 +11 -1
- package/src/browser/react/use-router.ts +29 -9
- package/src/browser/rsc-router.tsx +168 -65
- package/src/browser/scroll-restoration.ts +36 -17
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +49 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +50 -24
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +223 -74
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +48 -7
- package/src/cache/cf/cf-cache-store.ts +455 -15
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.tsx +84 -230
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -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 +27 -2
- package/src/route-definition/dsl-helpers.ts +240 -40
- package/src/route-definition/helpers-types.ts +73 -20
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +101 -25
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +159 -21
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +127 -192
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +94 -17
- package/src/router/match-middleware/cache-store.ts +53 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +104 -10
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +8 -30
- package/src/router/middleware.ts +58 -13
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +60 -9
- 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 +6 -1
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +198 -20
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +452 -307
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +10 -4
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +60 -8
- package/src/rsc/handler.ts +478 -374
- 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 +16 -2
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +17 -1
- package/src/rsc/server-action.ts +10 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +109 -23
- package/src/server/context.ts +166 -17
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +194 -60
- package/src/ssr/index.tsx +4 -0
- 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 +19 -1
- package/src/types/segments.ts +2 -0
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +48 -13
- 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 -6
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +51 -79
- 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 +53 -31
- 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/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +204 -217
- package/src/vite/router-discovery.ts +732 -94
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +37 -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,
|
|
@@ -135,8 +135,11 @@ export function createVersionPlugin(): Plugin {
|
|
|
135
135
|
let server: any = null;
|
|
136
136
|
const clientModuleSignatures = new Map<string, ClientModuleSignature>();
|
|
137
137
|
|
|
138
|
+
let versionCounter = 0;
|
|
138
139
|
const bumpVersion = (reason: string) => {
|
|
139
|
-
|
|
140
|
+
// Use timestamp + counter to guarantee uniqueness even when multiple
|
|
141
|
+
// bumps happen within the same millisecond (e.g. cascading HMR events).
|
|
142
|
+
currentVersion = Date.now().toString(16) + String(++versionCounter);
|
|
140
143
|
console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
|
|
141
144
|
|
|
142
145
|
const rscEnv = server?.environments?.rsc;
|
|
@@ -211,6 +214,15 @@ export function createVersionPlugin(): Plugin {
|
|
|
211
214
|
|
|
212
215
|
if (!isRscModule) return;
|
|
213
216
|
|
|
217
|
+
// Skip re-bumping when the version virtual module itself is invalidated
|
|
218
|
+
// (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
|
|
219
|
+
if (
|
|
220
|
+
ctx.modules.length === 1 &&
|
|
221
|
+
ctx.modules[0].id === "\0" + VIRTUAL_IDS.version
|
|
222
|
+
) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
214
226
|
if (isCodeModule(ctx.file)) {
|
|
215
227
|
const filePath = normalizeModuleId(ctx.file);
|
|
216
228
|
const previousSignature = clientModuleSignatures.get(filePath);
|