@rangojs/router 0.0.0-experimental.145 → 0.0.0-experimental.147

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.
Files changed (46) hide show
  1. package/dist/bin/rango.js +8 -40
  2. package/dist/vite/index.js +840 -243
  3. package/package.json +6 -1
  4. package/src/browser/event-controller.ts +16 -2
  5. package/src/browser/rsc-router.tsx +11 -0
  6. package/src/cache/cache-scope.ts +41 -3
  7. package/src/cache/cf/cf-cache-store.ts +23 -0
  8. package/src/cache/handle-snapshot.ts +22 -1
  9. package/src/cache/memory-segment-store.ts +32 -0
  10. package/src/cache/segment-codec.ts +47 -0
  11. package/src/cache/shell-snapshot.ts +47 -0
  12. package/src/cache/types.ts +27 -0
  13. package/src/cache/vercel/vercel-cache-store.ts +71 -2
  14. package/src/deps/ssr.ts +4 -1
  15. package/src/prerender/build-shell-capture.ts +237 -0
  16. package/src/prerender/shell-manifest-key.ts +20 -0
  17. package/src/prerender/store.ts +10 -1
  18. package/src/router/loader-resolution.ts +16 -0
  19. package/src/router/match-api.ts +9 -2
  20. package/src/router/match-handlers.ts +13 -0
  21. package/src/router/match-middleware/cache-lookup.ts +12 -1
  22. package/src/router/prerender-match.ts +21 -0
  23. package/src/router/segment-resolution/mask-nested.ts +19 -3
  24. package/src/rsc/capture-queue.ts +67 -0
  25. package/src/rsc/rsc-rendering.ts +136 -25
  26. package/src/rsc/shell-build-manifest.ts +244 -0
  27. package/src/rsc/shell-capture.ts +194 -43
  28. package/src/segment-fragments.ts +124 -0
  29. package/src/segment-system.tsx +49 -19
  30. package/src/server/request-context.ts +112 -11
  31. package/src/ssr/index.tsx +151 -22
  32. package/src/ssr/inject-rsc-eager.ts +2 -2
  33. package/src/ssr/preinit-client-references.ts +106 -0
  34. package/src/ssr/ssr-root.tsx +35 -2
  35. package/src/vite/discovery/discover-routers.ts +27 -0
  36. package/src/vite/discovery/prerender-collection.ts +16 -0
  37. package/src/vite/discovery/shell-prerender-phase.ts +395 -0
  38. package/src/vite/discovery/state.ts +42 -0
  39. package/src/vite/index.ts +1 -0
  40. package/src/vite/plugin-types.ts +33 -0
  41. package/src/vite/plugins/version-plugin.ts +8 -0
  42. package/src/vite/plugins/virtual-entries.ts +37 -4
  43. package/src/vite/rango.ts +11 -2
  44. package/src/vite/router-discovery.ts +292 -8
  45. package/src/vite/utils/prerender-utils.ts +25 -6
  46. package/src/vite/utils/shared-utils.ts +4 -2
@@ -1,6 +1,201 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res, err) => function __init() {
4
+ if (err) throw err[0];
5
+ try {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ } catch (e) {
8
+ throw err = [e], e;
9
+ }
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+
16
+ // src/vite/debug.ts
17
+ import debugFactory from "debug";
18
+ function createRangoDebugger(namespace) {
19
+ const primary = debugFactory(namespace);
20
+ const shadow = debugFactory(`vite:${namespace}`);
21
+ if (primary.enabled) return primary;
22
+ if (shadow.enabled) return shadow;
23
+ return void 0;
24
+ }
25
+ async function timed(debug12, label, fn) {
26
+ if (!debug12) return await fn();
27
+ const start = performance.now();
28
+ try {
29
+ return await fn();
30
+ } finally {
31
+ debug12("%s (%sms)", label, (performance.now() - start).toFixed(1));
32
+ }
33
+ }
34
+ function timedSync(debug12, label, fn) {
35
+ if (!debug12) return fn();
36
+ const start = performance.now();
37
+ try {
38
+ return fn();
39
+ } finally {
40
+ debug12("%s (%sms)", label, (performance.now() - start).toFixed(1));
41
+ }
42
+ }
43
+ function createCounter(debug12, label) {
44
+ if (!debug12) return void 0;
45
+ let n = 0;
46
+ let totalMs = 0;
47
+ let slowestMs = 0;
48
+ let slowestFile = "";
49
+ const record = (file, ms) => {
50
+ n++;
51
+ totalMs += ms;
52
+ if (ms > slowestMs) {
53
+ slowestMs = ms;
54
+ slowestFile = file;
55
+ }
56
+ };
57
+ return {
58
+ record,
59
+ time(file, fn) {
60
+ const start = performance.now();
61
+ let out;
62
+ try {
63
+ out = fn();
64
+ } catch (err) {
65
+ record(file, performance.now() - start);
66
+ throw err;
67
+ }
68
+ if (out && typeof out.then === "function") {
69
+ return out.finally(
70
+ () => record(file, performance.now() - start)
71
+ );
72
+ }
73
+ record(file, performance.now() - start);
74
+ return out;
75
+ },
76
+ flush() {
77
+ if (n === 0) return;
78
+ debug12(
79
+ "%s: %d files, %sms total, slowest %sms %s",
80
+ label,
81
+ n,
82
+ totalMs.toFixed(1),
83
+ slowestMs.toFixed(1),
84
+ slowestFile
85
+ );
86
+ n = 0;
87
+ totalMs = 0;
88
+ slowestMs = 0;
89
+ slowestFile = "";
90
+ }
91
+ };
92
+ }
93
+ var NS;
94
+ var init_debug = __esm({
95
+ "src/vite/debug.ts"() {
96
+ "use strict";
97
+ NS = {
98
+ config: "rango:config",
99
+ discovery: "rango:discovery",
100
+ routes: "rango:routes",
101
+ prerender: "rango:prerender",
102
+ build: "rango:build",
103
+ dev: "rango:dev",
104
+ transform: "rango:transform",
105
+ chunks: "rango:chunks"
106
+ };
107
+ if (process.env.INTERNAL_RANGO_DEBUG) {
108
+ const existing = debugFactory.disable();
109
+ debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
110
+ }
111
+ }
112
+ });
113
+
114
+ // src/vite/plugins/client-ref-hashing.ts
115
+ var client_ref_hashing_exports = {};
116
+ __export(client_ref_hashing_exports, {
117
+ computeProductionHash: () => computeProductionHash,
118
+ hashClientRefs: () => hashClientRefs,
119
+ hashRefKey: () => hashRefKey,
120
+ transformClientRefs: () => transformClientRefs
121
+ });
122
+ import { relative as relative2 } from "node:path";
123
+ import { createHash as createHash2 } from "node:crypto";
124
+ function hashRefKey(relativeId) {
125
+ return createHash2("sha256").update(relativeId).digest("hex").slice(0, 12);
126
+ }
127
+ function computeProductionHash(projectRoot, refKey) {
128
+ let toHash;
129
+ if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
130
+ toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
131
+ } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
132
+ const absPath = decodeURIComponent(
133
+ refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
134
+ );
135
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
136
+ } else if (refKey.startsWith(FS_PREFIX)) {
137
+ const absPath = refKey.slice(FS_PREFIX.length - 1);
138
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
139
+ } else if (refKey.startsWith("/")) {
140
+ toHash = refKey.slice(1);
141
+ } else {
142
+ return refKey;
143
+ }
144
+ return hashRefKey(toHash);
145
+ }
146
+ function transformClientRefs(code, projectRoot) {
147
+ if (!code.includes("registerClientReference")) return null;
148
+ let hasReplacement = false;
149
+ const result = code.replace(
150
+ REGISTER_CLIENT_REF_RE,
151
+ (match, refKey) => {
152
+ const hash = computeProductionHash(projectRoot, refKey);
153
+ if (hash === refKey) return match;
154
+ hasReplacement = true;
155
+ return match.replace(`"${refKey}"`, `"${hash}"`);
156
+ }
157
+ );
158
+ return hasReplacement ? result : null;
159
+ }
160
+ function hashClientRefs(projectRoot) {
161
+ const counter = createCounter(debug7, "hash-client-refs");
162
+ return {
163
+ name: "@rangojs/router:hash-client-refs",
164
+ enforce: "post",
165
+ applyToEnvironment(env) {
166
+ return env.name === "rsc";
167
+ },
168
+ buildEnd() {
169
+ counter?.flush();
170
+ },
171
+ transform(code, id) {
172
+ const start = counter ? performance.now() : 0;
173
+ try {
174
+ const result = transformClientRefs(code, projectRoot);
175
+ if (result === null) return;
176
+ return { code: result, map: null };
177
+ } finally {
178
+ counter?.record(id, performance.now() - start);
179
+ }
180
+ }
181
+ };
182
+ }
183
+ var debug7, CLIENT_PKG_PROXY_PREFIX, CLIENT_IN_SERVER_PKG_PROXY_PREFIX, FS_PREFIX, REGISTER_CLIENT_REF_RE;
184
+ var init_client_ref_hashing = __esm({
185
+ "src/vite/plugins/client-ref-hashing.ts"() {
186
+ "use strict";
187
+ init_debug();
188
+ debug7 = createRangoDebugger(NS.transform);
189
+ CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
190
+ CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
191
+ FS_PREFIX = "/@fs/";
192
+ REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
193
+ }
194
+ });
195
+
1
196
  // src/vite/rango.ts
2
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
3
- import { resolve as resolve11 } from "node:path";
197
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "node:fs";
198
+ import { resolve as resolve12 } from "node:path";
4
199
 
5
200
  // src/vite/plugins/expose-action-id.ts
6
201
  import MagicString from "magic-string";
