@rangojs/router 0.0.0-experimental.fb4fdc18 → 0.0.0-experimental.fce7fbd1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/bin/rango.js +147 -57
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +914 -485
- package/package.json +55 -11
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +220 -30
- package/skills/caching/SKILL.md +116 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +214 -18
- package/skills/host-router/SKILL.md +45 -20
- package/skills/intercept/SKILL.md +26 -4
- package/skills/layout/SKILL.md +6 -7
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +149 -6
- package/skills/middleware/SKILL.md +13 -9
- package/skills/migrate-nextjs/SKILL.md +1 -1
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +5 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +242 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +58 -9
- package/skills/route/SKILL.md +13 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +53 -41
- package/skills/testing/SKILL.md +599 -0
- package/skills/typesafety/SKILL.md +310 -26
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/event-controller.ts +42 -66
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +6 -6
- package/src/browser/navigation-client.ts +12 -15
- package/src/browser/navigation-store.ts +7 -8
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +9 -19
- package/src/browser/react/NavigationProvider.tsx +29 -40
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-params.ts +3 -4
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +14 -1
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +30 -16
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +2 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-scope.ts +28 -42
- package/src/cache/cf/cf-cache-store.ts +49 -6
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +10 -8
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +26 -13
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +6 -4
- package/src/index.ts +13 -6
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +238 -263
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +37 -14
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/handler-context.ts +4 -42
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +2 -2
- package/src/router/loader-resolution.ts +16 -2
- package/src/router/match-handlers.ts +62 -20
- package/src/router/match-middleware/cache-lookup.ts +44 -91
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +32 -30
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +1 -1
- package/src/router/middleware.ts +46 -78
- package/src/router/prerender-match.ts +1 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +43 -1
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +40 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +19 -6
- package/src/router/segment-resolution/revalidation.ts +19 -6
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/types.ts +8 -0
- package/src/router.ts +37 -21
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +20 -65
- package/src/rsc/helpers.ts +22 -2
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/response-route-handler.ts +32 -52
- package/src/rsc/rsc-rendering.ts +27 -53
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +13 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +2 -2
- package/src/search-params.ts +4 -4
- package/src/segment-system.tsx +121 -65
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +118 -51
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +10 -0
- package/src/static-handler.ts +1 -1
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +193 -0
- package/src/testing/render-route.tsx +536 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +170 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +183 -0
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +56 -11
- package/src/types/index.ts +1 -0
- package/src/types/segments.ts +18 -1
- package/src/urls/include-helper.ts +10 -53
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +11 -3
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +413 -42
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +70 -48
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +19 -25
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +3 -7
- package/src/vite/plugins/client-ref-hashing.ts +12 -1
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
- package/src/vite/plugins/expose-action-id.ts +2 -2
- package/src/vite/plugins/expose-id-utils.ts +12 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-internal-ids.ts +47 -67
- package/src/vite/plugins/performance-tracks.ts +12 -16
- package/src/vite/plugins/use-cache-transform.ts +13 -11
- package/src/vite/plugins/version-injector.ts +2 -12
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +67 -15
- package/src/vite/router-discovery.ts +208 -63
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/shared-utils.ts +107 -26
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
findNestedRouterConflict,
|
|
18
18
|
findRouterFiles,
|
|
19
19
|
} from "../build/generate-route-types.js";
|
|
20
|
+
import { firstCodeMatchIndex } from "../build/route-types/source-scan.js";
|
|
20
21
|
import { createVersionPlugin } from "./plugins/version-plugin.js";
|
|
21
22
|
import { createVirtualStubPlugin } from "./plugins/virtual-stub-plugin.js";
|
|
22
23
|
import {
|
|
@@ -52,6 +53,11 @@ import {
|
|
|
52
53
|
import { postprocessBundle } from "./discovery/bundle-postprocess.js";
|
|
53
54
|
import { createDiscoveryGate } from "./discovery/gate-state.js";
|
|
54
55
|
import { resetStagedBuildAssets } from "./utils/prerender-utils.js";
|
|
56
|
+
import { resolveRscEntryFromConfig } from "./utils/shared-utils.js";
|
|
57
|
+
import {
|
|
58
|
+
pickForwardedRunnerConfig,
|
|
59
|
+
selectForwardableResolvePlugins,
|
|
60
|
+
} from "./utils/forward-user-plugins.js";
|
|
55
61
|
import { createRangoDebugger, timed, timedSync, NS } from "./debug.js";
|
|
56
62
|
|
|
57
63
|
const debugDiscovery = createRangoDebugger(NS.discovery);
|
|
@@ -99,7 +105,7 @@ function ensureCloudflareProtocolLoaderRegistered(): void {
|
|
|
99
105
|
// register() requires Node 18.19+ / 20.6+. Older Node still has the
|
|
100
106
|
// Vite transform as primary defense.
|
|
101
107
|
console.warn(
|
|
102
|
-
`[
|
|
108
|
+
`[rango] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
|
|
103
109
|
);
|
|
104
110
|
}
|
|
105
111
|
}
|
|
@@ -129,14 +135,26 @@ async function createTempRscServer(
|
|
|
129
135
|
// instead of crashing Node's native loader.
|
|
130
136
|
ensureCloudflareProtocolLoaderRegistered();
|
|
131
137
|
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
138
|
+
// Mirror the user's resolution config + plugins so discovery (and the
|
|
139
|
+
// prerender/static rendering that shares this runner) resolves modules the
|
|
140
|
+
// same way the real environment does. Falls back to the legacy alias-only
|
|
141
|
+
// behavior if configResolved hasn't populated the parity slice yet.
|
|
142
|
+
const runnerConfig = state.userRunnerConfig;
|
|
143
|
+
const resolveConfig = runnerConfig?.resolve ?? {
|
|
144
|
+
alias: state.userResolveAlias,
|
|
145
|
+
};
|
|
146
|
+
const oxcConfig = runnerConfig?.oxc ?? {
|
|
147
|
+
jsx: { runtime: "automatic", importSource: "react" },
|
|
148
|
+
};
|
|
132
149
|
return createViteServer({
|
|
133
150
|
root: state.projectRoot,
|
|
134
151
|
configFile: false,
|
|
135
152
|
server: { middlewareMode: true },
|
|
136
153
|
appType: "custom",
|
|
137
154
|
logLevel: "silent",
|
|
138
|
-
resolve:
|
|
139
|
-
|
|
155
|
+
resolve: resolveConfig,
|
|
156
|
+
...(runnerConfig?.define ? { define: runnerConfig.define } : {}),
|
|
157
|
+
oxc: oxcConfig as any,
|
|
140
158
|
...(options.cacheDir && { cacheDir: options.cacheDir }),
|
|
141
159
|
plugins: [
|
|
142
160
|
rsc({
|
|
@@ -155,6 +173,10 @@ async function createTempRscServer(
|
|
|
155
173
|
// runtime. forceBuild produces hashed IDs for production bundle consistency.
|
|
156
174
|
exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
|
|
157
175
|
exposeRouterId(),
|
|
176
|
+
// Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
|
|
177
|
+
// to resolveId/load and placed last so framework resolution runs first;
|
|
178
|
+
// Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
|
|
179
|
+
...state.userResolvePlugins,
|
|
158
180
|
],
|
|
159
181
|
});
|
|
160
182
|
}
|
|
@@ -182,7 +204,7 @@ async function resolveBuildEnv(
|
|
|
182
204
|
if (option === "auto") {
|
|
183
205
|
if (factoryCtx.preset !== "cloudflare") {
|
|
184
206
|
throw new Error(
|
|
185
|
-
'[
|
|
207
|
+
'[rango] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
|
|
186
208
|
"Use a factory function or plain object for other presets.",
|
|
187
209
|
);
|
|
188
210
|
}
|
|
@@ -204,7 +226,7 @@ async function resolveBuildEnv(
|
|
|
204
226
|
};
|
|
205
227
|
} catch (err: any) {
|
|
206
228
|
throw new Error(
|
|
207
|
-
'[
|
|
229
|
+
'[rango] buildEnv: "auto" requires wrangler to be installed.\n' +
|
|
208
230
|
`Install it with: pnpm add -D wrangler\n${err.message}`,
|
|
209
231
|
);
|
|
210
232
|
}
|
|
@@ -256,7 +278,7 @@ async function releaseBuildEnv(s: DiscoveryState): Promise<void> {
|
|
|
256
278
|
try {
|
|
257
279
|
await s.buildEnvDispose();
|
|
258
280
|
} catch (err: any) {
|
|
259
|
-
console.warn(`[
|
|
281
|
+
console.warn(`[rango] buildEnv dispose failed: ${err.message}`);
|
|
260
282
|
}
|
|
261
283
|
s.buildEnvDispose = null;
|
|
262
284
|
}
|
|
@@ -309,23 +331,28 @@ export function createRouterDiscoveryPlugin(
|
|
|
309
331
|
viteMode = config.mode;
|
|
310
332
|
// Capture user's resolve aliases for the temp server
|
|
311
333
|
s.userResolveAlias = config.resolve.alias;
|
|
334
|
+
// Capture the data-only resolution config (resolve.*, define, oxc) and
|
|
335
|
+
// the user's resolution plugins (resolveId/load) so the discovery temp
|
|
336
|
+
// server resolves modules the same way the real environment does.
|
|
337
|
+
// Without this, both flavors of user resolution are absent during
|
|
338
|
+
// discovery/prerender/static rendering even though they apply at request
|
|
339
|
+
// time: third-party resolvers (e.g. vite-tsconfig-paths, forwarded as
|
|
340
|
+
// plugins) and Vite 8's native resolve.tsconfigPaths (forwarded in the
|
|
341
|
+
// data slice). See utils/forward-user-plugins.ts.
|
|
342
|
+
s.userRunnerConfig = pickForwardedRunnerConfig(config);
|
|
343
|
+
s.userResolvePlugins = selectForwardableResolvePlugins(
|
|
344
|
+
config.plugins as any,
|
|
345
|
+
);
|
|
312
346
|
// Node preset: pick up auto-discovered router path from the config() hook.
|
|
313
347
|
// The auto-discover plugin runs in config() using Vite's resolved root,
|
|
314
348
|
// populating the mutable ref before configResolved fires.
|
|
315
349
|
if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
|
|
316
350
|
s.resolvedEntryPath = opts.routerPathRef.path;
|
|
317
351
|
}
|
|
318
|
-
// Cloudflare preset:
|
|
319
|
-
// The @cloudflare/vite-plugin reads wrangler config (toml/json/jsonc)
|
|
320
|
-
// and sets optimizeDeps.entries on the RSC environment.
|
|
352
|
+
// Cloudflare preset: entry comes from the resolved RSC env config.
|
|
321
353
|
if (!s.resolvedEntryPath) {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
if (typeof entries === "string") {
|
|
325
|
-
s.resolvedEntryPath = entries;
|
|
326
|
-
} else if (Array.isArray(entries) && entries.length > 0) {
|
|
327
|
-
s.resolvedEntryPath = entries[0];
|
|
328
|
-
}
|
|
354
|
+
const entry = resolveRscEntryFromConfig(config);
|
|
355
|
+
if (entry) s.resolvedEntryPath = entry;
|
|
329
356
|
}
|
|
330
357
|
// Generate combined named-routes.gen.ts from static source parsing.
|
|
331
358
|
// Runs before the dev server starts so the gen file exists immediately for IDE.
|
|
@@ -512,9 +539,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
512
539
|
"getOrCreateTempServer: FAILED message=%s",
|
|
513
540
|
err.message,
|
|
514
541
|
);
|
|
515
|
-
console.warn(
|
|
516
|
-
`[rsc-router] Failed to create temp runner: ${err.message}`,
|
|
517
|
-
);
|
|
542
|
+
console.warn(`[rango] Failed to create temp runner: ${err.message}`);
|
|
518
543
|
}
|
|
519
544
|
return null;
|
|
520
545
|
}
|
|
@@ -653,7 +678,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
653
678
|
}
|
|
654
679
|
} catch (err: any) {
|
|
655
680
|
console.warn(
|
|
656
|
-
`[
|
|
681
|
+
`[rango] Cloudflare dev discovery failed: ${err.message}\n${err.stack}`,
|
|
657
682
|
);
|
|
658
683
|
}
|
|
659
684
|
|
|
@@ -705,7 +730,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
705
730
|
);
|
|
706
731
|
} catch (err: any) {
|
|
707
732
|
console.warn(
|
|
708
|
-
`[
|
|
733
|
+
`[rango] Router discovery failed: ${err.message}\n${err.stack}`,
|
|
709
734
|
);
|
|
710
735
|
} finally {
|
|
711
736
|
debugDiscovery?.(
|
|
@@ -764,20 +789,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
764
789
|
if (s.mergedRouteTrie && serverMod.setRouteTrie) {
|
|
765
790
|
serverMod.setRouteTrie(s.mergedRouteTrie);
|
|
766
791
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
777
|
-
if (serverMod.setRouterPrecomputedEntries) {
|
|
778
|
-
for (const [routerId, entries] of s.perRouterPrecomputedMap) {
|
|
779
|
-
serverMod.setRouterPrecomputedEntries(routerId, entries);
|
|
780
|
-
}
|
|
792
|
+
const perRouterSetters: Array<[Map<string, any>, string]> = [
|
|
793
|
+
[s.perRouterManifestDataMap, "setRouterManifest"],
|
|
794
|
+
[s.perRouterTrieMap, "setRouterTrie"],
|
|
795
|
+
[s.perRouterPrecomputedMap, "setRouterPrecomputedEntries"],
|
|
796
|
+
];
|
|
797
|
+
for (const [map, fn] of perRouterSetters) {
|
|
798
|
+
const setter = serverMod[fn];
|
|
799
|
+
if (typeof setter !== "function") continue;
|
|
800
|
+
for (const [routerId, value] of map) setter(routerId, value);
|
|
781
801
|
}
|
|
782
802
|
};
|
|
783
803
|
|
|
@@ -820,7 +840,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
820
840
|
registry = serverMod.RouterRegistry ?? null;
|
|
821
841
|
} catch (err: any) {
|
|
822
842
|
console.warn(
|
|
823
|
-
`[
|
|
843
|
+
`[rango] Dev prerender module refresh failed: ${err.message}`,
|
|
824
844
|
);
|
|
825
845
|
res.statusCode = 500;
|
|
826
846
|
res.end(`Prerender handler error: ${err.message}`);
|
|
@@ -886,7 +906,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
886
906
|
return;
|
|
887
907
|
} catch (err: any) {
|
|
888
908
|
console.warn(
|
|
889
|
-
`[
|
|
909
|
+
`[rango] Dev prerender failed for ${pathname}: ${err.message}`,
|
|
890
910
|
);
|
|
891
911
|
}
|
|
892
912
|
}
|
|
@@ -987,9 +1007,34 @@ export function createRouterDiscoveryPlugin(
|
|
|
987
1007
|
writeRouteTypesFiles(s),
|
|
988
1008
|
);
|
|
989
1009
|
}
|
|
1010
|
+
if (s.lastDiscoveryError) {
|
|
1011
|
+
debugDiscovery?.(
|
|
1012
|
+
"hmr: cleared lastDiscoveryError (%s) after successful rediscovery",
|
|
1013
|
+
s.lastDiscoveryError.message,
|
|
1014
|
+
);
|
|
1015
|
+
s.lastDiscoveryError = null;
|
|
1016
|
+
}
|
|
1017
|
+
// Cloudflare dev: on a successful cycle drop the workerd runner's
|
|
1018
|
+
// cached worker-entry chain so the next request re-evaluates
|
|
1019
|
+
// createRouter() with the new routes. Fired here in the work path
|
|
1020
|
+
// (not the caller's .then()) so a queued follow-up cycle that
|
|
1021
|
+
// succeeds after an earlier failed cycle still reloads:
|
|
1022
|
+
// runRefreshCycle recurses queued work without awaiting it, so the
|
|
1023
|
+
// original call already resolved on the failed cycle. A failed
|
|
1024
|
+
// cycle throws above and never reaches here, so a broken edit
|
|
1025
|
+
// never reloads the worker onto bad source.
|
|
1026
|
+
if (rscEnv && !rscEnv.runner) forceCloudflareWorkerReload(rscEnv);
|
|
990
1027
|
} catch (err: any) {
|
|
1028
|
+
s.lastDiscoveryError = {
|
|
1029
|
+
message: err?.message ?? String(err),
|
|
1030
|
+
at: Date.now(),
|
|
1031
|
+
};
|
|
991
1032
|
console.warn(
|
|
992
|
-
`[
|
|
1033
|
+
`[rango] Runtime re-discovery failed: ${err.message}`,
|
|
1034
|
+
);
|
|
1035
|
+
debugDiscovery?.(
|
|
1036
|
+
"hmr: lastDiscoveryError set (%s) — manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
|
|
1037
|
+
err?.message,
|
|
993
1038
|
);
|
|
994
1039
|
} finally {
|
|
995
1040
|
debugDiscovery?.(
|
|
@@ -1000,6 +1045,49 @@ export function createRouterDiscoveryPlugin(
|
|
|
1000
1045
|
});
|
|
1001
1046
|
};
|
|
1002
1047
|
|
|
1048
|
+
// Cloudflare dev only. workerd serves every request through the
|
|
1049
|
+
// runner-worker singleton, which re-resolves the worker entry per
|
|
1050
|
+
// request via runner.import("virtual:cloudflare/worker-entry"). The
|
|
1051
|
+
// route table lives in the user's createRouter() instance, captured
|
|
1052
|
+
// when that entry chain (entry -> router -> urls) was last evaluated
|
|
1053
|
+
// and then cached in the runner's evaluatedModules. The route-file
|
|
1054
|
+
// watcher refreshes discovery + types on the Node side, but the worker
|
|
1055
|
+
// keeps serving the cached (stale) router: route-definition modules
|
|
1056
|
+
// have no import.meta.hot boundary, so Vite never sends the worker an
|
|
1057
|
+
// HMR update for them and the entry chain is never evicted.
|
|
1058
|
+
//
|
|
1059
|
+
// Fix: after discovery completes, (1) invalidate the worker env's
|
|
1060
|
+
// Node-side module graph, then (2) send a full-reload to the worker.
|
|
1061
|
+
// Step (2) alone is insufficient: the full-reload handler clears the
|
|
1062
|
+
// runner's evaluatedModules and re-imports entrypoints, but each
|
|
1063
|
+
// re-import fetches the module back through this Node-side graph, which
|
|
1064
|
+
// still holds the pre-edit transform of urls.tsx — so createRouter()
|
|
1065
|
+
// rebuilds the stale route table and the new route 404s/hits the
|
|
1066
|
+
// catch-all. Invalidating the graph forces a fresh transform on
|
|
1067
|
+
// re-fetch (the same mechanism refreshTempRscEnv uses for discovery),
|
|
1068
|
+
// so the re-import re-runs createRouter() with the new routes. This is
|
|
1069
|
+
// the programmatic equivalent of the dev-server "r + enter" restart,
|
|
1070
|
+
// scoped to the worker environment instead of tearing down the server.
|
|
1071
|
+
const forceCloudflareWorkerReload = (rscEnv: any) => {
|
|
1072
|
+
if (!rscEnv?.hot) return;
|
|
1073
|
+
try {
|
|
1074
|
+
const graph = rscEnv.moduleGraph;
|
|
1075
|
+
if (graph?.invalidateAll) {
|
|
1076
|
+
graph.invalidateAll();
|
|
1077
|
+
debugDiscovery?.("hmr: invalidated workerd rsc module graph");
|
|
1078
|
+
}
|
|
1079
|
+
rscEnv.hot.send({ type: "full-reload" });
|
|
1080
|
+
debugDiscovery?.(
|
|
1081
|
+
"hmr: forced workerd rsc env reload (full-reload)",
|
|
1082
|
+
);
|
|
1083
|
+
} catch (err: any) {
|
|
1084
|
+
debugDiscovery?.(
|
|
1085
|
+
"hmr: workerd reload failed: %s",
|
|
1086
|
+
err?.message ?? err,
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1003
1091
|
const scheduleRouteRegeneration = () => {
|
|
1004
1092
|
clearTimeout(routeChangeTimer);
|
|
1005
1093
|
routeChangeTimer = setTimeout(() => {
|
|
@@ -1028,9 +1116,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1028
1116
|
}
|
|
1029
1117
|
}
|
|
1030
1118
|
} catch (err: any) {
|
|
1031
|
-
console.error(
|
|
1032
|
-
`[rsc-router] Route regeneration error: ${err.message}`,
|
|
1033
|
-
);
|
|
1119
|
+
console.error(`[rango] Route regeneration error: ${err.message}`);
|
|
1034
1120
|
}
|
|
1035
1121
|
debugDiscovery?.(
|
|
1036
1122
|
"watcher: regenerated gen files (%sms)",
|
|
@@ -1040,12 +1126,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
1040
1126
|
// routes that the static parser cannot resolve. Resolves the
|
|
1041
1127
|
// discovery gate when complete.
|
|
1042
1128
|
if (s.perRouterManifests.length > 0) {
|
|
1129
|
+
// The cloudflare workerd reload fires inside refreshRuntimeDiscovery
|
|
1130
|
+
// on the successful cycle (see forceCloudflareWorkerReload call
|
|
1131
|
+
// there) so queued follow-up cycles also trigger it.
|
|
1043
1132
|
refreshRuntimeDiscovery().catch((err: any) => {
|
|
1044
1133
|
console.warn(
|
|
1045
|
-
`[
|
|
1134
|
+
`[rango] Runtime re-discovery error: ${err.message}`,
|
|
1046
1135
|
);
|
|
1047
|
-
// Even on error, unblock the gate so workerd's reload
|
|
1048
|
-
//
|
|
1136
|
+
// Even on error, unblock the gate so workerd's reload doesn't
|
|
1137
|
+
// hang indefinitely against the previous manifest.
|
|
1049
1138
|
resolveDiscoveryGate();
|
|
1050
1139
|
});
|
|
1051
1140
|
}
|
|
@@ -1059,27 +1148,74 @@ export function createRouterDiscoveryPlugin(
|
|
|
1059
1148
|
!filePath.endsWith(".tsx") &&
|
|
1060
1149
|
!filePath.endsWith(".js") &&
|
|
1061
1150
|
!filePath.endsWith(".jsx")
|
|
1062
|
-
)
|
|
1151
|
+
) {
|
|
1152
|
+
if (s.lastDiscoveryError) {
|
|
1153
|
+
debugDiscovery?.(
|
|
1154
|
+
"watcher: skip non-source %s [LASTERR %s]",
|
|
1155
|
+
filePath,
|
|
1156
|
+
s.lastDiscoveryError.message,
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1063
1159
|
return;
|
|
1160
|
+
}
|
|
1064
1161
|
// Apply scan filter as early-exit before reading file
|
|
1065
|
-
if (s.scanFilter && !s.scanFilter(filePath))
|
|
1162
|
+
if (s.scanFilter && !s.scanFilter(filePath)) {
|
|
1163
|
+
if (s.lastDiscoveryError) {
|
|
1164
|
+
debugDiscovery?.(
|
|
1165
|
+
"watcher: skip scan-filter %s [LASTERR %s]",
|
|
1166
|
+
filePath,
|
|
1167
|
+
s.lastDiscoveryError.message,
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
// Recovery mode: when the previous HMR re-discovery failed, the
|
|
1173
|
+
// import graph is incomplete and the manifest is stuck at the
|
|
1174
|
+
// last-good state. The fix may land in a non-route file (e.g. a
|
|
1175
|
+
// helper imported by the router, a missing module being created,
|
|
1176
|
+
// or a "use client" component) that the narrow content sniff
|
|
1177
|
+
// would otherwise filter out. While in recovery, treat any
|
|
1178
|
+
// in-scan source change as a candidate for rediscovery; the
|
|
1179
|
+
// tighter filter resumes once discovery succeeds again.
|
|
1180
|
+
const inRecoveryMode = !!s.lastDiscoveryError;
|
|
1066
1181
|
try {
|
|
1067
1182
|
const source = readFileSync(filePath, "utf-8");
|
|
1068
1183
|
const trimmed = source.trimStart();
|
|
1069
|
-
|
|
1184
|
+
const isUseClient =
|
|
1070
1185
|
trimmed.startsWith('"use client"') ||
|
|
1071
|
-
trimmed.startsWith("'use client'")
|
|
1072
|
-
)
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
)
|
|
1186
|
+
trimmed.startsWith("'use client'");
|
|
1187
|
+
if (!inRecoveryMode && isUseClient) return;
|
|
1188
|
+
// Cheap raw pre-check first; only when a candidate token is present
|
|
1189
|
+
// do we confirm it occurs in real code (not a comment/string) via a
|
|
1190
|
+
// single allocation-free code-region scan. Most saved files contain
|
|
1191
|
+
// neither token and skip the scan entirely. This avoids a comment or
|
|
1192
|
+
// string mention spuriously marking a file relevant and triggering an
|
|
1193
|
+
// unnecessary re-discovery on save.
|
|
1194
|
+
let hasUrls = source.includes("urls(");
|
|
1195
|
+
let hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
|
|
1196
|
+
if (hasUrls) hasUrls = firstCodeMatchIndex(source, /urls\(/g) >= 0;
|
|
1197
|
+
if (hasCreateRouter) {
|
|
1198
|
+
hasCreateRouter =
|
|
1199
|
+
firstCodeMatchIndex(source, /\bcreateRouter\s*[<(]/g) >= 0;
|
|
1200
|
+
}
|
|
1201
|
+
if (!inRecoveryMode && !hasUrls && !hasCreateRouter) return;
|
|
1202
|
+
if (inRecoveryMode) {
|
|
1203
|
+
debugDiscovery?.(
|
|
1204
|
+
"watcher: recovery rediscovery for %s (urls=%s, router=%s, useClient=%s) [LASTERR %s]",
|
|
1205
|
+
filePath,
|
|
1206
|
+
hasUrls,
|
|
1207
|
+
hasCreateRouter,
|
|
1208
|
+
isUseClient,
|
|
1209
|
+
s.lastDiscoveryError!.message,
|
|
1210
|
+
);
|
|
1211
|
+
} else {
|
|
1212
|
+
debugDiscovery?.(
|
|
1213
|
+
"watcher: %s matches (urls=%s, router=%s)",
|
|
1214
|
+
filePath,
|
|
1215
|
+
hasUrls,
|
|
1216
|
+
hasCreateRouter,
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1083
1219
|
// Invalidate cache when a router file changes (new router added/removed)
|
|
1084
1220
|
if (hasCreateRouter) {
|
|
1085
1221
|
const nestedRouterConflict = findNestedRouterConflict([
|
|
@@ -1106,7 +1242,15 @@ export function createRouterDiscoveryPlugin(
|
|
|
1106
1242
|
gate.noteRouteEvent();
|
|
1107
1243
|
}
|
|
1108
1244
|
scheduleRouteRegeneration();
|
|
1109
|
-
} catch {
|
|
1245
|
+
} catch (readErr: any) {
|
|
1246
|
+
if (s.lastDiscoveryError) {
|
|
1247
|
+
debugDiscovery?.(
|
|
1248
|
+
"watcher: read error %s: %s [LASTERR %s]",
|
|
1249
|
+
filePath,
|
|
1250
|
+
readErr?.message,
|
|
1251
|
+
s.lastDiscoveryError.message,
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1110
1254
|
// Ignore read errors for deleted/moved files
|
|
1111
1255
|
}
|
|
1112
1256
|
};
|
|
@@ -1169,7 +1313,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1169
1313
|
const rscEnv = (tempServer.environments as any)?.rsc;
|
|
1170
1314
|
if (!rscEnv?.runner) {
|
|
1171
1315
|
console.warn(
|
|
1172
|
-
"[
|
|
1316
|
+
"[rango] RSC environment runner not available during build, skipping manifest generation",
|
|
1173
1317
|
);
|
|
1174
1318
|
return;
|
|
1175
1319
|
}
|
|
@@ -1212,7 +1356,8 @@ export function createRouterDiscoveryPlugin(
|
|
|
1212
1356
|
.filter(Boolean)
|
|
1213
1357
|
.join("\n");
|
|
1214
1358
|
throw new Error(
|
|
1215
|
-
`[
|
|
1359
|
+
`[rango] Build-time router discovery failed:\n${details}`,
|
|
1360
|
+
{ cause: err },
|
|
1216
1361
|
);
|
|
1217
1362
|
} finally {
|
|
1218
1363
|
delete (globalThis as any).__rscRouterDiscoveryActive;
|
|
@@ -1239,7 +1384,7 @@ export function createRouterDiscoveryPlugin(
|
|
|
1239
1384
|
// `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
|
|
1240
1385
|
// AND vite's own HMR pipeline (which invalidates the gen file's
|
|
1241
1386
|
// importers and triggers a second workerd full reload — visible to the
|
|
1242
|
-
// user as a duplicate "[
|
|
1387
|
+
// user as a duplicate "[Rango] HMR: version changed" on the client).
|
|
1243
1388
|
//
|
|
1244
1389
|
// `peekSelfGenWrite` is the authoritative filter: its map only contains
|
|
1245
1390
|
// paths that `markSelfGenWrite` has registered, so it natively works
|
|
@@ -48,7 +48,7 @@ function findImportInsertionPos(
|
|
|
48
48
|
): number {
|
|
49
49
|
let program: ProgramNode;
|
|
50
50
|
try {
|
|
51
|
-
program = parseAst(code, {
|
|
51
|
+
program = parseAst(code, { lang: "tsx" });
|
|
52
52
|
} catch {
|
|
53
53
|
return 0;
|
|
54
54
|
}
|
|
@@ -127,7 +127,7 @@ export function findHandlerCalls(
|
|
|
127
127
|
): HandlerCallSite[] {
|
|
128
128
|
let program: ProgramNode;
|
|
129
129
|
try {
|
|
130
|
-
program = parseAst(code, {
|
|
130
|
+
program = parseAst(code, { lang: "tsx" });
|
|
131
131
|
} catch {
|
|
132
132
|
return [];
|
|
133
133
|
}
|
|
@@ -239,7 +239,7 @@ export function getImportedLocalNames(
|
|
|
239
239
|
parseAst: (code: string, options?: any) => ProgramNode,
|
|
240
240
|
): Set<string> {
|
|
241
241
|
try {
|
|
242
|
-
const program = parseAst(code, {
|
|
242
|
+
const program = parseAst(code, { lang: "tsx" });
|
|
243
243
|
return getImportedLocalNamesFromProgram(program, importedName);
|
|
244
244
|
} catch {
|
|
245
245
|
return new Set<string>();
|
|
@@ -256,7 +256,7 @@ export function extractImportDeclarations(
|
|
|
256
256
|
): string[] {
|
|
257
257
|
let program: ProgramNode;
|
|
258
258
|
try {
|
|
259
|
-
program = parseAst(code, {
|
|
259
|
+
program = parseAst(code, { lang: "tsx" });
|
|
260
260
|
} catch {
|
|
261
261
|
return [];
|
|
262
262
|
}
|
|
@@ -380,7 +380,7 @@ export function extractModuleLevelDeclarations(
|
|
|
380
380
|
): string[] {
|
|
381
381
|
let program: ProgramNode;
|
|
382
382
|
try {
|
|
383
|
-
program = parseAst(code, {
|
|
383
|
+
program = parseAst(code, { lang: "tsx" });
|
|
384
384
|
} catch {
|
|
385
385
|
return [];
|
|
386
386
|
}
|
|
@@ -468,19 +468,19 @@ export function transformInlineHandlers(
|
|
|
468
468
|
handlerNames,
|
|
469
469
|
);
|
|
470
470
|
|
|
471
|
-
// Track line occurrences for same-line collision handling
|
|
472
|
-
const lineCounts = new Map<number, number>();
|
|
473
|
-
|
|
474
471
|
// Collect all import statements to prepend
|
|
475
472
|
const importStatements: string[] = [];
|
|
476
473
|
|
|
477
|
-
for (const site of inlineSites) {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
474
|
+
for (const [siteIndex, site] of inlineSites.entries()) {
|
|
475
|
+
// Key the extracted handler on its source-order index (per fnName), NOT its
|
|
476
|
+
// line number. The id flows into BOTH the export name and the virtual module
|
|
477
|
+
// path (which hashId hashes for the runtime $$id), and line numbers shift
|
|
478
|
+
// between the prerender and production build contexts. The index is invariant
|
|
479
|
+
// to those shifts, keeping the prerender manifest key == the runtime id.
|
|
480
|
+
const hash = hashInlineId(filePath, fnName, siteIndex);
|
|
482
481
|
const exportName = `__sh_${hash}`;
|
|
483
|
-
const
|
|
482
|
+
const idSuffix = `${filePath}:${fnName}:${siteIndex}`;
|
|
483
|
+
const virtualId = `\0${virtualPrefix}${idSuffix}`;
|
|
484
484
|
|
|
485
485
|
// Extract the full handler call expression text
|
|
486
486
|
const handlerCode = code.slice(site.callStart, site.callEnd);
|
|
@@ -498,7 +498,7 @@ export function transformInlineHandlers(
|
|
|
498
498
|
s.overwrite(site.callStart, site.callEnd, exportName);
|
|
499
499
|
|
|
500
500
|
// Build the import specifier for this virtual module
|
|
501
|
-
const importId = `${virtualPrefix}${
|
|
501
|
+
const importId = `${virtualPrefix}${idSuffix}`;
|
|
502
502
|
importStatements.push(`import { ${exportName} } from "${importId}";`);
|
|
503
503
|
}
|
|
504
504
|
|
|
@@ -59,7 +59,7 @@ export function extractHandlerExportsFromChunk(
|
|
|
59
59
|
if (detectPassthrough) {
|
|
60
60
|
const eFnName = escapeRegExp(fnName);
|
|
61
61
|
const callStartRe = new RegExp(
|
|
62
|
-
`const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
|
|
62
|
+
`(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
|
|
63
63
|
);
|
|
64
64
|
const callStart = callStartRe.exec(chunkCode);
|
|
65
65
|
if (callStart) {
|
|
@@ -98,8 +98,10 @@ export function evictHandlerCode(
|
|
|
98
98
|
if (passthrough) continue;
|
|
99
99
|
|
|
100
100
|
const eName = escapeRegExp(name);
|
|
101
|
+
// Match const/let/var: Rolldown (Vite 8) emits top-level bindings in the
|
|
102
|
+
// non-minified RSC bundle as `var`, whereas Rollup used `const`.
|
|
101
103
|
const callStartRe = new RegExp(
|
|
102
|
-
`const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
|
|
104
|
+
`(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
|
|
103
105
|
);
|
|
104
106
|
const startMatch = callStartRe.exec(modified);
|
|
105
107
|
if (!startMatch) continue;
|