@rangojs/router 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/bin/rango.js +11 -1
- package/dist/types/browser/dev-discovery.d.ts +11 -0
- package/dist/types/browser/navigation-store.d.ts +3 -13
- package/dist/types/browser/prefetch/loader.d.ts +10 -0
- package/dist/types/browser/prefetch/runtime.d.ts +3 -0
- package/dist/types/browser/types.d.ts +4 -16
- package/dist/types/build/generate-manifest.d.ts +6 -0
- package/dist/types/cache/cache-scope.d.ts +7 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +10 -9
- package/dist/types/cache/document-cache.d.ts +3 -1
- package/dist/types/cache/memory-segment-store.d.ts +6 -6
- package/dist/types/cache/shell-snapshot.d.ts +21 -8
- package/dist/types/cache/types.d.ts +25 -7
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +9 -8
- package/dist/types/dev-discovery-protocol.d.ts +5 -0
- package/dist/types/prerender/store.d.ts +1 -0
- package/dist/types/route-definition/helpers-types.d.ts +6 -6
- package/dist/types/route-map-builder.d.ts +4 -29
- package/dist/types/router/match-handlers.d.ts +2 -3
- package/dist/types/router/prerender-match.d.ts +4 -1
- package/dist/types/router/router-interfaces.d.ts +3 -1
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +3 -4
- package/dist/types/router/transition-when.d.ts +13 -0
- package/dist/types/rsc/handler-context.d.ts +1 -0
- package/dist/types/rsc/render-pipeline.d.ts +3 -2
- package/dist/types/rsc/rsc-rendering.d.ts +5 -10
- package/dist/types/rsc/shell-capture.d.ts +4 -8
- package/dist/types/rsc/shell-serve.d.ts +2 -0
- package/dist/types/rsc/transition-gate.d.ts +10 -14
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/server/context.d.ts +16 -0
- package/dist/types/server/request-context.d.ts +22 -8
- package/dist/types/server.d.ts +1 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/index.d.ts +2 -2
- package/dist/types/testing/run-transition-when.d.ts +6 -5
- package/dist/types/testing/shell-status.d.ts +23 -3
- package/dist/types/types/route-entry.d.ts +3 -0
- package/dist/types/types/segments.d.ts +27 -22
- package/dist/types/urls/path-helper-types.d.ts +4 -3
- package/dist/types/vite/discovery/state.d.ts +3 -2
- package/dist/types/vite/utils/manifest-utils.d.ts +1 -1
- package/dist/vite/index.js +194 -107
- package/package.json +24 -20
- package/skills/cache-guide/SKILL.md +6 -3
- package/skills/caching/SKILL.md +1 -1
- package/skills/catalog.json +7 -1
- package/skills/deployment-caching/SKILL.md +176 -0
- package/skills/document-cache/SKILL.md +30 -3
- package/skills/ppr/SKILL.md +90 -14
- package/skills/prerender/SKILL.md +15 -8
- package/skills/rango/SKILL.md +20 -17
- package/skills/testing/SKILL.md +1 -1
- package/skills/testing/cache-prerender.md +5 -1
- package/skills/vercel/SKILL.md +22 -1
- package/skills/view-transitions/SKILL.md +12 -8
- package/src/browser/dev-discovery.ts +66 -0
- package/src/browser/navigation-client.ts +6 -0
- package/src/browser/navigation-store.ts +4 -271
- package/src/browser/partial-update.ts +7 -14
- package/src/browser/prefetch/cache.ts +1 -1
- package/src/browser/prefetch/loader.ts +110 -0
- package/src/browser/prefetch/runtime.ts +7 -0
- package/src/browser/react/Link.tsx +7 -10
- package/src/browser/react/NavigationProvider.tsx +1 -1
- package/src/browser/react/use-router.ts +1 -1
- package/src/browser/rsc-router.tsx +4 -2
- package/src/browser/types.ts +4 -27
- package/src/build/generate-manifest.ts +11 -0
- package/src/cache/cache-scope.ts +23 -7
- package/src/cache/cf/cf-cache-store.ts +63 -39
- package/src/cache/document-cache.ts +4 -2
- package/src/cache/memory-segment-store.ts +17 -6
- package/src/cache/shell-snapshot.ts +45 -15
- package/src/cache/types.ts +24 -6
- package/src/cache/vercel/vercel-cache-store.ts +52 -22
- package/src/dev-discovery-protocol.ts +11 -0
- package/src/prerender/build-shell-capture.ts +7 -1
- package/src/prerender/store.ts +5 -0
- package/src/route-definition/dsl-helpers.ts +7 -2
- package/src/route-definition/helpers-types.ts +6 -6
- package/src/route-map-builder.ts +56 -49
- package/src/router/handler-context.ts +13 -5
- package/src/router/lazy-includes.ts +1 -0
- package/src/router/match-api.ts +8 -4
- package/src/router/match-handlers.ts +44 -6
- package/src/router/match-middleware/cache-lookup.ts +10 -3
- package/src/router/prerender-match.ts +19 -2
- package/src/router/router-interfaces.ts +4 -0
- package/src/router/segment-resolution/static-store.ts +36 -10
- package/src/router/segment-resolution/view-transition-default.ts +9 -5
- package/src/router/transition-when.ts +76 -0
- package/src/router.ts +34 -3
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +3 -1
- package/src/rsc/loader-fetch.ts +2 -2
- package/src/rsc/manifest-init.ts +4 -11
- package/src/rsc/progressive-enhancement.ts +9 -14
- package/src/rsc/render-pipeline.ts +10 -2
- package/src/rsc/rsc-rendering.ts +319 -147
- package/src/rsc/shell-capture.ts +34 -11
- package/src/rsc/shell-serve.ts +3 -0
- package/src/rsc/transition-gate.ts +37 -40
- package/src/rsc/types.ts +2 -0
- package/src/server/context.ts +28 -0
- package/src/server/request-context.ts +38 -10
- package/src/server.ts +0 -2
- package/src/testing/dispatch.ts +1 -0
- package/src/testing/e2e/index.ts +5 -0
- package/src/testing/index.ts +9 -1
- package/src/testing/run-transition-when.ts +42 -9
- package/src/testing/shell-status.ts +92 -3
- package/src/types/route-entry.ts +3 -0
- package/src/types/segments.ts +27 -22
- package/src/urls/include-helper.ts +1 -0
- package/src/urls/path-helper-types.ts +4 -3
- package/src/urls/path-helper.ts +8 -4
- package/src/vite/discovery/bundle-postprocess.ts +10 -7
- package/src/vite/discovery/discover-routers.ts +7 -66
- package/src/vite/discovery/prerender-collection.ts +13 -2
- package/src/vite/discovery/state.ts +4 -4
- package/src/vite/discovery/virtual-module-codegen.ts +75 -42
- package/src/vite/plugins/version-injector.ts +0 -1
- package/src/vite/plugins/virtual-entries.ts +11 -1
- package/src/vite/router-discovery.ts +132 -22
- package/src/vite/utils/manifest-utils.ts +1 -4
- package/src/vite/utils/shared-utils.ts +7 -0
package/dist/vite/index.js
CHANGED
|
@@ -2309,7 +2309,17 @@ async function initializeApp() {
|
|
|
2309
2309
|
// context, including strictMode (default true) from createRouter. StrictMode
|
|
2310
2310
|
// is the default; createRouter({ strictMode: false }) ships the opt-out in the
|
|
2311
2311
|
// payload metadata. StrictMode emits no DOM, so toggling never changes markup.
|
|
2312
|
-
const { strictMode } = await initBrowserApp({ rscStream, deps });
|
|
2312
|
+
const { strictMode, initialPayload } = await initBrowserApp({ rscStream, deps });
|
|
2313
|
+
|
|
2314
|
+
if (import.meta.hot) {
|
|
2315
|
+
const { startDevDiscoveryHandshake } = await import(
|
|
2316
|
+
"@rangojs/router/internal/browser/dev-discovery"
|
|
2317
|
+
);
|
|
2318
|
+
startDevDiscoveryHandshake(
|
|
2319
|
+
initialPayload.metadata?.devDiscoveryEpoch,
|
|
2320
|
+
import.meta.hot
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2313
2323
|
|
|
2314
2324
|
const app = createElement(Rango);
|
|
2315
2325
|
hydrateRoot(
|
|
@@ -2520,7 +2530,7 @@ import { resolve } from "node:path";
|
|
|
2520
2530
|
// package.json
|
|
2521
2531
|
var package_default = {
|
|
2522
2532
|
name: "@rangojs/router",
|
|
2523
|
-
version: "0.
|
|
2533
|
+
version: "0.2.0",
|
|
2524
2534
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
2525
2535
|
keywords: [
|
|
2526
2536
|
"react",
|
|
@@ -2620,6 +2630,10 @@ var package_default = {
|
|
|
2620
2630
|
"react-server": "./src/rsc/handler.ts",
|
|
2621
2631
|
default: "./src/rsc/handler.ts"
|
|
2622
2632
|
},
|
|
2633
|
+
"./internal/browser/dev-discovery": {
|
|
2634
|
+
types: "./dist/types/browser/dev-discovery.d.ts",
|
|
2635
|
+
default: "./src/browser/dev-discovery.ts"
|
|
2636
|
+
},
|
|
2623
2637
|
"./cache": {
|
|
2624
2638
|
types: "./dist/types/cache/index.d.ts",
|
|
2625
2639
|
"react-server": "./src/cache/index.ts",
|
|
@@ -2699,7 +2713,8 @@ var package_default = {
|
|
|
2699
2713
|
typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
|
|
2700
2714
|
test: "playwright test",
|
|
2701
2715
|
"test:ui": "playwright test --ui",
|
|
2702
|
-
"test:hmr-local": "playwright test --project=dev-warmup --project=hmr-
|
|
2716
|
+
"test:hmr-local": "playwright test --project=dev-warmup --project=hmr-basename --project=hmr-prerender --no-deps --workers=1 && RANGO_E2E_ROUTE_HMR_ONLY=1 playwright test --project=hmr-routes --no-deps --workers=1",
|
|
2717
|
+
"test:hmr-routes-local": "RANGO_E2E_ROUTE_HMR_ONLY=1 playwright test --project=hmr-routes --no-deps --workers=1",
|
|
2703
2718
|
"test:unit": "pnpm run build:types && vitest run",
|
|
2704
2719
|
"test:unit:watch": "vitest",
|
|
2705
2720
|
"test:unit:rsc": "vitest run --config vitest.rsc.config.ts"
|
|
@@ -4236,6 +4251,11 @@ function onwarn(warning, defaultHandler) {
|
|
|
4236
4251
|
}
|
|
4237
4252
|
function getManualChunks(id) {
|
|
4238
4253
|
const normalized = Vite.normalizePath(id);
|
|
4254
|
+
if (/\/browser\/prefetch\/(?:runtime|fetch|queue|observer|policy|resource-ready)\.[cm]?[jt]sx?(?:[?#].*)?$/.test(
|
|
4255
|
+
normalized
|
|
4256
|
+
)) {
|
|
4257
|
+
return void 0;
|
|
4258
|
+
}
|
|
4239
4259
|
if (normalized.includes("node_modules/react/") || normalized.includes("node_modules/react-dom/") || normalized.includes("node_modules/react-server-dom-webpack/") || normalized.includes("node_modules/@vitejs/plugin-rsc/")) {
|
|
4240
4260
|
return "react";
|
|
4241
4261
|
}
|
|
@@ -4673,6 +4693,12 @@ import { readFileSync as readFileSync7 } from "node:fs";
|
|
|
4673
4693
|
import { createRequire as createRequire3, register } from "node:module";
|
|
4674
4694
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
4675
4695
|
|
|
4696
|
+
// src/dev-discovery-protocol.ts
|
|
4697
|
+
var DEV_DISCOVERY_READY_EVENT = "rango:dev-discovery-ready";
|
|
4698
|
+
var DEV_DISCOVERY_QUERY_EVENT = "rango:dev-discovery-query";
|
|
4699
|
+
var DEV_DISCOVERY_PROBE_HEADER = "x-rango-dev-discovery-probe";
|
|
4700
|
+
var DEV_DISCOVERY_EPOCH_HEADER = "x-rango-dev-discovery-epoch";
|
|
4701
|
+
|
|
4676
4702
|
// src/rsc/shell-serve.ts
|
|
4677
4703
|
import React from "react";
|
|
4678
4704
|
|
|
@@ -4955,8 +4981,6 @@ function createDiscoveryState(entryPath, opts) {
|
|
|
4955
4981
|
opts,
|
|
4956
4982
|
mergedRouteManifest: null,
|
|
4957
4983
|
perRouterManifests: [],
|
|
4958
|
-
mergedPrecomputedEntries: null,
|
|
4959
|
-
mergedRouteTrie: null,
|
|
4960
4984
|
perRouterTrieMap: /* @__PURE__ */ new Map(),
|
|
4961
4985
|
perRouterPrecomputedMap: /* @__PURE__ */ new Map(),
|
|
4962
4986
|
perRouterManifestDataMap: /* @__PURE__ */ new Map(),
|
|
@@ -4971,6 +4995,7 @@ function createDiscoveryState(entryPath, opts) {
|
|
|
4971
4995
|
resolvedPrerenderModules: void 0,
|
|
4972
4996
|
resolvedStaticModules: void 0,
|
|
4973
4997
|
discoveryDone: null,
|
|
4998
|
+
devDiscoveryEpoch: opts?.preset === "cloudflare" ? Date.now() : void 0,
|
|
4974
4999
|
devServerOrigin: null,
|
|
4975
5000
|
devServer: null,
|
|
4976
5001
|
selfWrittenGenFiles: /* @__PURE__ */ new Map(),
|
|
@@ -5887,15 +5912,21 @@ async function renderStaticHandlers(state, rscEnv, registry) {
|
|
|
5887
5912
|
if (def.options?.passthrough) continue;
|
|
5888
5913
|
const startHandler = performance.now();
|
|
5889
5914
|
let handled = false;
|
|
5890
|
-
|
|
5915
|
+
const owner = def.$$routerId;
|
|
5916
|
+
const owned = owner !== void 0 ? registry.get(owner) : void 0;
|
|
5917
|
+
const candidates = owned ? [owned] : [...registry.values()];
|
|
5918
|
+
for (const routerInstance of candidates) {
|
|
5891
5919
|
if (!routerInstance.renderStaticSegment) continue;
|
|
5892
5920
|
try {
|
|
5893
5921
|
const result = await routerInstance.renderStaticSegment(
|
|
5894
5922
|
def.handler,
|
|
5895
5923
|
def.$$id,
|
|
5896
|
-
|
|
5924
|
+
// Full route name when stamped; the historical $$routePrefix is a
|
|
5925
|
+
// name PREFIX (never in the root-scope registry) kept as fallback.
|
|
5926
|
+
def.$$routeName ?? def.$$routePrefix,
|
|
5897
5927
|
state.resolvedBuildEnv,
|
|
5898
|
-
!state.isBuildMode
|
|
5928
|
+
!state.isBuildMode,
|
|
5929
|
+
def.$$rootScoped
|
|
5899
5930
|
);
|
|
5900
5931
|
if (result) {
|
|
5901
5932
|
const hasHandles = result.handles !== "";
|
|
@@ -6107,12 +6138,10 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6107
6138
|
throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
|
|
6108
6139
|
}
|
|
6109
6140
|
const newMergedRouteManifest = {};
|
|
6110
|
-
const newMergedPrecomputedEntries = [];
|
|
6111
6141
|
const newPerRouterManifests = [];
|
|
6112
6142
|
const newPerRouterManifestDataMap = /* @__PURE__ */ new Map();
|
|
6113
6143
|
const newPerRouterPrecomputedMap = /* @__PURE__ */ new Map();
|
|
6114
6144
|
const newPerRouterTrieMap = /* @__PURE__ */ new Map();
|
|
6115
|
-
let mergedRouteTrailingSlash = {};
|
|
6116
6145
|
let routerMountIndex = 0;
|
|
6117
6146
|
const allManifests = [];
|
|
6118
6147
|
const clientChunkCtx = state.opts?.clientChunkCtx;
|
|
@@ -6133,6 +6162,7 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6133
6162
|
router.urlpatterns,
|
|
6134
6163
|
routerMountIndex,
|
|
6135
6164
|
{
|
|
6165
|
+
routerId: id,
|
|
6136
6166
|
...router.__basename ? { urlPrefix: router.__basename } : {},
|
|
6137
6167
|
...collectClientFallbackRef ? { collectClientFallbackRef } : {}
|
|
6138
6168
|
}
|
|
@@ -6170,20 +6200,17 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6170
6200
|
newPerRouterManifests.push({
|
|
6171
6201
|
id,
|
|
6172
6202
|
routeManifest: manifest.routeManifest,
|
|
6203
|
+
routeTrailingSlash: manifest.routeTrailingSlash,
|
|
6173
6204
|
routeSearchSchemas: manifest.routeSearchSchemas,
|
|
6174
6205
|
sourceFile: router.__sourceFile,
|
|
6175
6206
|
factoryOnlyPrefixes
|
|
6176
6207
|
});
|
|
6177
|
-
if (manifest.routeTrailingSlash) {
|
|
6178
|
-
Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
|
|
6179
|
-
}
|
|
6180
6208
|
const routerPrecomputed = [];
|
|
6181
6209
|
flattenLeafEntries(
|
|
6182
6210
|
manifest.prefixTree,
|
|
6183
6211
|
manifest.routeManifest,
|
|
6184
6212
|
routerPrecomputed
|
|
6185
6213
|
);
|
|
6186
|
-
newMergedPrecomputedEntries.push(...routerPrecomputed);
|
|
6187
6214
|
newPerRouterManifestDataMap.set(id, manifest.routeManifest);
|
|
6188
6215
|
newPerRouterPrecomputedMap.set(id, routerPrecomputed);
|
|
6189
6216
|
console.log(
|
|
@@ -6205,44 +6232,8 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6205
6232
|
allManifests.length,
|
|
6206
6233
|
(performance.now() - manifestGenStart).toFixed(1)
|
|
6207
6234
|
);
|
|
6208
|
-
let newMergedRouteTrie = null;
|
|
6209
6235
|
const trieStart = debug10 ? performance.now() : 0;
|
|
6210
6236
|
if (Object.keys(newMergedRouteManifest).length > 0) {
|
|
6211
|
-
const routeToStaticPrefix = {};
|
|
6212
|
-
for (const { manifest } of allManifests) {
|
|
6213
|
-
for (const name of Object.keys(manifest.routeManifest)) {
|
|
6214
|
-
if (!(name in routeToStaticPrefix)) {
|
|
6215
|
-
routeToStaticPrefix[name] = "";
|
|
6216
|
-
}
|
|
6217
|
-
}
|
|
6218
|
-
buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
|
|
6219
|
-
}
|
|
6220
|
-
const prerenderRouteNames = /* @__PURE__ */ new Set();
|
|
6221
|
-
const passthroughRouteNames = /* @__PURE__ */ new Set();
|
|
6222
|
-
const mergedResponseTypeRoutes = {};
|
|
6223
|
-
for (const { manifest } of allManifests) {
|
|
6224
|
-
if (manifest.prerenderRoutes) {
|
|
6225
|
-
for (const name of manifest.prerenderRoutes) {
|
|
6226
|
-
prerenderRouteNames.add(name);
|
|
6227
|
-
}
|
|
6228
|
-
}
|
|
6229
|
-
if (manifest.passthroughRoutes) {
|
|
6230
|
-
for (const name of manifest.passthroughRoutes) {
|
|
6231
|
-
passthroughRouteNames.add(name);
|
|
6232
|
-
}
|
|
6233
|
-
}
|
|
6234
|
-
if (manifest.responseTypeRoutes) {
|
|
6235
|
-
Object.assign(mergedResponseTypeRoutes, manifest.responseTypeRoutes);
|
|
6236
|
-
}
|
|
6237
|
-
}
|
|
6238
|
-
newMergedRouteTrie = buildRouteTrie(
|
|
6239
|
-
newMergedRouteManifest,
|
|
6240
|
-
routeToStaticPrefix,
|
|
6241
|
-
mergedRouteTrailingSlash,
|
|
6242
|
-
prerenderRouteNames,
|
|
6243
|
-
passthroughRouteNames,
|
|
6244
|
-
mergedResponseTypeRoutes
|
|
6245
|
-
);
|
|
6246
6237
|
for (const { id, manifest } of allManifests) {
|
|
6247
6238
|
const perRouterTrie = buildPerRouterTrie(manifest);
|
|
6248
6239
|
if (perRouterTrie) {
|
|
@@ -6255,15 +6246,10 @@ async function discoverRouters(state, rscEnv) {
|
|
|
6255
6246
|
(performance.now() - trieStart).toFixed(1)
|
|
6256
6247
|
);
|
|
6257
6248
|
state.mergedRouteManifest = newMergedRouteManifest;
|
|
6258
|
-
state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
|
|
6259
6249
|
state.perRouterManifests = newPerRouterManifests;
|
|
6260
6250
|
state.perRouterManifestDataMap = newPerRouterManifestDataMap;
|
|
6261
6251
|
state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
|
|
6262
6252
|
state.perRouterTrieMap = newPerRouterTrieMap;
|
|
6263
|
-
state.mergedRouteTrie = newMergedRouteTrie;
|
|
6264
|
-
if (serverMod.setRouteTrie && newMergedRouteTrie) {
|
|
6265
|
-
serverMod.setRouteTrie(newMergedRouteTrie);
|
|
6266
|
-
}
|
|
6267
6253
|
const perRouterSetters = [
|
|
6268
6254
|
[newPerRouterManifestDataMap, "setRouterManifest"],
|
|
6269
6255
|
[newPerRouterTrieMap, "setRouterTrie"],
|
|
@@ -6696,12 +6682,18 @@ function supplementGenFilesWithRuntimeRoutes(state) {
|
|
|
6696
6682
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6697
6683
|
import { dirname as dirname4, basename, join as join6 } from "node:path";
|
|
6698
6684
|
var MANIFEST_EXTERNALIZE_THRESHOLD = 512 * 1024;
|
|
6685
|
+
function devDiscoveryBootstrap(state) {
|
|
6686
|
+
if (state.isBuildMode || state.opts?.preset !== "cloudflare" || state.devDiscoveryEpoch === void 0) {
|
|
6687
|
+
return [];
|
|
6688
|
+
}
|
|
6689
|
+
const epoch = state.devDiscoveryEpoch;
|
|
6690
|
+
return [`globalThis.__RANGO_DEV_DISCOVERY_EPOCH = ${epoch};`];
|
|
6691
|
+
}
|
|
6699
6692
|
function generateRoutesManifestModule(state) {
|
|
6700
6693
|
const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
|
|
6701
6694
|
if (hasManifest) {
|
|
6702
6695
|
const genFileImports = [];
|
|
6703
|
-
const
|
|
6704
|
-
const routersWithoutGenFile = [];
|
|
6696
|
+
const genFileVarByEntry = [];
|
|
6705
6697
|
let varIdx = 0;
|
|
6706
6698
|
for (const entry of state.perRouterManifests) {
|
|
6707
6699
|
if (entry.sourceFile) {
|
|
@@ -6718,16 +6710,19 @@ function generateRoutesManifestModule(state) {
|
|
|
6718
6710
|
genFileImports.push(
|
|
6719
6711
|
`import { NamedRoutes as ${varName} } from ${JSON.stringify(genPath)};`
|
|
6720
6712
|
);
|
|
6721
|
-
|
|
6713
|
+
genFileVarByEntry.push(varName);
|
|
6722
6714
|
} else {
|
|
6723
|
-
|
|
6724
|
-
id: entry.id,
|
|
6725
|
-
manifest: entry.routeManifest
|
|
6726
|
-
});
|
|
6715
|
+
genFileVarByEntry.push(null);
|
|
6727
6716
|
}
|
|
6728
6717
|
}
|
|
6729
|
-
const
|
|
6730
|
-
|
|
6718
|
+
const serverImports = [
|
|
6719
|
+
"setCachedManifest",
|
|
6720
|
+
"setRouterManifest",
|
|
6721
|
+
...state.isBuildMode ? ["registerRouterManifestLoader"] : [],
|
|
6722
|
+
"clearAllRouterData"
|
|
6723
|
+
];
|
|
6724
|
+
const lines2 = [
|
|
6725
|
+
`import { ${serverImports.join(", ")} } from "@rangojs/router/server";`,
|
|
6731
6726
|
...genFileImports,
|
|
6732
6727
|
// Clear stale per-router cached data (manifest, trie, precomputed entries)
|
|
6733
6728
|
// before re-populating. In Cloudflare dev mode, program reloads re-evaluate
|
|
@@ -6736,53 +6731,56 @@ function generateRoutesManifestModule(state) {
|
|
|
6736
6731
|
// handler finds stale trie data and never rebuilds from updated urlpatterns.
|
|
6737
6732
|
`clearAllRouterData();`
|
|
6738
6733
|
];
|
|
6739
|
-
if (
|
|
6740
|
-
|
|
6734
|
+
if (varIdx > 0) {
|
|
6735
|
+
lines2.push(
|
|
6741
6736
|
`function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`
|
|
6742
6737
|
);
|
|
6743
6738
|
}
|
|
6744
|
-
|
|
6745
|
-
|
|
6739
|
+
const routerMapVars = [];
|
|
6740
|
+
for (const [i, entry] of state.perRouterManifests.entries()) {
|
|
6741
|
+
const mapVar = `__m${i}`;
|
|
6742
|
+
const genVar = genFileVarByEntry[i];
|
|
6743
|
+
const init = genVar ? `__flat(${genVar})` : jsonParseExpression(entry.routeManifest);
|
|
6744
|
+
lines2.push(`const ${mapVar} = ${init};`);
|
|
6745
|
+
routerMapVars.push(mapVar);
|
|
6746
|
+
}
|
|
6747
|
+
if (routerMapVars.length === 1) {
|
|
6748
|
+
lines2.push(`setCachedManifest(${routerMapVars[0]});`);
|
|
6746
6749
|
} else {
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
parts.push(`...${jsonParseExpression(manifest)}`);
|
|
6751
|
-
lines.push(`setCachedManifest({ ${parts.join(", ")} });`);
|
|
6750
|
+
lines2.push(
|
|
6751
|
+
`setCachedManifest({ ${routerMapVars.map((v) => `...${v}`).join(", ")} });`
|
|
6752
|
+
);
|
|
6752
6753
|
}
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
`setRouterManifest(${JSON.stringify(entry.id)}, ${jsonParseExpression(entry.routeManifest)});`
|
|
6754
|
+
for (const [i, entry] of state.perRouterManifests.entries()) {
|
|
6755
|
+
lines2.push(
|
|
6756
|
+
`setRouterManifest(${JSON.stringify(entry.id)}, ${routerMapVars[i]});`
|
|
6757
|
+
);
|
|
6758
|
+
}
|
|
6759
|
+
if (state.isBuildMode) {
|
|
6760
|
+
for (const routerId of state.perRouterManifestDataMap.keys()) {
|
|
6761
|
+
lines2.push(
|
|
6762
|
+
`registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`
|
|
6763
6763
|
);
|
|
6764
6764
|
}
|
|
6765
6765
|
}
|
|
6766
|
-
for (const routerId of state.perRouterManifestDataMap.keys()) {
|
|
6767
|
-
lines.push(
|
|
6768
|
-
`registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`
|
|
6769
|
-
);
|
|
6770
|
-
}
|
|
6771
6766
|
if (!state.isBuildMode && state.devServerOrigin) {
|
|
6772
|
-
|
|
6767
|
+
lines2.push(
|
|
6773
6768
|
`globalThis.__PRERENDER_DEV_URL = ${JSON.stringify(state.devServerOrigin)};`
|
|
6774
6769
|
);
|
|
6775
6770
|
}
|
|
6776
|
-
|
|
6771
|
+
lines2.push(...devDiscoveryBootstrap(state));
|
|
6772
|
+
return lines2.join("\n");
|
|
6777
6773
|
}
|
|
6774
|
+
const lines = [];
|
|
6778
6775
|
if (!state.isBuildMode) {
|
|
6779
6776
|
const origin = state.devServerOrigin || state.devServer?.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || state.devServer && `http://localhost:${state.devServer.config.server.port || 5173}`;
|
|
6780
6777
|
if (origin) {
|
|
6781
6778
|
state.devServerOrigin = origin;
|
|
6782
|
-
|
|
6779
|
+
lines.push(`globalThis.__PRERENDER_DEV_URL = ${JSON.stringify(origin)};`);
|
|
6783
6780
|
}
|
|
6784
6781
|
}
|
|
6785
|
-
|
|
6782
|
+
lines.push(...devDiscoveryBootstrap(state));
|
|
6783
|
+
return lines.join("\n") || `// Route manifest will be populated at runtime`;
|
|
6786
6784
|
}
|
|
6787
6785
|
function shortHash(input) {
|
|
6788
6786
|
let h = 2166136261;
|
|
@@ -6965,7 +6963,7 @@ function postprocessBundle(state) {
|
|
|
6965
6963
|
);
|
|
6966
6964
|
writeFileSync6(manifestPath, manifestCode);
|
|
6967
6965
|
totalBytes += Buffer.byteLength(manifestCode);
|
|
6968
|
-
const injection = `import
|
|
6966
|
+
const injection = `globalThis.__loadStaticManifestModule = () => import("./__static-manifest.js");
|
|
6969
6967
|
`;
|
|
6970
6968
|
writeFileSync6(rscEntryPath, injection + rscCode);
|
|
6971
6969
|
const totalKB = (totalBytes / 1024).toFixed(1);
|
|
@@ -7300,6 +7298,43 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7300
7298
|
if (s.isBuildMode) return;
|
|
7301
7299
|
if (globalThis.__rscRouterDiscoveryActive) return;
|
|
7302
7300
|
s.devServer = server;
|
|
7301
|
+
let workerReadyEpoch;
|
|
7302
|
+
const publishDevDiscoveryReady = (epoch) => {
|
|
7303
|
+
if (epoch <= (workerReadyEpoch ?? -1)) return;
|
|
7304
|
+
workerReadyEpoch = epoch;
|
|
7305
|
+
server.environments?.client?.hot?.send({
|
|
7306
|
+
type: "custom",
|
|
7307
|
+
event: DEV_DISCOVERY_READY_EVENT,
|
|
7308
|
+
data: { epoch }
|
|
7309
|
+
});
|
|
7310
|
+
debugDiscovery?.("hmr: workerd ready at epoch %d", epoch);
|
|
7311
|
+
};
|
|
7312
|
+
if (opts?.preset === "cloudflare") {
|
|
7313
|
+
const registeredClientHotChannels = /* @__PURE__ */ new Set();
|
|
7314
|
+
const registerDevDiscoveryHotChannels = () => {
|
|
7315
|
+
const clientHot = server.environments?.client?.hot;
|
|
7316
|
+
if (clientHot && !registeredClientHotChannels.has(clientHot)) {
|
|
7317
|
+
registeredClientHotChannels.add(clientHot);
|
|
7318
|
+
clientHot.on(
|
|
7319
|
+
DEV_DISCOVERY_QUERY_EVENT,
|
|
7320
|
+
(_payload, client) => {
|
|
7321
|
+
if (workerReadyEpoch === void 0) return;
|
|
7322
|
+
client.send(DEV_DISCOVERY_READY_EVENT, {
|
|
7323
|
+
epoch: workerReadyEpoch
|
|
7324
|
+
});
|
|
7325
|
+
}
|
|
7326
|
+
);
|
|
7327
|
+
}
|
|
7328
|
+
debugDiscovery?.(
|
|
7329
|
+
"hmr: dev discovery browser channel registered (client=%s)",
|
|
7330
|
+
!!clientHot
|
|
7331
|
+
);
|
|
7332
|
+
};
|
|
7333
|
+
registerDevDiscoveryHotChannels();
|
|
7334
|
+
if (server.httpServer && !server.httpServer.listening) {
|
|
7335
|
+
server.httpServer.once("listening", registerDevDiscoveryHotChannels);
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7303
7338
|
server.middlewares.use(internalDebugNoCacheMiddleware());
|
|
7304
7339
|
let resolveDiscovery;
|
|
7305
7340
|
const discoveryPromise = new Promise((resolve13) => {
|
|
@@ -7309,9 +7344,11 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7309
7344
|
const beginDiscoveryGate = gate.beginGate;
|
|
7310
7345
|
const resolveDiscoveryGate = gate.resolveGate;
|
|
7311
7346
|
const getDevServerOrigin = () => server.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || `http://localhost:${server.config.server.port || 5173}`;
|
|
7347
|
+
let devServerClosed = false;
|
|
7312
7348
|
let prerenderTempServer = null;
|
|
7313
7349
|
let prerenderNodeRegistry = null;
|
|
7314
7350
|
server.httpServer?.on("close", () => {
|
|
7351
|
+
devServerClosed = true;
|
|
7315
7352
|
if (prerenderTempServer) {
|
|
7316
7353
|
prerenderTempServer.close().catch(() => {
|
|
7317
7354
|
});
|
|
@@ -7587,12 +7624,6 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7587
7624
|
if (s.mergedRouteManifest && serverMod.setCachedManifest) {
|
|
7588
7625
|
serverMod.setCachedManifest(s.mergedRouteManifest);
|
|
7589
7626
|
}
|
|
7590
|
-
if (s.mergedPrecomputedEntries && s.mergedPrecomputedEntries.length > 0 && serverMod.setPrecomputedEntries) {
|
|
7591
|
-
serverMod.setPrecomputedEntries(s.mergedPrecomputedEntries);
|
|
7592
|
-
}
|
|
7593
|
-
if (s.mergedRouteTrie && serverMod.setRouteTrie) {
|
|
7594
|
-
serverMod.setRouteTrie(s.mergedRouteTrie);
|
|
7595
|
-
}
|
|
7596
7627
|
const perRouterSetters = [
|
|
7597
7628
|
[s.perRouterManifestDataMap, "setRouterManifest"],
|
|
7598
7629
|
[s.perRouterTrieMap, "setRouterTrie"],
|
|
@@ -7928,6 +7959,21 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7928
7959
|
writeCombinedRouteTypesWithTracking(s);
|
|
7929
7960
|
}
|
|
7930
7961
|
};
|
|
7962
|
+
const routeShapeSignature = () => JSON.stringify(
|
|
7963
|
+
s.perRouterManifests.map(
|
|
7964
|
+
({
|
|
7965
|
+
id,
|
|
7966
|
+
routeManifest,
|
|
7967
|
+
routeTrailingSlash,
|
|
7968
|
+
routeSearchSchemas
|
|
7969
|
+
}) => ({
|
|
7970
|
+
id,
|
|
7971
|
+
routeManifest,
|
|
7972
|
+
routeTrailingSlash,
|
|
7973
|
+
routeSearchSchemas
|
|
7974
|
+
})
|
|
7975
|
+
)
|
|
7976
|
+
);
|
|
7931
7977
|
const maybeHandleGeneratedRouteFileMutation = (filePath) => {
|
|
7932
7978
|
if (!isGeneratedRouteFile(filePath)) return false;
|
|
7933
7979
|
if (consumeSelfGenWrite(s, filePath)) return true;
|
|
@@ -7941,6 +7987,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7941
7987
|
const rscEnv = server.environments?.rsc;
|
|
7942
7988
|
const hasMainRunner = !!rscEnv?.runner;
|
|
7943
7989
|
if (!hasMainRunner && s.perRouterManifests.length === 0) return;
|
|
7990
|
+
let previousRouteShape = routeShapeSignature();
|
|
7944
7991
|
await gate.runRefreshCycle(async () => {
|
|
7945
7992
|
const hmrStart = performance.now();
|
|
7946
7993
|
try {
|
|
@@ -7989,7 +8036,19 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
7989
8036
|
);
|
|
7990
8037
|
s.lastDiscoveryError = null;
|
|
7991
8038
|
}
|
|
7992
|
-
if (rscEnv && !rscEnv.runner)
|
|
8039
|
+
if (rscEnv && !rscEnv.runner) {
|
|
8040
|
+
const nextRouteShape = routeShapeSignature();
|
|
8041
|
+
let expectedEpoch;
|
|
8042
|
+
if (nextRouteShape !== previousRouteShape) {
|
|
8043
|
+
expectedEpoch = Math.max(
|
|
8044
|
+
Date.now(),
|
|
8045
|
+
(s.devDiscoveryEpoch ?? 0) + 1
|
|
8046
|
+
);
|
|
8047
|
+
s.devDiscoveryEpoch = expectedEpoch;
|
|
8048
|
+
}
|
|
8049
|
+
previousRouteShape = nextRouteShape;
|
|
8050
|
+
forceCloudflareWorkerReload(rscEnv, expectedEpoch);
|
|
8051
|
+
}
|
|
7993
8052
|
} catch (err) {
|
|
7994
8053
|
s.lastDiscoveryError = {
|
|
7995
8054
|
message: err?.message ?? String(err),
|
|
@@ -8010,10 +8069,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
8010
8069
|
}
|
|
8011
8070
|
});
|
|
8012
8071
|
};
|
|
8013
|
-
const forceCloudflareWorkerReload = (rscEnv) => {
|
|
8072
|
+
const forceCloudflareWorkerReload = (rscEnv, expectedEpoch) => {
|
|
8014
8073
|
if (!rscEnv?.hot) return;
|
|
8015
|
-
|
|
8016
|
-
|
|
8074
|
+
const graph = rscEnv.moduleGraph;
|
|
8075
|
+
const reloadWorkerd = () => {
|
|
8017
8076
|
if (graph?.invalidateAll) {
|
|
8018
8077
|
graph.invalidateAll();
|
|
8019
8078
|
debugDiscovery?.("hmr: invalidated workerd rsc module graph");
|
|
@@ -8022,12 +8081,40 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
|
|
|
8022
8081
|
debugDiscovery?.(
|
|
8023
8082
|
"hmr: forced workerd rsc env reload (full-reload)"
|
|
8024
8083
|
);
|
|
8025
|
-
}
|
|
8084
|
+
};
|
|
8085
|
+
reloadWorkerd();
|
|
8086
|
+
if (expectedEpoch === void 0) return;
|
|
8087
|
+
void (async () => {
|
|
8088
|
+
const deadline = Date.now() + 15e3;
|
|
8089
|
+
do {
|
|
8090
|
+
if (devServerClosed || expectedEpoch !== s.devDiscoveryEpoch) {
|
|
8091
|
+
return;
|
|
8092
|
+
}
|
|
8093
|
+
await new Promise((resolve13) => setTimeout(resolve13, 25));
|
|
8094
|
+
if (devServerClosed || expectedEpoch !== s.devDiscoveryEpoch) {
|
|
8095
|
+
return;
|
|
8096
|
+
}
|
|
8097
|
+
try {
|
|
8098
|
+
const response = await fetch(getDevServerOrigin() + "/", {
|
|
8099
|
+
cache: "no-store",
|
|
8100
|
+
headers: {
|
|
8101
|
+
[DEV_DISCOVERY_PROBE_HEADER]: String(expectedEpoch)
|
|
8102
|
+
},
|
|
8103
|
+
signal: AbortSignal.timeout(1e3)
|
|
8104
|
+
});
|
|
8105
|
+
if (!devServerClosed && expectedEpoch === s.devDiscoveryEpoch && response.headers.get(DEV_DISCOVERY_EPOCH_HEADER) === String(expectedEpoch)) {
|
|
8106
|
+
publishDevDiscoveryReady(expectedEpoch);
|
|
8107
|
+
return;
|
|
8108
|
+
}
|
|
8109
|
+
reloadWorkerd();
|
|
8110
|
+
} catch {
|
|
8111
|
+
}
|
|
8112
|
+
} while (Date.now() < deadline);
|
|
8026
8113
|
debugDiscovery?.(
|
|
8027
|
-
"hmr: workerd
|
|
8028
|
-
|
|
8114
|
+
"hmr: workerd readiness probe timed out at epoch %d",
|
|
8115
|
+
expectedEpoch
|
|
8029
8116
|
);
|
|
8030
|
-
}
|
|
8117
|
+
})();
|
|
8031
8118
|
};
|
|
8032
8119
|
const scheduleRouteRegeneration = () => {
|
|
8033
8120
|
clearTimeout(routeChangeTimer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangojs/router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -100,6 +100,10 @@
|
|
|
100
100
|
"react-server": "./src/rsc/handler.ts",
|
|
101
101
|
"default": "./src/rsc/handler.ts"
|
|
102
102
|
},
|
|
103
|
+
"./internal/browser/dev-discovery": {
|
|
104
|
+
"types": "./dist/types/browser/dev-discovery.d.ts",
|
|
105
|
+
"default": "./src/browser/dev-discovery.ts"
|
|
106
|
+
},
|
|
103
107
|
"./cache": {
|
|
104
108
|
"types": "./dist/types/cache/index.d.ts",
|
|
105
109
|
"react-server": "./src/cache/index.ts",
|
|
@@ -172,18 +176,6 @@
|
|
|
172
176
|
"access": "public",
|
|
173
177
|
"tag": "latest"
|
|
174
178
|
},
|
|
175
|
-
"scripts": {
|
|
176
|
-
"build": "pnpm run build:types && pnpm exec esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm exec esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm exec esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
|
|
177
|
-
"build:types": "rm -rf dist/types && pnpm exec tsc -p tsconfig.types.json",
|
|
178
|
-
"prepublishOnly": "pnpm build",
|
|
179
|
-
"typecheck": "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
|
|
180
|
-
"test": "playwright test",
|
|
181
|
-
"test:ui": "playwright test --ui",
|
|
182
|
-
"test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
|
|
183
|
-
"test:unit": "pnpm run build:types && vitest run",
|
|
184
|
-
"test:unit:watch": "vitest",
|
|
185
|
-
"test:unit:rsc": "vitest run --config vitest.rsc.config.ts"
|
|
186
|
-
},
|
|
187
179
|
"dependencies": {
|
|
188
180
|
"@types/debug": "^4.1.12",
|
|
189
181
|
"@vitejs/plugin-rsc": "^0.5.27",
|
|
@@ -199,19 +191,19 @@
|
|
|
199
191
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
200
192
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
201
193
|
"@playwright/test": "^1.49.1",
|
|
202
|
-
"@shared/e2e": "workspace:*",
|
|
203
194
|
"@testing-library/dom": "^10.4.1",
|
|
204
195
|
"@testing-library/react": "^16.3.2",
|
|
205
196
|
"@types/node": "^24.10.1",
|
|
206
|
-
"@types/react": "
|
|
207
|
-
"@types/react-dom": "
|
|
197
|
+
"@types/react": "^19.2.7",
|
|
198
|
+
"@types/react-dom": "^19.2.3",
|
|
208
199
|
"esbuild": "^0.28.1",
|
|
209
200
|
"happy-dom": "^20.10.1",
|
|
210
201
|
"jiti": "^2.7.0",
|
|
211
|
-
"react": "
|
|
212
|
-
"react-dom": "
|
|
202
|
+
"react": "^19.2.6",
|
|
203
|
+
"react-dom": "^19.2.6",
|
|
213
204
|
"typescript": "^5.3.0",
|
|
214
|
-
"vitest": "^4.1.9"
|
|
205
|
+
"vitest": "^4.1.9",
|
|
206
|
+
"@shared/e2e": "0.0.1"
|
|
215
207
|
},
|
|
216
208
|
"peerDependencies": {
|
|
217
209
|
"@cloudflare/vite-plugin": "^1.42.1",
|
|
@@ -250,5 +242,17 @@
|
|
|
250
242
|
},
|
|
251
243
|
"engines": {
|
|
252
244
|
"node": ">=24.0.0"
|
|
245
|
+
},
|
|
246
|
+
"scripts": {
|
|
247
|
+
"build": "pnpm run build:types && pnpm exec esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm exec esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm exec esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
|
|
248
|
+
"build:types": "rm -rf dist/types && pnpm exec tsc -p tsconfig.types.json",
|
|
249
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
|
|
250
|
+
"test": "playwright test",
|
|
251
|
+
"test:ui": "playwright test --ui",
|
|
252
|
+
"test:hmr-local": "playwright test --project=dev-warmup --project=hmr-basename --project=hmr-prerender --no-deps --workers=1 && RANGO_E2E_ROUTE_HMR_ONLY=1 playwright test --project=hmr-routes --no-deps --workers=1",
|
|
253
|
+
"test:hmr-routes-local": "RANGO_E2E_ROUTE_HMR_ONLY=1 playwright test --project=hmr-routes --no-deps --workers=1",
|
|
254
|
+
"test:unit": "pnpm run build:types && vitest run",
|
|
255
|
+
"test:unit:watch": "vitest",
|
|
256
|
+
"test:unit:rsc": "vitest run --config vitest.rsc.config.ts"
|
|
253
257
|
}
|
|
254
|
-
}
|
|
258
|
+
}
|
|
@@ -131,8 +131,10 @@ recompute for a merely-aging entry.
|
|
|
131
131
|
layer.
|
|
132
132
|
- **Client forward/back** is SWR after a mutation — see "Correctness &
|
|
133
133
|
invalidation" → Client cache.
|
|
134
|
-
- **
|
|
135
|
-
|
|
134
|
+
- **Store-backed document layer** uses the HTTP `stale-while-revalidate`
|
|
135
|
+
directive as policy for `createDocumentCacheMiddleware`; see
|
|
136
|
+
`/document-cache`. A platform CDN may independently consume the same header
|
|
137
|
+
and bypass the function on hits; see `/deployment-caching`.
|
|
136
138
|
|
|
137
139
|
SWR softens normal TTL expiry, **not** a cross-deploy cold cache — a new build
|
|
138
140
|
has no stale entry to serve (see version-segmented store keys above).
|
|
@@ -485,5 +487,6 @@ overrides — see `/loader` for the full reference.
|
|
|
485
487
|
|
|
486
488
|
- `/caching` — cache() DSL setup, stores, nested boundaries
|
|
487
489
|
- `/use-cache` — "use cache" directive details, profiles, transforms, guards
|
|
488
|
-
- `/document-cache` —
|
|
490
|
+
- `/document-cache` — store-backed complete-response middleware
|
|
491
|
+
- `/deployment-caching` — in-function versus external CDN cache boundaries
|
|
489
492
|
- `/ppr` — PPR shell caching: cached HTML shell + live loader holes (different layer)
|
package/skills/caching/SKILL.md
CHANGED
|
@@ -171,7 +171,7 @@ export async function POST() {
|
|
|
171
171
|
| `updateTag(...tags)` | awaitable (`Promise<void>`) | server actions | immediate; next read is fresh |
|
|
172
172
|
| `revalidateTag(...tags)` | background (`void`) | route handlers / webhooks | background (non-blocking); next read re-renders fresh |
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
All three built-in stores support tags. For `CFCacheStore`, distributed (cross-colo)
|
|
175
175
|
invalidation requires a `kv` namespace — the tag-invalidation markers live in
|
|
176
176
|
that same namespace; there is **no** separate tag-invalidation store to wire.
|
|
177
177
|
If no tag-capable store is configured, `updateTag`/`revalidateTag` warn and no-op.
|
package/skills/catalog.json
CHANGED
|
@@ -69,9 +69,15 @@
|
|
|
69
69
|
"argumentHint": "",
|
|
70
70
|
"path": "skills/defer-hydration/SKILL.md"
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
"name": "deployment-caching",
|
|
74
|
+
"description": "Choose the deployment cache boundary for a Rango app: in-function segment/prerender/PPR caches, store-backed whole-response caching, or an external CDN cache. Use when comparing Cloudflare, Vercel, and Node deployments; deciding whether Cache-Control can reduce origin work; or reasoning about middleware, live loaders, PPR, and CDN behavior.",
|
|
75
|
+
"argumentHint": "[cloudflare|vercel|node]",
|
|
76
|
+
"path": "skills/deployment-caching/SKILL.md"
|
|
77
|
+
},
|
|
72
78
|
{
|
|
73
79
|
"name": "document-cache",
|
|
74
|
-
"description": "Cache
|
|
80
|
+
"description": "Cache complete HTTP responses in Rango's configured app store with createDocumentCacheMiddleware, using Cache-Control s-maxage as policy. Use when reusing a whole HTML/RSC response, comparing the store-backed middleware with a platform CDN cache, or deciding whether full-response caching is safe.",
|
|
75
81
|
"argumentHint": "[setup]",
|
|
76
82
|
"path": "skills/document-cache/SKILL.md"
|
|
77
83
|
},
|