@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80
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/AGENTS.md +9 -0
- package/README.md +942 -4
- package/dist/bin/rango.js +1689 -0
- package/dist/vite/index.js +4960 -935
- package/package.json +70 -60
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +294 -0
- package/skills/caching/SKILL.md +93 -23
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +12 -8
- package/skills/document-cache/SKILL.md +18 -16
- package/skills/fonts/SKILL.md +167 -0
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +334 -72
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +151 -8
- package/skills/layout/SKILL.md +122 -3
- package/skills/links/SKILL.md +92 -31
- package/skills/loader/SKILL.md +404 -44
- package/skills/middleware/SKILL.md +205 -37
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +764 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +263 -1
- package/skills/prerender/SKILL.md +685 -0
- package/skills/rango/SKILL.md +87 -16
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +281 -14
- package/skills/router-setup/SKILL.md +210 -32
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +328 -89
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +102 -4
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +92 -64
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +317 -560
- package/src/browser/navigation-client.ts +206 -68
- package/src/browser/navigation-store.ts +73 -55
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +343 -316
- package/src/browser/prefetch/cache.ts +216 -0
- package/src/browser/prefetch/fetch.ts +206 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +160 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +253 -74
- package/src/browser/react/NavigationProvider.tsx +87 -11
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +12 -12
- package/src/browser/react/location-state-shared.ts +95 -53
- package/src/browser/react/location-state.ts +60 -15
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +29 -51
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +30 -126
- package/src/browser/react/use-href.tsx +2 -2
- package/src/browser/react/use-link-status.ts +6 -5
- package/src/browser/react/use-navigation.ts +44 -65
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +76 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +80 -97
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +214 -58
- package/src/browser/scroll-restoration.ts +127 -52
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +16 -0
- package/src/browser/server-action-bridge.ts +510 -603
- package/src/browser/shallow.ts +6 -1
- package/src/browser/types.ts +141 -48
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +235 -24
- package/src/build/generate-route-types.ts +39 -0
- package/src/build/index.ts +13 -0
- package/src/build/route-trie.ts +291 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +418 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +618 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +342 -0
- package/src/cache/cache-scope.ts +167 -309
- package/src/cache/cf/cf-cache-store.ts +571 -17
- package/src/cache/cf/index.ts +13 -3
- package/src/cache/document-cache.ts +116 -77
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +1 -15
- package/src/cache/memory-segment-store.ts +191 -13
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +72 -122
- package/src/client.rsc.tsx +3 -1
- package/src/client.tsx +135 -301
- package/src/component-utils.ts +4 -4
- package/src/components/DefaultDocument.tsx +5 -1
- package/src/context-var.ts +156 -0
- package/src/debug.ts +19 -9
- package/src/errors.ts +108 -2
- package/src/handle.ts +55 -29
- package/src/handles/MetaTags.tsx +73 -20
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/handles/meta.ts +30 -13
- package/src/host/cookie-handler.ts +21 -15
- package/src/host/errors.ts +8 -8
- package/src/host/index.ts +4 -7
- package/src/host/pattern-matcher.ts +27 -27
- package/src/host/router.ts +61 -39
- package/src/host/testing.ts +8 -8
- package/src/host/types.ts +15 -7
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +119 -29
- package/src/index.rsc.ts +155 -19
- package/src/index.ts +251 -30
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +26 -157
- package/src/loader.ts +27 -10
- package/src/network-error-thrower.tsx +3 -1
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +186 -0
- package/src/prerender.ts +524 -0
- package/src/reverse.ts +354 -0
- package/src/root-error-boundary.tsx +41 -29
- package/src/route-content-wrapper.tsx +7 -4
- package/src/route-definition/dsl-helpers.ts +1121 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +478 -0
- package/src/route-definition/index.ts +55 -0
- package/src/route-definition/redirect.ts +101 -0
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-definition.ts +1 -1428
- package/src/route-map-builder.ts +217 -123
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +77 -8
- package/src/router/content-negotiation.ts +215 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +9 -9
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +438 -86
- package/src/router/intercept-resolution.ts +402 -0
- package/src/router/lazy-includes.ts +237 -0
- package/src/router/loader-resolution.ts +356 -128
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +163 -35
- package/src/router/match-api.ts +555 -0
- package/src/router/match-context.ts +5 -3
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +108 -93
- package/src/router/match-middleware/cache-lookup.ts +460 -10
- package/src/router/match-middleware/cache-store.ts +98 -26
- package/src/router/match-middleware/intercept-resolution.ts +57 -17
- package/src/router/match-middleware/segment-resolution.ts +80 -6
- package/src/router/match-pipelines.ts +10 -45
- package/src/router/match-result.ts +135 -35
- package/src/router/metrics.ts +240 -15
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +220 -0
- package/src/router/middleware.ts +324 -369
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +211 -43
- package/src/router/prerender-match.ts +502 -0
- package/src/router/preview-match.ts +98 -0
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +137 -38
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +41 -21
- package/src/router/router-interfaces.ts +484 -0
- package/src/router/router-options.ts +618 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +748 -0
- package/src/router/segment-resolution/helpers.ts +268 -0
- package/src/router/segment-resolution/loader-cache.ts +199 -0
- package/src/router/segment-resolution/revalidation.ts +1379 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +78 -3
- package/src/router.ts +740 -4252
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +907 -797
- package/src/rsc/helpers.ts +140 -6
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +229 -0
- package/src/rsc/manifest-init.ts +90 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +391 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +246 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +356 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +46 -11
- package/src/search-params.ts +230 -0
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +134 -36
- package/src/server/context.ts +341 -61
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +113 -15
- package/src/server/loader-registry.ts +24 -64
- package/src/server/request-context.ts +607 -81
- package/src/server.ts +35 -130
- package/src/ssr/index.tsx +103 -30
- package/src/static-handler.ts +126 -0
- package/src/theme/ThemeProvider.tsx +21 -15
- package/src/theme/ThemeScript.tsx +5 -5
- package/src/theme/constants.ts +5 -2
- package/src/theme/index.ts +4 -14
- package/src/theme/theme-context.ts +4 -30
- package/src/theme/theme-script.ts +21 -18
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +791 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +210 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +120 -0
- package/src/types/segments.ts +150 -0
- package/src/types.ts +1 -1623
- package/src/urls/include-helper.ts +207 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +372 -0
- package/src/urls/path-helper.ts +364 -0
- package/src/urls/pattern-types.ts +107 -0
- package/src/urls/response-types.ts +116 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -802
- package/src/use-loader.tsx +161 -81
- package/src/vite/discovery/bundle-postprocess.ts +181 -0
- package/src/vite/discovery/discover-routers.ts +348 -0
- package/src/vite/discovery/prerender-collection.ts +439 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +117 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +15 -1133
- package/src/vite/plugin-types.ts +103 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
- package/src/vite/plugins/expose-id-utils.ts +299 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +786 -0
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +462 -0
- package/src/vite/router-discovery.ts +918 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
- package/src/vite/utils/prerender-utils.ts +221 -0
- package/src/vite/utils/shared-utils.ts +170 -0
- package/CLAUDE.md +0 -43
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/href.ts +0 -255
- package/src/server/route-manifest-cache.ts +0 -173
- package/src/vite/expose-handle-id.ts +0 -209
- package/src/vite/expose-loader-id.ts +0 -426
- package/src/vite/expose-location-state-id.ts +0 -177
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
/**
|
|
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
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { poke } from "@rangojs/router/vite";
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* plugins: [rango(), poke()],
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function poke(): Plugin {
|
|
22
|
+
return {
|
|
23
|
+
name: "vite-plugin-poke",
|
|
24
|
+
apply: "serve",
|
|
25
|
+
|
|
26
|
+
configureServer(server) {
|
|
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
|
+
}
|
|
83
|
+
|
|
84
|
+
if (stdin.isTTY) {
|
|
85
|
+
server.config.logger.info(
|
|
86
|
+
" poke ready: press e + enter to reload browser (ctrl+r also works when available)",
|
|
87
|
+
{ timestamp: true },
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const onData = (data: string | Buffer) => {
|
|
92
|
+
if (debug) {
|
|
93
|
+
server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
|
|
94
|
+
timestamp: true,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
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");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
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
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
stdin.on("data", onData);
|
|
121
|
+
|
|
122
|
+
server.httpServer?.on("close", () => {
|
|
123
|
+
stdin.off("data", onData);
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "use cache" Vite Transform Plugin
|
|
3
|
+
*
|
|
4
|
+
* Detects "use cache" directives at file-level and function-level,
|
|
5
|
+
* then wraps exports with registerCachedFunction() from the cache runtime.
|
|
6
|
+
*
|
|
7
|
+
* File-level: "use cache" at top of file wraps all exports (except
|
|
8
|
+
* layout/template default exports which receive children).
|
|
9
|
+
*
|
|
10
|
+
* Function-level: "use cache: profileName" inside a function body
|
|
11
|
+
* hoists the function and wraps it.
|
|
12
|
+
*
|
|
13
|
+
* Uses transform helpers from @vitejs/plugin-rsc/transforms:
|
|
14
|
+
* - hasDirective() for file-level detection
|
|
15
|
+
* - transformWrapExport() for file-level wrapping
|
|
16
|
+
* - transformHoistInlineDirective() for function-level hoisting
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { Plugin } from "vite";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import MagicString from "magic-string";
|
|
22
|
+
import { normalizePath, hashId } from "./expose-id-utils.js";
|
|
23
|
+
|
|
24
|
+
const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
|
|
25
|
+
|
|
26
|
+
// Files whose default export receives {children} from the framework
|
|
27
|
+
// and should not be wrapped (children can't be cache-keyed).
|
|
28
|
+
const LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
|
|
29
|
+
|
|
30
|
+
export function useCacheTransform(): Plugin {
|
|
31
|
+
let projectRoot = "";
|
|
32
|
+
let isBuild = false;
|
|
33
|
+
let rscTransforms: typeof import("@vitejs/plugin-rsc/transforms") | null =
|
|
34
|
+
null;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
name: "@rangojs/router:use-cache",
|
|
38
|
+
enforce: "post",
|
|
39
|
+
|
|
40
|
+
configResolved(config) {
|
|
41
|
+
projectRoot = config.root;
|
|
42
|
+
isBuild = config.command === "build";
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async transform(code, id) {
|
|
46
|
+
// Only process in RSC environment
|
|
47
|
+
if (this.environment?.name !== "rsc") return;
|
|
48
|
+
|
|
49
|
+
// Quick bail: no "use cache" in source
|
|
50
|
+
if (!code.includes("use cache")) return;
|
|
51
|
+
|
|
52
|
+
// Skip node_modules and virtual modules
|
|
53
|
+
if (id.includes("/node_modules/") || id.startsWith("\0")) return;
|
|
54
|
+
|
|
55
|
+
// Only JS/TS files
|
|
56
|
+
if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
|
|
57
|
+
|
|
58
|
+
// Lazy-load transform helpers
|
|
59
|
+
if (!rscTransforms) {
|
|
60
|
+
try {
|
|
61
|
+
rscTransforms = await import("@vitejs/plugin-rsc/transforms");
|
|
62
|
+
} catch {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const {
|
|
68
|
+
hasDirective,
|
|
69
|
+
transformWrapExport,
|
|
70
|
+
transformHoistInlineDirective,
|
|
71
|
+
} = rscTransforms;
|
|
72
|
+
|
|
73
|
+
// Parse AST
|
|
74
|
+
let ast: any;
|
|
75
|
+
try {
|
|
76
|
+
const { parseAst } = await import("vite");
|
|
77
|
+
ast = parseAst(code);
|
|
78
|
+
} catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const filePath = normalizePath(path.relative(projectRoot, id));
|
|
83
|
+
const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
|
|
84
|
+
|
|
85
|
+
// Check for file-level "use cache"
|
|
86
|
+
if (hasDirective(ast.body, "use cache")) {
|
|
87
|
+
return transformFileLevelUseCache(
|
|
88
|
+
code,
|
|
89
|
+
ast,
|
|
90
|
+
filePath,
|
|
91
|
+
id,
|
|
92
|
+
isBuild,
|
|
93
|
+
isLayoutOrTemplate,
|
|
94
|
+
transformWrapExport,
|
|
95
|
+
);
|
|
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
|
+
|
|
109
|
+
// Always check for near-miss directives, even when valid directives
|
|
110
|
+
// exist. A file may contain both valid and invalid "use cache" directives
|
|
111
|
+
// in different functions — the invalid ones should still warn.
|
|
112
|
+
warnOnNearMissDirectives(ast, id, this.warn.bind(this));
|
|
113
|
+
|
|
114
|
+
if (functionResult) return functionResult;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function transformFileLevelUseCache(
|
|
120
|
+
code: string,
|
|
121
|
+
ast: any,
|
|
122
|
+
filePath: string,
|
|
123
|
+
sourceId: string,
|
|
124
|
+
isBuild: boolean,
|
|
125
|
+
isLayoutOrTemplate: boolean,
|
|
126
|
+
transformWrapExport: (typeof import("@vitejs/plugin-rsc/transforms"))["transformWrapExport"],
|
|
127
|
+
) {
|
|
128
|
+
// Collect non-function exports to report after wrapping
|
|
129
|
+
const nonFunctionExports: string[] = [];
|
|
130
|
+
|
|
131
|
+
const { exportNames, output } = transformWrapExport(code, ast, {
|
|
132
|
+
runtime: (value: string, name: string) => {
|
|
133
|
+
const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
|
|
134
|
+
return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, "default")`;
|
|
135
|
+
},
|
|
136
|
+
rejectNonAsyncFunction: false,
|
|
137
|
+
filter: (name: string, meta: { isFunction?: boolean }) => {
|
|
138
|
+
// Skip default export of layout/template files (they receive children)
|
|
139
|
+
if (name === "default" && isLayoutOrTemplate) return false;
|
|
140
|
+
// Non-function exports cannot be wrapped with registerCachedFunction
|
|
141
|
+
if (meta.isFunction === false) {
|
|
142
|
+
nonFunctionExports.push(name);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (nonFunctionExports.length > 0) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`[rango:use-cache] File-level "use cache" in ${sourceId} cannot wrap ` +
|
|
152
|
+
`non-function export${nonFunctionExports.length > 1 ? "s" : ""}: ` +
|
|
153
|
+
`${nonFunctionExports.map((n) => `"${n}"`).join(", ")}. ` +
|
|
154
|
+
`Only function exports can be cached. Either remove "use cache" from ` +
|
|
155
|
+
`the file level and add it inside individual functions, or move the ` +
|
|
156
|
+
`non-function exports to a separate module.`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (exportNames.length === 0) {
|
|
161
|
+
// Even if no exports were wrapped, strip the directive
|
|
162
|
+
const s = new MagicString(code);
|
|
163
|
+
const directive = findFileLevelDirective(ast);
|
|
164
|
+
if (directive) {
|
|
165
|
+
s.overwrite(
|
|
166
|
+
directive.start,
|
|
167
|
+
directive.end,
|
|
168
|
+
`/* "use cache" -- wrapped by rango */`,
|
|
169
|
+
);
|
|
170
|
+
return {
|
|
171
|
+
code: s.toString(),
|
|
172
|
+
map: s.generateMap({ source: sourceId, hires: "boundary" }),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Prepend the import
|
|
179
|
+
output.prepend(
|
|
180
|
+
`import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};\n`,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Replace the directive with a comment
|
|
184
|
+
const directive = findFileLevelDirective(ast);
|
|
185
|
+
if (directive) {
|
|
186
|
+
output.overwrite(
|
|
187
|
+
directive.start,
|
|
188
|
+
directive.end,
|
|
189
|
+
`/* "use cache" -- wrapped by rango */`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
code: output.toString(),
|
|
195
|
+
map: output.generateMap({ source: sourceId, hires: "boundary" }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function transformFunctionLevelUseCache(
|
|
200
|
+
code: string,
|
|
201
|
+
ast: any,
|
|
202
|
+
filePath: string,
|
|
203
|
+
sourceId: string,
|
|
204
|
+
isBuild: boolean,
|
|
205
|
+
transformHoistInlineDirective: (typeof import("@vitejs/plugin-rsc/transforms"))["transformHoistInlineDirective"],
|
|
206
|
+
) {
|
|
207
|
+
try {
|
|
208
|
+
const { output, names } = transformHoistInlineDirective(code, ast, {
|
|
209
|
+
directive: /^use cache(:\s*[\w-]+)?$/,
|
|
210
|
+
runtime: (
|
|
211
|
+
value: string,
|
|
212
|
+
name: string,
|
|
213
|
+
meta: { directiveMatch: RegExpMatchArray },
|
|
214
|
+
) => {
|
|
215
|
+
const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
|
|
216
|
+
const profileMatch = meta.directiveMatch[1];
|
|
217
|
+
const profileName = profileMatch
|
|
218
|
+
? profileMatch.replace(/^:\s*/, "").trim()
|
|
219
|
+
: "default";
|
|
220
|
+
return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, ${JSON.stringify(profileName)})`;
|
|
221
|
+
},
|
|
222
|
+
rejectNonAsyncFunction: false,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (names.length === 0) return;
|
|
226
|
+
|
|
227
|
+
// Use a top-level import instead of await import() — the hoisted wrapper
|
|
228
|
+
// may be placed in a non-async context (e.g., inside a synchronous
|
|
229
|
+
// urls() callback) where await is not allowed.
|
|
230
|
+
output.prepend(
|
|
231
|
+
`import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};\n`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
code: output.toString(),
|
|
236
|
+
map: output.generateMap({ source: sourceId, hires: "boundary" }),
|
|
237
|
+
};
|
|
238
|
+
} catch {
|
|
239
|
+
// Transform failed (e.g., syntax not supported), skip
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Find the file-level "use cache" directive AST node for removal.
|
|
246
|
+
*/
|
|
247
|
+
function findFileLevelDirective(
|
|
248
|
+
ast: any,
|
|
249
|
+
): { start: number; end: number } | null {
|
|
250
|
+
for (const node of ast.body ?? []) {
|
|
251
|
+
if (
|
|
252
|
+
node.type === "ExpressionStatement" &&
|
|
253
|
+
node.expression?.type === "Literal" &&
|
|
254
|
+
typeof node.expression.value === "string" &&
|
|
255
|
+
node.expression.value.startsWith("use cache")
|
|
256
|
+
) {
|
|
257
|
+
return { start: node.start, end: node.end };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The valid directive regex (must stay in sync with transformFunctionLevelUseCache).
|
|
265
|
+
*/
|
|
266
|
+
const VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Regex for near-miss: starts with "use cache:" but has invalid tokens.
|
|
270
|
+
*/
|
|
271
|
+
const NEAR_MISS_RE = /^use cache:\s*.+$/;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Walk the AST looking for string literals that look like malformed
|
|
275
|
+
* "use cache" directives and emit a Vite warning for each.
|
|
276
|
+
*
|
|
277
|
+
* This catches cases like `"use cache: bad.name"` or `"use cache: "`
|
|
278
|
+
* that the transform regex silently ignores.
|
|
279
|
+
*/
|
|
280
|
+
function warnOnNearMissDirectives(
|
|
281
|
+
ast: any,
|
|
282
|
+
fileId: string,
|
|
283
|
+
warn: (message: string) => void,
|
|
284
|
+
): void {
|
|
285
|
+
const visit = (node: any) => {
|
|
286
|
+
if (!node || typeof node !== "object") return;
|
|
287
|
+
|
|
288
|
+
if (
|
|
289
|
+
node.type === "ExpressionStatement" &&
|
|
290
|
+
node.expression?.type === "Literal" &&
|
|
291
|
+
typeof node.expression.value === "string"
|
|
292
|
+
) {
|
|
293
|
+
const value = node.expression.value;
|
|
294
|
+
if (
|
|
295
|
+
value.startsWith("use cache") &&
|
|
296
|
+
NEAR_MISS_RE.test(value) &&
|
|
297
|
+
!VALID_DIRECTIVE_RE.test(value)
|
|
298
|
+
) {
|
|
299
|
+
const profilePart = value.slice("use cache:".length).trim();
|
|
300
|
+
warn(
|
|
301
|
+
`[rango:use-cache] "${value}" in ${fileId} has an invalid profile name "${profilePart}". ` +
|
|
302
|
+
`Profile names must match [a-zA-Z0-9_-]+. This directive will be ignored.`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Walk into function bodies where directives appear
|
|
308
|
+
for (const key of Object.keys(node)) {
|
|
309
|
+
const child = node[key];
|
|
310
|
+
if (Array.isArray(child)) {
|
|
311
|
+
for (const item of child) {
|
|
312
|
+
visit(item);
|
|
313
|
+
}
|
|
314
|
+
} else if (child && typeof child === "object" && child.type) {
|
|
315
|
+
visit(child);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
for (const node of ast.body ?? []) {
|
|
321
|
+
visit(node);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import * as Vite from "vite";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin that auto-injects VERSION and routes-manifest into custom entry.rsc files.
|
|
7
|
+
* If a custom entry.rsc file uses createRSCHandler but doesn't pass version,
|
|
8
|
+
* this transform adds the import and property automatically.
|
|
9
|
+
* Also ensures the routes-manifest virtual module is always imported.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export function createVersionInjectorPlugin(
|
|
13
|
+
rscEntryPath: string | undefined,
|
|
14
|
+
): Plugin {
|
|
15
|
+
let resolvedEntryPath = "";
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
name: "@rangojs/router:version-injector",
|
|
19
|
+
enforce: "pre",
|
|
20
|
+
|
|
21
|
+
configResolved(config) {
|
|
22
|
+
let entryPath = rscEntryPath;
|
|
23
|
+
// Cloudflare preset: read entry from resolved environment config.
|
|
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
|
+
}
|
|
35
|
+
if (entryPath) {
|
|
36
|
+
resolvedEntryPath = resolve(config.root, entryPath);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
transform(code, id) {
|
|
41
|
+
if (!resolvedEntryPath) return null;
|
|
42
|
+
// Only transform the RSC entry file
|
|
43
|
+
const normalizedId = Vite.normalizePath(id);
|
|
44
|
+
const normalizedEntry = Vite.normalizePath(resolvedEntryPath);
|
|
45
|
+
|
|
46
|
+
if (normalizedId !== normalizedEntry) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Prepend imports at the top of the file. ES imports are hoisted
|
|
51
|
+
// by the module system, so source position is irrelevant.
|
|
52
|
+
const prepend: string[] = [];
|
|
53
|
+
let newCode = code;
|
|
54
|
+
|
|
55
|
+
if (!code.includes("virtual:rsc-router/routes-manifest")) {
|
|
56
|
+
prepend.push(`import "virtual:rsc-router/routes-manifest";`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Auto-inject VERSION if file uses createRSCHandler without version
|
|
60
|
+
const needsVersion =
|
|
61
|
+
code.includes("createRSCHandler") &&
|
|
62
|
+
!code.includes("@rangojs/router:version") &&
|
|
63
|
+
/createRSCHandler\s*\(\s*\{/.test(code);
|
|
64
|
+
|
|
65
|
+
if (needsVersion) {
|
|
66
|
+
prepend.push(`import { VERSION } from "@rangojs/router:version";`);
|
|
67
|
+
newCode = newCode.replace(
|
|
68
|
+
/createRSCHandler\s*\(\s*\{/,
|
|
69
|
+
"createRSCHandler({\n version: VERSION,",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (prepend.length === 0 && newCode === code) return null;
|
|
74
|
+
|
|
75
|
+
newCode = prepend.join("\n") + (prepend.length > 0 ? "\n" : "") + newCode;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
code: newCode,
|
|
79
|
+
map: null,
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|