@@ -251,99 +446,8 @@ function findCallParenAfterGenerics(code, afterCalleeIndex) {
251
446
  return -1;
252
447
  }
253
448
 
254
- // src/vite/debug.ts
255
- import debugFactory from "debug";
256
- var NS = {
257
- config: "rango:config",
258
- discovery: "rango:discovery",
259
- routes: "rango:routes",
260
- prerender: "rango:prerender",
261
- build: "rango:build",
262
- dev: "rango:dev",
263
- transform: "rango:transform",
264
- chunks: "rango:chunks"
265
- };
266
- if (process.env.INTERNAL_RANGO_DEBUG) {
267
- const existing = debugFactory.disable();
268
- debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
269
- }
270
- function createRangoDebugger(namespace) {
271
- const primary = debugFactory(namespace);
272
- const shadow = debugFactory(`vite:${namespace}`);
273
- if (primary.enabled) return primary;
274
- if (shadow.enabled) return shadow;
275
- return void 0;
276
- }
277
- async function timed(debug11, label, fn) {
278
- if (!debug11) return await fn();
279
- const start = performance.now();
280
- try {
281
- return await fn();
282
- } finally {
283
- debug11("%s (%sms)", label, (performance.now() - start).toFixed(1));
284
- }
285
- }
286
- function timedSync(debug11, label, fn) {
287
- if (!debug11) return fn();
288
- const start = performance.now();
289
- try {
290
- return fn();
291
- } finally {
292
- debug11("%s (%sms)", label, (performance.now() - start).toFixed(1));
293
- }
294
- }
295
- function createCounter(debug11, label) {
296
- if (!debug11) return void 0;
297
- let n = 0;
298
- let totalMs = 0;
299
- let slowestMs = 0;
300
- let slowestFile = "";
301
- const record = (file, ms) => {
302
- n++;
303
- totalMs += ms;
304
- if (ms > slowestMs) {
305
- slowestMs = ms;
306
- slowestFile = file;
307
- }
308
- };
309
- return {
310
- record,
311
- time(file, fn) {
312
- const start = performance.now();
313
- let out;
314
- try {
315
- out = fn();
316
- } catch (err) {
317
- record(file, performance.now() - start);
318
- throw err;
319
- }
320
- if (out && typeof out.then === "function") {
321
- return out.finally(
322
- () => record(file, performance.now() - start)
323
- );
324
- }
325
- record(file, performance.now() - start);
326
- return out;
327
- },
328
- flush() {
329
- if (n === 0) return;
330
- debug11(
331
- "%s: %d files, %sms total, slowest %sms %s",
332
- label,
333
- n,
334
- totalMs.toFixed(1),
335
- slowestMs.toFixed(1),
336
- slowestFile
337
- );
338
- n = 0;
339
- totalMs = 0;
340
- slowestMs = 0;
341
- slowestFile = "";
342
- }
343
- };
344
- }
345
-
346
449
  // src/vite/plugins/expose-action-id.ts
450
+ init_debug();
347
451
  var debug = createRangoDebugger(NS.transform);
