@nifrajs/web 4.0.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/build-vite.d.ts +19 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +31 -10
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +8 -10
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +90 -31
- package/dist/build.js.map +1 -1
- package/dist/cdn.d.ts +8 -0
- package/dist/cdn.d.ts.map +1 -1
- package/dist/cdn.js +14 -15
- package/dist/cdn.js.map +1 -1
- package/dist/client.d.ts +6 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +46 -18
- package/dist/client.js.map +1 -1
- package/dist/deferred.d.ts +2 -2
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +28 -7
- package/dist/deferred.js.map +1 -1
- package/dist/dev-feed.d.ts.map +1 -1
- package/dist/dev-feed.js +24 -5
- package/dist/dev-feed.js.map +1 -1
- package/dist/dev-port.d.ts +3 -2
- package/dist/dev-port.d.ts.map +1 -1
- package/dist/dev-port.js +2 -6
- package/dist/dev-port.js.map +1 -1
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +4 -14
- package/dist/dev-session.js.map +1 -1
- package/dist/dev.d.ts +7 -17
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +13 -1
- package/dist/dev.js.map +1 -1
- package/dist/draft.d.ts +8 -5
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +30 -13
- package/dist/draft.js.map +1 -1
- package/dist/fonts-google.d.ts.map +1 -1
- package/dist/fonts-google.js +7 -0
- package/dist/fonts-google.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/codegen.d.ts +1 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +56 -29
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/disposable-dir.d.ts +8 -0
- package/dist/internal/disposable-dir.d.ts.map +1 -0
- package/dist/internal/disposable-dir.js +25 -0
- package/dist/internal/disposable-dir.js.map +1 -0
- package/dist/internal/entry-chunks.d.ts +9 -0
- package/dist/internal/entry-chunks.d.ts.map +1 -0
- package/dist/internal/entry-chunks.js +15 -0
- package/dist/internal/entry-chunks.js.map +1 -0
- package/dist/internal/evidence-traversal.d.ts +12 -0
- package/dist/internal/evidence-traversal.d.ts.map +1 -0
- package/dist/internal/evidence-traversal.js +48 -0
- package/dist/internal/evidence-traversal.js.map +1 -0
- package/dist/internal/head-merge.d.ts +6 -0
- package/dist/internal/head-merge.d.ts.map +1 -1
- package/dist/internal/head-merge.js +9 -0
- package/dist/internal/head-merge.js.map +1 -1
- package/dist/internal/js-string.d.ts +2 -0
- package/dist/internal/js-string.d.ts.map +1 -1
- package/dist/internal/js-string.js +5 -1
- package/dist/internal/js-string.js.map +1 -1
- package/dist/internal/mount-shadow.js +1 -1
- package/dist/internal/mount-shadow.js.map +1 -1
- package/dist/internal/output-guard.d.ts +7 -0
- package/dist/internal/output-guard.d.ts.map +1 -1
- package/dist/internal/output-guard.js +80 -8
- package/dist/internal/output-guard.js.map +1 -1
- package/dist/internal/package-entries.d.ts +34 -0
- package/dist/internal/package-entries.d.ts.map +1 -0
- package/dist/internal/package-entries.js +324 -0
- package/dist/internal/package-entries.js.map +1 -0
- package/dist/internal/page-execution.d.ts +6 -0
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +437 -114
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +15 -2
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +8 -0
- package/dist/internal/private-env.d.ts.map +1 -1
- package/dist/internal/private-env.js +25 -10
- package/dist/internal/private-env.js.map +1 -1
- package/dist/internal/progressive-cookies.d.ts +14 -0
- package/dist/internal/progressive-cookies.d.ts.map +1 -0
- package/dist/internal/progressive-cookies.js +43 -0
- package/dist/internal/progressive-cookies.js.map +1 -0
- package/dist/internal/public-env.d.ts +2 -0
- package/dist/internal/public-env.d.ts.map +1 -0
- package/dist/internal/public-env.js +13 -0
- package/dist/internal/public-env.js.map +1 -0
- package/dist/internal/render-document.d.ts +5 -44
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +51 -63
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/response-controls.d.ts +2 -0
- package/dist/internal/response-controls.d.ts.map +1 -1
- package/dist/internal/response-controls.js +26 -16
- package/dist/internal/response-controls.js.map +1 -1
- package/dist/internal/revive-dates.d.ts +16 -0
- package/dist/internal/revive-dates.d.ts.map +1 -0
- package/dist/internal/revive-dates.js +32 -0
- package/dist/internal/revive-dates.js.map +1 -0
- package/dist/internal/route-path.d.ts +14 -0
- package/dist/internal/route-path.d.ts.map +1 -0
- package/dist/internal/route-path.js +19 -0
- package/dist/internal/route-path.js.map +1 -0
- package/dist/internal/secret-scan.d.ts.map +1 -1
- package/dist/internal/secret-scan.js +190 -20
- package/dist/internal/secret-scan.js.map +1 -1
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +33 -5
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/web-app.d.ts +25 -18
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +81 -24
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/wire-date.d.ts +3 -0
- package/dist/internal/wire-date.d.ts.map +1 -0
- package/dist/internal/wire-date.js +14 -0
- package/dist/internal/wire-date.js.map +1 -0
- package/dist/internal/without-query.d.ts +4 -0
- package/dist/internal/without-query.d.ts.map +1 -0
- package/dist/internal/without-query.js +9 -0
- package/dist/internal/without-query.js.map +1 -0
- package/dist/internal/zone-graph.d.ts +4 -0
- package/dist/internal/zone-graph.d.ts.map +1 -1
- package/dist/internal/zone-graph.js +71 -20
- package/dist/internal/zone-graph.js.map +1 -1
- package/dist/isr.d.ts +59 -3
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +311 -41
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +2 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +29 -3
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +37 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +4 -0
- package/dist/manifest.js.map +1 -1
- package/dist/open-resolved.d.ts +18 -0
- package/dist/open-resolved.d.ts.map +1 -0
- package/dist/open-resolved.js +62 -0
- package/dist/open-resolved.js.map +1 -0
- package/dist/plugins/kit.d.ts +1 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +26 -11
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +9 -1
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +84 -7
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
- package/dist/plugins/vite-zone-guard.js +17 -21
- package/dist/plugins/vite-zone-guard.js.map +1 -1
- package/dist/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +44 -3
- package/dist/plugins/zone-guard.js.map +1 -1
- package/dist/prerender.d.ts +5 -1
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +41 -19
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +8 -1
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +17 -6
- package/dist/public-dir.js.map +1 -1
- package/dist/query.d.ts +13 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +89 -26
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +25 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +1 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-types.d.ts.map +1 -1
- package/dist/route-types.js +20 -9
- package/dist/route-types.js.map +1 -1
- package/dist/router.d.ts +9 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +47 -50
- package/dist/router.js.map +1 -1
- package/dist/seo.d.ts +49 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +48 -0
- package/dist/seo.js.map +1 -0
- package/dist/server-components/build-info.d.ts +3 -0
- package/dist/server-components/build-info.d.ts.map +1 -0
- package/dist/server-components/build-info.js +7 -0
- package/dist/server-components/build-info.js.map +1 -0
- package/dist/server-components/build-target.d.ts +12 -0
- package/dist/server-components/build-target.d.ts.map +1 -0
- package/dist/server-components/build-target.js +153 -0
- package/dist/server-components/build-target.js.map +1 -0
- package/dist/server-components/build.d.ts +24 -0
- package/dist/server-components/build.d.ts.map +1 -0
- package/dist/server-components/build.js +366 -0
- package/dist/server-components/build.js.map +1 -0
- package/dist/server-components/bundle-proof.d.ts +3 -0
- package/dist/server-components/bundle-proof.d.ts.map +1 -0
- package/dist/server-components/bundle-proof.js +23 -0
- package/dist/server-components/bundle-proof.js.map +1 -0
- package/dist/server-components/client.d.ts +46 -0
- package/dist/server-components/client.d.ts.map +1 -0
- package/dist/server-components/client.js +594 -0
- package/dist/server-components/client.js.map +1 -0
- package/dist/server-components/definition.d.ts +11 -0
- package/dist/server-components/definition.d.ts.map +1 -0
- package/dist/server-components/definition.js +94 -0
- package/dist/server-components/definition.js.map +1 -0
- package/dist/server-components/dev-http.d.ts +5 -0
- package/dist/server-components/dev-http.d.ts.map +1 -0
- package/dist/server-components/dev-http.js +71 -0
- package/dist/server-components/dev-http.js.map +1 -0
- package/dist/server-components/error.d.ts +9 -0
- package/dist/server-components/error.d.ts.map +1 -0
- package/dist/server-components/error.js +24 -0
- package/dist/server-components/error.js.map +1 -0
- package/dist/server-components/frames.d.ts +7 -0
- package/dist/server-components/frames.d.ts.map +1 -0
- package/dist/server-components/frames.js +169 -0
- package/dist/server-components/frames.js.map +1 -0
- package/dist/server-components/html-policy.d.ts +6 -0
- package/dist/server-components/html-policy.d.ts.map +1 -0
- package/dist/server-components/html-policy.js +75 -0
- package/dist/server-components/html-policy.js.map +1 -0
- package/dist/server-components/html.d.ts +4 -0
- package/dist/server-components/html.d.ts.map +1 -0
- package/dist/server-components/html.js +67 -0
- package/dist/server-components/html.js.map +1 -0
- package/dist/server-components/islands.d.ts +44 -0
- package/dist/server-components/islands.d.ts.map +1 -0
- package/dist/server-components/islands.js +42 -0
- package/dist/server-components/islands.js.map +1 -0
- package/dist/server-components/native.d.ts +4 -0
- package/dist/server-components/native.d.ts.map +1 -0
- package/dist/server-components/native.js +81 -0
- package/dist/server-components/native.js.map +1 -0
- package/dist/server-components/portal.d.ts +6 -0
- package/dist/server-components/portal.d.ts.map +1 -0
- package/dist/server-components/portal.js +35 -0
- package/dist/server-components/portal.js.map +1 -0
- package/dist/server-components/props.d.ts +3 -0
- package/dist/server-components/props.d.ts.map +1 -0
- package/dist/server-components/props.js +14 -0
- package/dist/server-components/props.js.map +1 -0
- package/dist/server-components/protocol.d.ts +44 -0
- package/dist/server-components/protocol.d.ts.map +1 -0
- package/dist/server-components/protocol.js +139 -0
- package/dist/server-components/protocol.js.map +1 -0
- package/dist/server-components/range.d.ts +24 -0
- package/dist/server-components/range.d.ts.map +1 -0
- package/dist/server-components/range.js +263 -0
- package/dist/server-components/range.js.map +1 -0
- package/dist/server-components/registry.d.ts +6 -0
- package/dist/server-components/registry.d.ts.map +1 -0
- package/dist/server-components/registry.js +30 -0
- package/dist/server-components/registry.js.map +1 -0
- package/dist/server-components/response.d.ts +4 -0
- package/dist/server-components/response.d.ts.map +1 -0
- package/dist/server-components/response.js +32 -0
- package/dist/server-components/response.js.map +1 -0
- package/dist/server-components/router-integration.d.ts +9 -0
- package/dist/server-components/router-integration.d.ts.map +1 -0
- package/dist/server-components/router-integration.js +29 -0
- package/dist/server-components/router-integration.js.map +1 -0
- package/dist/server-components/runtime.d.ts +14 -0
- package/dist/server-components/runtime.d.ts.map +1 -0
- package/dist/server-components/runtime.js +276 -0
- package/dist/server-components/runtime.js.map +1 -0
- package/dist/server-components/scheduler.d.ts +14 -0
- package/dist/server-components/scheduler.d.ts.map +1 -0
- package/dist/server-components/scheduler.js +74 -0
- package/dist/server-components/scheduler.js.map +1 -0
- package/dist/server-components/schema.d.ts +4 -0
- package/dist/server-components/schema.d.ts.map +1 -0
- package/dist/server-components/schema.js +77 -0
- package/dist/server-components/schema.js.map +1 -0
- package/dist/server-components/server-entry.d.ts +12 -0
- package/dist/server-components/server-entry.d.ts.map +1 -0
- package/dist/server-components/server-entry.js +90 -0
- package/dist/server-components/server-entry.js.map +1 -0
- package/dist/server-components/source.d.ts +15 -0
- package/dist/server-components/source.d.ts.map +1 -0
- package/dist/server-components/source.js +269 -0
- package/dist/server-components/source.js.map +1 -0
- package/dist/server-components/state.d.ts +4 -0
- package/dist/server-components/state.d.ts.map +1 -0
- package/dist/server-components/state.js +123 -0
- package/dist/server-components/state.js.map +1 -0
- package/dist/server-components/stream.d.ts +6 -0
- package/dist/server-components/stream.d.ts.map +1 -0
- package/dist/server-components/stream.js +70 -0
- package/dist/server-components/stream.js.map +1 -0
- package/dist/server-components/trusted-html.d.ts +3 -0
- package/dist/server-components/trusted-html.d.ts.map +1 -0
- package/dist/server-components/trusted-html.js +14 -0
- package/dist/server-components/trusted-html.js.map +1 -0
- package/dist/server-components/types.d.ts +107 -0
- package/dist/server-components/types.d.ts.map +1 -0
- package/dist/server-components/types.js +2 -0
- package/dist/server-components/types.js.map +1 -0
- package/dist/server-components/typescript.d.ts +11 -0
- package/dist/server-components/typescript.d.ts.map +1 -0
- package/dist/server-components/typescript.js +28 -0
- package/dist/server-components/typescript.js.map +1 -0
- package/dist/server-components/value.d.ts +5 -0
- package/dist/server-components/value.d.ts.map +1 -0
- package/dist/server-components/value.js +77 -0
- package/dist/server-components/value.js.map +1 -0
- package/dist/server-components/vite.d.ts +12 -0
- package/dist/server-components/vite.d.ts.map +1 -0
- package/dist/server-components/vite.js +197 -0
- package/dist/server-components/vite.js.map +1 -0
- package/dist/server-components.d.ts +5 -0
- package/dist/server-components.d.ts.map +1 -0
- package/dist/server-components.js +4 -0
- package/dist/server-components.js.map +1 -0
- package/dist/service-worker.d.ts +8 -1
- package/dist/service-worker.d.ts.map +1 -1
- package/dist/service-worker.js +20 -6
- package/dist/service-worker.js.map +1 -1
- package/dist/vite.d.ts +12 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +7 -1
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +64 -2
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +331 -28
- package/dist/zones.js.map +1 -1
- package/package.json +102 -4
- package/src/build-vite.ts +31 -9
- package/src/build.ts +109 -31
- package/src/cdn.ts +18 -12
- package/src/client.ts +48 -17
- package/src/deferred.ts +27 -9
- package/src/dev-feed.ts +24 -5
- package/src/dev-port.ts +4 -10
- package/src/dev-session.ts +4 -13
- package/src/dev.ts +29 -1
- package/src/draft.ts +35 -14
- package/src/fonts-google.ts +10 -0
- package/src/index.ts +5 -6
- package/src/internal/codegen.ts +60 -29
- package/src/internal/disposable-dir.ts +24 -0
- package/src/internal/entry-chunks.ts +18 -0
- package/src/internal/evidence-traversal.ts +57 -0
- package/src/internal/head-merge.ts +16 -0
- package/src/internal/js-string.ts +6 -1
- package/src/internal/mount-shadow.ts +1 -1
- package/src/internal/output-guard.ts +88 -7
- package/src/internal/package-entries.ts +331 -0
- package/src/internal/page-execution.ts +573 -114
- package/src/internal/parity.ts +15 -2
- package/src/internal/private-env.ts +27 -13
- package/src/internal/progressive-cookies.ts +60 -0
- package/src/internal/public-env.ts +14 -0
- package/src/internal/render-document.ts +66 -89
- package/src/internal/response-controls.ts +31 -14
- package/src/internal/revive-dates.ts +35 -0
- package/src/internal/route-path.ts +24 -0
- package/src/internal/secret-scan.ts +190 -18
- package/src/internal/server-boundary.ts +29 -5
- package/src/internal/web-app.ts +126 -42
- package/src/internal/wire-date.ts +16 -0
- package/src/internal/without-query.ts +8 -0
- package/src/internal/zone-graph.ts +78 -25
- package/src/isr.ts +364 -45
- package/src/llms-txt.ts +32 -3
- package/src/manifest.ts +38 -4
- package/src/open-resolved.ts +64 -0
- package/src/plugins/kit.ts +31 -11
- package/src/plugins/vite-leak-guard.ts +104 -8
- package/src/plugins/vite-zone-guard.ts +21 -19
- package/src/plugins/zone-guard.ts +41 -2
- package/src/prerender.ts +39 -18
- package/src/public-dir.ts +27 -8
- package/src/query.ts +107 -26
- package/src/render-seam.ts +30 -2
- package/src/route-types.ts +34 -7
- package/src/router.ts +66 -59
- package/src/seo.ts +68 -0
- package/src/server-components/build-info.ts +11 -0
- package/src/server-components/build-target.ts +194 -0
- package/src/server-components/build.ts +419 -0
- package/src/server-components/bundle-proof.ts +33 -0
- package/src/server-components/client.ts +663 -0
- package/src/server-components/definition.ts +125 -0
- package/src/server-components/dev-http.ts +75 -0
- package/src/server-components/error.ts +24 -0
- package/src/server-components/frames.ts +181 -0
- package/src/server-components/html-policy.ts +94 -0
- package/src/server-components/html.ts +81 -0
- package/src/server-components/islands.ts +111 -0
- package/src/server-components/native.ts +101 -0
- package/src/server-components/portal.ts +42 -0
- package/src/server-components/props.ts +19 -0
- package/src/server-components/protocol.ts +167 -0
- package/src/server-components/range.ts +313 -0
- package/src/server-components/registry.ts +34 -0
- package/src/server-components/response.ts +44 -0
- package/src/server-components/router-integration.ts +40 -0
- package/src/server-components/runtime.ts +376 -0
- package/src/server-components/scheduler.ts +84 -0
- package/src/server-components/schema.ts +69 -0
- package/src/server-components/server-entry.ts +98 -0
- package/src/server-components/source.ts +324 -0
- package/src/server-components/state.ts +143 -0
- package/src/server-components/stream.ts +71 -0
- package/src/server-components/trusted-html.ts +21 -0
- package/src/server-components/types.ts +138 -0
- package/src/server-components/typescript.ts +39 -0
- package/src/server-components/value.ts +82 -0
- package/src/server-components/vite.ts +242 -0
- package/src/server-components.ts +32 -0
- package/src/service-worker.ts +24 -7
- package/src/vite.ts +8 -1
- package/src/zones.ts +418 -32
package/src/router.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { Router as CoreRouter } from "@nifrajs/core/router"
|
|
|
9
9
|
import type { StandardSchemaV1 } from "@nifrajs/core/server"
|
|
10
10
|
import type { BoundaryDescriptor, BoundaryStates } from "./boundary.ts"
|
|
11
11
|
import { parseNdjsonData } from "./deferred.ts"
|
|
12
|
+
import { parsePageData } from "./internal/revive-dates.ts"
|
|
13
|
+
import { appUrl, requestPattern } from "./internal/route-path.ts"
|
|
12
14
|
import type { ClientActionResult, ClientRequestBody, ClientRouteHooks } from "./manifest.ts"
|
|
13
15
|
import type { MatchChain } from "./render-seam.ts"
|
|
14
16
|
import { isClientOnlySearchChange } from "./search.ts"
|
|
@@ -90,6 +92,7 @@ export const NAV_FROM_HEADER = "x-nifra-from"
|
|
|
90
92
|
* structure rather than by assuming the deploy was atomic.
|
|
91
93
|
*/
|
|
92
94
|
export interface RouteDataEnvelope {
|
|
95
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState | undefined
|
|
93
96
|
readonly v: 1
|
|
94
97
|
readonly data: unknown
|
|
95
98
|
readonly layoutData?: readonly unknown[]
|
|
@@ -129,6 +132,7 @@ export interface Submission {
|
|
|
129
132
|
|
|
130
133
|
/** The router's observable state. A new object is published on every transition. */
|
|
131
134
|
export interface RouterState {
|
|
135
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState | undefined
|
|
132
136
|
readonly routeId: string
|
|
133
137
|
readonly params: Record<string, string>
|
|
134
138
|
/** The current URL path (used to revalidate the active loader after an action). */
|
|
@@ -173,7 +177,7 @@ export function createMatcher(
|
|
|
173
177
|
for (const pattern of patterns) {
|
|
174
178
|
// Pass the literal to the public router seam: package type resolution may point the two core
|
|
175
179
|
// subpaths at different source/dist declarations, while registration-time compilation is cheap.
|
|
176
|
-
routeIndex.add("GET", pattern.pattern, pattern.routeId)
|
|
180
|
+
routeIndex.add("GET", requestPattern(pattern.pattern), pattern.routeId)
|
|
177
181
|
}
|
|
178
182
|
return (path) => {
|
|
179
183
|
// Strip the query without allocating a `split("?")` array - matcher runs per match.
|
|
@@ -258,8 +262,9 @@ export interface ClientRouter {
|
|
|
258
262
|
* loader is revalidated so the mutation is reflected. Pass `{ revalidate: false }` to SKIP that
|
|
259
263
|
* revalidation - keep the current `data` and just publish the action's `actionData` (useful when
|
|
260
264
|
* the action already returned everything that changed, saving the extra round-trip). A redirect
|
|
261
|
-
* always loads its target regardless. Rejects on failure (caller falls back); once the
|
|
262
|
-
*
|
|
265
|
+
* always loads its target regardless. Rejects on failure (caller falls back); once the server has
|
|
266
|
+
* answered, at any status, the rejection's `redirectTo` names the page to load instead of posting
|
|
267
|
+
* again.
|
|
263
268
|
*/
|
|
264
269
|
submit: (
|
|
265
270
|
action: string,
|
|
@@ -310,6 +315,10 @@ export interface ClientRouterOptions {
|
|
|
310
315
|
/** Terminal status → client route id. Generated entries populate this from `_404` and
|
|
311
316
|
* `_<status>` files so soft navigation renders the same boundary as a hard request. */
|
|
312
317
|
readonly statusRoutes?: Readonly<Record<number, string>>
|
|
318
|
+
/** Status route ids whose page renders inside layouts (all but `inLayouts = false`), filled in by the generated entry's
|
|
319
|
+
* `loadModule`. A soft navigation that ends on one loads the URL as a document, as one ending on a
|
|
320
|
+
* nested `_404` does: only the server runs those layouts' loaders. */
|
|
321
|
+
readonly layoutStatusRoutes?: Readonly<Record<string, true>>
|
|
313
322
|
/** routeId → the route's client-only search keys (its `searchClientKeys` export). When a soft
|
|
314
323
|
* navigation stays on the same route + pathname and changes ONLY these keys, `navigate` publishes the
|
|
315
324
|
* new URL WITHOUT re-running the loader (re-render, not revalidate). Populated lazily by the generated
|
|
@@ -346,7 +355,7 @@ export interface MountRouterOptions {
|
|
|
346
355
|
const readResponseData = (res: Response, signal?: AbortSignal): Promise<unknown> =>
|
|
347
356
|
(res.headers.get("content-type") ?? "").includes("application/x-ndjson") && res.body !== null
|
|
348
357
|
? parseNdjsonData(res.body, signal)
|
|
349
|
-
: res.
|
|
358
|
+
: res.text().then(parsePageData)
|
|
350
359
|
|
|
351
360
|
// Index scan, not `/\/+$/` - the unanchored-start trailing-run replace backtracks quadratically and
|
|
352
361
|
// the pathname is caller-controlled.
|
|
@@ -404,8 +413,8 @@ const targetUrl = (to: string): URL | undefined => {
|
|
|
404
413
|
}
|
|
405
414
|
}
|
|
406
415
|
|
|
407
|
-
const redirected = (to: string): Error =>
|
|
408
|
-
Object.assign(new Error(`[nifra/web] redirected to ${to}
|
|
416
|
+
const redirected = (to: string, cause?: unknown): Error =>
|
|
417
|
+
Object.assign(new Error(`[nifra/web] redirected to ${to}`, { cause }), { redirectTo: to })
|
|
409
418
|
|
|
410
419
|
const defaultFetchData: FetchRouteData = async (path, _match, signal, navigation) => {
|
|
411
420
|
// SSG fast path: if this path was prerendered, its loader data is a static file - fetch that (no
|
|
@@ -420,7 +429,7 @@ const defaultFetchData: FetchRouteData = async (path, _match, signal, navigation
|
|
|
420
429
|
const cut = query === -1 ? hash : hash === -1 ? query : Math.min(query, hash)
|
|
421
430
|
const pathname = cut === -1 ? path : path.slice(0, cut)
|
|
422
431
|
if (prerenderedSetOf(prerendered).has(pathname)) {
|
|
423
|
-
const staticRes = await fetch(dataUrlFor(pathname), { signal: signal ?? null })
|
|
432
|
+
const staticRes = await fetch(appUrl(dataUrlFor(pathname)), { signal: signal ?? null })
|
|
424
433
|
if (staticRes.ok) return readResponseData(staticRes, signal)
|
|
425
434
|
}
|
|
426
435
|
}
|
|
@@ -429,7 +438,7 @@ const defaultFetchData: FetchRouteData = async (path, _match, signal, navigation
|
|
|
429
438
|
headers.set(RETAIN_HEADER, navigation.retain.join(","))
|
|
430
439
|
headers.set(NAV_FROM_HEADER, navigation.from)
|
|
431
440
|
}
|
|
432
|
-
const res = await fetch(path, { headers, signal: signal ?? null })
|
|
441
|
+
const res = await fetch(appUrl(path), { headers, signal: signal ?? null })
|
|
433
442
|
const redirectTo = res.headers.get(REDIRECT_HEADER)
|
|
434
443
|
if (redirectTo !== null) throw redirected(redirectTo)
|
|
435
444
|
const status = Number(res.headers.get(STATUS_HEADER))
|
|
@@ -519,11 +528,18 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
519
528
|
* an older build be read by newer client code.
|
|
520
529
|
*/
|
|
521
530
|
type LoadedRouteData = {
|
|
531
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState | undefined
|
|
522
532
|
readonly data: unknown
|
|
523
533
|
readonly layoutData?: readonly unknown[] | undefined
|
|
524
534
|
readonly terminalRouteId?: string | undefined
|
|
525
535
|
readonly boundaries?: BoundaryStates
|
|
526
536
|
}
|
|
537
|
+
const dataFields = (value: LoadedRouteData) => ({
|
|
538
|
+
data: value.data,
|
|
539
|
+
layoutData: value.layoutData,
|
|
540
|
+
serverComponents: value.serverComponents,
|
|
541
|
+
...(value.boundaries === undefined ? {} : { boundaries: value.boundaries }),
|
|
542
|
+
})
|
|
527
543
|
const loadRouteData = async (
|
|
528
544
|
path: string,
|
|
529
545
|
match: { routeId: string; params: Record<string, string> },
|
|
@@ -551,24 +567,21 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
551
567
|
}
|
|
552
568
|
return { data: null, terminalRouteId }
|
|
553
569
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
570
|
+
let layoutData = payload.layoutData
|
|
571
|
+
if (retainFrom !== undefined && payload.retained !== undefined) {
|
|
572
|
+
const retained = new Set(
|
|
573
|
+
payload.retained.filter(
|
|
574
|
+
(index) => Number.isInteger(index) && index >= 0 && index < retainFrom.layoutData.length,
|
|
575
|
+
),
|
|
576
|
+
)
|
|
577
|
+
layoutData = layoutData?.map((value, index) =>
|
|
578
|
+
retained.has(index) ? retainFrom.layoutData[index] : value,
|
|
579
|
+
)
|
|
560
580
|
}
|
|
561
|
-
const retained = new Set(
|
|
562
|
-
payload.retained.filter(
|
|
563
|
-
(index) => Number.isInteger(index) && index >= 0 && index < retainFrom.layoutData.length,
|
|
564
|
-
),
|
|
565
|
-
)
|
|
566
|
-
const layoutData = payload.layoutData?.map((value, index) =>
|
|
567
|
-
retained.has(index) ? retainFrom.layoutData[index] : value,
|
|
568
|
-
)
|
|
569
581
|
return {
|
|
570
582
|
data: payload.data,
|
|
571
583
|
layoutData,
|
|
584
|
+
serverComponents: payload.serverComponents,
|
|
572
585
|
...(payload.boundaries !== undefined ? { boundaries: payload.boundaries } : {}),
|
|
573
586
|
}
|
|
574
587
|
}
|
|
@@ -644,11 +657,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
644
657
|
navAbort?.abort()
|
|
645
658
|
const ac = new AbortController()
|
|
646
659
|
navAbort = ac
|
|
647
|
-
const initial =
|
|
648
|
-
data: state.data,
|
|
649
|
-
layoutData: state.layoutData,
|
|
650
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
651
|
-
}
|
|
660
|
+
const initial = dataFields(state)
|
|
652
661
|
const loaded = await applyClientLoader(
|
|
653
662
|
state.path,
|
|
654
663
|
route,
|
|
@@ -663,9 +672,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
663
672
|
} else {
|
|
664
673
|
state = {
|
|
665
674
|
...state,
|
|
666
|
-
|
|
667
|
-
layoutData: loaded.layoutData,
|
|
668
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
675
|
+
...dataFields(loaded),
|
|
669
676
|
}
|
|
670
677
|
}
|
|
671
678
|
emit()
|
|
@@ -707,6 +714,10 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
707
714
|
}
|
|
708
715
|
>()
|
|
709
716
|
const cachePut = (path: string, loaded: LoadedRouteData): void => {
|
|
717
|
+
if (loaded.serverComponents !== undefined) {
|
|
718
|
+
cache.delete(path)
|
|
719
|
+
return
|
|
720
|
+
}
|
|
710
721
|
if (!cache.has(path) && cache.size >= MAX_CACHE) {
|
|
711
722
|
const oldest = cache.keys().next().value
|
|
712
723
|
if (oldest !== undefined) cache.delete(oldest)
|
|
@@ -789,7 +800,14 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
789
800
|
() => loadRouteData(at, route, signal, keepLayouts),
|
|
790
801
|
prefetchedData,
|
|
791
802
|
)
|
|
792
|
-
if (loaded.terminalRouteId !== undefined)
|
|
803
|
+
if (loaded.terminalRouteId !== undefined) {
|
|
804
|
+
await loadModule?.(loaded.terminalRouteId)
|
|
805
|
+
if (options.layoutStatusRoutes?.[loaded.terminalRouteId] === true) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`[nifra/web] ${loaded.terminalRouteId} renders inside layouts; loading ${at} as a document`,
|
|
808
|
+
)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
793
811
|
return [at, route, loaded]
|
|
794
812
|
} catch (error) {
|
|
795
813
|
const next = hops < 20 ? appRedirect(redirectOf(error)) : undefined
|
|
@@ -817,9 +835,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
817
835
|
cachePut(state.path, loaded)
|
|
818
836
|
state = {
|
|
819
837
|
...state,
|
|
820
|
-
|
|
821
|
-
layoutData: loaded.layoutData,
|
|
822
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
838
|
+
...dataFields(loaded),
|
|
823
839
|
pending: false,
|
|
824
840
|
}
|
|
825
841
|
emit()
|
|
@@ -927,7 +943,11 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
927
943
|
}
|
|
928
944
|
fEmit()
|
|
929
945
|
try {
|
|
930
|
-
const res = await fetch(action, {
|
|
946
|
+
const res = await fetch(appUrl(action), {
|
|
947
|
+
method: "POST",
|
|
948
|
+
body,
|
|
949
|
+
headers: { [DATA_HEADER]: "1" },
|
|
950
|
+
})
|
|
931
951
|
if (!res.ok)
|
|
932
952
|
throw new Error(`[nifra/web] fetcher action failed (${res.status}): ${action}`)
|
|
933
953
|
const actionData = res.status === 204 ? undefined : await readResponseData(res, ac.signal)
|
|
@@ -989,11 +1009,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
989
1009
|
) {
|
|
990
1010
|
++generation // supersede any in-flight navigation so its late result is dropped
|
|
991
1011
|
navAbort?.abort()
|
|
992
|
-
cachePut(path,
|
|
993
|
-
data: state.data,
|
|
994
|
-
layoutData: state.layoutData,
|
|
995
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
996
|
-
})
|
|
1012
|
+
cachePut(path, dataFields(state))
|
|
997
1013
|
state = { ...state, path, pending: false, pendingPath: undefined }
|
|
998
1014
|
emit()
|
|
999
1015
|
return
|
|
@@ -1041,9 +1057,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1041
1057
|
routeId: loaded.terminalRouteId ?? target.routeId,
|
|
1042
1058
|
params: loaded.terminalRouteId === undefined ? target.params : {},
|
|
1043
1059
|
path: to,
|
|
1044
|
-
|
|
1045
|
-
layoutData: loaded.layoutData,
|
|
1046
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
1060
|
+
...dataFields(loaded),
|
|
1047
1061
|
actionData: undefined, // a fresh navigation has no action result
|
|
1048
1062
|
pending: false,
|
|
1049
1063
|
}
|
|
@@ -1084,13 +1098,14 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1084
1098
|
state = { ...state, actionData: prepared.optimisticData }
|
|
1085
1099
|
emit()
|
|
1086
1100
|
}
|
|
1087
|
-
const res = await fetch(action, {
|
|
1101
|
+
const res = await fetch(appUrl(action), {
|
|
1088
1102
|
method: "POST",
|
|
1089
1103
|
body: prepared.body,
|
|
1090
1104
|
headers: { [DATA_HEADER]: "1" },
|
|
1091
1105
|
})
|
|
1092
|
-
|
|
1106
|
+
// Whatever the status, the server took the POST: the action may have run before it failed.
|
|
1093
1107
|
posted = true
|
|
1108
|
+
if (!res.ok) throw new Error(`[nifra/web] action failed (${res.status}): ${action}`)
|
|
1094
1109
|
const redirectTo = res.headers.get(REDIRECT_HEADER)
|
|
1095
1110
|
if (redirectTo !== null) {
|
|
1096
1111
|
// The action redirected (Post/Redirect/Get) - treat it as a client navigation.
|
|
@@ -1114,9 +1129,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1114
1129
|
routeId: loaded.terminalRouteId ?? target.routeId,
|
|
1115
1130
|
params: loaded.terminalRouteId === undefined ? target.params : {},
|
|
1116
1131
|
path: to,
|
|
1117
|
-
|
|
1118
|
-
layoutData: loaded.layoutData,
|
|
1119
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
1132
|
+
...dataFields(loaded),
|
|
1120
1133
|
actionData: undefined,
|
|
1121
1134
|
pending: false,
|
|
1122
1135
|
}
|
|
@@ -1137,9 +1150,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1137
1150
|
const active = { routeId: state.routeId, params: state.params }
|
|
1138
1151
|
const loaded = skipActive
|
|
1139
1152
|
? {
|
|
1140
|
-
|
|
1141
|
-
layoutData: state.layoutData,
|
|
1142
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
1153
|
+
...dataFields(state),
|
|
1143
1154
|
}
|
|
1144
1155
|
: await applyClientLoader(state.path, active, ac.signal, () =>
|
|
1145
1156
|
loadRouteData(state.path, active, ac.signal),
|
|
@@ -1154,9 +1165,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1154
1165
|
routeId: state.routeId,
|
|
1155
1166
|
params: state.params,
|
|
1156
1167
|
path: state.path,
|
|
1157
|
-
|
|
1158
|
-
layoutData: loaded.layoutData,
|
|
1159
|
-
...(loaded.boundaries !== undefined ? { boundaries: loaded.boundaries } : {}),
|
|
1168
|
+
...dataFields(loaded),
|
|
1160
1169
|
actionData,
|
|
1161
1170
|
pending: false,
|
|
1162
1171
|
}
|
|
@@ -1172,9 +1181,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1172
1181
|
routeId: state.routeId,
|
|
1173
1182
|
params: state.params,
|
|
1174
1183
|
path: state.path,
|
|
1175
|
-
|
|
1176
|
-
layoutData: state.layoutData,
|
|
1177
|
-
...(state.boundaries !== undefined ? { boundaries: state.boundaries } : {}),
|
|
1184
|
+
...dataFields(state),
|
|
1178
1185
|
actionData: previousActionData,
|
|
1179
1186
|
pending: false,
|
|
1180
1187
|
}
|
|
@@ -1182,7 +1189,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1182
1189
|
}
|
|
1183
1190
|
if (!posted) throw err
|
|
1184
1191
|
if (mine !== generation) return // a newer navigation or submit owns the page
|
|
1185
|
-
throw redirectOf(err) === undefined ? redirected(landing ?? state.path) : err
|
|
1192
|
+
throw redirectOf(err) === undefined ? redirected(landing ?? state.path, err) : err
|
|
1186
1193
|
}
|
|
1187
1194
|
},
|
|
1188
1195
|
hydrate,
|
|
@@ -1216,7 +1223,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1216
1223
|
const oldest = prefetched.keys().next().value
|
|
1217
1224
|
if (oldest !== undefined) prefetched.delete(oldest)
|
|
1218
1225
|
}
|
|
1219
|
-
prefetched.set(path, [loaded, Date.now()])
|
|
1226
|
+
if (loaded.serverComponents === undefined) prefetched.set(path, [loaded, Date.now()])
|
|
1220
1227
|
} catch {
|
|
1221
1228
|
// Best-effort: a failed prefetch just means the eventual navigate fetches normally.
|
|
1222
1229
|
} finally {
|
package/src/seo.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Head helpers for a route's `meta`: `canonical()`, `openGraph()` and `jsonLd()`. `meta()` runs on the
|
|
3
|
+
* server and in the browser, and this entry imports nothing at runtime, so browser code can take the
|
|
4
|
+
* helpers from here without bundling the server modules the package root also exports.
|
|
5
|
+
*/
|
|
6
|
+
import type { LinkDescriptor, MetaDescriptor, ScriptDescriptor } from "./manifest.ts"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A `<link rel="canonical">` descriptor for a route's `meta.link`. The canonical URL tells search
|
|
10
|
+
* engines which URL is authoritative for a page (deduping query-string / tracking variants).
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* export const meta = (a) => ({ link: [canonical(`https://site.com/posts/${a.params.slug}`)] })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function canonical(href: string): LinkDescriptor {
|
|
17
|
+
return { rel: "canonical", href }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Inputs for {@link openGraph} - the common Open Graph properties. All optional; only the provided
|
|
21
|
+
* ones become tags. `type` defaults to `"website"`. */
|
|
22
|
+
export interface OpenGraphInput {
|
|
23
|
+
readonly title?: string
|
|
24
|
+
readonly description?: string
|
|
25
|
+
/** Absolute URL of the share image (`og:image`). */
|
|
26
|
+
readonly image?: string
|
|
27
|
+
/** Canonical URL of the page (`og:url`). */
|
|
28
|
+
readonly url?: string
|
|
29
|
+
/** Object type (`og:type`) - `"website"`, `"article"`, … Default `"website"`. */
|
|
30
|
+
readonly type?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build the Open Graph `<meta property="og:*">` entries for a route's `meta.meta`. Returns only the
|
|
35
|
+
* properties you supplied (plus `og:type`, defaulting to `"website"`), so it composes with other meta.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* export const meta = { meta: [...openGraph({ title: "Nifra", image: "https://site.com/og.png" })] }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function openGraph(input: OpenGraphInput): MetaDescriptor[] {
|
|
42
|
+
const tags: MetaDescriptor[] = []
|
|
43
|
+
const add = (property: string, content: string | undefined): void => {
|
|
44
|
+
if (content !== undefined) tags.push({ property, content })
|
|
45
|
+
}
|
|
46
|
+
add("og:title", input.title)
|
|
47
|
+
add("og:description", input.description)
|
|
48
|
+
add("og:image", input.image)
|
|
49
|
+
add("og:url", input.url)
|
|
50
|
+
// `og:type` always present (the spec's required property) - default "website" when unset.
|
|
51
|
+
tags.push({ property: "og:type", content: input.type ?? "website" })
|
|
52
|
+
return tags
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a JSON-LD `<script type="application/ld+json">` entry for a route's `meta.script` from a plain
|
|
57
|
+
* object. `JSON.stringify` produces the body; the head renderer breakout-escapes it (see
|
|
58
|
+
* `escapeScriptContent`), so a string field containing `</script>` is embedded safely.
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* export const meta = {
|
|
62
|
+
* script: [jsonLd({ "@context": "https://schema.org", "@type": "Article", headline: "Hi" })],
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function jsonLd(data: Record<string, unknown>): ScriptDescriptor {
|
|
67
|
+
return { type: "application/ld+json", content: JSON.stringify(data) }
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const __NIFRA_COMPONENT_BUILD__: string
|
|
2
|
+
|
|
3
|
+
/** The coordinated compiler replaces this value in every environment. */
|
|
4
|
+
export const componentBuild: string =
|
|
5
|
+
typeof __NIFRA_COMPONENT_BUILD__ === "string"
|
|
6
|
+
? __NIFRA_COMPONENT_BUILD__
|
|
7
|
+
: (() => {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"[nifra/components] build-info requires serverComponentsConfig or nifra build",
|
|
10
|
+
)
|
|
11
|
+
})()
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises"
|
|
3
|
+
import { relative, resolve } from "node:path"
|
|
4
|
+
import { build } from "vite"
|
|
5
|
+
import { unsupportedBuiltin } from "../internal/target-compat.ts"
|
|
6
|
+
import type { ComponentPipelineOptions } from "./build.ts"
|
|
7
|
+
import { componentBundleImports } from "./bundle-proof.ts"
|
|
8
|
+
import { componentServerEntry } from "./server-entry.ts"
|
|
9
|
+
|
|
10
|
+
export interface ComponentBuildResult {
|
|
11
|
+
readonly outDir: string
|
|
12
|
+
readonly build: string
|
|
13
|
+
readonly target: NonNullable<ComponentPipelineOptions["target"]>
|
|
14
|
+
readonly entry: string
|
|
15
|
+
readonly styles: readonly string[]
|
|
16
|
+
readonly assets: readonly string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Asset {
|
|
20
|
+
readonly file: string
|
|
21
|
+
readonly isEntry?: boolean
|
|
22
|
+
readonly css?: readonly string[]
|
|
23
|
+
}
|
|
24
|
+
const assetPath = /^assets\/[A-Za-z0-9_./-]+$/
|
|
25
|
+
function readAssets(value: unknown): readonly Asset[] {
|
|
26
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
27
|
+
throw new Error("[nifra/components] Missing client build manifest")
|
|
28
|
+
return Object.values(value).map((item: unknown) => {
|
|
29
|
+
if (typeof item !== "object" || item === null || Array.isArray(item))
|
|
30
|
+
throw new Error("[nifra/components] Invalid client manifest")
|
|
31
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Manifest object shape, asset paths and optional field types are checked before constructing the target manifest.
|
|
32
|
+
const record = item as Record<string, unknown>
|
|
33
|
+
if (
|
|
34
|
+
typeof record.file !== "string" ||
|
|
35
|
+
!assetPath.test(record.file) ||
|
|
36
|
+
record.file.split("/").includes("..") ||
|
|
37
|
+
(record.isEntry !== undefined && typeof record.isEntry !== "boolean") ||
|
|
38
|
+
(record.css !== undefined &&
|
|
39
|
+
(!Array.isArray(record.css) ||
|
|
40
|
+
record.css.some(
|
|
41
|
+
(css) =>
|
|
42
|
+
typeof css !== "string" || !assetPath.test(css) || css.split("/").includes(".."),
|
|
43
|
+
)))
|
|
44
|
+
)
|
|
45
|
+
throw new Error("[nifra/components] Invalid build asset URL")
|
|
46
|
+
const asset: Asset = { file: record.file }
|
|
47
|
+
if (record.isEntry !== undefined) Object.assign(asset, { isEntry: record.isEntry })
|
|
48
|
+
if (record.css !== undefined) Object.assign(asset, { css: record.css })
|
|
49
|
+
return asset
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function files(directory: string): Promise<string[]> {
|
|
54
|
+
const result: string[] = []
|
|
55
|
+
for (const item of await readdir(directory, { withFileTypes: true })) {
|
|
56
|
+
if (item.isSymbolicLink())
|
|
57
|
+
throw new Error("[nifra/components] Build artifacts cannot be symbolic links")
|
|
58
|
+
const path = resolve(directory, item.name)
|
|
59
|
+
if (item.isDirectory()) result.push(...(await files(path)))
|
|
60
|
+
else if (item.isFile()) result.push(path)
|
|
61
|
+
}
|
|
62
|
+
return result.sort()
|
|
63
|
+
}
|
|
64
|
+
const digest = (value: string | Uint8Array): string =>
|
|
65
|
+
createHash("sha256").update(value).digest("hex")
|
|
66
|
+
|
|
67
|
+
/** Assemble one complete generation before the build orchestrator publishes its directory. */
|
|
68
|
+
export async function assembleComponentTarget(
|
|
69
|
+
options: ComponentPipelineOptions,
|
|
70
|
+
generation: string,
|
|
71
|
+
): Promise<ComponentBuildResult> {
|
|
72
|
+
const out = resolve(options.root, options.outDir ?? "dist")
|
|
73
|
+
const target = options.target ?? "bun"
|
|
74
|
+
const records = readAssets(
|
|
75
|
+
JSON.parse(await readFile(resolve(out, "client/.vite/manifest.json"), "utf8")),
|
|
76
|
+
)
|
|
77
|
+
const entries = records.filter((record) => record.isEntry)
|
|
78
|
+
if (entries.length !== 1)
|
|
79
|
+
throw new Error("[nifra/components] Exactly one client entry is required")
|
|
80
|
+
const assets = (await files(resolve(out, "client/assets"))).map(
|
|
81
|
+
(file) => `/${relative(resolve(out, "client"), file).replaceAll("\\", "/")}`,
|
|
82
|
+
)
|
|
83
|
+
const entry = `/${entries[0]!.file}`
|
|
84
|
+
const styles = [...new Set(records.flatMap((record) => record.css ?? []))]
|
|
85
|
+
.map((file) => `/${file}`)
|
|
86
|
+
.sort()
|
|
87
|
+
if (!assets.includes(entry) || styles.some((file) => !assets.includes(file)))
|
|
88
|
+
throw new Error("[nifra/components] Missing client generation artifacts")
|
|
89
|
+
const serverDirectory = options.adapter === "react" ? "ssr" : "server"
|
|
90
|
+
const serverEntry = `${serverDirectory}/index.js`
|
|
91
|
+
const staticRoot = target === "vercel" ? resolve(out, "static") : out
|
|
92
|
+
await cp(resolve(out, "client/assets"), resolve(staticRoot, "assets"), { recursive: true })
|
|
93
|
+
await rm(resolve(out, "client"), { recursive: true })
|
|
94
|
+
const metadata = {
|
|
95
|
+
v: 1,
|
|
96
|
+
build: generation,
|
|
97
|
+
adapter: options.adapter,
|
|
98
|
+
target,
|
|
99
|
+
entry,
|
|
100
|
+
styles,
|
|
101
|
+
assets,
|
|
102
|
+
}
|
|
103
|
+
if (target === "cloudflare") {
|
|
104
|
+
const source = componentServerEntry({ ...metadata, serverEntry, platform: "cloudflare" })
|
|
105
|
+
const input = resolve(out, ".nifra-entry.backend.js")
|
|
106
|
+
await writeFile(input, source)
|
|
107
|
+
await build({
|
|
108
|
+
root: out,
|
|
109
|
+
configFile: false,
|
|
110
|
+
publicDir: false,
|
|
111
|
+
ssr: { noExternal: true },
|
|
112
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
113
|
+
build: {
|
|
114
|
+
ssr: input,
|
|
115
|
+
outDir: resolve(out, ".nifra-worker"),
|
|
116
|
+
emptyOutDir: true,
|
|
117
|
+
minify: true,
|
|
118
|
+
rollupOptions: { output: { inlineDynamicImports: true, entryFileNames: "index.js" } },
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
await cp(resolve(out, ".nifra-worker/index.js"), resolve(out, "_worker.js"))
|
|
122
|
+
const worker = await readFile(resolve(out, "_worker.js"), "utf8")
|
|
123
|
+
if (
|
|
124
|
+
componentBundleImports(worker).some((specifier) => unsupportedBuiltin(specifier, "browser"))
|
|
125
|
+
)
|
|
126
|
+
throw new Error("[nifra/components] Unsupported built-in in the assembled edge worker")
|
|
127
|
+
await rm(input)
|
|
128
|
+
await rm(resolve(out, ".nifra-worker"), { recursive: true })
|
|
129
|
+
await rm(resolve(out, serverDirectory), { recursive: true })
|
|
130
|
+
if (options.adapter === "react") await rm(resolve(out, "rsc"), { recursive: true })
|
|
131
|
+
await writeFile(
|
|
132
|
+
resolve(out, "_routes.json"),
|
|
133
|
+
`${JSON.stringify({ version: 1, include: ["/*"], exclude: ["/assets/*"] })}\n`,
|
|
134
|
+
)
|
|
135
|
+
await writeFile(resolve(out, ".assetsignore"), "*\n!assets/\n!assets/**\n")
|
|
136
|
+
} else {
|
|
137
|
+
const runtimeRoot = target === "vercel" ? resolve(out, "functions/index.func") : out
|
|
138
|
+
await mkdir(runtimeRoot, { recursive: true })
|
|
139
|
+
if (runtimeRoot !== out) {
|
|
140
|
+
await cp(resolve(out, serverDirectory), resolve(runtimeRoot, serverDirectory), {
|
|
141
|
+
recursive: true,
|
|
142
|
+
})
|
|
143
|
+
await rm(resolve(out, serverDirectory), { recursive: true })
|
|
144
|
+
if (options.adapter === "react") {
|
|
145
|
+
await cp(resolve(out, "rsc"), resolve(runtimeRoot, "rsc"), { recursive: true })
|
|
146
|
+
await rm(resolve(out, "rsc"), { recursive: true })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const privateFiles = await files(resolve(runtimeRoot, serverDirectory))
|
|
150
|
+
if (options.adapter === "react")
|
|
151
|
+
privateFiles.push(...(await files(resolve(runtimeRoot, "rsc"))))
|
|
152
|
+
if (target !== "vercel") privateFiles.push(...(await files(resolve(runtimeRoot, "assets"))))
|
|
153
|
+
const hashes = Object.fromEntries(
|
|
154
|
+
await Promise.all(
|
|
155
|
+
privateFiles.map(async (file) => [
|
|
156
|
+
relative(runtimeRoot, file).replaceAll("\\", "/"),
|
|
157
|
+
digest(await readFile(file)),
|
|
158
|
+
]),
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
const manifest = `${JSON.stringify({ ...metadata, files: hashes })}\n`
|
|
162
|
+
await writeFile(resolve(runtimeRoot, "component-manifest.json"), manifest)
|
|
163
|
+
await writeFile(
|
|
164
|
+
resolve(runtimeRoot, target === "vercel" ? "index.js" : "server.js"),
|
|
165
|
+
componentServerEntry({
|
|
166
|
+
...metadata,
|
|
167
|
+
serverEntry,
|
|
168
|
+
platform: target,
|
|
169
|
+
manifestDigest: digest(manifest),
|
|
170
|
+
}),
|
|
171
|
+
)
|
|
172
|
+
await writeFile(resolve(runtimeRoot, "package.json"), '{"type":"module"}\n')
|
|
173
|
+
if (target === "vercel") {
|
|
174
|
+
await writeFile(
|
|
175
|
+
resolve(runtimeRoot, ".vc-config.json"),
|
|
176
|
+
`${JSON.stringify({
|
|
177
|
+
runtime: "nodejs22.x",
|
|
178
|
+
handler: "index.js",
|
|
179
|
+
launcherType: "Nodejs",
|
|
180
|
+
shouldAddHelpers: false,
|
|
181
|
+
})}\n`,
|
|
182
|
+
)
|
|
183
|
+
await writeFile(
|
|
184
|
+
resolve(out, "config.json"),
|
|
185
|
+
`${JSON.stringify({
|
|
186
|
+
version: 3,
|
|
187
|
+
routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/index" }],
|
|
188
|
+
})}\n`,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
await writeFile(resolve(out, "component-build.json"), `${JSON.stringify(metadata)}\n`)
|
|
193
|
+
return { outDir: out, build: generation, target, entry, styles, assets }
|
|
194
|
+
}
|