@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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/AGENTS.md +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- 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 +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- 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 +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- 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 +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- 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 +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- 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 +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- 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/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -0,0 +1,384 @@
|
|
|
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 { escapeRegExp } from "../../regex-escape.js";
|
|
50
|
+
import type {
|
|
51
|
+
RangoVercelOptions,
|
|
52
|
+
VercelPresetOptions,
|
|
53
|
+
} from "../plugin-types.js";
|
|
54
|
+
|
|
55
|
+
// Minimal structural types for the esbuild API we use, resolved dynamically from
|
|
56
|
+
// the app so @rangojs/router does not depend on esbuild's type package.
|
|
57
|
+
interface EsbuildPluginBuild {
|
|
58
|
+
onResolve(
|
|
59
|
+
options: { filter: RegExp },
|
|
60
|
+
callback: () => { path: string; external: boolean },
|
|
61
|
+
): void;
|
|
62
|
+
}
|
|
63
|
+
type EsbuildBuild = (options: Record<string, unknown>) => Promise<unknown>;
|
|
64
|
+
interface EsbuildModule {
|
|
65
|
+
build?: EsbuildBuild;
|
|
66
|
+
default?: { build?: EsbuildBuild };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
|
|
70
|
+
import { waitUntil } from "@vercel/functions";
|
|
71
|
+
import rscHandler from "./rsc/index.js";
|
|
72
|
+
|
|
73
|
+
// The Vercel Node launcher invokes a Node (req, res) handler, not a Web fetch
|
|
74
|
+
// handler. srvx's toNodeHandler bridges the Rango Web fetch handler and pipes
|
|
75
|
+
// the streamed Response to the Node response (set supportsResponseStreaming).
|
|
76
|
+
const onVercel = Boolean(process.env.VERCEL);
|
|
77
|
+
|
|
78
|
+
const fetchHandler = (request) =>
|
|
79
|
+
rscHandler(request, {
|
|
80
|
+
env: process.env,
|
|
81
|
+
// Forward Vercel's waitUntil so cache writes / revalidation run off the
|
|
82
|
+
// response path. Omitted off-platform so those writes settle inline.
|
|
83
|
+
ctx: onVercel ? { waitUntil } : undefined,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export default toNodeHandler(fetchHandler);
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Reject a non-Node runtime for the vercel preset. The preset only emits a Node
|
|
91
|
+
* serverless function (launcherType "Nodejs", bundled Node APIs, response
|
|
92
|
+
* streaming). Vercel's Edge runtime needs a different Build Output primitive
|
|
93
|
+
* (EdgeFunction, a Web-handler entry, no node_modules) that this assembler does
|
|
94
|
+
* not produce, so a non-nodejs runtime would emit a config the platform rejects
|
|
95
|
+
* or mis-runs. Fail fast at build time instead of shipping a broken function.
|
|
96
|
+
*/
|
|
97
|
+
export function assertVercelNodeRuntime(runtime: string | undefined): void {
|
|
98
|
+
if (runtime != null && !runtime.startsWith("nodejs")) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`[rango] preset "vercel": runtime "${runtime}" is not supported. ` +
|
|
101
|
+
`This preset emits a Node serverless function; use a "nodejs*" runtime ` +
|
|
102
|
+
`(default "nodejs22.x"). The Edge runtime is not supported.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The function name becomes a `.func` directory segment and the config.json
|
|
109
|
+
* route `dest` (`/${functionName}`). An empty or space/slash-containing value
|
|
110
|
+
* would land raw in both, producing a broken function path, so restrict it to a
|
|
111
|
+
* safe single path segment and fail loudly rather than emit unroutable output.
|
|
112
|
+
*/
|
|
113
|
+
export function assertValidVercelFunctionName(functionName: string): void {
|
|
114
|
+
if (!/^[A-Za-z0-9._-]+$/.test(functionName)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`[rango] preset "vercel": invalid functionName ${JSON.stringify(
|
|
117
|
+
functionName,
|
|
118
|
+
)}. Use letters, digits, ".", "_" or "-" only (it becomes the .func directory and the routing dest).`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The `.vc-config.json` body: a Node serverless function with streaming. */
|
|
124
|
+
export function buildVercelVcConfig(
|
|
125
|
+
vercel: VercelPresetOptions,
|
|
126
|
+
): Record<string, unknown> {
|
|
127
|
+
const vcConfig: Record<string, unknown> = {
|
|
128
|
+
runtime: vercel.runtime ?? "nodejs22.x",
|
|
129
|
+
handler: "index.mjs",
|
|
130
|
+
launcherType: "Nodejs",
|
|
131
|
+
shouldAddHelpers: false,
|
|
132
|
+
supportsResponseStreaming: true,
|
|
133
|
+
maxDuration: vercel.maxDuration ?? 30,
|
|
134
|
+
};
|
|
135
|
+
if (vercel.memory != null) vcConfig.memory = vercel.memory;
|
|
136
|
+
if (vercel.regions != null) vcConfig.regions = vercel.regions;
|
|
137
|
+
return vcConfig;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The Build Output API v3 `config.json` body. Routes, in order: long-cache the
|
|
142
|
+
* content-hashed assets under `${assetsDir}/` (safe to serve `immutable`;
|
|
143
|
+
* without this Vercel serves them `max-age=0, must-revalidate` and browsers
|
|
144
|
+
* re-request every asset on each visit), then the filesystem handler, then
|
|
145
|
+
* everything to the function. The header route uses `continue: true` so it falls
|
|
146
|
+
* through to the filesystem handler that actually serves the file.
|
|
147
|
+
*
|
|
148
|
+
* Route `src` values are REGEXES on Vercel, so the prefix is regex-escaped
|
|
149
|
+
* (an unescaped `assetsDir: "static.v2"` would immutable-stamp any
|
|
150
|
+
* `/static?v2/...` path, including function-rendered pages). An empty
|
|
151
|
+
* assetsDir (assets at the outDir root) gets no header route at all: there is
|
|
152
|
+
* no prefix separating hashed from non-hashed files, so fall back to Vercel's
|
|
153
|
+
* safe default headers. Two accepted edges, matching what other framework
|
|
154
|
+
* adapters (Astro, SvelteKit) emit: files a user places in
|
|
155
|
+
* `public/${assetsDir}/` land under the same prefix and are also stamped
|
|
156
|
+
* immutable (assemble() warns about the collision), and a request for a
|
|
157
|
+
* MISSING asset falls through to the function whose 404 carries the header.
|
|
158
|
+
*/
|
|
159
|
+
export function buildVercelOutputConfig(
|
|
160
|
+
functionName: string,
|
|
161
|
+
assetsDir: string,
|
|
162
|
+
): { version: number; routes: unknown[] } {
|
|
163
|
+
const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
|
|
164
|
+
const assetHeaderRoute = assetsPrefix
|
|
165
|
+
? [
|
|
166
|
+
{
|
|
167
|
+
src: `/${escapeRegExp(assetsPrefix)}/(.*)`,
|
|
168
|
+
headers: { "cache-control": "public, max-age=31536000, immutable" },
|
|
169
|
+
continue: true,
|
|
170
|
+
},
|
|
171
|
+
]
|
|
172
|
+
: [];
|
|
173
|
+
return {
|
|
174
|
+
version: 3,
|
|
175
|
+
routes: [
|
|
176
|
+
...assetHeaderRoute,
|
|
177
|
+
{ handle: "filesystem" },
|
|
178
|
+
{ src: "/(.*)", dest: `/${functionName}` },
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function assemble(
|
|
184
|
+
root: string,
|
|
185
|
+
options: RangoVercelOptions,
|
|
186
|
+
assetsDir: string,
|
|
187
|
+
publicDir: string,
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const vercel = options.vercel ?? {};
|
|
190
|
+
// Validate config before touching the build output.
|
|
191
|
+
assertVercelNodeRuntime(vercel.runtime);
|
|
192
|
+
|
|
193
|
+
// Files in public/<assetsDir>/ are copied into the same output prefix as the
|
|
194
|
+
// content-hashed build assets, so the immutable cache-control route stamps
|
|
195
|
+
// them too -- replacing such a file after a deploy never reaches returning
|
|
196
|
+
// visitors. Warn instead of silently pinning.
|
|
197
|
+
const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
|
|
198
|
+
if (publicDir && assetsPrefix && existsSync(join(publicDir, assetsPrefix))) {
|
|
199
|
+
console.warn(
|
|
200
|
+
`[rango] preset "vercel": ${join(publicDir, assetsPrefix)} exists. ` +
|
|
201
|
+
`Files under public/${assetsPrefix}/ share the /${assetsPrefix}/ URL prefix ` +
|
|
202
|
+
`with hashed build assets and will be served with a one-year immutable ` +
|
|
203
|
+
`cache-control header. Move un-hashed public files out of "${assetsPrefix}/".`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const dist = join(root, "dist");
|
|
208
|
+
for (const dir of ["client", "rsc", "ssr"]) {
|
|
209
|
+
if (!existsSync(join(dist, dir))) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`[rango] preset "vercel": missing dist/${dir}. Run the production build first.`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const functionName = vercel.functionName ?? "index";
|
|
216
|
+
assertValidVercelFunctionName(functionName);
|
|
217
|
+
const out = join(root, ".vercel", "output");
|
|
218
|
+
const funcDir = join(out, "functions", `${functionName}.func`);
|
|
219
|
+
|
|
220
|
+
await rm(out, { recursive: true, force: true });
|
|
221
|
+
await mkdir(funcDir, { recursive: true });
|
|
222
|
+
|
|
223
|
+
// 1. Static client assets -> served from the CDN at the root.
|
|
224
|
+
await cp(join(dist, "client"), join(out, "static"), { recursive: true });
|
|
225
|
+
|
|
226
|
+
// 2. Server bundle into the function. Preserve the rsc -> ../ssr/index.js
|
|
227
|
+
// relative import by mirroring the dist/{rsc,ssr} layout inside the func.
|
|
228
|
+
await cp(join(dist, "rsc"), join(funcDir, "rsc"), { recursive: true });
|
|
229
|
+
await cp(join(dist, "ssr"), join(funcDir, "ssr"), { recursive: true });
|
|
230
|
+
|
|
231
|
+
// Prerender/static payload manifests (present only when routes are prerendered).
|
|
232
|
+
if (existsSync(join(dist, "static"))) {
|
|
233
|
+
await cp(join(dist, "static"), join(funcDir, "static"), {
|
|
234
|
+
recursive: true,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 3. Bundle the Node launcher. srvx (a @rangojs/router dependency) is aliased
|
|
239
|
+
// to its resolved path; @vercel/functions resolves from the app; the RSC
|
|
240
|
+
// server bundle stays a runtime-relative external.
|
|
241
|
+
const rangoRequire = createRequire(import.meta.url);
|
|
242
|
+
let srvxNodePath: string;
|
|
243
|
+
try {
|
|
244
|
+
srvxNodePath = rangoRequire.resolve("srvx/node");
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error(
|
|
247
|
+
'[rango] preset "vercel" requires "srvx" (a dependency of @rangojs/router). Reinstall dependencies.',
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// esbuild ships with Vite, so we never add it as a @rangojs/router dependency.
|
|
252
|
+
// It is a DIRECT dependency of Vite but only a TRANSITIVE one from the app's
|
|
253
|
+
// view, so under strict pnpm it is NOT resolvable from the app root. Resolve it
|
|
254
|
+
// through Vite's module location (Vite is a direct app dependency, and esbuild
|
|
255
|
+
// is a direct dependency of Vite). Minimal structural types avoid coupling to
|
|
256
|
+
// esbuild's type package at compile time.
|
|
257
|
+
const appRequire = createRequire(join(root, "package.json"));
|
|
258
|
+
const resolveEsbuildPath = (): string => {
|
|
259
|
+
try {
|
|
260
|
+
const viteRequire = createRequire(appRequire.resolve("vite"));
|
|
261
|
+
return viteRequire.resolve("esbuild");
|
|
262
|
+
} catch {
|
|
263
|
+
// Intentionally empty: fall through to the app/rango fallbacks below.
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
return appRequire.resolve("esbuild");
|
|
267
|
+
} catch {
|
|
268
|
+
// Intentionally empty: last resort is @rangojs/router's own resolver.
|
|
269
|
+
}
|
|
270
|
+
return rangoRequire.resolve("esbuild");
|
|
271
|
+
};
|
|
272
|
+
let esbuildModule: EsbuildModule;
|
|
273
|
+
try {
|
|
274
|
+
esbuildModule = (await import(
|
|
275
|
+
pathToFileURL(resolveEsbuildPath()).href
|
|
276
|
+
)) as EsbuildModule;
|
|
277
|
+
} catch {
|
|
278
|
+
throw new Error(
|
|
279
|
+
'[rango] preset "vercel" requires "esbuild" to bundle the function launcher. It ships with Vite; reinstall dependencies (or add esbuild to your app dependencies).',
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const esbuildBuild = esbuildModule.build ?? esbuildModule.default?.build;
|
|
283
|
+
if (typeof esbuildBuild !== "function") {
|
|
284
|
+
throw new Error('[rango] preset "vercel": could not load esbuild.build.');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
await esbuildBuild({
|
|
289
|
+
stdin: {
|
|
290
|
+
contents: LAUNCHER_SOURCE,
|
|
291
|
+
resolveDir: root,
|
|
292
|
+
sourcefile: "func-entry.mjs",
|
|
293
|
+
loader: "js",
|
|
294
|
+
},
|
|
295
|
+
outfile: join(funcDir, "index.mjs"),
|
|
296
|
+
bundle: true,
|
|
297
|
+
format: "esm",
|
|
298
|
+
platform: "node",
|
|
299
|
+
target: "node18",
|
|
300
|
+
alias: { "srvx/node": srvxNodePath },
|
|
301
|
+
plugins: [
|
|
302
|
+
{
|
|
303
|
+
name: "external-rsc-entry",
|
|
304
|
+
setup(b: EsbuildPluginBuild) {
|
|
305
|
+
b.onResolve({ filter: /^\.\/rsc\/index\.js$/ }, () => ({
|
|
306
|
+
path: "./rsc/index.js",
|
|
307
|
+
external: true,
|
|
308
|
+
}));
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
});
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
315
|
+
if (/@vercel\/functions/.test(message)) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
'[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' +
|
|
318
|
+
message,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 3b. Mark the function as ESM. The rsc/ssr bundles are .js ESM files with no
|
|
325
|
+
// package.json in scope on the deployed function (it is isolated at
|
|
326
|
+
// /var/task), so Node would load them as CommonJS and fail on `import`.
|
|
327
|
+
// Locally this is masked because the func inherits the app's
|
|
328
|
+
// "type": "module" up the tree; the deployed func has nothing above it.
|
|
329
|
+
await writeFile(
|
|
330
|
+
join(funcDir, "package.json"),
|
|
331
|
+
JSON.stringify({ type: "module" }, null, 2) + "\n",
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// 4. Function config: Node serverless with response streaming.
|
|
335
|
+
await writeFile(
|
|
336
|
+
join(funcDir, ".vc-config.json"),
|
|
337
|
+
JSON.stringify(buildVercelVcConfig(vercel), null, 2) + "\n",
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// 5. Routing config (immutable assets -> filesystem -> function).
|
|
341
|
+
await writeFile(
|
|
342
|
+
join(out, "config.json"),
|
|
343
|
+
JSON.stringify(buildVercelOutputConfig(functionName, assetsDir), null, 2) +
|
|
344
|
+
"\n",
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
console.log(
|
|
348
|
+
`[rango] assembled .vercel/output (function: ${functionName}.func)`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function createVercelOutputPlugin(options: RangoVercelOptions): Plugin {
|
|
353
|
+
let root = process.cwd();
|
|
354
|
+
let isBuild = false;
|
|
355
|
+
// The client build's assetsDir (Vite default "assets"); used to scope the
|
|
356
|
+
// immutable cache-control route to the content-hashed asset output.
|
|
357
|
+
let assetsDir = "assets";
|
|
358
|
+
// Resolved publicDir ("" when disabled); used to warn when public/<assetsDir>
|
|
359
|
+
// exists, since its un-hashed files share the immutable-header prefix.
|
|
360
|
+
let publicDir = "";
|
|
361
|
+
return {
|
|
362
|
+
name: "@rangojs/router:vercel-output",
|
|
363
|
+
configResolved(config) {
|
|
364
|
+
root = resolve(config.root);
|
|
365
|
+
isBuild = config.command === "build";
|
|
366
|
+
assetsDir =
|
|
367
|
+
config.environments?.client?.build?.assetsDir ??
|
|
368
|
+
config.build?.assetsDir ??
|
|
369
|
+
"assets";
|
|
370
|
+
publicDir = config.publicDir || "";
|
|
371
|
+
},
|
|
372
|
+
// buildApp runs once after the whole multi-environment build (rsc, client,
|
|
373
|
+
// ssr), so dist/ is complete here. closeBundle is unusable for this: it
|
|
374
|
+
// fires per environment, and twice for ssr (the server-reference scan and
|
|
375
|
+
// the real build), so it would run before dist/client exists.
|
|
376
|
+
buildApp: {
|
|
377
|
+
order: "post",
|
|
378
|
+
async handler() {
|
|
379
|
+
if (!isBuild) return;
|
|
380
|
+
await assemble(root, options, assetsDir, publicDir);
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -2,12 +2,25 @@ import type { Plugin } from "vite";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import * as Vite from "vite";
|
|
4
4
|
import { resolveRscEntryFromConfig } from "../utils/shared-utils.js";
|
|
5
|
+
import { RSC_ENTRY_BOOTSTRAP_IMPORTS } from "./virtual-entries.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
* Plugin that auto-injects VERSION and
|
|
8
|
-
* If a custom entry.rsc file uses createRSCHandler but
|
|
9
|
-
* this transform adds the import and property
|
|
10
|
-
*
|
|
8
|
+
* Plugin that auto-injects VERSION, routes-manifest, and loader-manifest into
|
|
9
|
+
* custom entry.rsc files. If a custom entry.rsc file uses createRSCHandler but
|
|
10
|
+
* doesn't pass version, this transform adds the import and property
|
|
11
|
+
* automatically. It also ensures the routes-manifest and loader-manifest
|
|
12
|
+
* virtual modules are always imported.
|
|
13
|
+
*
|
|
14
|
+
* The loader-manifest import is what makes fetchable loaders resolvable on a
|
|
15
|
+
* custom worker entry. The virtual RSC entry (getVirtualEntryRSC) imports the
|
|
16
|
+
* loader manifest itself, but a hand-written entry (e.g. a Cloudflare
|
|
17
|
+
* worker.rsc.tsx) does not — so without this injection, setLoaderImports() is
|
|
18
|
+
* never bundled, lazyLoaderImports stays null at runtime, and a fetchable
|
|
19
|
+
* loader that is never imported by the server graph (not registered via
|
|
20
|
+
* loader(), reachable only through a client component) cannot be found by the
|
|
21
|
+
* _rsc_loader endpoint in production. Dev still works because it resolves
|
|
22
|
+
* loaders by parsing their id into a file path; only production relied on the
|
|
23
|
+
* manifest, hence the production-only failure this fixes.
|
|
11
24
|
* @internal
|
|
12
25
|
*/
|
|
13
26
|
export function createVersionInjectorPlugin(
|
|
@@ -29,7 +42,6 @@ export function createVersionInjectorPlugin(
|
|
|
29
42
|
|
|
30
43
|
transform(code, id) {
|
|
31
44
|
if (!resolvedEntryPath) return null;
|
|
32
|
-
// Only transform the RSC entry file
|
|
33
45
|
const normalizedId = Vite.normalizePath(id);
|
|
34
46
|
const normalizedEntry = Vite.normalizePath(resolvedEntryPath);
|
|
35
47
|
|
|
@@ -37,25 +49,12 @@ export function createVersionInjectorPlugin(
|
|
|
37
49
|
return null;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// with the full route table.
|
|
46
|
-
//
|
|
47
|
-
// ES module evaluation order matters here: while imports are *parsed*
|
|
48
|
-
// hoisted, side-effect imports are evaluated in source order in the
|
|
49
|
-
// dependency graph. A user-authored `import "virtual:rsc-router/..."`
|
|
50
|
-
// placed after `import "./router.js"` runs too late: the manifest
|
|
51
|
-
// gate fires after router.tsx has already crashed on a stale gen file.
|
|
52
|
-
// We always prepend; ESM dedups any user-written duplicate, so module
|
|
53
|
-
// initialization still runs once.
|
|
54
|
-
const prepend: string[] = [
|
|
55
|
-
`import "virtual:rsc-router/routes-manifest";`,
|
|
56
|
-
];
|
|
52
|
+
// Same startup bootstrap imports the generated virtual RSC entry uses,
|
|
53
|
+
// from the single shared list so the two paths cannot drift.
|
|
54
|
+
const prepend: string[] = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
|
|
55
|
+
(id) => `import "${id}";`,
|
|
56
|
+
);
|
|
57
57
|
|
|
58
|
-
// Auto-inject VERSION if file uses createRSCHandler without version
|
|
59
58
|
let newCode = code;
|
|
60
59
|
const needsVersion =
|
|
61
60
|
code.includes("createRSCHandler") &&
|
|
@@ -70,10 +69,6 @@ export function createVersionInjectorPlugin(
|
|
|
70
69
|
);
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
// Insert after any leading `/// <reference ... />` triple-slash
|
|
74
|
-
// directives (and surrounding blank lines). TypeScript requires those
|
|
75
|
-
// directives to precede all other code; putting our imports above
|
|
76
|
-
// them silently demotes the directives to plain comments.
|
|
77
72
|
const lines = newCode.split("\n");
|
|
78
73
|
let insertAt = 0;
|
|
79
74
|
while (insertAt < lines.length) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseAst, type Plugin } from "vite";
|
|
2
2
|
import { VIRTUAL_IDS, getVirtualVersionContent } from "./virtual-entries.js";
|
|
3
|
+
import { hasUseClientDirective } from "../utils/directive-prologue.js";
|
|
3
4
|
|
|
4
5
|
interface ClientModuleSignature {
|
|
5
6
|
key: string;
|
|
@@ -16,6 +17,10 @@ function normalizeModuleId(id: string): string {
|
|
|
16
17
|
function getClientModuleSignature(
|
|
17
18
|
source: string,
|
|
18
19
|
): ClientModuleSignature | undefined {
|
|
20
|
+
// Same leading-directive sniff the rango HMR plugin uses (shared helper, so the
|
|
21
|
+
// two agree on what counts as a client module). A parse failure yields false.
|
|
22
|
+
if (!hasUseClientDirective(source)) return undefined;
|
|
23
|
+
|
|
19
24
|
let program: any;
|
|
20
25
|
try {
|
|
21
26
|
program = parseAst(source, { lang: "tsx" });
|
|
@@ -23,23 +28,6 @@ function getClientModuleSignature(
|
|
|
23
28
|
return undefined;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
let isUseClient = false;
|
|
27
|
-
for (const node of program.body ?? []) {
|
|
28
|
-
if (
|
|
29
|
-
node?.type === "ExpressionStatement" &&
|
|
30
|
-
node.expression?.type === "Literal" &&
|
|
31
|
-
typeof node.expression.value === "string"
|
|
32
|
-
) {
|
|
33
|
-
if (node.expression.value === "use client") {
|
|
34
|
-
isUseClient = true;
|
|
35
|
-
}
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!isUseClient) return undefined;
|
|
42
|
-
|
|
43
31
|
const exports = new Set<string>();
|
|
44
32
|
let hasDefault = false;
|
|
45
33
|
let hasExportAll = false;
|
|
@@ -138,8 +126,6 @@ export function createVersionPlugin(): Plugin {
|
|
|
138
126
|
|
|
139
127
|
let versionCounter = 0;
|
|
140
128
|
const bumpVersion = (reason: string) => {
|
|
141
|
-
// Use timestamp + counter to guarantee uniqueness even when multiple
|
|
142
|
-
// bumps happen within the same millisecond (e.g. cascading HMR events).
|
|
143
129
|
currentVersion = Date.now().toString(16) + String(++versionCounter);
|
|
144
130
|
console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
|
|
145
131
|
|
|
@@ -158,9 +144,6 @@ export function createVersionPlugin(): Plugin {
|
|
|
158
144
|
|
|
159
145
|
configResolved(config) {
|
|
160
146
|
isDev = config.command === "serve";
|
|
161
|
-
// Capture the resolved cacheDir so we can ignore optimizer-output
|
|
162
|
-
// writes inside it. Vite resolves cacheDir against the project root,
|
|
163
|
-
// so this is a stable absolute path for the lifetime of the server.
|
|
164
147
|
resolvedCacheDir = config.cacheDir
|
|
165
148
|
? String(config.cacheDir).replace(/\\/g, "/")
|
|
166
149
|
: undefined;
|
|
@@ -210,27 +193,15 @@ export function createVersionPlugin(): Plugin {
|
|
|
210
193
|
return null;
|
|
211
194
|
},
|
|
212
195
|
|
|
213
|
-
// Track RSC module changes and update version
|
|
214
196
|
async hotUpdate(ctx) {
|
|
215
197
|
if (!isDev) return;
|
|
216
198
|
|
|
217
|
-
// Check if this is an RSC environment update (not client/ssr)
|
|
218
|
-
// RSC modules affect server-rendered content and cached payloads
|
|
219
|
-
// In Vite 6, environment is accessed via `this.environment`
|
|
220
199
|
const isRscModule = this.environment?.name === "rsc";
|
|
221
200
|
|
|
222
201
|
if (!isRscModule) return;
|
|
223
202
|
|
|
224
|
-
// Skip Vite's own pre-bundled dep cache writes. The optimizer rewrites
|
|
225
|
-
// files inside the configured `cacheDir` on every discovery cycle
|
|
226
|
-
// (and when other dev servers under the same cwd populate their own
|
|
227
|
-
// isolated cache dirs). These are not user-source changes, so bumping
|
|
228
|
-
// the app version on them produces spurious version mismatches that
|
|
229
|
-
// surface as forced reloads on in-flight actions.
|
|
230
203
|
if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
|
|
231
204
|
|
|
232
|
-
// Skip re-bumping when the version virtual module itself is invalidated
|
|
233
|
-
// (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
|
|
234
205
|
if (
|
|
235
206
|
ctx.modules.length === 1 &&
|
|
236
207
|
ctx.modules[0].id === "\0" + VIRTUAL_IDS.version
|
|
@@ -245,9 +216,6 @@ export function createVersionPlugin(): Plugin {
|
|
|
245
216
|
const source = await ctx.read();
|
|
246
217
|
const nextSignature = getClientModuleSignature(source);
|
|
247
218
|
if (nextSignature) {
|
|
248
|
-
// "use client" file — compare export signatures.
|
|
249
|
-
// client-component-hmr may have cleared ctx.modules, so we
|
|
250
|
-
// cannot rely on ctx.modules.length for these files.
|
|
251
219
|
clientModuleSignatures.set(filePath, nextSignature);
|
|
252
220
|
if (
|
|
253
221
|
previousSignature &&
|
|
@@ -258,20 +226,11 @@ export function createVersionPlugin(): Plugin {
|
|
|
258
226
|
} else {
|
|
259
227
|
clientModuleSignatures.delete(filePath);
|
|
260
228
|
if (!previousSignature) {
|
|
261
|
-
// Not and never was "use client" — use module graph check.
|
|
262
|
-
// ctx.modules is reliable for pure server files (only
|
|
263
|
-
// client-component-hmr clears it for "use client" modules).
|
|
264
229
|
if (ctx.modules.length === 0) return;
|
|
265
230
|
}
|
|
266
|
-
// Was "use client" but directive removed — boundary changed,
|
|
267
|
-
// bump below.
|
|
268
231
|
}
|
|
269
|
-
} catch {
|
|
270
|
-
// Fail open: if we can't read or parse the update, invalidate.
|
|
271
|
-
}
|
|
232
|
+
} catch {}
|
|
272
233
|
} else {
|
|
273
|
-
// Non-code file (json, css, etc.) — only bump if it's actually
|
|
274
|
-
// referenced by the RSC module graph.
|
|
275
234
|
if (ctx.modules.length === 0) return;
|
|
276
235
|
}
|
|
277
236
|
|
|
@@ -280,20 +239,6 @@ export function createVersionPlugin(): Plugin {
|
|
|
280
239
|
};
|
|
281
240
|
}
|
|
282
241
|
|
|
283
|
-
/**
|
|
284
|
-
* Match Vite's pre-bundled dep cache directories. These paths are rewritten
|
|
285
|
-
* by the dep optimizer (and by isolated test fixtures sharing the same cwd),
|
|
286
|
-
* not by user source changes, so they should not bump the app version (which
|
|
287
|
-
* would force a client reload mid-request).
|
|
288
|
-
*
|
|
289
|
-
* Two checks:
|
|
290
|
-
* 1. Anything inside the resolved `cacheDir` (precise — covers custom paths
|
|
291
|
-
* like the `RANGO_E2E_VITE_CACHE_DIR` overrides in the test fixtures).
|
|
292
|
-
* 2. Heuristic match for any `node_modules/.vite*` directory or a
|
|
293
|
-
* `.vite-isolated/` segment anywhere in the path. This catches the
|
|
294
|
-
* *other* dev servers in the same cwd whose cacheDir we cannot read
|
|
295
|
-
* (we only see config of the server we're attached to).
|
|
296
|
-
*/
|
|
297
242
|
export function isViteDepCachePath(
|
|
298
243
|
filePath: string | undefined,
|
|
299
244
|
cacheDir?: string,
|
|
@@ -311,11 +256,6 @@ export function isViteDepCachePath(
|
|
|
311
256
|
}
|
|
312
257
|
}
|
|
313
258
|
|
|
314
|
-
// Vite/optimizer convention: cache dirs always sit directly under
|
|
315
|
-
// `node_modules/` and start with `.vite` (e.g. `.vite`, `.vite-temp`,
|
|
316
|
-
// `.vite_rango_generate`, `.vite-e2e-test-app`). The `/.vite-isolated/`
|
|
317
|
-
// segment covers the test-fixture pattern that places the cache outside
|
|
318
|
-
// node_modules.
|
|
319
259
|
return (
|
|
320
260
|
/\/node_modules\/\.vite[^/]*\//.test(normalized) ||
|
|
321
261
|
normalized.includes("/.vite-isolated/")
|