@rangojs/router 0.0.0-experimental.98 → 0.0.0-experimental.98914650
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 +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +60 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +22 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +310 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +3 -6
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -11
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -42
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +20 -58
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -47
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
getImportedFnNames,
|
|
29
29
|
collectCreateExportBindings,
|
|
30
30
|
buildUnsupportedShapeWarning,
|
|
31
|
+
findUnsupportedCreateCallSites,
|
|
31
32
|
isExportOnlyFile,
|
|
32
33
|
} from "./expose-ids/export-analysis.js";
|
|
33
34
|
import {
|
|
@@ -39,7 +40,6 @@ import {
|
|
|
39
40
|
transformHandles,
|
|
40
41
|
transformLocationState,
|
|
41
42
|
generateWholeFileStubs,
|
|
42
|
-
generateExprStubs,
|
|
43
43
|
stubHandlerExprs,
|
|
44
44
|
transformHandlerIds,
|
|
45
45
|
} from "./expose-ids/handler-transform.js";
|
|
@@ -184,6 +184,16 @@ ${lazyImports.join(",\n")}
|
|
|
184
184
|
async buildStart() {
|
|
185
185
|
if (!isBuild) return;
|
|
186
186
|
|
|
187
|
+
// The loader pre-scan walks and reads the entire project, but the
|
|
188
|
+
// loaderRegistry it populates is consumed only by the RSC loader-manifest
|
|
189
|
+
// virtual module (and the transform hook already gates its registry writes
|
|
190
|
+
// with isRscEnv). plugin-rsc runs ~5 build passes (rsc-scan, ssr-scan, rsc,
|
|
191
|
+
// client, ssr) over this single shared plugin instance; without this gate
|
|
192
|
+
// the full-tree I/O ran on every pass with no consumer on the non-RSC
|
|
193
|
+
// ones. Skip only when the environment is known and not RSC, so an
|
|
194
|
+
// unavailable environment still falls through (no empty registry).
|
|
195
|
+
if (this.environment && this.environment.name !== "rsc") return;
|
|
196
|
+
|
|
187
197
|
const fs = await import("node:fs/promises");
|
|
188
198
|
|
|
189
199
|
const SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage"]);
|
|
@@ -335,7 +345,7 @@ ${lazyImports.join(",\n")}
|
|
|
335
345
|
}
|
|
336
346
|
if (_cachedAst !== undefined || _astParseFailed) return _cachedAst;
|
|
337
347
|
try {
|
|
338
|
-
_cachedAst = parseAst(code, {
|
|
348
|
+
_cachedAst = parseAst(code, { lang: "tsx" });
|
|
339
349
|
} catch {
|
|
340
350
|
_astParseFailed = true;
|
|
341
351
|
}
|
|
@@ -371,14 +381,21 @@ ${lazyImports.join(",\n")}
|
|
|
371
381
|
if (!hasCode) continue;
|
|
372
382
|
|
|
373
383
|
const fnNames = getFnNames(cfg.fnName);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
384
|
+
// Locate the real (comment/string-free) create* calls not covered by
|
|
385
|
+
// a supported, id-injectable export shape. Empty means every call is
|
|
386
|
+
// fine — in particular, a create*() token in a comment or string no
|
|
387
|
+
// longer trips a spurious warning.
|
|
388
|
+
const sites = findUnsupportedCreateCallSites(
|
|
389
|
+
code,
|
|
390
|
+
fnNames,
|
|
391
|
+
getBindings(code, fnNames),
|
|
392
|
+
);
|
|
393
|
+
if (sites.length === 0) continue;
|
|
377
394
|
|
|
378
395
|
const warnKey = `${id}::${cfg.fnName}`;
|
|
379
396
|
if (unsupportedShapeWarnings.has(warnKey)) continue;
|
|
380
397
|
unsupportedShapeWarnings.add(warnKey);
|
|
381
|
-
this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
|
|
398
|
+
this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName, sites));
|
|
382
399
|
}
|
|
383
400
|
|
|
384
401
|
// --- Loader: track for manifest (RSC env only) ---
|
|
@@ -424,17 +441,6 @@ ${lazyImports.join(",\n")}
|
|
|
424
441
|
if (wholeFile) return wholeFile;
|
|
425
442
|
}
|
|
426
443
|
|
|
427
|
-
// --- PrerenderHandler: RSC build module tracking ---
|
|
428
|
-
if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
|
|
429
|
-
const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
|
|
430
|
-
const exportNames = getBindings(code, fnNames).map(
|
|
431
|
-
(b) => b.exportNames[0],
|
|
432
|
-
);
|
|
433
|
-
if (exportNames.length > 0) {
|
|
434
|
-
prerenderHandlerModules.set(id, exportNames);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
444
|
// --- Inline handler extraction to virtual modules ---
|
|
439
445
|
// Runs before stubs/tracking so inline calls become imports, then
|
|
440
446
|
// the existing regex fast path handles both the original file's
|
|
@@ -710,14 +716,27 @@ ${lazyImports.join(",\n")}
|
|
|
710
716
|
}
|
|
711
717
|
}
|
|
712
718
|
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
719
|
+
// RSC build module tracking (prerender + static), consumed via the
|
|
720
|
+
// plugin API for prerender freezing. Export-binding sets are invariant
|
|
721
|
+
// across the inline-extraction loop, so tracking both here is equivalent
|
|
722
|
+
// to the pre-extraction prerender tracking this replaces.
|
|
723
|
+
if (isRscEnv && isBuild) {
|
|
724
|
+
const trackTypes: Array<
|
|
725
|
+
[boolean, HandlerTransformConfig, Map<string, string[]>]
|
|
726
|
+
> = [
|
|
727
|
+
[
|
|
728
|
+
hasPrerenderHandlerCode,
|
|
729
|
+
PRERENDER_CONFIG,
|
|
730
|
+
prerenderHandlerModules,
|
|
731
|
+
],
|
|
732
|
+
[hasStaticHandlerCode, STATIC_CONFIG, staticHandlerModules],
|
|
733
|
+
];
|
|
734
|
+
for (const [has, cfg, trackMap] of trackTypes) {
|
|
735
|
+
if (!has) continue;
|
|
736
|
+
const exportNames = getBindings(code, getFnNames(cfg.fnName)).map(
|
|
737
|
+
(b) => b.exportNames[0],
|
|
738
|
+
);
|
|
739
|
+
if (exportNames.length > 0) trackMap.set(id, exportNames);
|
|
721
740
|
}
|
|
722
741
|
}
|
|
723
742
|
|
|
@@ -758,48 +777,19 @@ ${lazyImports.join(",\n")}
|
|
|
758
777
|
isBuild,
|
|
759
778
|
) || changed;
|
|
760
779
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
) ||
|
|
773
|
-
|
|
774
|
-
// Non-RSC mixed-export file: replace Prerender() calls with stubs
|
|
775
|
-
// on the shared MagicString so sourcemaps stay accurate.
|
|
776
|
-
changed =
|
|
777
|
-
stubHandlerExprs(
|
|
778
|
-
PRERENDER_CONFIG,
|
|
779
|
-
bindings,
|
|
780
|
-
s,
|
|
781
|
-
filePath,
|
|
782
|
-
isBuild,
|
|
783
|
-
) || changed;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
if (hasStaticHandlerCode) {
|
|
787
|
-
const fnNames = getFnNames(STATIC_CONFIG.fnName);
|
|
788
|
-
const bindings = getBindings(code, fnNames);
|
|
789
|
-
if (isRscEnv) {
|
|
790
|
-
changed =
|
|
791
|
-
transformHandlerIds(
|
|
792
|
-
STATIC_CONFIG,
|
|
793
|
-
bindings,
|
|
794
|
-
s,
|
|
795
|
-
filePath,
|
|
796
|
-
isBuild,
|
|
797
|
-
) || changed;
|
|
798
|
-
} else {
|
|
799
|
-
changed =
|
|
800
|
-
stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) ||
|
|
801
|
-
changed;
|
|
802
|
-
}
|
|
780
|
+
// Prerender + Static share the RSC inject-id vs non-RSC stub dispatch.
|
|
781
|
+
// Call sites are disjoint (distinct fnNames), so loop order is irrelevant.
|
|
782
|
+
const finalHandlerConfigs = [
|
|
783
|
+
hasPrerenderHandlerCode && PRERENDER_CONFIG,
|
|
784
|
+
hasStaticHandlerCode && STATIC_CONFIG,
|
|
785
|
+
].filter((c): c is HandlerTransformConfig => !!c);
|
|
786
|
+
for (const cfg of finalHandlerConfigs) {
|
|
787
|
+
const bindings = getBindings(code, getFnNames(cfg.fnName));
|
|
788
|
+
changed =
|
|
789
|
+
(isRscEnv
|
|
790
|
+
? transformHandlerIds(cfg, bindings, s, filePath, isBuild)
|
|
791
|
+
: stubHandlerExprs(cfg, bindings, s, filePath, isBuild)) ||
|
|
792
|
+
changed;
|
|
803
793
|
}
|
|
804
794
|
|
|
805
795
|
if (!changed) return;
|
|
@@ -48,25 +48,18 @@ export function patchRsdwClientDebugInfoRecovery(code: string): {
|
|
|
48
48
|
|
|
49
49
|
export function performanceTracksOptimizeDepsPlugin(): {
|
|
50
50
|
name: string;
|
|
51
|
-
|
|
51
|
+
load(id: string): Promise<{ code: string } | null>;
|
|
52
52
|
} {
|
|
53
|
+
const RSDW_CLIENT_RE =
|
|
54
|
+
/react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
|
|
53
55
|
return {
|
|
54
56
|
name: "@rangojs/router:performance-tracks-optimize-deps",
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
async (args: { path: string }) => {
|
|
62
|
-
const code = await readFile(args.path, "utf8");
|
|
63
|
-
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
64
|
-
return {
|
|
65
|
-
contents: patched.code,
|
|
66
|
-
loader: "js",
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
);
|
|
57
|
+
async load(id: string): Promise<{ code: string } | null> {
|
|
58
|
+
const cleanId = id.split("?")[0] ?? id;
|
|
59
|
+
if (!RSDW_CLIENT_RE.test(cleanId)) return null;
|
|
60
|
+
const code = await readFile(cleanId, "utf8");
|
|
61
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
62
|
+
return { code: patched.code };
|
|
70
63
|
},
|
|
71
64
|
};
|
|
72
65
|
}
|
|
@@ -30,6 +30,8 @@ const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
|
|
|
30
30
|
// and should not be wrapped (children can't be cache-keyed).
|
|
31
31
|
const LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
|
|
32
32
|
|
|
33
|
+
export const USE_CACHE_DIRECTIVE_RE: RegExp = /^use cache(:\s*[\w-]+)?$/;
|
|
34
|
+
|
|
33
35
|
export function useCacheTransform(): Plugin {
|
|
34
36
|
let projectRoot = "";
|
|
35
37
|
let isBuild = false;
|
|
@@ -65,7 +67,6 @@ export function useCacheTransform(): Plugin {
|
|
|
65
67
|
|
|
66
68
|
const start = counter ? performance.now() : 0;
|
|
67
69
|
try {
|
|
68
|
-
// Lazy-load transform helpers
|
|
69
70
|
if (!rscTransforms) {
|
|
70
71
|
try {
|
|
71
72
|
rscTransforms = await import("@vitejs/plugin-rsc/transforms");
|
|
@@ -80,11 +81,10 @@ export function useCacheTransform(): Plugin {
|
|
|
80
81
|
transformHoistInlineDirective,
|
|
81
82
|
} = rscTransforms;
|
|
82
83
|
|
|
83
|
-
// Parse AST
|
|
84
84
|
let ast: any;
|
|
85
85
|
try {
|
|
86
86
|
const { parseAst } = await import("vite");
|
|
87
|
-
ast = parseAst(code);
|
|
87
|
+
ast = parseAst(code, { lang: "tsx" });
|
|
88
88
|
} catch {
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
@@ -92,7 +92,6 @@ export function useCacheTransform(): Plugin {
|
|
|
92
92
|
const filePath = normalizePath(path.relative(projectRoot, id));
|
|
93
93
|
const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
|
|
94
94
|
|
|
95
|
-
// Check for file-level "use cache"
|
|
96
95
|
if (hasDirective(ast.body, "use cache")) {
|
|
97
96
|
return transformFileLevelUseCache(
|
|
98
97
|
code,
|
|
@@ -105,8 +104,6 @@ export function useCacheTransform(): Plugin {
|
|
|
105
104
|
);
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
// Check for function-level "use cache" / "use cache: profileName"
|
|
109
|
-
// (only if there's no file-level directive but code still contains the string)
|
|
110
107
|
const functionResult = transformFunctionLevelUseCache(
|
|
111
108
|
code,
|
|
112
109
|
ast,
|
|
@@ -116,9 +113,6 @@ export function useCacheTransform(): Plugin {
|
|
|
116
113
|
transformHoistInlineDirective,
|
|
117
114
|
);
|
|
118
115
|
|
|
119
|
-
// Always check for near-miss directives, even when valid directives
|
|
120
|
-
// exist. A file may contain both valid and invalid "use cache" directives
|
|
121
|
-
// in different functions — the invalid ones should still warn.
|
|
122
116
|
warnOnNearMissDirectives(ast, id, this.warn.bind(this));
|
|
123
117
|
|
|
124
118
|
if (functionResult) return functionResult;
|
|
@@ -138,8 +132,7 @@ function transformFileLevelUseCache(
|
|
|
138
132
|
isLayoutOrTemplate: boolean,
|
|
139
133
|
transformWrapExport: (typeof import("@vitejs/plugin-rsc/transforms"))["transformWrapExport"],
|
|
140
134
|
) {
|
|
141
|
-
|
|
142
|
-
const nonFunctionExports: string[] = [];
|
|
135
|
+
const unconfirmedExports: string[] = [];
|
|
143
136
|
|
|
144
137
|
const { exportNames, output } = transformWrapExport(code, ast, {
|
|
145
138
|
runtime: (value: string, name: string) => {
|
|
@@ -148,30 +141,38 @@ function transformFileLevelUseCache(
|
|
|
148
141
|
},
|
|
149
142
|
rejectNonAsyncFunction: false,
|
|
150
143
|
filter: (name: string, meta: { isFunction?: boolean }) => {
|
|
151
|
-
// Skip default export of layout/template files (they receive children)
|
|
152
144
|
if (name === "default" && isLayoutOrTemplate) return false;
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
145
|
+
// isFunction is boolean | undefined: true = confirmed function, false =
|
|
146
|
+
// confirmed non-function, undefined = cannot tell statically (e.g. a
|
|
147
|
+
// factory/HOF initializer `const x = makeCached(fn)`). Deliberate policy:
|
|
148
|
+
// require a confirmed function and reject everything else, including
|
|
149
|
+
// indeterminate initializers that may be functions at runtime -- rewrite
|
|
150
|
+
// those as direct async functions. (Pre-#1246 plugin-rsc reported false,
|
|
151
|
+
// not undefined, here, so === false would wrongly wrap them post-bump.)
|
|
152
|
+
if (meta.isFunction !== true) {
|
|
153
|
+
unconfirmedExports.push(name);
|
|
156
154
|
return false;
|
|
157
155
|
}
|
|
158
156
|
return true;
|
|
159
157
|
},
|
|
160
158
|
});
|
|
161
159
|
|
|
162
|
-
if (
|
|
160
|
+
if (unconfirmedExports.length > 0) {
|
|
161
|
+
const plural = unconfirmedExports.length > 1;
|
|
163
162
|
throw new Error(
|
|
164
|
-
`[rango:use-cache] File-level "use cache" in ${sourceId}
|
|
165
|
-
`
|
|
166
|
-
`${
|
|
167
|
-
|
|
168
|
-
`
|
|
169
|
-
`
|
|
163
|
+
`[rango:use-cache] File-level "use cache" in ${sourceId} only wraps ` +
|
|
164
|
+
`exports that are statically-confirmed functions. ` +
|
|
165
|
+
`${plural ? "These exports are" : "This export is"} not: ` +
|
|
166
|
+
`${unconfirmedExports.map((n) => `"${n}"`).join(", ")}. ` +
|
|
167
|
+
`Declare them directly (export async function foo() {} or ` +
|
|
168
|
+
`export const foo = async () => {}). A factory or otherwise ` +
|
|
169
|
+
`statically-indeterminate initializer (export const foo = makeCached(fn)) ` +
|
|
170
|
+
`is rejected even if it returns a function at runtime -- rewrite it as a ` +
|
|
171
|
+
`direct async function, or move non-function exports to a separate module.`,
|
|
170
172
|
);
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
if (exportNames.length === 0) {
|
|
174
|
-
// Even if no exports were wrapped, strip the directive
|
|
175
176
|
const s = new MagicString(code);
|
|
176
177
|
const directive = findFileLevelDirective(ast);
|
|
177
178
|
if (directive) {
|
|
@@ -188,12 +189,10 @@ function transformFileLevelUseCache(
|
|
|
188
189
|
return;
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
// Prepend the import
|
|
192
192
|
output.prepend(
|
|
193
193
|
`import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};\n`,
|
|
194
194
|
);
|
|
195
195
|
|
|
196
|
-
// Replace the directive with a comment
|
|
197
196
|
const directive = findFileLevelDirective(ast);
|
|
198
197
|
if (directive) {
|
|
199
198
|
output.overwrite(
|
|
@@ -219,7 +218,7 @@ function transformFunctionLevelUseCache(
|
|
|
219
218
|
) {
|
|
220
219
|
try {
|
|
221
220
|
const { output, names } = transformHoistInlineDirective(code, ast, {
|
|
222
|
-
directive:
|
|
221
|
+
directive: USE_CACHE_DIRECTIVE_RE,
|
|
223
222
|
runtime: (
|
|
224
223
|
value: string,
|
|
225
224
|
name: string,
|
|
@@ -237,9 +236,6 @@ function transformFunctionLevelUseCache(
|
|
|
237
236
|
|
|
238
237
|
if (names.length === 0) return;
|
|
239
238
|
|
|
240
|
-
// Use a top-level import instead of await import() — the hoisted wrapper
|
|
241
|
-
// may be placed in a non-async context (e.g., inside a synchronous
|
|
242
|
-
// urls() callback) where await is not allowed.
|
|
243
239
|
output.prepend(
|
|
244
240
|
`import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};\n`,
|
|
245
241
|
);
|
|
@@ -254,9 +250,6 @@ function transformFunctionLevelUseCache(
|
|
|
254
250
|
}
|
|
255
251
|
}
|
|
256
252
|
|
|
257
|
-
/**
|
|
258
|
-
* Find the file-level "use cache" directive AST node for removal.
|
|
259
|
-
*/
|
|
260
253
|
function findFileLevelDirective(
|
|
261
254
|
ast: any,
|
|
262
255
|
): { start: number; end: number } | null {
|
|
@@ -273,23 +266,8 @@ function findFileLevelDirective(
|
|
|
273
266
|
return null;
|
|
274
267
|
}
|
|
275
268
|
|
|
276
|
-
/**
|
|
277
|
-
* The valid directive regex (must stay in sync with transformFunctionLevelUseCache).
|
|
278
|
-
*/
|
|
279
|
-
const VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Regex for near-miss: starts with "use cache:" but has invalid tokens.
|
|
283
|
-
*/
|
|
284
269
|
const NEAR_MISS_RE = /^use cache:\s*.+$/;
|
|
285
270
|
|
|
286
|
-
/**
|
|
287
|
-
* Walk the AST looking for string literals that look like malformed
|
|
288
|
-
* "use cache" directives and emit a Vite warning for each.
|
|
289
|
-
*
|
|
290
|
-
* This catches cases like `"use cache: bad.name"` or `"use cache: "`
|
|
291
|
-
* that the transform regex silently ignores.
|
|
292
|
-
*/
|
|
293
271
|
function warnOnNearMissDirectives(
|
|
294
272
|
ast: any,
|
|
295
273
|
fileId: string,
|
|
@@ -307,7 +285,7 @@ function warnOnNearMissDirectives(
|
|
|
307
285
|
if (
|
|
308
286
|
value.startsWith("use cache") &&
|
|
309
287
|
NEAR_MISS_RE.test(value) &&
|
|
310
|
-
!
|
|
288
|
+
!USE_CACHE_DIRECTIVE_RE.test(value)
|
|
311
289
|
) {
|
|
312
290
|
const profilePart = value.slice("use cache:".length).trim();
|
|
313
291
|
warn(
|
|
@@ -317,7 +295,6 @@ function warnOnNearMissDirectives(
|
|
|
317
295
|
}
|
|
318
296
|
}
|
|
319
297
|
|
|
320
|
-
// Walk into function bodies where directives appear
|
|
321
298
|
for (const key of Object.keys(node)) {
|
|
322
299
|
const child = node[key];
|
|
323
300
|
if (Array.isArray(child)) {
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel Build Output (Build Output API v3) emitter for `preset: "vercel"`.
|
|
3
|
+
*
|
|
4
|
+
* After the full app build, restructures dist/ into .vercel/output:
|
|
5
|
+
*
|
|
6
|
+
* .vercel/output/
|
|
7
|
+
* config.json routing: static first, else the function
|
|
8
|
+
* static/ dist/client (browser assets, served at /)
|
|
9
|
+
* functions/<name>.func/
|
|
10
|
+
* .vc-config.json Node serverless, response streaming
|
|
11
|
+
* index.mjs bundled launcher (srvx + @vercel/functions)
|
|
12
|
+
* rsc/ dist/rsc (self-contained RSC server bundle)
|
|
13
|
+
* ssr/ dist/ssr (rsc imports ../ssr/index.js)
|
|
14
|
+
*
|
|
15
|
+
* A prebuilt .vercel/output gets no `npm install`, so everything the function
|
|
16
|
+
* imports must physically live inside the .func directory. This relies on two
|
|
17
|
+
* things the vercel preset arranges (each is a failure only a real deploy — or
|
|
18
|
+
* the isolated smoke test — catches, since a local in-place run is masked by the
|
|
19
|
+
* app's own package.json + node_modules up the tree):
|
|
20
|
+
*
|
|
21
|
+
* 1. The rsc/ssr builds are fully bundled (`resolve.noExternal`, set in
|
|
22
|
+
* rango.ts for this preset). The node default externalizes node_modules
|
|
23
|
+
* deps, which works under `vite preview` but leaves bare imports
|
|
24
|
+
* (@vercel/functions, react-dom/server.edge, ...) that have no node_modules
|
|
25
|
+
* to resolve against on Vercel.
|
|
26
|
+
* 2. A `package.json` with `"type": "module"` is written into the .func dir.
|
|
27
|
+
* The rsc/ssr bundles are ESM but use a `.js` extension; without a
|
|
28
|
+
* type:module in scope the deployed (isolated) function loads them as
|
|
29
|
+
* CommonJS and fails on the first `import`.
|
|
30
|
+
*
|
|
31
|
+
* The launcher is bundled with srvx (the Web->Node streaming bridge, a
|
|
32
|
+
* @rangojs/router dependency) and @vercel/functions (resolved from the app)
|
|
33
|
+
* inlined, keeping the RSC bundle a runtime-relative external.
|
|
34
|
+
*
|
|
35
|
+
* Timing: this runs in the `buildApp` hook (order "post"), which fires once
|
|
36
|
+
* after every environment has built, so dist/{client,rsc,ssr} all exist.
|
|
37
|
+
* closeBundle is unusable here — it fires per environment, and twice for ssr
|
|
38
|
+
* (the server-reference scan and the real build), so it would run before
|
|
39
|
+
* dist/client exists. rango's own prerender/static emitters hardcode dist/rsc,
|
|
40
|
+
* so we build to dist/ and restructure here rather than retargeting outDir.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import type { Plugin } from "vite";
|
|
44
|
+
import { rm, mkdir, cp, writeFile } from "node:fs/promises";
|
|
45
|
+
import { existsSync } from "node:fs";
|
|
46
|
+
import { resolve, join } from "node:path";
|
|
47
|
+
import { createRequire } from "node:module";
|
|
48
|
+
import { pathToFileURL } from "node:url";
|
|
49
|
+
import type { RangoVercelOptions } from "../plugin-types.js";
|
|
50
|
+
|
|
51
|
+
// Minimal structural types for the esbuild API we use, resolved dynamically from
|
|
52
|
+
// the app so @rangojs/router does not depend on esbuild's type package.
|
|
53
|
+
interface EsbuildPluginBuild {
|
|
54
|
+
onResolve(
|
|
55
|
+
options: { filter: RegExp },
|
|
56
|
+
callback: () => { path: string; external: boolean },
|
|
57
|
+
): void;
|
|
58
|
+
}
|
|
59
|
+
type EsbuildBuild = (options: Record<string, unknown>) => Promise<unknown>;
|
|
60
|
+
interface EsbuildModule {
|
|
61
|
+
build?: EsbuildBuild;
|
|
62
|
+
default?: { build?: EsbuildBuild };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
|
|
66
|
+
import { waitUntil } from "@vercel/functions";
|
|
67
|
+
import rscHandler from "./rsc/index.js";
|
|
68
|
+
|
|
69
|
+
// The Vercel Node launcher invokes a Node (req, res) handler, not a Web fetch
|
|
70
|
+
// handler. srvx's toNodeHandler bridges the Rango Web fetch handler and pipes
|
|
71
|
+
// the streamed Response to the Node response (set supportsResponseStreaming).
|
|
72
|
+
const onVercel = Boolean(process.env.VERCEL);
|
|
73
|
+
|
|
74
|
+
const fetchHandler = (request) =>
|
|
75
|
+
rscHandler(request, {
|
|
76
|
+
env: process.env,
|
|
77
|
+
// Forward Vercel's waitUntil so cache writes / revalidation run off the
|
|
78
|
+
// response path. Omitted off-platform so those writes settle inline.
|
|
79
|
+
ctx: onVercel ? { waitUntil } : undefined,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export default toNodeHandler(fetchHandler);
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
async function assemble(
|
|
86
|
+
root: string,
|
|
87
|
+
options: RangoVercelOptions,
|
|
88
|
+
): Promise<void> {
|
|
89
|
+
const dist = join(root, "dist");
|
|
90
|
+
for (const dir of ["client", "rsc", "ssr"]) {
|
|
91
|
+
if (!existsSync(join(dist, dir))) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`[rango] preset "vercel": missing dist/${dir}. Run the production build first.`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const vercel = options.vercel ?? {};
|
|
99
|
+
const functionName = vercel.functionName ?? "index";
|
|
100
|
+
const out = join(root, ".vercel", "output");
|
|
101
|
+
const funcDir = join(out, "functions", `${functionName}.func`);
|
|
102
|
+
|
|
103
|
+
await rm(out, { recursive: true, force: true });
|
|
104
|
+
await mkdir(funcDir, { recursive: true });
|
|
105
|
+
|
|
106
|
+
// 1. Static client assets -> served from the CDN at the root.
|
|
107
|
+
await cp(join(dist, "client"), join(out, "static"), { recursive: true });
|
|
108
|
+
|
|
109
|
+
// 2. Server bundle into the function. Preserve the rsc -> ../ssr/index.js
|
|
110
|
+
// relative import by mirroring the dist/{rsc,ssr} layout inside the func.
|
|
111
|
+
await cp(join(dist, "rsc"), join(funcDir, "rsc"), { recursive: true });
|
|
112
|
+
await cp(join(dist, "ssr"), join(funcDir, "ssr"), { recursive: true });
|
|
113
|
+
|
|
114
|
+
// Prerender/static payload manifests (present only when routes are prerendered).
|
|
115
|
+
if (existsSync(join(dist, "static"))) {
|
|
116
|
+
await cp(join(dist, "static"), join(funcDir, "static"), {
|
|
117
|
+
recursive: true,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 3. Bundle the Node launcher. srvx (a @rangojs/router dependency) is aliased
|
|
122
|
+
// to its resolved path; @vercel/functions resolves from the app; the RSC
|
|
123
|
+
// server bundle stays a runtime-relative external.
|
|
124
|
+
const rangoRequire = createRequire(import.meta.url);
|
|
125
|
+
let srvxNodePath: string;
|
|
126
|
+
try {
|
|
127
|
+
srvxNodePath = rangoRequire.resolve("srvx/node");
|
|
128
|
+
} catch {
|
|
129
|
+
throw new Error(
|
|
130
|
+
'[rango] preset "vercel" requires "srvx" (a dependency of @rangojs/router). Reinstall dependencies.',
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// esbuild ships with Vite; resolve it from the app so we never add it as a
|
|
135
|
+
// @rangojs/router dependency. Minimal structural types avoid coupling to
|
|
136
|
+
// esbuild's type package at compile time.
|
|
137
|
+
const appRequire = createRequire(join(root, "package.json"));
|
|
138
|
+
let esbuildModule: EsbuildModule;
|
|
139
|
+
try {
|
|
140
|
+
esbuildModule = (await import(
|
|
141
|
+
pathToFileURL(appRequire.resolve("esbuild")).href
|
|
142
|
+
)) as EsbuildModule;
|
|
143
|
+
} catch {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'[rango] preset "vercel" requires "esbuild" to bundle the function launcher. It ships with Vite; reinstall dependencies.',
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const esbuildBuild = esbuildModule.build ?? esbuildModule.default?.build;
|
|
149
|
+
if (typeof esbuildBuild !== "function") {
|
|
150
|
+
throw new Error('[rango] preset "vercel": could not load esbuild.build.');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await esbuildBuild({
|
|
155
|
+
stdin: {
|
|
156
|
+
contents: LAUNCHER_SOURCE,
|
|
157
|
+
resolveDir: root,
|
|
158
|
+
sourcefile: "func-entry.mjs",
|
|
159
|
+
loader: "js",
|
|
160
|
+
},
|
|
161
|
+
outfile: join(funcDir, "index.mjs"),
|
|
162
|
+
bundle: true,
|
|
163
|
+
format: "esm",
|
|
164
|
+
platform: "node",
|
|
165
|
+
target: "node18",
|
|
166
|
+
alias: { "srvx/node": srvxNodePath },
|
|
167
|
+
plugins: [
|
|
168
|
+
{
|
|
169
|
+
name: "external-rsc-entry",
|
|
170
|
+
setup(b: EsbuildPluginBuild) {
|
|
171
|
+
b.onResolve({ filter: /^\.\/rsc\/index\.js$/ }, () => ({
|
|
172
|
+
path: "./rsc/index.js",
|
|
173
|
+
external: true,
|
|
174
|
+
}));
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
181
|
+
if (/@vercel\/functions/.test(message)) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' +
|
|
184
|
+
message,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 3b. Mark the function as ESM. The rsc/ssr bundles are .js ESM files with no
|
|
191
|
+
// package.json in scope on the deployed function (it is isolated at
|
|
192
|
+
// /var/task), so Node would load them as CommonJS and fail on `import`.
|
|
193
|
+
// Locally this is masked because the func inherits the app's
|
|
194
|
+
// "type": "module" up the tree; the deployed func has nothing above it.
|
|
195
|
+
await writeFile(
|
|
196
|
+
join(funcDir, "package.json"),
|
|
197
|
+
JSON.stringify({ type: "module" }, null, 2) + "\n",
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// 4. Function config: Node serverless with response streaming.
|
|
201
|
+
const vcConfig: Record<string, unknown> = {
|
|
202
|
+
runtime: vercel.runtime ?? "nodejs22.x",
|
|
203
|
+
handler: "index.mjs",
|
|
204
|
+
launcherType: "Nodejs",
|
|
205
|
+
shouldAddHelpers: false,
|
|
206
|
+
supportsResponseStreaming: true,
|
|
207
|
+
maxDuration: vercel.maxDuration ?? 30,
|
|
208
|
+
};
|
|
209
|
+
if (vercel.memory != null) vcConfig.memory = vercel.memory;
|
|
210
|
+
if (vercel.regions != null) vcConfig.regions = vercel.regions;
|
|
211
|
+
await writeFile(
|
|
212
|
+
join(funcDir, ".vc-config.json"),
|
|
213
|
+
JSON.stringify(vcConfig, null, 2) + "\n",
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// 5. Routing: filesystem (static/) first, then everything to the function.
|
|
217
|
+
await writeFile(
|
|
218
|
+
join(out, "config.json"),
|
|
219
|
+
JSON.stringify(
|
|
220
|
+
{
|
|
221
|
+
version: 3,
|
|
222
|
+
routes: [
|
|
223
|
+
{ handle: "filesystem" },
|
|
224
|
+
{ src: "/(.*)", dest: `/${functionName}` },
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
null,
|
|
228
|
+
2,
|
|
229
|
+
) + "\n",
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
console.log(
|
|
233
|
+
`[rango] assembled .vercel/output (function: ${functionName}.func)`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function createVercelOutputPlugin(options: RangoVercelOptions): Plugin {
|
|
238
|
+
let root = process.cwd();
|
|
239
|
+
let isBuild = false;
|
|
240
|
+
return {
|
|
241
|
+
name: "@rangojs/router:vercel-output",
|
|
242
|
+
configResolved(config) {
|
|
243
|
+
root = resolve(config.root);
|
|
244
|
+
isBuild = config.command === "build";
|
|
245
|
+
},
|
|
246
|
+
// buildApp runs once after the whole multi-environment build (rsc, client,
|
|
247
|
+
// ssr), so dist/ is complete here. closeBundle is unusable for this: it
|
|
248
|
+
// fires per environment, and twice for ssr (the server-reference scan and
|
|
249
|
+
// the real build), so it would run before dist/client exists.
|
|
250
|
+
buildApp: {
|
|
251
|
+
order: "post",
|
|
252
|
+
async handler() {
|
|
253
|
+
if (!isBuild) return;
|
|
254
|
+
await assemble(root, options);
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|