348
452
  function getRscPluginApi(config) {
349
453
  let plugin = config.plugins.find((p) => p.name === "rsc:minimal");
@@ -1299,10 +1403,14 @@ ${binding.localName}.$$id = "${handlerId}";`;
1299
1403
  return hasChanges;
1300
1404
  }
1301
1405
 
1406
+ // src/vite/plugins/expose-internal-ids.ts
1407
+ init_debug();
1408
+
1302
1409
  // src/vite/plugins/expose-ids/router-transform.ts
1303
1410
  import MagicString2 from "magic-string";
1304
1411
  import path3 from "node:path";
1305
1412
  import { createHash } from "node:crypto";
1413
+ init_debug();
1306
1414
  var debug2 = createRangoDebugger(NS.transform);
1307
1415
  function skipLeadingTrivia(code, start, end) {
1308
1416
  let i = start;
@@ -1922,6 +2030,7 @@ ${lazyImports.join(",\n")}
1922
2030
  // src/vite/plugins/use-cache-transform.ts
1923
2031
  import path5 from "node:path";
1924
2032
  import MagicString4 from "magic-string";
2033
+ init_debug();
1925
2034
  var debug4 = createRangoDebugger(NS.transform);
1926
2035
  var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
1927
2036
  var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
@@ -2115,6 +2224,7 @@ function warnOnNearMissDirectives(ast, fileId, warn) {
2115
2224
  }
2116
2225
 
2117
2226
  // src/vite/plugins/client-ref-dedup.ts
2227
+ init_debug();
2118
2228
  var debug5 = createRangoDebugger(NS.transform);
2119
2229
  var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
2120
2230
  function extractPackageName(absolutePath) {
@@ -2210,21 +2320,35 @@ async function initializeApp() {
2210
2320
 
2211
2321
  initializeApp().catch(console.error);
2212
2322
  `.trim();
2213
- var VIRTUAL_ENTRY_SSR = `
2214
- import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
2323
+ function getVirtualEntrySSR(headScripts = "preinit") {
2324
+ const preinit = headScripts !== "preload";
2325
+ const depsImportNames = preinit ? "createFromReadableStream,\n setOnClientReference," : "createFromReadableStream,";
2326
+ const ssrImportNames = preinit ? "\n installClientReferencePreinit," : "";
2327
+ const install = preinit ? `
2328
+ // Upgrade client-reference modulepreload hints to executing module scripts in
2329
+ // the document head, for every render pass (live SSR, shell capture, resume).
2330
+ // See src/ssr/preinit-client-references.ts for the full rationale.
2331
+ installClientReferencePreinit(setOnClientReference);
2332
+ ` : "";
2333
+ const hs = JSON.stringify(headScripts);
2334
+ return `
2335
+ import {
2336
+ ${depsImportNames}
2337
+ } from "@rangojs/router/internal/deps/ssr";
2215
2338
  import { renderToReadableStream, resume } from "react-dom/server.edge";
2216
2339
  import { prerender } from "react-dom/static.edge";
2217
2340
  import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
2218
2341
  import {
2219
2342
  createSSRHandler,
2220
2343
  createShellCaptureHandler,
2221
- createShellResumeHandler,
2344
+ createShellResumeHandler,${ssrImportNames}
2222
2345
  } from "@rangojs/router/ssr";
2223
-
2346
+ ${install}
2224
2347
  export const renderHTML = createSSRHandler({
2225
2348
  createFromReadableStream,
2226
2349
  renderToReadableStream,
2227
2350
  injectRSCPayload,
2351
+ headScripts: ${hs},
2228
2352
  loadBootstrapScriptContent: () =>
2229
2353
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2230
2354
  });
@@ -2235,6 +2359,7 @@ export const captureShellHTML = createShellCaptureHandler({
2235
2359
  injectRSCPayload,
2236
2360
  prerender,
2237
2361
  resume,
2362
+ headScripts: ${hs},
2238
2363
  loadBootstrapScriptContent: () =>
2239
2364
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2240
2365
  });
@@ -2245,10 +2370,12 @@ export const resumeShellHTML = createShellResumeHandler({
2245
2370
  injectRSCPayload,
2246
2371
  prerender,
2247
2372
  resume,
2373
+ headScripts: ${hs},
2248
2374
  loadBootstrapScriptContent: () =>
2249
2375
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2250
2376
  });
2251
2377
  `.trim();
2378
+ }
2252
2379
  var RSC_ENTRY_BOOTSTRAP_IMPORTS = [
2253
2380
  "virtual:rsc-router/routes-manifest",
2254
2381
  "virtual:rsc-router/loader-manifest"
@@ -2393,7 +2520,7 @@ import { resolve } from "node:path";
2393
2520
  // package.json
2394
2521
  var package_default = {
2395
2522
  name: "@rangojs/router",
2396
- version: "0.0.0-experimental.145",
2523
+ version: "0.0.0-experimental.147",
2397
2524
  description: "Django-inspired RSC router with composable URL patterns",
2398
2525
  keywords: [
2399
2526
  "react",
@@ -2521,6 +2648,11 @@ var package_default = {
2521
2648
  types: "./src/build/index.ts",
2522
2649
  import: "./src/build/index.ts"
2523
2650
  },
2651
+ "./build/shell-capture": {
2652
+ types: "./src/prerender/build-shell-capture.ts",
2653
+ "react-server": "./src/prerender/build-shell-capture.ts",
2654
+ import: "./src/prerender/build-shell-capture.ts"
2655
+ },
2524
2656
  "./host": {
2525
2657
  types: "./src/host/index.ts",
2526
2658
  "react-server": "./src/host/index.ts",
@@ -3832,6 +3964,13 @@ function createVersionPlugin() {
3832
3964
  return {
3833
3965
  name: "@rangojs/router:version",
3834
3966
  enforce: "pre",
3967
+ // The build-time shell capture phase (producer B, #699) stamps entries
3968
+ // with THIS instance's version — the value folded into the shipped worker
3969
+ // — never the discovery temp server's own version-plugin value (a
3970
+ // different Date.now() stamp that would fail the serve-side gate).
3971
+ api: {
3972
+ getBuildVersion: () => currentVersion
3973
+ },
3835
3974
  configResolved(config) {
3836
3975
  isDev = config.command === "serve";
3837
3976
  resolvedCacheDir = config.cacheDir ? String(config.cacheDir).replace(/\\/g, "/") : void 0;
@@ -3922,6 +4061,7 @@ import * as Vite from "vite";
3922
4061
  import { isAbsolute, resolve as resolve4 } from "node:path";
3923
4062
 
3924
4063
  // src/vite/plugins/performance-tracks.ts
4064
+ init_debug();
3925
4065
  import { readFile } from "node:fs/promises";
3926
4066
  var debug6 = createRangoDebugger(NS.transform);
3927
4067
  var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
@@ -4017,13 +4157,13 @@ function normalizeHostRouterEntry(rawInput, root, exists) {
4017
4157
  }
4018
4158
  return exists(resolve4(root, raw)) ? "./" + raw : raw;
4019
4159
  }
4020
- function createVirtualEntriesPlugin(entries, routerPathRef) {
4160
+ function createVirtualEntriesPlugin(entries, routerPathRef, options) {
4021
4161
  const virtualModules = {};
4022
4162
  if (entries.client === VIRTUAL_IDS.browser) {
4023
4163
  virtualModules[VIRTUAL_IDS.browser] = VIRTUAL_ENTRY_BROWSER;
4024
4164
  }
4025
4165
  if (entries.ssr === VIRTUAL_IDS.ssr) {
4026
- virtualModules[VIRTUAL_IDS.ssr] = VIRTUAL_ENTRY_SSR;
4166
+ virtualModules[VIRTUAL_IDS.ssr] = getVirtualEntrySSR(options?.headScripts);
4027
4167
  }
4028
4168
  const knownIds = new Set(Object.keys(virtualModules));
4029
4169
  if (entries.rsc === VIRTUAL_IDS.rsc) {
@@ -4104,75 +4244,9 @@ function getManualChunks(id) {
4104
4244
  return void 0;
4105
4245
  }
4106
4246
 
4107
- // src/vite/plugins/client-ref-hashing.ts
4108
- import { relative as relative2 } from "node:path";
4109
- import { createHash as createHash2 } from "node:crypto";
4110
- var debug7 = createRangoDebugger(NS.transform);
4111
- var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
4112
- var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
4113
- var FS_PREFIX = "/@fs/";
4114
- function hashRefKey(relativeId) {
4115
- return createHash2("sha256").update(relativeId).digest("hex").slice(0, 12);
4116
- }
4117
- function computeProductionHash(projectRoot, refKey) {
4118
- let toHash;
4119
- if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
4120
- toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
4121
- } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
4122
- const absPath = decodeURIComponent(
4123
- refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
4124
- );
4125
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
4126
- } else if (refKey.startsWith(FS_PREFIX)) {
4127
- const absPath = refKey.slice(FS_PREFIX.length - 1);
4128
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
4129
- } else if (refKey.startsWith("/")) {
4130
- toHash = refKey.slice(1);
4131
- } else {
4132
- return refKey;
4133
- }
4134
- return hashRefKey(toHash);
4135
- }
4136
- var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
4137
- function transformClientRefs(code, projectRoot) {
4138
- if (!code.includes("registerClientReference")) return null;
4139
- let hasReplacement = false;
4140
- const result = code.replace(
4141
- REGISTER_CLIENT_REF_RE,
4142
- (match, refKey) => {
4143
- const hash = computeProductionHash(projectRoot, refKey);
4144
- if (hash === refKey) return match;
4145
- hasReplacement = true;
4146
- return match.replace(`"${refKey}"`, `"${hash}"`);
4147
- }
4148
- );
4149
- return hasReplacement ? result : null;
4150
- }
4151
- function hashClientRefs(projectRoot) {
4152
- const counter = createCounter(debug7, "hash-client-refs");
4153
- return {
4154
- name: "@rangojs/router:hash-client-refs",
4155
- enforce: "post",
4156
- applyToEnvironment(env) {
4157
- return env.name === "rsc";
4158
- },
4159
- buildEnd() {
4160
- counter?.flush();
4161
- },
4162
- transform(code, id) {
4163
- const start = counter ? performance.now() : 0;
4164
- try {
4165
- const result = transformClientRefs(code, projectRoot);
4166
- if (result === null) return;
4167
- return { code: result, map: null };
4168
- } finally {
4169
- counter?.record(id, performance.now() - start);
4170
- }
4171
- }
4172
- };
4173
- }
4174
-
4175
4247
  // src/vite/utils/client-chunks.ts
4248
+ init_debug();
4249
+ init_client_ref_hashing();
4176
4250
  var debugChunks = createRangoDebugger(NS.chunks);
4177
4251
  function isSharedRuntime(meta) {
4178
4252
  return [meta.id, meta.normalizedId].some(
@@ -4528,6 +4602,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
4528
4602
  }
4529
4603
 
4530
4604
  // src/vite/plugins/cjs-to-esm.ts
4605
+ init_debug();
4531
4606
  var debug8 = createRangoDebugger(NS.transform);
4532
4607
  function createCjsToEsmPlugin() {
4533
4608
  let isProduction = false;
@@ -4591,8 +4666,8 @@ function createCjsToEsmPlugin() {
4591
4666
 
4592
4667
  // src/vite/router-discovery.ts
4593
4668
  import { createServer as createViteServer } from "vite";
4594
- import { resolve as resolve10 } from "node:path";
4595
- import { readFileSync as readFileSync6 } from "node:fs";
4669
+ import { resolve as resolve11 } from "node:path";
4670
+ import { readFileSync as readFileSync7 } from "node:fs";
4596
4671
  import { createRequire as createRequire3, register } from "node:module";
4597
4672
  import { pathToFileURL as pathToFileURL2 } from "node:url";
4598
4673
 
@@ -4756,6 +4831,9 @@ function walk(node, visit) {
4756
4831
  }
4757
4832
  }
4758
4833
 
4834
+ // src/vite/router-discovery.ts
4835
+ init_client_ref_hashing();
4836
+
4759
4837
  // src/vite/utils/bundle-analysis.ts
4760
4838
  function findMatchingParenInBundle(code, openParenPos) {
4761
4839
  let depth = 1;
@@ -4860,6 +4938,9 @@ function createDiscoveryState(entryPath, opts) {
4860
4938
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
4861
4939
  prerenderManifestEntries: null,
4862
4940
  staticManifestEntries: null,
4941
+ shellCandidates: null,
4942
+ prerenderPayloadValues: null,
4943
+ shellPhaseTempServer: null,
4863
4944
  handlerChunkInfoMap: /* @__PURE__ */ new Map(),
4864
4945
  staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
4865
4946
  rscEntryFileName: null,
@@ -5442,18 +5523,24 @@ function getStagedAssetDir(projectRoot) {
5442
5523
  function resetStagedBuildAssets(projectRoot) {
5443
5524
  rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
5444
5525
  }
5445
- function stageBuildAssetModule(projectRoot, prefix, exportValue) {
5446
- const stagedDir = getStagedAssetDir(projectRoot);
5447
- mkdirSync(stagedDir, { recursive: true });
5526
+ function writeBuildAssetModule(dir, prefix, exportValue) {
5527
+ mkdirSync(dir, { recursive: true });
5448
5528
  const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
5449
5529
  const fileName = `${prefix}-${contentHash}.js`;
5450
- const filePath = resolve7(stagedDir, fileName);
5530
+ const filePath = resolve7(dir, fileName);
5451
5531
  if (!existsSync5(filePath)) {
5452
5532
  writeFileSync2(filePath, `export default ${exportValue};
5453
5533
  `);
5454
5534
  }
5455
5535
  return fileName;
5456
5536
  }
5537
+ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
5538
+ return writeBuildAssetModule(
5539
+ getStagedAssetDir(projectRoot),
5540
+ prefix,
5541
+ exportValue
5542
+ );
5543
+ }
5457
5544
  function copyStagedBuildAssets(projectRoot, fileNames) {
5458
5545
  const stagedDir = getStagedAssetDir(projectRoot);
5459
5546
  const distAssetsDir = resolve7(projectRoot, "dist/rsc/assets");
@@ -5469,6 +5556,7 @@ function copyStagedBuildAssets(projectRoot, fileNames) {
5469
5556
  }
5470
5557
 
5471
5558
  // src/vite/discovery/prerender-collection.ts
5559
+ init_debug();
5472
5560
  var debug9 = createRangoDebugger(NS.prerender);
5473
5561
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
5474
5562
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode) return;
@@ -5676,6 +5764,18 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
5676
5764
  "__pr",
5677
5765
  mainValue
5678
5766
  );
5767
+ if (result.ppr !== void 0 && result.ppr !== false) {
5768
+ (state.shellCandidates ??= []).push({
5769
+ urlPath: entry.urlPath,
5770
+ routeName: result.routeName,
5771
+ paramHash,
5772
+ ppr: result.ppr === true ? true : result.ppr
5773
+ });
5774
+ (state.prerenderPayloadValues ??= /* @__PURE__ */ new Map()).set(
5775
+ mainKey,
5776
+ mainValue
5777
+ );
5778
+ }
5679
5779
  if (result.interceptSegments?.length) {
5680
5780
  const interceptKey = `${result.routeName}/${paramHash}/i`;
5681
5781
  const interceptValue = JSON.stringify({
@@ -5926,6 +6026,8 @@ function describeDiscoveryFailure(err, opts = {}) {
5926
6026
  }
5927
6027
 
5928
6028
  // src/vite/discovery/discover-routers.ts
6029
+ init_debug();
6030
+ init_client_ref_hashing();
5929
6031
  var debug10 = createRangoDebugger(NS.discovery);
5930
6032
  async function discoverRouters(state, rscEnv) {
5931
6033
  if (!state.resolvedEntryPath) return;
@@ -6146,11 +6248,284 @@ async function discoverRouters(state, rscEnv) {
6146
6248
  state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
6147
6249
  state.perRouterTrieMap = newPerRouterTrieMap;
6148
6250
  state.mergedRouteTrie = newMergedRouteTrie;
6251
+ if (serverMod.setRouteTrie && newMergedRouteTrie) {
6252
+ serverMod.setRouteTrie(newMergedRouteTrie);
6253
+ }
6254
+ const perRouterSetters = [
6255
+ [newPerRouterManifestDataMap, "setRouterManifest"],
6256
+ [newPerRouterTrieMap, "setRouterTrie"],
6257
+ [newPerRouterPrecomputedMap, "setRouterPrecomputedEntries"]
6258
+ ];
6259
+ for (const [map, fn] of perRouterSetters) {
6260
+ const setter = serverMod[fn];
6261
+ if (typeof setter !== "function") continue;
6262
+ for (const [routerId, value] of map) setter(routerId, value);
6263
+ }
6149
6264
  await expandPrerenderRoutes(state, rscEnv, registry, allManifests);
6150
6265
  await renderStaticHandlers(state, rscEnv, registry);
6151
6266
  return serverMod;
6152
6267
  }
6153
6268
 
6269
+ // src/vite/discovery/shell-prerender-phase.ts
6270
+ import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
6271
+ import { join as join4, resolve as resolve8 } from "node:path";
6272
+
6273
+ // src/prerender/shell-manifest-key.ts
6274
+ function buildShellManifestKey(pathname) {
6275
+ return pathname;
6276
+ }
6277
+
6278
+ // src/vite/discovery/shell-prerender-phase.ts
6279
+ init_debug();
6280
+ var debug11 = createRangoDebugger(NS.prerender);
6281
+ async function runShellPrerenderPhase(s, builder) {
6282
+ const tempServer = s.shellPhaseTempServer;
6283
+ if (!s.isBuildMode || !s.shellCandidates?.length || !tempServer) return;
6284
+ const candidates = s.shellCandidates;
6285
+ const startTotal = performance.now();
6286
+ console.log(`[rango] Shell-prerendering ${candidates.length} URL(s)...`);
6287
+ let restoreClientRequire;
6288
+ try {
6289
+ const rscEnv = tempServer.environments?.rsc;
6290
+ const ssrEnv = tempServer.environments?.ssr;
6291
+ if (!rscEnv?.runner || !ssrEnv?.runner) {
6292
+ console.warn(
6293
+ `[rango] shell prerender: temp server runners unavailable (rsc=${String(!!rscEnv?.runner)}, ssr=${String(!!ssrEnv?.runner)}); skipping \u2014 routes keep runtime shell capture.`
6294
+ );
6295
+ return;
6296
+ }
6297
+ const versionPlugin = (builder?.config?.plugins ?? []).find(
6298
+ (p) => p?.name === "@rangojs/router:version"
6299
+ );
6300
+ const buildVersion = versionPlugin?.api?.getBuildVersion?.();
6301
+ if (!buildVersion) {
6302
+ console.warn(
6303
+ "[rango] shell prerender: main build version unavailable; skipping \u2014 routes keep runtime shell capture."
6304
+ );
6305
+ return;
6306
+ }
6307
+ const payloads = s.prerenderPayloadValues ?? /* @__PURE__ */ new Map();
6308
+ const fetchedKeys = /* @__PURE__ */ new Set();
6309
+ globalThis.__loadPrerenderManifestModule = async () => ({
6310
+ default: Object.fromEntries([...payloads.keys()].map((k) => [k, k])),
6311
+ loadPrerenderAsset: async (spec) => {
6312
+ fetchedKeys.add(spec);
6313
+ return { default: JSON.parse(payloads.get(spec)) };
6314
+ }
6315
+ });
6316
+ const ssrRunner = ssrEnv.runner;
6317
+ const ssrPkg = await ssrRunner.import("@rangojs/router/ssr");
6318
+ const ssrDeps = await ssrRunner.import("@rangojs/router/internal/deps/ssr");
6319
+ const reactDomServer = await ssrRunner.import("react-dom/server.edge");
6320
+ const reactDomStatic = await ssrRunner.import("react-dom/static.edge");
6321
+ const htmlStream = await ssrRunner.import(
6322
+ "@rangojs/router/internal/deps/html-stream-server"
6323
+ );
6324
+ const clientOutDir = builder?.environments?.client?.config?.build?.outDir ?? resolve8(s.projectRoot, "dist/client");
6325
+ const clientAssetsDir = join4(clientOutDir, "assets");
6326
+ const entryFile = existsSync6(clientAssetsDir) ? readdirSync2(clientAssetsDir).find((f) => /^index-.*\.js$/.test(f)) : void 0;
6327
+ if (!entryFile) {
6328
+ console.warn(
6329
+ `[rango] shell prerender: built client entry not found under ${clientAssetsDir}; skipping \u2014 routes keep runtime shell capture.`
6330
+ );
6331
+ return;
6332
+ }
6333
+ const base = builder?.config?.base ?? "/";
6334
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
6335
+ const bootstrapContent = `import("${normalizedBase}assets/${entryFile}")`;
6336
+ debug11?.("shell prerender bootstrap: %s", bootstrapContent);
6337
+ const minimalPlugin = (tempServer.config?.plugins ?? []).find(
6338
+ (p) => p?.name === "rsc:minimal"
6339
+ );
6340
+ const rscManager = minimalPlugin?.api?.manager;
6341
+ const { computeProductionHash: computeProductionHash2 } = await Promise.resolve().then(() => (init_client_ref_hashing(), client_ref_hashing_exports));
6342
+ let hashToDevKey = /* @__PURE__ */ new Map();
6343
+ const rebuildHashMap = () => {
6344
+ hashToDevKey = /* @__PURE__ */ new Map();
6345
+ const metaMap = rscManager?.clientReferenceMetaMap ?? {};
6346
+ for (const meta of Object.values(metaMap)) {
6347
+ hashToDevKey.set(
6348
+ computeProductionHash2(s.projectRoot, meta.referenceKey),
6349
+ meta.referenceKey
6350
+ );
6351
+ }
6352
+ };
6353
+ const origClientRequire = globalThis.__vite_rsc_client_require__;
6354
+ if (typeof origClientRequire === "function") {
6355
+ restoreClientRequire = () => {
6356
+ globalThis.__vite_rsc_client_require__ = origClientRequire;
6357
+ };
6358
+ globalThis.__vite_rsc_client_require__ = (id) => {
6359
+ const base2 = id.split("$$cache=")[0];
6360
+ let mapped = hashToDevKey.get(base2);
6361
+ if (mapped === void 0) {
6362
+ rebuildHashMap();
6363
+ mapped = hashToDevKey.get(base2);
6364
+ }
6365
+ return origClientRequire(mapped ?? id);
6366
+ };
6367
+ }
6368
+ const captureShellHTML = ssrPkg.createShellCaptureHandler({
6369
+ createFromReadableStream: ssrDeps.createFromReadableStream,
6370
+ renderToReadableStream: reactDomServer.renderToReadableStream ?? reactDomServer.default?.renderToReadableStream,
6371
+ resume: reactDomServer.resume ?? reactDomServer.default?.resume,
6372
+ prerender: reactDomStatic.prerender ?? reactDomStatic.default?.prerender,
6373
+ injectRSCPayload: htmlStream.injectRSCPayload,
6374
+ headScripts: "preinit",
6375
+ loadBootstrapScriptContent: async () => bootstrapContent
6376
+ });
6377
+ const rscRunner = rscEnv.runner;
6378
+ const captureMod = await rscRunner.import(
6379
+ "@rangojs/router/build/shell-capture"
6380
+ );
6381
+ const serverMod = await rscRunner.import("@rangojs/router/server");
6382
+ const registry = serverMod.RouterRegistry;
6383
+ const staged = [];
6384
+ let skipCount = 0;
6385
+ const pathCounts = /* @__PURE__ */ new Map();
6386
+ for (const cand of candidates) {
6387
+ pathCounts.set(cand.urlPath, (pathCounts.get(cand.urlPath) ?? 0) + 1);
6388
+ }
6389
+ for (const cand of candidates) {
6390
+ if ((pathCounts.get(cand.urlPath) ?? 0) > 1) {
6391
+ console.warn(
6392
+ `[rango] SHELL SKIP ${cand.urlPath.padEnd(34)} - pathname claimed by multiple prerender routes; routes keep runtime capture`
6393
+ );
6394
+ skipCount++;
6395
+ continue;
6396
+ }
6397
+ const startUrl = performance.now();
6398
+ const policy = captureMod.resolveBuildPprConfig(cand.ppr);
6399
+ const mainKey = `${cand.routeName}/${cand.paramHash}`;
6400
+ let handled = false;
6401
+ const mismatches = [];
6402
+ for (const [, routerInstance] of registry) {
6403
+ if (typeof routerInstance.match !== "function") continue;
6404
+ try {
6405
+ const res = await captureMod.captureShellForBuild({
6406
+ router: routerInstance,
6407
+ urlPath: cand.urlPath,
6408
+ routeName: cand.routeName,
6409
+ key: `${cand.urlPath}:shell`,
6410
+ ttl: policy.ttl,
6411
+ swr: policy.swr,
6412
+ tags: policy.tags,
6413
+ buildEnv: s.resolvedBuildEnv,
6414
+ buildVersion,
6415
+ captureShellHTML,
6416
+ debug: !!debug11
6417
+ });
6418
+ if (res.outcome === "route-mismatch") {
6419
+ mismatches.push(res.matchedRouteName ?? "(no match)");
6420
+ continue;
6421
+ }
6422
+ handled = true;
6423
+ const elapsed = (performance.now() - startUrl).toFixed(0);
6424
+ if (res.outcome !== "stored" || !res.entry) {
6425
+ console.warn(
6426
+ `[rango] SHELL SKIP ${cand.urlPath.padEnd(34)} (${elapsed}ms) - capture ${res.outcome}; route keeps runtime capture`
6427
+ );
6428
+ skipCount++;
6429
+ break;
6430
+ }
6431
+ if (!fetchedKeys.has(mainKey)) {
6432
+ console.warn(
6433
+ `[rango] SHELL SKIP ${cand.urlPath.padEnd(34)} (${elapsed}ms) - capture did not replay the prerender store; route keeps runtime capture`
6434
+ );
6435
+ skipCount++;
6436
+ break;
6437
+ }
6438
+ const value = {
6439
+ entry: res.entry,
6440
+ ttl: policy.ttl,
6441
+ swr: policy.swr,
6442
+ tags: res.tags,
6443
+ routeName: cand.routeName
6444
+ };
6445
+ staged.push({
6446
+ key: buildShellManifestKey(cand.urlPath),
6447
+ value: JSON.stringify(value)
6448
+ });
6449
+ console.log(
6450
+ `[rango] SHELL OK ${cand.urlPath.padEnd(34)} (${elapsed}ms)`
6451
+ );
6452
+ break;
6453
+ } catch (err) {
6454
+ handled = true;
6455
+ const elapsed = (performance.now() - startUrl).toFixed(0);
6456
+ console.warn(
6457
+ `[rango] SHELL SKIP ${cand.urlPath.padEnd(34)} (${elapsed}ms) - ${err?.message ?? err}; route keeps runtime capture`
6458
+ );
6459
+ skipCount++;
6460
+ break;
6461
+ }
6462
+ }
6463
+ if (!handled) {
6464
+ console.warn(
6465
+ `[rango] SHELL SKIP ${cand.urlPath.padEnd(34)} - no router matched "${cand.routeName}"` + (mismatches.length > 0 ? ` (matched: ${mismatches.join(", ")})` : "") + "; route keeps runtime capture"
6466
+ );
6467
+ skipCount++;
6468
+ }
6469
+ }
6470
+ if (staged.length > 0) {
6471
+ const rscOutDir = builder?.environments?.rsc?.config?.build?.outDir ?? resolve8(s.projectRoot, "dist/rsc");
6472
+ writeShellManifest(s, rscOutDir, staged);
6473
+ }
6474
+ const totalElapsed = (performance.now() - startTotal).toFixed(0);
6475
+ const parts = [`${staged.length} done`];
6476
+ if (skipCount > 0) parts.push(`${skipCount} skipped`);
6477
+ console.log(
6478
+ `[rango] Shell prerender complete: ${parts.join(", ")} (${totalElapsed}ms total)`
6479
+ );
6480
+ } catch (err) {
6481
+ console.warn(
6482
+ `[rango] shell prerender phase failed: ${err?.message ?? err}; routes keep runtime shell capture.`
6483
+ );
6484
+ } finally {
6485
+ delete globalThis.__loadPrerenderManifestModule;
6486
+ restoreClientRequire?.();
6487
+ }
6488
+ }
6489
+ function writeShellManifest(s, rscOutDir, staged) {
6490
+ const rscEntryPath = resolve8(rscOutDir, s.rscEntryFileName ?? "index.js");
6491
+ if (!existsSync6(rscEntryPath)) {
6492
+ console.warn(
6493
+ `[rango] shell prerender: RSC entry not found at ${rscEntryPath}; entries not persisted \u2014 routes keep runtime capture.`
6494
+ );
6495
+ return;
6496
+ }
6497
+ const assetsDir = resolve8(rscOutDir, "assets");
6498
+ let totalBytes = 0;
6499
+ const manifestMap = {};
6500
+ const countedFiles = /* @__PURE__ */ new Set();
6501
+ for (const { key, value } of staged) {
6502
+ const fileName = writeBuildAssetModule(assetsDir, "__ps", value);
6503
+ if (!countedFiles.has(fileName)) {
6504
+ countedFiles.add(fileName);
6505
+ totalBytes += Buffer.byteLength(value) + "export default ;\n".length;
6506
+ }
6507
+ manifestMap[key] = `./assets/${fileName}`;
6508
+ }
6509
+ const manifestCode = [
6510
+ `const m=${jsonParseExpression(manifestMap)};`,
6511
+ `export function loadShellAsset(s){return import(s)}`,
6512
+ `export default m;`,
6513
+ ""
6514
+ ].join("\n");
6515
+ writeFileSync3(resolve8(rscOutDir, "__shell-manifest.js"), manifestCode);
6516
+ totalBytes += Buffer.byteLength(manifestCode);
6517
+ const rscCode = readFileSync4(rscEntryPath, "utf-8");
6518
+ if (!rscCode.includes("__shell-manifest.js")) {
6519
+ const injection = `globalThis.__loadShellManifestModule = () => import("./__shell-manifest.js");
6520
+ `;
6521
+ writeFileSync3(rscEntryPath, injection + rscCode);
6522
+ }
6523
+ const totalKB = (totalBytes / 1024).toFixed(1);
6524
+ console.log(
6525
+ `[rango] Wrote shell assets (${totalKB} KB total, ${staged.length} entries)`
6526
+ );
6527
+ }
6528
+
6154
6529
  // src/vite/discovery/dev-prerender-cache.ts
6155
6530
  function payloadBodiesFromResult(result) {
6156
6531
  const main = JSON.stringify({
@@ -6189,8 +6564,8 @@ function createDevPrerenderCache(maxEntriesPerRouter = MAX_ENTRIES_PER_ROUTER) {
6189
6564
  }
6190
6565
 
6191
6566
  // src/vite/discovery/route-types-writer.ts
6192
- import { dirname as dirname3, join as join4, resolve as resolve8 } from "node:path";
6193
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6, unlinkSync as unlinkSync2 } from "node:fs";
6567
+ import { dirname as dirname3, join as join5, resolve as resolve9 } from "node:path";
6568
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, unlinkSync as unlinkSync2 } from "node:fs";
6194
6569
  function filterUserNamedRoutes(manifest) {
6195
6570
  const filtered = {};
6196
6571
  for (const [name, pattern] of Object.entries(manifest)) {
@@ -6201,10 +6576,10 @@ function filterUserNamedRoutes(manifest) {
6201
6576
  return filtered;
6202
6577
  }
6203
6578
  function writeGenFileIfChanged(state, outPath, source, opts) {
6204
- const existing = existsSync6(outPath) ? readFileSync4(outPath, "utf-8") : null;
6579
+ const existing = existsSync7(outPath) ? readFileSync5(outPath, "utf-8") : null;
6205
6580
  if (existing === source) return;
6206
6581
  markSelfGenWrite(state, outPath, source);
6207
- writeFileSync3(outPath, source);
6582
+ writeFileSync4(outPath, source);
6208
6583
  if (opts?.log) console.log(`[rango] Generated route types -> ${outPath}`);
6209
6584
  }
6210
6585
  function writeCombinedRouteTypesWithTracking(state, opts) {
@@ -6219,10 +6594,10 @@ function writeRouteTypesFiles(state) {
6219
6594
  if (state.perRouterManifests.length === 0) return;
6220
6595
  try {
6221
6596
  const entryDir = dirname3(
6222
- resolve8(state.projectRoot, state.resolvedEntryPath)
6597
+ resolve9(state.projectRoot, state.resolvedEntryPath)
6223
6598
  );
6224
- const oldCombinedPath = join4(entryDir, "named-routes.gen.ts");
6225
- if (existsSync6(oldCombinedPath)) {
6599
+ const oldCombinedPath = join5(entryDir, "named-routes.gen.ts");
6600
+ if (existsSync7(oldCombinedPath)) {
6226
6601
  unlinkSync2(oldCombinedPath);
6227
6602
  console.log(
6228
6603
  `[rango] Removed stale combined route types: ${oldCombinedPath}`
@@ -6303,7 +6678,7 @@ function supplementGenFilesWithRuntimeRoutes(state) {
6303
6678
  }
6304
6679
 
6305
6680
  // src/vite/discovery/virtual-module-codegen.ts
6306
- import { dirname as dirname4, basename, join as join5 } from "node:path";
6681
+ import { dirname as dirname4, basename, join as join6 } from "node:path";
6307
6682
  function generateRoutesManifestModule(state) {
6308
6683
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
6309
6684
  if (hasManifest) {
@@ -6318,7 +6693,7 @@ function generateRoutesManifestModule(state) {
6318
6693
  /\.(tsx?|jsx?)$/,
6319
6694
  ""
6320
6695
  );
6321
- const genPath = join5(
6696
+ const genPath = join6(
6322
6697
  routerDir,
6323
6698
  `${routerBasename}.named-routes.gen.js`
6324
6699
  ).replaceAll("\\", "/");
@@ -6403,7 +6778,7 @@ function generatePerRouterModule(state, routerId) {
6403
6778
  /\.(tsx?|jsx?)$/,
6404
6779
  ""
6405
6780
  );
6406
- const genPath = join5(
6781
+ const genPath = join6(
6407
6782
  routerDir,
6408
6783
  `${routerBasename}.named-routes.gen.js`
6409
6784
  ).replaceAll("\\", "/");
@@ -6430,13 +6805,13 @@ function generatePerRouterModule(state, routerId) {
6430
6805
  }
6431
6806
 
6432
6807
  // src/vite/discovery/bundle-postprocess.ts
6433
- import { resolve as resolve9 } from "node:path";
6434
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
6808
+ import { resolve as resolve10 } from "node:path";
6809
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "node:fs";
6435
6810
  function postprocessBundle(state) {
6436
6811
  const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
6437
6812
  const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
6438
6813
  if (!hasPrerenderData && !hasStaticData) return;
6439
- const rscEntryPath = resolve9(
6814
+ const rscEntryPath = resolve10(
6440
6815
  state.projectRoot,
6441
6816
  "dist/rsc",
6442
6817
  state.rscEntryFileName ?? "index.js"
@@ -6457,9 +6832,9 @@ function postprocessBundle(state) {
6457
6832
  ];
6458
6833
  for (const target of evictionTargets) {
6459
6834
  for (const info of target.infos) {
6460
- const chunkPath = resolve9(state.projectRoot, "dist/rsc", info.fileName);
6835
+ const chunkPath = resolve10(state.projectRoot, "dist/rsc", info.fileName);
6461
6836
  try {
6462
- const code = readFileSync5(chunkPath, "utf-8");
6837
+ const code = readFileSync6(chunkPath, "utf-8");
6463
6838
  const result = evictHandlerCode(
6464
6839
  code,
6465
6840
  info.exports,
@@ -6467,7 +6842,7 @@ function postprocessBundle(state) {
6467
6842
  target.brand
6468
6843
  );
6469
6844
  if (result) {
6470
- writeFileSync4(chunkPath, result.code);
6845
+ writeFileSync5(chunkPath, result.code);
6471
6846
  const savedKB = (result.savedBytes / 1024).toFixed(1);
6472
6847
  console.log(
6473
6848
  `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
@@ -6482,8 +6857,8 @@ function postprocessBundle(state) {
6482
6857
  }
6483
6858
  state.handlerChunkInfoMap.clear();
6484
6859
  state.staticHandlerChunkInfoMap.clear();
6485
- if (hasPrerenderData && existsSync7(rscEntryPath)) {
6486
- const rscCode = readFileSync5(rscEntryPath, "utf-8");
6860
+ if (hasPrerenderData && existsSync8(rscEntryPath)) {
6861
+ const rscCode = readFileSync6(rscEntryPath, "utf-8");
6487
6862
  if (!rscCode.includes("__prerender-manifest.js")) {
6488
6863
  try {
6489
6864
  let totalBytes = copyStagedBuildAssets(
@@ -6502,15 +6877,15 @@ function postprocessBundle(state) {
6502
6877
  `export default m;`,
6503
6878
  ""
6504
6879
  ].join("\n");
6505
- const manifestPath = resolve9(
6880
+ const manifestPath = resolve10(
6506
6881
  state.projectRoot,
6507
6882
  "dist/rsc/__prerender-manifest.js"
6508
6883
  );
6509
- writeFileSync4(manifestPath, manifestCode);
6884
+ writeFileSync5(manifestPath, manifestCode);
6510
6885
  totalBytes += Buffer.byteLength(manifestCode);
6511
6886
  const injection = `globalThis.__loadPrerenderManifestModule = () => import("./__prerender-manifest.js");
6512
6887
  `;
6513
- writeFileSync4(rscEntryPath, injection + rscCode);
6888
+ writeFileSync5(rscEntryPath, injection + rscCode);
6514
6889
  const totalKB = (totalBytes / 1024).toFixed(1);
6515
6890
  console.log(
6516
6891
  `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
@@ -6522,8 +6897,8 @@ function postprocessBundle(state) {
6522
6897
  }
6523
6898
  }
6524
6899
  }
6525
- if (hasStaticData && existsSync7(rscEntryPath)) {
6526
- const rscCode = readFileSync5(rscEntryPath, "utf-8");
6900
+ if (hasStaticData && existsSync8(rscEntryPath)) {
6901
+ const rscCode = readFileSync6(rscEntryPath, "utf-8");
6527
6902
  if (!rscCode.includes("__static-manifest.js")) {
6528
6903
  try {
6529
6904
  const manifestEntries = [];
@@ -6540,15 +6915,15 @@ function postprocessBundle(state) {
6540
6915
  }
6541
6916
  const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;
6542
6917
  `;
6543
- const manifestPath = resolve9(
6918
+ const manifestPath = resolve10(
6544
6919
  state.projectRoot,
6545
6920
  "dist/rsc/__static-manifest.js"
6546
6921
  );
6547
- writeFileSync4(manifestPath, manifestCode);
6922
+ writeFileSync5(manifestPath, manifestCode);
6548
6923
  totalBytes += Buffer.byteLength(manifestCode);
6549
6924
  const injection = `import "./__static-manifest.js";
6550
6925
  `;
6551
- writeFileSync4(rscEntryPath, injection + rscCode);
6926
+ writeFileSync5(rscEntryPath, injection + rscCode);
6552
6927
  const totalKB = (totalBytes / 1024).toFixed(1);
6553
6928
  console.log(
6554
6929
  `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
@@ -6563,7 +6938,7 @@ function postprocessBundle(state) {
6563
6938
  }
6564
6939
 
6565
6940
  // src/vite/discovery/gate-state.ts
6566
- function createDiscoveryGate(s, debug11) {
6941
+ function createDiscoveryGate(s, debug12) {
6567
6942
  let gatePending = false;
6568
6943
  let gateResolver = () => {
6569
6944
  };
@@ -6572,15 +6947,15 @@ function createDiscoveryGate(s, debug11) {
6572
6947
  let pendingEvents = false;
6573
6948
  const beginGate = () => {
6574
6949
  if (gatePending) return;
6575
- s.discoveryDone = new Promise((resolve12) => {
6576
- gateResolver = resolve12;
6950
+ s.discoveryDone = new Promise((resolve13) => {
6951
+ gateResolver = resolve13;
6577
6952
  });
6578
6953
  gatePending = true;
6579
6954
  };
6580
6955
  const resolveGate = () => {
6581
6956
  if (!gatePending) return;
6582
6957
  if (inProgress || queued || pendingEvents) {
6583
- debug11?.(
6958
+ debug12?.(
6584
6959
  "hmr: resolveGate deferred \u2014 work in flight (inProgress=%s queued=%s pendingEvents=%s)",
6585
6960
  inProgress,
6586
6961
  queued,
@@ -6589,7 +6964,7 @@ function createDiscoveryGate(s, debug11) {
6589
6964
  return;
6590
6965
  }
6591
6966
  gatePending = false;
6592
- debug11?.("hmr: discoveryDone resolved");
6967
+ debug12?.("hmr: discoveryDone resolved");
6593
6968
  gateResolver();
6594
6969
  };
6595
6970
  const noteRouteEvent = () => {
@@ -6599,7 +6974,7 @@ function createDiscoveryGate(s, debug11) {
6599
6974
  const runRefreshCycle = async (work) => {
6600
6975
  if (inProgress) {
6601
6976
  queued = true;
6602
- debug11?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
6977
+ debug12?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
6603
6978
  return;
6604
6979
  }
6605
6980
  pendingEvents = false;
@@ -6610,16 +6985,16 @@ function createDiscoveryGate(s, debug11) {
6610
6985
  inProgress = false;
6611
6986
  if (queued) {
6612
6987
  queued = false;
6613
- debug11?.("hmr: consuming queued rediscovery");
6988
+ debug12?.("hmr: consuming queued rediscovery");
6614
6989
  runRefreshCycle(work).catch((err) => {
6615
- debug11?.(
6990
+ debug12?.(
6616
6991
  "hmr: queued cycle rejected \u2014 releasing gate (%s)",
6617
6992
  err instanceof Error ? err.message : String(err)
6618
6993
  );
6619
6994
  resolveGate();
6620
6995
  });
6621
6996
  } else if (pendingEvents) {
6622
- debug11?.(
6997
+ debug12?.(
6623
6998
  "hmr: holding gate for pending events (debounce not yet fired)"
6624
6999
  );
6625
7000
  } else {
@@ -6665,15 +7040,15 @@ function selectForwardableResolvePlugins(plugins) {
6665
7040
  }
6666
7041
  function pickForwardedRunnerConfig(config) {
6667
7042
  const r = config.resolve ?? {};
6668
- const resolve12 = {};
6669
- if (r.alias !== void 0) resolve12.alias = r.alias;
6670
- if (r.dedupe !== void 0) resolve12.dedupe = r.dedupe;
6671
- if (r.conditions !== void 0) resolve12.conditions = r.conditions;
6672
- if (r.mainFields !== void 0) resolve12.mainFields = r.mainFields;
6673
- if (r.extensions !== void 0) resolve12.extensions = r.extensions;
7043
+ const resolve13 = {};
7044
+ if (r.alias !== void 0) resolve13.alias = r.alias;
7045
+ if (r.dedupe !== void 0) resolve13.dedupe = r.dedupe;
7046
+ if (r.conditions !== void 0) resolve13.conditions = r.conditions;
7047
+ if (r.mainFields !== void 0) resolve13.mainFields = r.mainFields;
7048
+ if (r.extensions !== void 0) resolve13.extensions = r.extensions;
6674
7049
  if (r.preserveSymlinks !== void 0)
6675
- resolve12.preserveSymlinks = r.preserveSymlinks;
6676
- if (r.tsconfigPaths !== void 0) resolve12.tsconfigPaths = r.tsconfigPaths;
7050
+ resolve13.preserveSymlinks = r.preserveSymlinks;
7051
+ if (r.tsconfigPaths !== void 0) resolve13.tsconfigPaths = r.tsconfigPaths;
6677
7052
  const userOxc = config.oxc;
6678
7053
  const userJsx = userOxc && typeof userOxc === "object" && typeof userOxc.jsx === "object" && userOxc.jsx !== null ? userOxc.jsx : {};
6679
7054
  const oxc = userOxc && typeof userOxc === "object" ? {
@@ -6681,13 +7056,14 @@ function pickForwardedRunnerConfig(config) {
6681
7056
  jsx: { ...userJsx, runtime: "automatic", importSource: "react" }
6682
7057
  } : { jsx: { runtime: "automatic", importSource: "react" } };
6683
7058
  return {
6684
- resolve: resolve12,
7059
+ resolve: resolve13,
6685
7060
  define: config.define,
6686
7061
  oxc
6687
7062
  };
6688
7063
  }
6689
7064
 
6690
7065
  // src/vite/router-discovery.ts
7066
+ init_debug();
6691
7067
  var debugDiscovery = createRangoDebugger(NS.discovery);
6692
7068
  var debugRoutes = createRangoDebugger(NS.routes);
6693
7069
  var debugBuild = createRangoDebugger(NS.build);
@@ -6737,6 +7113,20 @@ async function createTempRscServer(state, options = {}) {
6737
7113
  // hashClientRefs only in build mode — production bundles need hashed refs
6738
7114
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
6739
7115
  createVersionPlugin(),
7116
+ // Before the stub plugin, so "virtual:entry-ssr" resolves to the real
7117
+ // SSR entry when the shell endpoint needs it (see the option doc).
7118
+ ...options.realSsrEntry ? [
7119
+ {
7120
+ name: "@rangojs/router:temp-real-ssr-entry",
7121
+ enforce: "pre",
7122
+ resolveId(id) {
7123
+ return id === "virtual:entry-ssr" ? "\0rango-temp-real-ssr-entry" : null;
7124
+ },
7125
+ load(id) {
7126
+ return id === "\0rango-temp-real-ssr-entry" ? getVirtualEntrySSR(state.opts?.headScripts) : null;
7127
+ }
7128
+ }
7129
+ ] : [],
6740
7130
  createVirtualStubPlugin(),
6741
7131
  createCloudflareProtocolStubPlugin(),
6742
7132
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
@@ -6760,7 +7150,7 @@ async function resolveBuildEnv(option, factoryCtx) {
6760
7150
  }
6761
7151
  try {
6762
7152
  const userRequire = createRequire3(
6763
- resolve10(factoryCtx.root, "package.json")
7153
+ resolve11(factoryCtx.root, "package.json")
6764
7154
  );
6765
7155
  const wranglerPath = userRequire.resolve("wrangler");
6766
7156
  const { getPlatformProxy } = await import(pathToFileURL2(wranglerPath).href);
@@ -6797,6 +7187,12 @@ async function acquireBuildEnv(s, command, mode) {
6797
7187
  globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
6798
7188
  return true;
6799
7189
  }
7190
+ function resetPrerenderCollection(s) {
7191
+ s.prerenderManifestEntries = null;
7192
+ s.staticManifestEntries = null;
7193
+ s.shellCandidates = null;
7194
+ s.prerenderPayloadValues = null;
7195
+ }
6800
7196
  async function releaseBuildEnv(s) {
6801
7197
  if (s.buildEnvDispose) {
6802
7198
  try {
@@ -6861,8 +7257,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6861
7257
  s.devServer = server;
6862
7258
  server.middlewares.use(internalDebugNoCacheMiddleware());
6863
7259
  let resolveDiscovery;
6864
- const discoveryPromise = new Promise((resolve12) => {
6865
- resolveDiscovery = resolve12;
7260
+ const discoveryPromise = new Promise((resolve13) => {
7261
+ resolveDiscovery = resolve13;
6866
7262
  });
6867
7263
  const gate = createDiscoveryGate(s, debugDiscovery);
6868
7264
  const beginDiscoveryGate = gate.beginGate;
@@ -6952,7 +7348,11 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
6952
7348
  );
6953
7349
  try {
6954
7350
  prerenderTempServer = await createTempRscServer(s, {
6955
- cacheDir: "node_modules/.vite_prerender"
7351
+ cacheDir: "node_modules/.vite_prerender",
7352
+ // The dev /__rsc_shell endpoint drives captureShellHTML in this
7353
+ // server's SSR realm; the entry is only imported when a shell
7354
+ // capture runs, so discovery cost is unchanged.
7355
+ realSsrEntry: true
6956
7356
  });
6957
7357
  const tempRscEnv = prerenderTempServer.environments?.rsc;
6958
7358
  if (tempRscEnv?.runner) {
@@ -7284,6 +7684,155 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
7284
7684
  res.end("No prerender match");
7285
7685
  logResult(404, "no match");
7286
7686
  });
7687
+ server.middlewares.use("/__rsc_shell", async (req, res) => {
7688
+ await s.discoveryDone;
7689
+ const url = new URL(req.url ?? "", "http://localhost");
7690
+ const pathname = url.searchParams.get("pathname");
7691
+ const routeName = url.searchParams.get("routeName");
7692
+ const version = url.searchParams.get("version");
7693
+ const ttlRaw = url.searchParams.get("ttl");
7694
+ if (!pathname || !routeName || !version || !ttlRaw) {
7695
+ res.statusCode = 400;
7696
+ res.end("Missing pathname/routeName/version/ttl");
7697
+ return;
7698
+ }
7699
+ const ttl = Number(ttlRaw);
7700
+ const swrRaw = url.searchParams.get("swr");
7701
+ const swr = swrRaw === null ? void 0 : Number(swrRaw);
7702
+ const tagsRaw = url.searchParams.get("tags");
7703
+ const tags = tagsRaw ? tagsRaw.split(",") : void 0;
7704
+ const rscEnvMain = server.environments?.rsc;
7705
+ let rscRealm = null;
7706
+ let ssrRealm = null;
7707
+ let ssrEntryId;
7708
+ if (rscEnvMain?.runner && s.resolvedEntryPath) {
7709
+ try {
7710
+ await rscEnvMain.runner.import(s.resolvedEntryPath);
7711
+ } catch (err) {
7712
+ res.statusCode = 500;
7713
+ res.end(`Shell capture module refresh failed: ${err.message}`);
7714
+ return;
7715
+ }
7716
+ rscRealm = rscEnvMain;
7717
+ ssrRealm = server.environments?.ssr;
7718
+ ssrEntryId = server.environments?.ssr?.config?.build?.rollupOptions?.input?.index ?? VIRTUAL_IDS.ssr;
7719
+ } else {
7720
+ const tempRscEnv = await getOrCreateTempServer();
7721
+ if (tempRscEnv) {
7722
+ try {
7723
+ await importEntryAndRegistry(tempRscEnv);
7724
+ } catch (err) {
7725
+ res.statusCode = 500;
7726
+ res.end(`Shell capture module refresh failed: ${err.message}`);
7727
+ return;
7728
+ }
7729
+ }
7730
+ rscRealm = tempRscEnv;
7731
+ ssrRealm = prerenderTempServer?.environments?.ssr;
7732
+ ssrEntryId = "virtual:entry-ssr";
7733
+ }
7734
+ if (!rscRealm?.runner || !ssrRealm?.runner) {
7735
+ res.statusCode = 503;
7736
+ res.end("Shell capture runners not available");
7737
+ return;
7738
+ }
7739
+ let registry = null;
7740
+ try {
7741
+ const serverMod = await rscRealm.runner.import(
7742
+ "@rangojs/router/server"
7743
+ );
7744
+ registry = serverMod.RouterRegistry ?? null;
7745
+ } catch {
7746
+ registry = null;
7747
+ }
7748
+ if (!registry || registry.size === 0) {
7749
+ res.statusCode = 503;
7750
+ res.end("Shell capture registry not available");
7751
+ return;
7752
+ }
7753
+ const cacheKey = `shell|${pathname}|r=${routeName}|t=${ttl}|s=${swr ?? ""}|g=${(tags ?? []).join("+")}|v=${version}`;
7754
+ for (const [, routerInstance] of registry) {
7755
+ if (typeof routerInstance.match !== "function") continue;
7756
+ const cached = devPrerenderCache.get(routerInstance, cacheKey);
7757
+ if (cached !== void 0) {
7758
+ res.setHeader("content-type", "application/json");
7759
+ res.setHeader("x-rango-shell-dev", "HIT");
7760
+ res.end(cached);
7761
+ return;
7762
+ }
7763
+ }
7764
+ if (s.devServerOrigin) {
7765
+ try {
7766
+ const probe = await fetch(
7767
+ `${s.devServerOrigin}/__rsc_prerender?pathname=${encodeURIComponent(pathname)}&routeName=${encodeURIComponent(routeName)}`,
7768
+ { signal: AbortSignal.timeout(1e4) }
7769
+ );
7770
+ if (!probe.ok) {
7771
+ res.statusCode = 404;
7772
+ res.end("Route is not prerenderable");
7773
+ return;
7774
+ }
7775
+ } catch {
7776
+ res.statusCode = 404;
7777
+ res.end("Prerender pre-flight failed");
7778
+ return;
7779
+ }
7780
+ }
7781
+ for (const [, routerInstance] of registry) {
7782
+ if (typeof routerInstance.match !== "function") continue;
7783
+ try {
7784
+ const ssrModule = await ssrRealm.runner.import(ssrEntryId);
7785
+ if (typeof ssrModule?.captureShellHTML !== "function") {
7786
+ res.statusCode = 404;
7787
+ res.end("SSR entry has no captureShellHTML");
7788
+ return;
7789
+ }
7790
+ const captureMod = await rscRealm.runner.import(
7791
+ "@rangojs/router/build/shell-capture"
7792
+ );
7793
+ const result = await captureMod.captureShellForBuild({
7794
+ router: routerInstance,
7795
+ urlPath: pathname,
7796
+ routeName,
7797
+ key: `${pathname}:shell`,
7798
+ ttl,
7799
+ swr,
7800
+ tags,
7801
+ buildEnv: s.resolvedBuildEnv,
7802
+ buildVersion: version,
7803
+ captureShellHTML: ssrModule.captureShellHTML,
7804
+ debug: !!debugDiscovery
7805
+ });
7806
+ if (result.outcome === "route-mismatch") continue;
7807
+ if (result.outcome !== "stored" || !result.entry) {
7808
+ res.statusCode = 404;
7809
+ res.end(`Shell capture ${result.outcome}`);
7810
+ return;
7811
+ }
7812
+ const body = JSON.stringify({
7813
+ entry: result.entry,
7814
+ ttl,
7815
+ swr,
7816
+ tags: result.tags,
7817
+ routeName
7818
+ });
7819
+ devPrerenderCache.set(routerInstance, cacheKey, body);
7820
+ res.setHeader("content-type", "application/json");
7821
+ res.setHeader("x-rango-shell-dev", "MISS");
7822
+ res.end(body);
7823
+ return;
7824
+ } catch (err) {
7825
+ console.warn(
7826
+ `[rango] Dev shell capture error for ${pathname} (route keeps runtime capture): ${err.message}`
7827
+ );
7828
+ res.statusCode = 404;
7829
+ res.end(`Shell capture error: ${err.message}`);
7830
+ return;
7831
+ }
7832
+ }
7833
+ res.statusCode = 404;
7834
+ res.end("No router matched");
7835
+ });
7287
7836
  if (opts?.staticRouteTypesGeneration !== false) {
7288
7837
  const isGeneratedRouteFile = (filePath) => filePath.endsWith(".gen.ts") && (filePath.includes("named-routes.gen.ts") || filePath.includes("urls.gen.ts"));
7289
7838
  const regenerateGeneratedRouteFiles = () => {
@@ -7453,7 +8002,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
7453
8002
  }
7454
8003
  const inRecoveryMode = !!s.lastDiscoveryError;
7455
8004
  try {
7456
- const source = readFileSync6(filePath, "utf-8");
8005
+ const source = readFileSync7(filePath, "utf-8");
7457
8006
  const trimmed = source.trimStart();
7458
8007
  const isUseClient = trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'");
7459
8008
  if (!inRecoveryMode && isUseClient) return;
@@ -7484,7 +8033,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
7484
8033
  if (hasCreateRouter) {
7485
8034
  const nestedRouterConflict = findNestedRouterConflict([
7486
8035
  ...s.cachedRouterFiles ?? [],
7487
- resolve10(filePath)
8036
+ resolve11(filePath)
7488
8037
  ]);
7489
8038
  if (nestedRouterConflict) {
7490
8039
  server.config.logger.error(
@@ -7534,8 +8083,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
7534
8083
  const buildStartTime = performance.now();
7535
8084
  debugDiscovery?.("build: start (env=%s)", this.environment?.name ?? "?");
7536
8085
  resetStagedBuildAssets(s.projectRoot);
7537
- s.prerenderManifestEntries = null;
7538
- s.staticManifestEntries = null;
8086
+ resetPrerenderCollection(s);
7539
8087
  await timed(
7540
8088
  debugDiscovery,
7541
8089
  "build acquireBuildEnv",
@@ -7590,20 +8138,59 @@ ${details}`,
7590
8138
  );
7591
8139
  } finally {
7592
8140
  delete globalThis.__rscRouterDiscoveryActive;
7593
- if (tempServer) {
7594
- await timed(
7595
- debugDiscovery,
7596
- "build tempServer.close",
7597
- () => tempServer.close()
7598
- );
8141
+ if (tempServer && s.shellCandidates?.length) {
8142
+ s.shellPhaseTempServer = tempServer;
8143
+ } else {
8144
+ if (tempServer) {
8145
+ await timed(
8146
+ debugDiscovery,
8147
+ "build tempServer.close",
8148
+ () => tempServer.close()
8149
+ );
8150
+ }
8151
+ await releaseBuildEnv(s);
7599
8152
  }
7600
- await releaseBuildEnv(s);
7601
8153
  debugDiscovery?.(
7602
8154
  "build discovery done (%sms)",
7603
8155
  (performance.now() - buildStartTime).toFixed(1)
7604
8156
  );
7605
8157
  }
7606
8158
  },
8159
+ // Post-build PPR shell capture (producer B, #699): runs after EVERY
8160
+ // environment bundle is written — the shell prelude embeds built client
8161
+ // asset URLs (bootstrap entry), which do not exist at buildStart. The
8162
+ // kept temp server and the buildEnv were deferred AS A PAIR in
8163
+ // buildStart's finally; this finally is the pair's success-path owner
8164
+ // (buildEnd below owns the aborted-build path) — the phase itself is a
8165
+ // pure producer and tears down only the globals it installs.
8166
+ buildApp: {
8167
+ order: "post",
8168
+ async handler(builder) {
8169
+ try {
8170
+ await runShellPrerenderPhase(s, builder);
8171
+ } finally {
8172
+ if (s.isBuildMode) {
8173
+ const tempServer = s.shellPhaseTempServer;
8174
+ s.shellPhaseTempServer = null;
8175
+ if (tempServer) await tempServer.close();
8176
+ await releaseBuildEnv(s);
8177
+ }
8178
+ }
8179
+ }
8180
+ },
8181
+ // An environment build failure aborts the builder before the buildApp
8182
+ // post hook — never leak the kept temp server (open handles hang the CLI)
8183
+ // or the deferred buildEnv (a live miniflare proxy).
8184
+ async buildEnd(error) {
8185
+ if (!error || !s.shellPhaseTempServer) return;
8186
+ const tempServer = s.shellPhaseTempServer;
8187
+ s.shellPhaseTempServer = null;
8188
+ try {
8189
+ await tempServer.close();
8190
+ } finally {
8191
+ await releaseBuildEnv(s);
8192
+ }
8193
+ },
7607
8194
  // Suppress vite's HMR cascade for our own gen-file writes.
7608
8195
  //
7609
8196
  // After every cf HMR cycle, refreshTempRscEnv → writeRouteTypesFiles
@@ -7768,6 +8355,7 @@ ${details}`,
7768
8355
  }
7769
8356
 
7770
8357
  // src/vite/rango.ts
8358
+ init_debug();
7771
8359
  var debugConfig = createRangoDebugger(NS.config);
7772
8360
  async function rango(options) {
7773
8361
  const rangoStart = performance.now();
@@ -7893,7 +8481,11 @@ async function rango(options) {
7893
8481
  }
7894
8482
  }
7895
8483
  });
7896
- plugins.push(createVirtualEntriesPlugin(finalEntries));
8484
+ plugins.push(
8485
+ createVirtualEntriesPlugin(finalEntries, void 0, {
8486
+ headScripts: resolvedOptions.headScripts
8487
+ })
8488
+ );
7897
8489
  plugins.push(performanceTracksPlugin());
7898
8490
  plugins.push(
7899
8491
  rsc({
@@ -7908,14 +8500,14 @@ async function rango(options) {
7908
8500
  name: "@rangojs/router:auto-discover",
7909
8501
  config(userConfig) {
7910
8502
  if (routerRef.path) return;
7911
- const root = userConfig.root ? resolve11(process.cwd(), userConfig.root) : process.cwd();
8503
+ const root = userConfig.root ? resolve12(process.cwd(), userConfig.root) : process.cwd();
7912
8504
  const toRootRelative = (abs) => (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
7913
8505
  const bulletList = (files) => files.map((f) => " - " + toRootRelative(f)).join("\n");
7914
8506
  if (explicitHostRouter) {
7915
8507
  routerRef.path = normalizeHostRouterEntry(
7916
8508
  explicitHostRouter,
7917
8509
  root,
7918
- existsSync8
8510
+ existsSync9
7919
8511
  );
7920
8512
  routerRef.kind = "host";
7921
8513
  return;
@@ -8073,7 +8665,11 @@ If this is a multi-app host router, export a createHostRouter() instance and set
8073
8665
  }
8074
8666
  }
8075
8667
  });
8076
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
8668
+ plugins.push(
8669
+ createVirtualEntriesPlugin(finalEntries, routerRef, {
8670
+ headScripts: resolvedOptions.headScripts
8671
+ })
8672
+ );
8077
8673
  plugins.push(performanceTracksPlugin());
8078
8674
  plugins.push(
8079
8675
  rsc({
@@ -8092,7 +8688,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
8092
8688
  if (!file.endsWith(".tsx") && !file.endsWith(".ts") && !file.endsWith(".jsx") && !file.endsWith(".js"))
8093
8689
  return;
8094
8690
  try {
8095
- const source = readFileSync7(file, "utf-8");
8691
+ const source = readFileSync8(file, "utf-8");
8096
8692
  if (hasUseClientDirective(source)) {
8097
8693
  return [];
8098
8694
  }
@@ -8119,7 +8715,8 @@ If this is a multi-app host router, export a createHostRouter() instance and set
8119
8715
  preset,
8120
8716
  prerenderOnError: options?.prerender?.onError,
8121
8717
  discovery: options?.discovery,
8122
- clientChunkCtx
8718
+ clientChunkCtx,
8719
+ headScripts: resolvedOptions.headScripts
8123
8720
  })
8124
8721
  );
8125
8722
  if (preset === "vercel") {
@@ -8143,7 +8740,7 @@ function poke() {
8143
8740
  apply: "serve",
8144
8741
  configureServer(server) {
8145
8742
  const stdin = process.stdin;
8146
- const debug11 = process.env.RANGO_POKE_DEBUG === "1";
8743
+ const debug12 = process.env.RANGO_POKE_DEBUG === "1";
8147
8744
  const triggerReload = (source) => {
8148
8745
  server.hot.send({ type: "full-reload", path: "*" });
8149
8746
  server.config.logger.info(` browser reload (${source})`, {
@@ -8176,7 +8773,7 @@ function poke() {
8176
8773
  lines.pop();
8177
8774
  return lines;
8178
8775
  };
8179
- if (debug11) {
8776
+ if (debug12) {
8180
8777
  server.config.logger.info(
8181
8778
  ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
8182
8779
  { timestamp: true }
@@ -8189,7 +8786,7 @@ function poke() {
8189
8786
  );
8190
8787
  }
8191
8788
  const onData = (data) => {
8192
- if (debug11) {
8789
+ if (debug12) {
8193
8790
  server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
8194
8791
  timestamp: true
8195
8792
  });