@rangojs/router 0.0.0-experimental.150 → 0.0.0-experimental.151
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/dist/vite/index.js +146 -76
- package/package.json +2 -2
- package/skills/debug-manifest/SKILL.md +2 -10
- package/skills/middleware/SKILL.md +33 -0
- package/skills/ppr/SKILL.md +18 -3
- package/skills/prerender/SKILL.md +44 -1
- package/src/build/generate-manifest.ts +5 -36
- package/src/build/route-trie.ts +5 -50
- package/src/cache/cache-key-utils.ts +0 -1
- package/src/prerender/build-shell-capture.ts +215 -21
- package/src/router/handler-context.ts +10 -1
- package/src/router/middleware-types.ts +17 -0
- package/src/router/middleware.ts +4 -0
- package/src/router/prerender-match.ts +7 -0
- package/src/router/router-interfaces.ts +0 -6
- package/src/router/router-options.ts +0 -8
- package/src/router.ts +0 -4
- package/src/rsc/handler.ts +55 -51
- package/src/rsc/helpers.ts +461 -0
- package/src/rsc/progressive-enhancement.ts +66 -27
- package/src/rsc/rsc-rendering.ts +91 -57
- package/src/rsc/server-action.ts +49 -46
- package/src/rsc/shell-build-manifest.ts +50 -8
- package/src/rsc/shell-capture.ts +17 -6
- package/src/server/context.ts +34 -10
- package/src/server/request-context.ts +31 -0
- package/src/testing/internal/context.ts +9 -0
- package/src/testing/render-handler.ts +14 -0
- package/src/testing/run-middleware.ts +14 -0
- package/src/types/handler-context.ts +12 -1
- package/src/vite/discovery/discover-routers.ts +44 -52
- package/src/vite/discovery/virtual-module-codegen.ts +112 -2
- package/src/vite/rango.ts +31 -0
- package/src/vite/router-discovery.ts +131 -22
package/dist/vite/index.js
CHANGED
|
@@ -2520,7 +2520,7 @@ import { resolve } from "node:path";
|
|
|
2520
2520
|
// package.json
|
|
2521
2521
|
var package_default = {
|
|
2522
2522
|
name: "@rangojs/router",
|
|
2523
|
-
version: "0.0.0-experimental.
|
|
2523
|
+
version: "0.0.0-experimental.151",
|
|
2524
2524
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
2525
2525
|
keywords: [
|
|
2526
2526
|
"react",
|
|
@@ -2768,7 +2768,7 @@ var package_default = {
|
|
|
2768
2768
|
}
|
|
2769
2769
|
},
|
|
2770
2770
|
engines: {
|
|
2771
|
-
node: "
|
|
2771
|
+
node: ">=24.0.0"
|
|
2772
2772
|
}
|
|
2773
2773
|
};
|
|
2774
2774
|
|
|
@@ -5140,10 +5140,9 @@ function buildRouteToStaticPrefix(prefixTree, result) {
|
|
|
5140
5140
|
}
|
|
5141
5141
|
|
|
5142
5142
|
// src/build/route-trie.ts
|
|
5143
|
-
function buildRouteTrie(routeManifest,
|
|
5143
|
+
function buildRouteTrie(routeManifest, routeToStaticPrefix, routeTrailingSlash, prerenderRouteNames, passthroughRouteNames, responseTypeRoutes) {
|
|
5144
5144
|
const root = {};
|
|
5145
5145
|
for (const [routeName, pattern] of Object.entries(routeManifest)) {
|
|
5146
|
-
const ancestry = routeAncestry[routeName] || [];
|
|
5147
5146
|
const staticPrefix = routeToStaticPrefix[routeName] || "";
|
|
5148
5147
|
const trailingSlash = routeTrailingSlash?.[routeName];
|
|
5149
5148
|
const responseType = responseTypeRoutes?.[routeName];
|
|
@@ -5153,7 +5152,6 @@ function buildRouteTrie(routeManifest, routeAncestry, routeToStaticPrefix, route
|
|
|
5153
5152
|
insertRoute(root, segments, 0, {
|
|
5154
5153
|
n: routeName,
|
|
5155
5154
|
sp: staticPrefix,
|
|
5156
|
-
a: ancestry,
|
|
5157
5155
|
...trailingSlash ? { ts: trailingSlash } : {},
|
|
5158
5156
|
...prerenderRouteNames?.has(routeName) ? { pr: true } : {},
|
|
5159
5157
|
...passthroughRouteNames?.has(routeName) ? { pt: true } : {},
|
|
@@ -5186,8 +5184,7 @@ function sortSuffixParams(node) {
|
|
|
5186
5184
|
}
|
|
5187
5185
|
}
|
|
5188
5186
|
function buildPerRouterTrie(manifest) {
|
|
5189
|
-
|
|
5190
|
-
if (!ancestry || Object.keys(ancestry).length === 0) {
|
|
5187
|
+
if (Object.keys(manifest.routeManifest).length === 0) {
|
|
5191
5188
|
return null;
|
|
5192
5189
|
}
|
|
5193
5190
|
const routeToStaticPrefix = {};
|
|
@@ -5199,7 +5196,6 @@ function buildPerRouterTrie(manifest) {
|
|
|
5199
5196
|
}
|
|
5200
5197
|
return buildRouteTrie(
|
|
5201
5198
|
manifest.routeManifest,
|
|
5202
|
-
ancestry,
|
|
5203
5199
|
routeToStaticPrefix,
|
|
5204
5200
|
manifest.routeTrailingSlash,
|
|
5205
5201
|
manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0,
|
|
@@ -6114,7 +6110,6 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6114
6110
|
const newPerRouterManifestDataMap = /* @__PURE__ */ new Map();
|
|
6115
6111
|
const newPerRouterPrecomputedMap = /* @__PURE__ */ new Map();
|
|
6116
6112
|
const newPerRouterTrieMap = /* @__PURE__ */ new Map();
|
|
6117
|
-
let mergedRouteAncestry = {};
|
|
6118
6113
|
let mergedRouteTrailingSlash = {};
|
|
6119
6114
|
let routerMountIndex = 0;
|
|
6120
6115
|
const allManifests = [];
|
|
@@ -6177,9 +6172,6 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6177
6172
|
sourceFile: router.__sourceFile,
|
|
6178
6173
|
factoryOnlyPrefixes
|
|
6179
6174
|
});
|
|
6180
|
-
if (manifest._routeAncestry) {
|
|
6181
|
-
Object.assign(mergedRouteAncestry, manifest._routeAncestry);
|
|
6182
|
-
}
|
|
6183
6175
|
if (manifest.routeTrailingSlash) {
|
|
6184
6176
|
Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
|
|
6185
6177
|
}
|
|
@@ -6214,49 +6206,46 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6214
6206
|
let newMergedRouteTrie = null;
|
|
6215
6207
|
const trieStart = debug10 ? performance.now() : 0;
|
|
6216
6208
|
if (Object.keys(newMergedRouteManifest).length > 0) {
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
for (const
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
routeToStaticPrefix[name] = "";
|
|
6223
|
-
}
|
|
6224
|
-
}
|
|
6225
|
-
buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
|
|
6226
|
-
}
|
|
6227
|
-
const prerenderRouteNames = /* @__PURE__ */ new Set();
|
|
6228
|
-
const passthroughRouteNames = /* @__PURE__ */ new Set();
|
|
6229
|
-
const mergedResponseTypeRoutes = {};
|
|
6230
|
-
for (const { manifest } of allManifests) {
|
|
6231
|
-
if (manifest.prerenderRoutes) {
|
|
6232
|
-
for (const name of manifest.prerenderRoutes) {
|
|
6233
|
-
prerenderRouteNames.add(name);
|
|
6234
|
-
}
|
|
6235
|
-
}
|
|
6236
|
-
if (manifest.passthroughRoutes) {
|
|
6237
|
-
for (const name of manifest.passthroughRoutes) {
|
|
6238
|
-
passthroughRouteNames.add(name);
|
|
6239
|
-
}
|
|
6209
|
+
const routeToStaticPrefix = {};
|
|
6210
|
+
for (const { manifest } of allManifests) {
|
|
6211
|
+
for (const name of Object.keys(manifest.routeManifest)) {
|
|
6212
|
+
if (!(name in routeToStaticPrefix)) {
|
|
6213
|
+
routeToStaticPrefix[name] = "";
|
|
6240
6214
|
}
|
|
6241
|
-
|
|
6242
|
-
|
|
6215
|
+
}
|
|
6216
|
+
buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
|
|
6217
|
+
}
|
|
6218
|
+
const prerenderRouteNames = /* @__PURE__ */ new Set();
|
|
6219
|
+
const passthroughRouteNames = /* @__PURE__ */ new Set();
|
|
6220
|
+
const mergedResponseTypeRoutes = {};
|
|
6221
|
+
for (const { manifest } of allManifests) {
|
|
6222
|
+
if (manifest.prerenderRoutes) {
|
|
6223
|
+
for (const name of manifest.prerenderRoutes) {
|
|
6224
|
+
prerenderRouteNames.add(name);
|
|
6243
6225
|
}
|
|
6244
6226
|
}
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
routeToStaticPrefix,
|
|
6249
|
-
mergedRouteTrailingSlash,
|
|
6250
|
-
prerenderRouteNames,
|
|
6251
|
-
passthroughRouteNames,
|
|
6252
|
-
mergedResponseTypeRoutes
|
|
6253
|
-
);
|
|
6254
|
-
for (const { id, manifest } of allManifests) {
|
|
6255
|
-
const perRouterTrie = buildPerRouterTrie(manifest);
|
|
6256
|
-
if (perRouterTrie) {
|
|
6257
|
-
newPerRouterTrieMap.set(id, perRouterTrie);
|
|
6227
|
+
if (manifest.passthroughRoutes) {
|
|
6228
|
+
for (const name of manifest.passthroughRoutes) {
|
|
6229
|
+
passthroughRouteNames.add(name);
|
|
6258
6230
|
}
|
|
6259
6231
|
}
|
|
6232
|
+
if (manifest.responseTypeRoutes) {
|
|
6233
|
+
Object.assign(mergedResponseTypeRoutes, manifest.responseTypeRoutes);
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
newMergedRouteTrie = buildRouteTrie(
|
|
6237
|
+
newMergedRouteManifest,
|
|
6238
|
+
routeToStaticPrefix,
|
|
6239
|
+
mergedRouteTrailingSlash,
|
|
6240
|
+
prerenderRouteNames,
|
|
6241
|
+
passthroughRouteNames,
|
|
6242
|
+
mergedResponseTypeRoutes
|
|
6243
|
+
);
|
|
6244
|
+
for (const { id, manifest } of allManifests) {
|
|
6245
|
+
const perRouterTrie = buildPerRouterTrie(manifest);
|
|
6246
|
+
if (perRouterTrie) {
|
|
6247
|
+
newPerRouterTrieMap.set(id, perRouterTrie);
|
|
6248
|
+
}
|
|
6260
6249
|
}
|
|
6261
6250
|
}
|
|
6262
6251
|
debug10?.(
|
|
@@ -6702,7 +6691,9 @@ function supplementGenFilesWithRuntimeRoutes(state) {
|
|
|
6702
6691
|
}
|
|
6703
6692
|
|
|
6704
6693
|
// src/vite/discovery/virtual-module-codegen.ts
|
|
6694
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6705
6695
|
import { dirname as dirname4, basename, join as join6 } from "node:path";
|
|
6696
|
+
var MANIFEST_EXTERNALIZE_THRESHOLD = 512 * 1024;
|
|
6706
6697
|
function generateRoutesManifestModule(state) {
|
|
6707
6698
|
const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
|
|
6708
6699
|
if (hasManifest) {
|
|
@@ -6791,6 +6782,14 @@ function generateRoutesManifestModule(state) {
|
|
|
6791
6782
|
}
|
|
6792
6783
|
return `// Route manifest will be populated at runtime`;
|
|
6793
6784
|
}
|
|
6785
|
+
function shortHash(input) {
|
|
6786
|
+
let h = 2166136261;
|
|
6787
|
+
for (let i = 0; i < input.length; i++) {
|
|
6788
|
+
h ^= input.charCodeAt(i);
|
|
6789
|
+
h = Math.imul(h, 16777619);
|
|
6790
|
+
}
|
|
6791
|
+
return (h >>> 0).toString(36);
|
|
6792
|
+
}
|
|
6794
6793
|
function generatePerRouterModule(state, routerId) {
|
|
6795
6794
|
const routerEntry = state.perRouterManifests.find((e) => e.id === routerId);
|
|
6796
6795
|
const trie = state.perRouterTrieMap.get(routerId);
|
|
@@ -6817,20 +6816,49 @@ function generatePerRouterModule(state, routerId) {
|
|
|
6817
6816
|
lines.push(`export const manifest = ${jsonParseExpression(manifest)};`);
|
|
6818
6817
|
}
|
|
6819
6818
|
}
|
|
6820
|
-
|
|
6819
|
+
const hasTrie = !!trie;
|
|
6820
|
+
const hasEntries = !!entries && entries.length > 0;
|
|
6821
|
+
const preset = state.opts?.preset ?? "node";
|
|
6822
|
+
const override = process.env.RANGO_MANIFEST_TEXT;
|
|
6823
|
+
const payload = manifestPayload(trie, hasTrie, entries, hasEntries);
|
|
6824
|
+
const payloadJson = hasTrie || hasEntries ? JSON.stringify(payload) : "";
|
|
6825
|
+
const useTextModule = state.isBuildMode && preset === "cloudflare" && override !== "0" && (hasTrie || hasEntries) && (override === "1" || payloadJson.length >= MANIFEST_EXTERNALIZE_THRESHOLD);
|
|
6826
|
+
if (useTextModule) {
|
|
6827
|
+
const dir = join6(state.projectRoot, "node_modules", ".rango");
|
|
6828
|
+
mkdirSync2(dir, { recursive: true });
|
|
6829
|
+
const safeId = `${routerId.replace(/[^a-zA-Z0-9_-]/g, "_")}-${shortHash(routerId)}`;
|
|
6830
|
+
const filePath = join6(dir, `manifest-${safeId}.txt`).replaceAll("\\", "/");
|
|
6831
|
+
writeFileSync5(filePath, payloadJson);
|
|
6832
|
+
lines.push(`import __manifestJson from ${JSON.stringify(filePath)};`);
|
|
6833
|
+
lines.push(`const __manifestData = JSON.parse(__manifestJson);`);
|
|
6834
|
+
emitManifestExports(lines, hasTrie, hasEntries);
|
|
6835
|
+
return lines.join("\n");
|
|
6836
|
+
}
|
|
6837
|
+
if (hasTrie) {
|
|
6821
6838
|
lines.push(`export const trie = ${jsonParseExpression(trie)};`);
|
|
6822
6839
|
}
|
|
6823
|
-
if (
|
|
6840
|
+
if (hasEntries) {
|
|
6824
6841
|
lines.push(
|
|
6825
6842
|
`export const precomputedEntries = ${jsonParseExpression(entries)};`
|
|
6826
6843
|
);
|
|
6827
6844
|
}
|
|
6828
6845
|
return lines.join("\n") || "";
|
|
6829
6846
|
}
|
|
6847
|
+
function manifestPayload(trie, hasTrie, entries, hasEntries) {
|
|
6848
|
+
const payload = {};
|
|
6849
|
+
if (hasTrie) payload.t = trie;
|
|
6850
|
+
if (hasEntries) payload.p = entries;
|
|
6851
|
+
return payload;
|
|
6852
|
+
}
|
|
6853
|
+
function emitManifestExports(lines, hasTrie, hasEntries) {
|
|
6854
|
+
if (hasTrie) lines.push(`export const trie = __manifestData.t;`);
|
|
6855
|
+
if (hasEntries)
|
|
6856
|
+
lines.push(`export const precomputedEntries = __manifestData.p;`);
|
|
6857
|
+
}
|
|
6830
6858
|
|
|
6831
6859
|
// src/vite/discovery/bundle-postprocess.ts
|
|
6832
6860
|
import { resolve as resolve10 } from "node:path";
|
|
6833
|
-
import { readFileSync as readFileSync6, writeFileSync as
|
|
6861
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "node:fs";
|
|
6834
6862
|
function postprocessBundle(state) {
|
|
6835
6863
|
const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
|
|
6836
6864
|
const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
|
|
@@ -6866,7 +6894,7 @@ function postprocessBundle(state) {
|
|
|
6866
6894
|
target.brand
|
|
6867
6895
|
);
|
|
6868
6896
|
if (result) {
|
|
6869
|
-
|
|
6897
|
+
writeFileSync6(chunkPath, result.code);
|
|
6870
6898
|
const savedKB = (result.savedBytes / 1024).toFixed(1);
|
|
6871
6899
|
console.log(
|
|
6872
6900
|
`[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
|
|
@@ -6905,11 +6933,11 @@ function postprocessBundle(state) {
|
|
|
6905
6933
|
state.projectRoot,
|
|
6906
6934
|
"dist/rsc/__prerender-manifest.js"
|
|
6907
6935
|
);
|
|
6908
|
-
|
|
6936
|
+
writeFileSync6(manifestPath, manifestCode);
|
|
6909
6937
|
totalBytes += Buffer.byteLength(manifestCode);
|
|
6910
6938
|
const injection = `globalThis.__loadPrerenderManifestModule = () => import("./__prerender-manifest.js");
|
|
6911
6939
|
`;
|
|
6912
|
-
|
|
6940
|
+
writeFileSync6(rscEntryPath, injection + rscCode);
|
|
6913
6941
|
const totalKB = (totalBytes / 1024).toFixed(1);
|
|
6914
6942
|
console.log(
|
|
6915
6943
|
`[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
|
|
@@ -6943,11 +6971,11 @@ function postprocessBundle(state) {
|
|
|
6943
6971
|
state.projectRoot,
|
|
6944
6972
|
"dist/rsc/__static-manifest.js"
|
|
6945
6973
|
);
|
|
6946
|
-
|
|
6974
|
+
writeFileSync6(manifestPath, manifestCode);
|
|
6947
6975
|
totalBytes += Buffer.byteLength(manifestCode);
|
|
6948
6976
|
const injection = `import "./__static-manifest.js";
|
|
6949
6977
|
`;
|
|
6950
|
-
|
|
6978
|
+
writeFileSync6(rscEntryPath, injection + rscCode);
|
|
6951
6979
|
const totalKB = (totalBytes / 1024).toFixed(1);
|
|
6952
6980
|
console.log(
|
|
6953
6981
|
`[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
|
|
@@ -7106,6 +7134,7 @@ function ensureCloudflareProtocolLoaderRegistered() {
|
|
|
7106
7134
|
);
|
|
7107
7135
|
}
|
|
7108
7136
|
}
|
|
7137
|
+
var injectedShellNotReadyPaths = /* @__PURE__ */ new Set();
|
|
7109
7138
|
async function createTempRscServer(state, options = {}) {
|
|
7110
7139
|
ensureCloudflareProtocolLoaderRegistered();
|
|
7111
7140
|
const { default: rsc } = await import("@vitejs/plugin-rsc");
|
|
@@ -7338,14 +7367,14 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7338
7367
|
debugDiscovery?.(
|
|
7339
7368
|
"getOrCreateTempServer: cached temp runner reused"
|
|
7340
7369
|
);
|
|
7341
|
-
return existingEnv;
|
|
7370
|
+
return { env: existingEnv, error: null };
|
|
7342
7371
|
}
|
|
7343
7372
|
debugDiscovery?.(
|
|
7344
7373
|
"getOrCreateTempServer: server alive but registry missing \u2014 re-importing"
|
|
7345
7374
|
);
|
|
7346
7375
|
try {
|
|
7347
7376
|
await importEntryAndRegistry(existingEnv);
|
|
7348
|
-
return existingEnv;
|
|
7377
|
+
return { env: existingEnv, error: null };
|
|
7349
7378
|
} catch (err) {
|
|
7350
7379
|
debugDiscovery?.(
|
|
7351
7380
|
"getOrCreateTempServer: reuse import failed (%s) \u2014 closing orphan and creating fresh",
|
|
@@ -7370,6 +7399,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7370
7399
|
"getOrCreateTempServer: creating new temp server, entry=%s",
|
|
7371
7400
|
s.resolvedEntryPath ?? "(unset)"
|
|
7372
7401
|
);
|
|
7402
|
+
let createError = null;
|
|
7373
7403
|
try {
|
|
7374
7404
|
prerenderTempServer = await createTempRscServer(s, {
|
|
7375
7405
|
cacheDir: "node_modules/.vite_prerender",
|
|
@@ -7381,12 +7411,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7381
7411
|
const tempRscEnv = prerenderTempServer.environments?.rsc;
|
|
7382
7412
|
if (tempRscEnv?.runner) {
|
|
7383
7413
|
await importEntryAndRegistry(tempRscEnv);
|
|
7384
|
-
return tempRscEnv;
|
|
7414
|
+
return { env: tempRscEnv, error: null };
|
|
7385
7415
|
}
|
|
7386
7416
|
debugDiscovery?.(
|
|
7387
7417
|
"getOrCreateTempServer: tempRscEnv.runner unavailable"
|
|
7388
7418
|
);
|
|
7389
7419
|
} catch (err) {
|
|
7420
|
+
createError = err;
|
|
7390
7421
|
debugDiscovery?.(
|
|
7391
7422
|
"getOrCreateTempServer: FAILED message=%s",
|
|
7392
7423
|
err.message
|
|
@@ -7397,7 +7428,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7397
7428
|
});
|
|
7398
7429
|
prerenderTempServer = null;
|
|
7399
7430
|
prerenderNodeRegistry = null;
|
|
7400
|
-
return null;
|
|
7431
|
+
return { env: null, error: createError };
|
|
7401
7432
|
}
|
|
7402
7433
|
async function clearTempRegistries(tempRscEnv) {
|
|
7403
7434
|
try {
|
|
@@ -7421,7 +7452,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7421
7452
|
}
|
|
7422
7453
|
}
|
|
7423
7454
|
async function refreshTempRscEnv() {
|
|
7424
|
-
|
|
7455
|
+
const tempRscEnv = (await getOrCreateTempServer()).env;
|
|
7425
7456
|
if (!tempRscEnv) return null;
|
|
7426
7457
|
const envGraph = tempRscEnv.moduleGraph;
|
|
7427
7458
|
const serverGraph = prerenderTempServer?.moduleGraph;
|
|
@@ -7436,7 +7467,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7436
7467
|
prerenderTempServer = null;
|
|
7437
7468
|
prerenderNodeRegistry = null;
|
|
7438
7469
|
}
|
|
7439
|
-
return await getOrCreateTempServer();
|
|
7470
|
+
return (await getOrCreateTempServer()).env;
|
|
7440
7471
|
}
|
|
7441
7472
|
debugDiscovery?.(
|
|
7442
7473
|
"refreshTempRscEnv: invalidating module graph (%s)",
|
|
@@ -7474,11 +7505,11 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7474
7505
|
"acquireBuildEnv",
|
|
7475
7506
|
() => acquireBuildEnv(s, viteCommand, viteMode)
|
|
7476
7507
|
);
|
|
7477
|
-
tempRscEnv = await timed(
|
|
7508
|
+
tempRscEnv = (await timed(
|
|
7478
7509
|
debugDiscovery,
|
|
7479
7510
|
"getOrCreateTempServer",
|
|
7480
7511
|
() => getOrCreateTempServer()
|
|
7481
|
-
);
|
|
7512
|
+
)).env;
|
|
7482
7513
|
if (tempRscEnv) {
|
|
7483
7514
|
optimizerHashBefore2 = tempRscEnv.depsOptimizer?.metadata?.browserHash;
|
|
7484
7515
|
await timed(
|
|
@@ -7623,7 +7654,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7623
7654
|
registry = mainRegistry;
|
|
7624
7655
|
}
|
|
7625
7656
|
if (!registry) {
|
|
7626
|
-
const tempRscEnv = await getOrCreateTempServer();
|
|
7657
|
+
const tempRscEnv = (await getOrCreateTempServer()).env;
|
|
7627
7658
|
if (tempRscEnv) {
|
|
7628
7659
|
try {
|
|
7629
7660
|
await importEntryAndRegistry(tempRscEnv);
|
|
@@ -7720,6 +7751,32 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7720
7751
|
res.end("Missing pathname/routeName/version/ttl");
|
|
7721
7752
|
return;
|
|
7722
7753
|
}
|
|
7754
|
+
const sendNotReady = (detail) => {
|
|
7755
|
+
res.statusCode = 503;
|
|
7756
|
+
res.setHeader("x-rango-shell-dev", "NOT-READY");
|
|
7757
|
+
res.end(detail);
|
|
7758
|
+
};
|
|
7759
|
+
const isReoptimizing = (err) => err?.code === "ERR_OUTDATED_OPTIMIZED_DEP" || /Outdated Optimize Dep|optimized dependency|new dependencies optimized/i.test(
|
|
7760
|
+
String(err?.message ?? "")
|
|
7761
|
+
);
|
|
7762
|
+
const handledAsReoptimizing = (err) => {
|
|
7763
|
+
if (!isReoptimizing(err)) return false;
|
|
7764
|
+
sendNotReady(`Shell capture re-optimizing: ${err.message}`);
|
|
7765
|
+
return true;
|
|
7766
|
+
};
|
|
7767
|
+
const handleShellImportError = (err) => {
|
|
7768
|
+
if (handledAsReoptimizing(err)) return;
|
|
7769
|
+
res.statusCode = 500;
|
|
7770
|
+
res.end(`Shell capture module refresh failed: ${err.message}`);
|
|
7771
|
+
};
|
|
7772
|
+
if (process.env.RANGO_E2E_INJECT_SHELL_NOTREADY === "1" && !injectedShellNotReadyPaths.has(pathname)) {
|
|
7773
|
+
injectedShellNotReadyPaths.add(pathname);
|
|
7774
|
+
const injected = Object.assign(
|
|
7775
|
+
new Error("Outdated Optimize Dep (injected boot-race)"),
|
|
7776
|
+
{ code: "ERR_OUTDATED_OPTIMIZED_DEP" }
|
|
7777
|
+
);
|
|
7778
|
+
if (handledAsReoptimizing(injected)) return;
|
|
7779
|
+
}
|
|
7723
7780
|
const ttl = Number(ttlRaw);
|
|
7724
7781
|
const swrRaw = url.searchParams.get("swr");
|
|
7725
7782
|
const swr = swrRaw === null ? void 0 : Number(swrRaw);
|
|
@@ -7734,47 +7791,52 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7734
7791
|
let rscRealm = null;
|
|
7735
7792
|
let ssrRealm = null;
|
|
7736
7793
|
let ssrEntryId;
|
|
7794
|
+
let tempServerError = null;
|
|
7737
7795
|
if (rscEnvMain?.runner && s.resolvedEntryPath) {
|
|
7738
7796
|
try {
|
|
7739
7797
|
await rscEnvMain.runner.import(s.resolvedEntryPath);
|
|
7740
7798
|
} catch (err) {
|
|
7741
|
-
|
|
7742
|
-
res.end(`Shell capture module refresh failed: ${err.message}`);
|
|
7799
|
+
handleShellImportError(err);
|
|
7743
7800
|
return;
|
|
7744
7801
|
}
|
|
7745
7802
|
rscRealm = rscEnvMain;
|
|
7746
7803
|
ssrRealm = server.environments?.ssr;
|
|
7747
7804
|
ssrEntryId = server.environments?.ssr?.config?.build?.rollupOptions?.input?.index ?? VIRTUAL_IDS.ssr;
|
|
7748
7805
|
} else {
|
|
7749
|
-
const
|
|
7750
|
-
if (
|
|
7806
|
+
const tempResult = await getOrCreateTempServer();
|
|
7807
|
+
if (tempResult.env) {
|
|
7751
7808
|
try {
|
|
7752
|
-
await importEntryAndRegistry(
|
|
7809
|
+
await importEntryAndRegistry(tempResult.env);
|
|
7753
7810
|
} catch (err) {
|
|
7754
|
-
|
|
7755
|
-
res.end(`Shell capture module refresh failed: ${err.message}`);
|
|
7811
|
+
handleShellImportError(err);
|
|
7756
7812
|
return;
|
|
7757
7813
|
}
|
|
7814
|
+
} else {
|
|
7815
|
+
tempServerError = tempResult.error;
|
|
7758
7816
|
}
|
|
7759
|
-
rscRealm =
|
|
7817
|
+
rscRealm = tempResult.env;
|
|
7760
7818
|
ssrRealm = prerenderTempServer?.environments?.ssr;
|
|
7761
7819
|
ssrEntryId = "virtual:entry-ssr";
|
|
7762
7820
|
}
|
|
7763
7821
|
if (!rscRealm?.runner || !ssrRealm?.runner) {
|
|
7822
|
+
if (tempServerError && handledAsReoptimizing(tempServerError)) return;
|
|
7764
7823
|
res.statusCode = 503;
|
|
7765
7824
|
res.end("Shell capture runners not available");
|
|
7766
7825
|
return;
|
|
7767
7826
|
}
|
|
7768
7827
|
let registry = null;
|
|
7828
|
+
let registryError = null;
|
|
7769
7829
|
try {
|
|
7770
7830
|
const serverMod = await rscRealm.runner.import(
|
|
7771
7831
|
"@rangojs/router/server"
|
|
7772
7832
|
);
|
|
7773
7833
|
registry = serverMod.RouterRegistry ?? null;
|
|
7774
|
-
} catch {
|
|
7834
|
+
} catch (err) {
|
|
7835
|
+
registryError = err;
|
|
7775
7836
|
registry = null;
|
|
7776
7837
|
}
|
|
7777
7838
|
if (!registry || registry.size === 0) {
|
|
7839
|
+
if (registryError && handledAsReoptimizing(registryError)) return;
|
|
7778
7840
|
res.statusCode = 503;
|
|
7779
7841
|
res.end("Shell capture registry not available");
|
|
7780
7842
|
return;
|
|
@@ -7853,6 +7915,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7853
7915
|
res.end(body);
|
|
7854
7916
|
return;
|
|
7855
7917
|
} catch (err) {
|
|
7918
|
+
if (handledAsReoptimizing(err)) return;
|
|
7856
7919
|
console.warn(
|
|
7857
7920
|
`[rango] Dev shell capture error for ${pathname} (route keeps runtime capture): ${err.message}`
|
|
7858
7921
|
);
|
|
@@ -8388,6 +8451,7 @@ ${details}`,
|
|
|
8388
8451
|
// src/vite/rango.ts
|
|
8389
8452
|
init_debug();
|
|
8390
8453
|
var debugConfig = createRangoDebugger(NS.config);
|
|
8454
|
+
var SERVER_BUILD_TARGET = "esnext";
|
|
8391
8455
|
async function rango(options) {
|
|
8392
8456
|
const rangoStart = performance.now();
|
|
8393
8457
|
const resolvedOptions = options ?? { preset: "node" };
|
|
@@ -8635,6 +8699,9 @@ If this is a multi-app host router, export a createHostRouter() instance and set
|
|
|
8635
8699
|
},
|
|
8636
8700
|
ssr: {
|
|
8637
8701
|
...vercelServerEnv ?? {},
|
|
8702
|
+
build: {
|
|
8703
|
+
target: SERVER_BUILD_TARGET
|
|
8704
|
+
},
|
|
8638
8705
|
optimizeDeps: {
|
|
8639
8706
|
entries: [VIRTUAL_IDS.ssr],
|
|
8640
8707
|
include: [
|
|
@@ -8654,6 +8721,9 @@ If this is a multi-app host router, export a createHostRouter() instance and set
|
|
|
8654
8721
|
},
|
|
8655
8722
|
rsc: {
|
|
8656
8723
|
...vercelServerEnv ?? {},
|
|
8724
|
+
build: {
|
|
8725
|
+
target: SERVER_BUILD_TARGET
|
|
8726
|
+
},
|
|
8657
8727
|
optimizeDeps: {
|
|
8658
8728
|
entries: [VIRTUAL_IDS.rsc],
|
|
8659
8729
|
include: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangojs/router",
|
|
3
|
-
"version": "0.0.0-experimental.
|
|
3
|
+
"version": "0.0.0-experimental.151",
|
|
4
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -248,6 +248,6 @@
|
|
|
248
248
|
}
|
|
249
249
|
},
|
|
250
250
|
"engines": {
|
|
251
|
-
"node": "
|
|
251
|
+
"node": ">=24.0.0"
|
|
252
252
|
}
|
|
253
253
|
}
|
|
@@ -8,18 +8,10 @@ argument-hint:
|
|
|
8
8
|
|
|
9
9
|
Inspect the route manifest to verify parent relationships, shortCodes, and route structure.
|
|
10
10
|
|
|
11
|
-
## Quick Access
|
|
12
|
-
|
|
13
|
-
In development, visit:
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
http://localhost:PORT/?__debug_manifest
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Returns formatted JSON. The HTTP endpoint shape is `{ routerId, routeManifest, routeAncestry, routeTrie, precomputedEntries }` (see below for the programmatic API shape).
|
|
20
|
-
|
|
21
11
|
## Programmatic Access
|
|
22
12
|
|
|
13
|
+
Call `router.debugManifest()` — an `async` method on the router instance:
|
|
14
|
+
|
|
23
15
|
```typescript
|
|
24
16
|
import { router } from "./router.js";
|
|
25
17
|
|
|
@@ -197,6 +197,7 @@ export const myMiddleware: Middleware = async (ctx, next) => {
|
|
|
197
197
|
ctx.request; // Request object
|
|
198
198
|
ctx.url; // Parsed URL
|
|
199
199
|
ctx.params; // Route parameters
|
|
200
|
+
ctx.build; // in middleware: true only during Prerender + ppr build-shell capture (plain Prerender does not run middleware)
|
|
200
201
|
|
|
201
202
|
// Access platform bindings (plain bindings from createRouter<TEnv>())
|
|
202
203
|
ctx.env.DB; // D1Database
|
|
@@ -205,6 +206,9 @@ export const myMiddleware: Middleware = async (ctx, next) => {
|
|
|
205
206
|
// Set variables for downstream handlers (typed via Rango.Vars)
|
|
206
207
|
ctx.set("user", { id: "123", name: "John" });
|
|
207
208
|
|
|
209
|
+
// Opt the current request out of PPR shell lookup/capture.
|
|
210
|
+
ctx.dynamic();
|
|
211
|
+
|
|
208
212
|
// Continue to next middleware/handler
|
|
209
213
|
await next();
|
|
210
214
|
|
|
@@ -246,6 +250,35 @@ This works alongside `ctx.get("key")` / `ctx.set("key", value)` (global typing
|
|
|
246
250
|
via Rango.Vars augmentation). Use `createVar` for route-local or feature-scoped
|
|
247
251
|
data; use Rango.Vars for app-wide middleware state.
|
|
248
252
|
|
|
253
|
+
## Build-Time PPR Middleware
|
|
254
|
+
|
|
255
|
+
Normal `Prerender` Flight payload collection does not run middleware: there is
|
|
256
|
+
no request to wrap. The exception is `Prerender` + `ppr` build-shell capture.
|
|
257
|
+
After the Flight payload exists, the shell producer replays global and route
|
|
258
|
+
middleware for each generated URL before it captures HTML.
|
|
259
|
+
|
|
260
|
+
In that build-shell pass:
|
|
261
|
+
|
|
262
|
+
- `ctx.build === true`;
|
|
263
|
+
- `ctx.waitUntil()` is inert;
|
|
264
|
+
- `ctx.dynamic()` skips the baked shell for that URL;
|
|
265
|
+
- context variables set by middleware are visible to the shell render.
|
|
266
|
+
|
|
267
|
+
Use this to keep side effects predictable:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
export const commerceMiddleware: Middleware = async (ctx, next) => {
|
|
271
|
+
if (ctx.build) {
|
|
272
|
+
ctx.dynamic(); // leave this shell to runtime PPR
|
|
273
|
+
return next();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const session = await commerce.auth(ctx.request);
|
|
277
|
+
ctx.set("session", session);
|
|
278
|
+
return next();
|
|
279
|
+
};
|
|
280
|
+
```
|
|
281
|
+
|
|
249
282
|
## Redirect with State in Middleware
|
|
250
283
|
|
|
251
284
|
```typescript
|
package/skills/ppr/SKILL.md
CHANGED
|
@@ -21,11 +21,12 @@ shell is shared per host+URL, the holes are per request.
|
|
|
21
21
|
|
|
22
22
|
- You want the WHOLE response frozen, loader output included — see
|
|
23
23
|
`/document-cache`.
|
|
24
|
-
- You want
|
|
25
|
-
`
|
|
24
|
+
- You want build-time Flight segment payloads from `Static()`/`Prerender()` —
|
|
25
|
+
see `/prerender`. A `Prerender` page may also declare `ppr`; then producer B
|
|
26
|
+
can bake the HTML shell at build time while loaders stay live.
|
|
26
27
|
- You are unsure which cache layer you need — start at `/cache-guide`.
|
|
27
28
|
|
|
28
|
-
## Setup: one path option, no middleware
|
|
29
|
+
## Setup: one path option, no PPR middleware to mount
|
|
29
30
|
|
|
30
31
|
PPR is a DOCUMENT-level property declared on the page route via the `ppr` path
|
|
31
32
|
option. Serving is **integral to the router** — there is nothing to mount. The
|
|
@@ -169,6 +170,20 @@ On a document GET to a ppr route the router runs:
|
|
|
169
170
|
point is after the chain, an unauthorized request NEVER sees shell bytes — put
|
|
170
171
|
auth middleware anywhere (global or route DSL) and it guards PPR for free.
|
|
171
172
|
|
|
173
|
+
### Opting out per request with `ctx.dynamic()`
|
|
174
|
+
|
|
175
|
+
Middleware and handlers can call `ctx.dynamic()` to force this request back to
|
|
176
|
+
axis 1. In middleware it runs before the PPR commit point, so the router skips
|
|
177
|
+
shell lookup, HIT serving, and MISS capture for that request. In handlers it is
|
|
178
|
+
too late to prevent a MISS render from already happening, but it still prevents
|
|
179
|
+
the follow-up shell capture.
|
|
180
|
+
|
|
181
|
+
During `Prerender` + `ppr` build-shell capture, middleware is replayed with
|
|
182
|
+
`ctx.build === true`, `ctx.waitUntil()` inert, and the same `ctx.dynamic()`
|
|
183
|
+
opt-out. Use that for routes where the shell depends on runtime-only auth,
|
|
184
|
+
cookies, or side-effectful SDK calls. A skipped build shell can still be owned
|
|
185
|
+
later by runtime capture when runtime middleware does not call `ctx.dynamic()`.
|
|
186
|
+
|
|
172
187
|
## Verifying it works
|
|
173
188
|
|
|
174
189
|
The header exists on DOCUMENT responses only. A bare `curl` gets the HTML
|