@nifrajs/web 3.3.0 → 4.0.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/dist/backend-only.d.ts +15 -0
- package/dist/backend-only.d.ts.map +1 -0
- package/dist/backend-only.js +16 -0
- package/dist/backend-only.js.map +1 -0
- package/dist/boundary.d.ts +5 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +11 -2
- package/dist/boundary.js.map +1 -1
- package/dist/build-plan.d.ts +35 -6
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +89 -35
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +18 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +97 -17
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +72 -23
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +411 -98
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +1 -1
- package/dist/bun-dev-entry.js +1 -1
- package/dist/cdn.d.ts +74 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +132 -0
- package/dist/cdn.js.map +1 -0
- package/dist/client.d.ts +21 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +226 -15
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +4 -3
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +12 -3
- package/dist/conformance.js.map +1 -1
- package/dist/csp.d.ts +93 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +122 -0
- package/dist/csp.js.map +1 -0
- package/dist/css-contract.d.ts +21 -0
- package/dist/css-contract.d.ts.map +1 -0
- package/dist/css-contract.js +28 -0
- package/dist/css-contract.js.map +1 -0
- package/dist/dev-client.d.ts +76 -0
- package/dist/dev-client.d.ts.map +1 -0
- package/dist/dev-client.js +280 -0
- package/dist/dev-client.js.map +1 -0
- package/dist/dev-diagnostics.d.ts +26 -2
- package/dist/dev-diagnostics.d.ts.map +1 -1
- package/dist/dev-diagnostics.js +38 -7
- package/dist/dev-diagnostics.js.map +1 -1
- package/dist/dev-error.d.ts +4 -13
- package/dist/dev-error.d.ts.map +1 -1
- package/dist/dev-error.js +46 -4
- package/dist/dev-error.js.map +1 -1
- package/dist/dev-feed.d.ts +298 -0
- package/dist/dev-feed.d.ts.map +1 -0
- package/dist/dev-feed.js +797 -0
- package/dist/dev-feed.js.map +1 -0
- package/dist/dev-indicator.d.ts +38 -0
- package/dist/dev-indicator.d.ts.map +1 -0
- package/dist/dev-indicator.js +93 -0
- package/dist/dev-indicator.js.map +1 -0
- package/dist/dev-session.d.ts +77 -0
- package/dist/dev-session.d.ts.map +1 -0
- package/dist/dev-session.js +580 -0
- package/dist/dev-session.js.map +1 -0
- package/dist/dev-sourcemap.d.ts +27 -0
- package/dist/dev-sourcemap.d.ts.map +1 -0
- package/dist/dev-sourcemap.js +235 -0
- package/dist/dev-sourcemap.js.map +1 -0
- package/dist/dev-ssr-graph.d.ts.map +1 -1
- package/dist/dev-ssr-graph.js +13 -5
- package/dist/dev-ssr-graph.js.map +1 -1
- package/dist/dev.d.ts +15 -2
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +69 -44
- package/dist/dev.js.map +1 -1
- package/dist/diagnostic-catalog.d.ts +35 -0
- package/dist/diagnostic-catalog.d.ts.map +1 -0
- package/dist/diagnostic-catalog.js +203 -0
- package/dist/diagnostic-catalog.js.map +1 -0
- package/dist/diagnostic-prompt.d.ts +49 -0
- package/dist/diagnostic-prompt.d.ts.map +1 -0
- package/dist/diagnostic-prompt.js +303 -0
- package/dist/diagnostic-prompt.js.map +1 -0
- package/dist/diagnostic.d.ts +17 -23
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +31 -39
- package/dist/diagnostic.js.map +1 -1
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +4 -1
- package/dist/draft.js.map +1 -1
- package/dist/fn.d.ts +5 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +13 -3
- package/dist/fn.js.map +1 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +4 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +15 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cdn/cloudflare.d.ts +48 -0
- package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
- package/dist/internal/cdn/cloudflare.js +94 -0
- package/dist/internal/cdn/cloudflare.js.map +1 -0
- package/dist/internal/cdn/core.d.ts +91 -0
- package/dist/internal/cdn/core.d.ts.map +1 -0
- package/dist/internal/cdn/core.js +220 -0
- package/dist/internal/cdn/core.js.map +1 -0
- package/dist/internal/cdn/fastly.d.ts +17 -0
- package/dist/internal/cdn/fastly.d.ts.map +1 -0
- package/dist/internal/cdn/fastly.js +43 -0
- package/dist/internal/cdn/fastly.js.map +1 -0
- package/dist/internal/cdn/vercel.d.ts +31 -0
- package/dist/internal/cdn/vercel.d.ts.map +1 -0
- package/dist/internal/cdn/vercel.js +56 -0
- package/dist/internal/cdn/vercel.js.map +1 -0
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/codegen.d.ts +3 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +151 -30
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/dev-csp.d.ts +24 -0
- package/dist/internal/dev-csp.d.ts.map +1 -0
- package/dist/internal/dev-csp.js +160 -0
- package/dist/internal/dev-csp.js.map +1 -0
- package/dist/internal/dev-reserved.d.ts +25 -0
- package/dist/internal/dev-reserved.d.ts.map +1 -0
- package/dist/internal/dev-reserved.js +39 -0
- package/dist/internal/dev-reserved.js.map +1 -0
- package/dist/internal/html-spans.d.ts +10 -0
- package/dist/internal/html-spans.d.ts.map +1 -0
- package/dist/internal/html-spans.js +46 -0
- package/dist/internal/html-spans.js.map +1 -0
- package/dist/internal/loading-runtime.d.ts +44 -0
- package/dist/internal/loading-runtime.d.ts.map +1 -0
- package/dist/internal/loading-runtime.js +206 -0
- package/dist/internal/loading-runtime.js.map +1 -0
- package/dist/internal/matches-runtime.d.ts +8 -0
- package/dist/internal/matches-runtime.d.ts.map +1 -0
- package/dist/internal/matches-runtime.js +61 -0
- package/dist/internal/matches-runtime.js.map +1 -0
- package/dist/internal/mount-shadow.d.ts +36 -0
- package/dist/internal/mount-shadow.d.ts.map +1 -0
- package/dist/internal/mount-shadow.js +65 -0
- package/dist/internal/mount-shadow.js.map +1 -0
- package/dist/internal/node-builtins.d.ts +3 -0
- package/dist/internal/node-builtins.d.ts.map +1 -0
- package/dist/internal/node-builtins.js +79 -0
- package/dist/internal/node-builtins.js.map +1 -0
- package/dist/internal/output-guard.d.ts +65 -0
- package/dist/internal/output-guard.d.ts.map +1 -0
- package/dist/internal/output-guard.js +630 -0
- package/dist/internal/output-guard.js.map +1 -0
- package/dist/internal/page-execution.d.ts +69 -0
- package/dist/internal/page-execution.d.ts.map +1 -0
- package/dist/internal/page-execution.js +1021 -0
- package/dist/internal/page-execution.js.map +1 -0
- package/dist/internal/parity.d.ts +21 -0
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +104 -22
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +12 -0
- package/dist/internal/private-env.d.ts.map +1 -0
- package/dist/internal/private-env.js +335 -0
- package/dist/internal/private-env.js.map +1 -0
- package/dist/internal/public-url.d.ts +3 -0
- package/dist/internal/public-url.d.ts.map +1 -0
- package/dist/internal/public-url.js +18 -0
- package/dist/internal/public-url.js.map +1 -0
- package/dist/internal/render-document.d.ts +37 -21
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +104 -65
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/request-url.d.ts +3 -0
- package/dist/internal/request-url.d.ts.map +1 -0
- package/dist/internal/request-url.js +14 -0
- package/dist/internal/request-url.js.map +1 -0
- package/dist/internal/response-controls.d.ts +75 -0
- package/dist/internal/response-controls.d.ts.map +1 -0
- package/dist/internal/response-controls.js +256 -0
- package/dist/internal/response-controls.js.map +1 -0
- package/dist/internal/runtime-contract.d.ts +11 -16
- package/dist/internal/runtime-contract.d.ts.map +1 -1
- package/dist/internal/runtime-contract.js.map +1 -1
- package/dist/internal/secret-scan.d.ts +104 -0
- package/dist/internal/secret-scan.d.ts.map +1 -0
- package/dist/internal/secret-scan.js +485 -0
- package/dist/internal/secret-scan.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +6 -29
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +6 -66
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/source-map.d.ts +47 -0
- package/dist/internal/source-map.d.ts.map +1 -0
- package/dist/internal/source-map.js +225 -0
- package/dist/internal/source-map.js.map +1 -0
- package/dist/internal/target-compat.d.ts +27 -0
- package/dist/internal/target-compat.d.ts.map +1 -0
- package/dist/internal/target-compat.js +77 -0
- package/dist/internal/target-compat.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +6 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -1
- package/dist/internal/timing-safe-equal.js +10 -0
- package/dist/internal/timing-safe-equal.js.map +1 -1
- package/dist/internal/vitals-runtime.d.ts +18 -0
- package/dist/internal/vitals-runtime.d.ts.map +1 -0
- package/dist/internal/vitals-runtime.js +38 -0
- package/dist/internal/vitals-runtime.js.map +1 -0
- package/dist/internal/web-app.d.ts +56 -8
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +215 -772
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/zone-graph.d.ts +89 -0
- package/dist/internal/zone-graph.d.ts.map +1 -0
- package/dist/internal/zone-graph.js +492 -0
- package/dist/internal/zone-graph.js.map +1 -0
- package/dist/isr.d.ts +83 -6
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +301 -39
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +3 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +37 -9
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +221 -14
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +386 -9
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +26 -5
- package/dist/module-graph.d.ts.map +1 -1
- package/dist/module-graph.js +17 -7
- package/dist/module-graph.js.map +1 -1
- package/dist/navigation.d.ts +17 -3
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +11 -5
- package/dist/navigation.js.map +1 -1
- package/dist/nonce.d.ts +47 -0
- package/dist/nonce.d.ts.map +1 -0
- package/dist/nonce.js +63 -0
- package/dist/nonce.js.map +1 -0
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +4 -3
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +9 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +18 -0
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/svg.d.ts +22 -3
- package/dist/plugins/svg.d.ts.map +1 -1
- package/dist/plugins/svg.js +112 -1
- package/dist/plugins/svg.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +107 -3
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +378 -17
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts +61 -0
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
- package/dist/plugins/vite-zone-guard.js +208 -0
- package/dist/plugins/vite-zone-guard.js.map +1 -0
- package/dist/plugins/zone-guard.d.ts +27 -0
- package/dist/plugins/zone-guard.d.ts.map +1 -0
- package/dist/plugins/zone-guard.js +72 -0
- package/dist/plugins/zone-guard.js.map +1 -0
- package/dist/prerender.d.ts +10 -0
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +35 -3
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +9 -0
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +131 -48
- package/dist/public-dir.js.map +1 -1
- package/dist/pwa-manifest.d.ts +68 -0
- package/dist/pwa-manifest.d.ts.map +1 -0
- package/dist/pwa-manifest.js +178 -0
- package/dist/pwa-manifest.js.map +1 -0
- package/dist/query.js +4 -4
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +42 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +6 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-manifest.d.ts +3 -0
- package/dist/route-manifest.d.ts.map +1 -1
- package/dist/route-manifest.js +26 -3
- package/dist/route-manifest.js.map +1 -1
- package/dist/route-types.d.ts +24 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +195 -0
- package/dist/route-types.js.map +1 -0
- package/dist/router.d.ts +28 -6
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +129 -40
- package/dist/router.js.map +1 -1
- package/dist/trusted-html.d.ts +34 -0
- package/dist/trusted-html.d.ts.map +1 -0
- package/dist/trusted-html.js +35 -0
- package/dist/trusted-html.js.map +1 -0
- package/dist/vitals.d.ts +33 -0
- package/dist/vitals.d.ts.map +1 -0
- package/dist/vitals.js +16 -0
- package/dist/vitals.js.map +1 -0
- package/dist/vite.d.ts +16 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +187 -36
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +70 -0
- package/dist/zones.d.ts.map +1 -0
- package/dist/zones.js +365 -0
- package/dist/zones.js.map +1 -0
- package/package.json +67 -14
- package/src/backend-only.ts +16 -0
- package/src/boundary.ts +18 -5
- package/src/build-plan.ts +113 -36
- package/src/build-vite.ts +124 -15
- package/src/build.ts +535 -102
- package/src/bun-dev-entry.ts +1 -1
- package/src/cdn.ts +216 -0
- package/src/client.ts +251 -18
- package/src/conformance.ts +16 -3
- package/src/csp.ts +194 -0
- package/src/css-contract.ts +44 -0
- package/src/dev-client.ts +349 -0
- package/src/dev-diagnostics.ts +60 -7
- package/src/dev-error.ts +49 -5
- package/src/dev-feed.ts +1140 -0
- package/src/dev-indicator.ts +113 -0
- package/src/dev-session.ts +729 -0
- package/src/dev-sourcemap.ts +281 -0
- package/src/dev-ssr-graph.ts +16 -5
- package/src/dev.ts +86 -43
- package/src/diagnostic-catalog.ts +243 -0
- package/src/diagnostic-prompt.ts +355 -0
- package/src/diagnostic.ts +63 -66
- package/src/draft.ts +4 -1
- package/src/fn.ts +19 -3
- package/src/fs.ts +13 -2
- package/src/index.ts +57 -10
- package/src/internal/cdn/cloudflare.ts +146 -0
- package/src/internal/cdn/core.ts +316 -0
- package/src/internal/cdn/fastly.ts +62 -0
- package/src/internal/cdn/vercel.ts +95 -0
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/codegen.ts +180 -29
- package/src/internal/dev-csp.ts +176 -0
- package/src/internal/dev-reserved.ts +52 -0
- package/src/internal/html-spans.ts +50 -0
- package/src/internal/loading-runtime.ts +244 -0
- package/src/internal/matches-runtime.ts +62 -0
- package/src/internal/mount-shadow.ts +88 -0
- package/src/internal/node-builtins.ts +79 -0
- package/src/internal/output-guard.ts +701 -0
- package/src/internal/page-execution.ts +1371 -0
- package/src/internal/parity.ts +150 -36
- package/src/internal/private-env.ts +334 -0
- package/src/internal/public-url.ts +22 -0
- package/src/internal/render-document.ts +153 -73
- package/src/internal/request-url.ts +14 -0
- package/src/internal/response-controls.ts +284 -0
- package/src/internal/runtime-contract.ts +10 -15
- package/src/internal/secret-scan.ts +623 -0
- package/src/internal/server-boundary.ts +6 -69
- package/src/internal/source-map.ts +256 -0
- package/src/internal/target-compat.ts +92 -0
- package/src/internal/timing-safe-equal.ts +13 -0
- package/src/internal/vitals-runtime.ts +53 -0
- package/src/internal/web-app.ts +290 -879
- package/src/internal/zone-graph.ts +593 -0
- package/src/isr.ts +366 -39
- package/src/llms-txt.ts +44 -8
- package/src/manifest.ts +671 -17
- package/src/module-graph.ts +53 -9
- package/src/navigation.ts +29 -6
- package/src/nonce.ts +104 -0
- package/src/plugins/css-modules.ts +5 -3
- package/src/plugins/kit.ts +24 -0
- package/src/plugins/svg.ts +135 -4
- package/src/plugins/vite-leak-guard.ts +529 -16
- package/src/plugins/vite-zone-guard.ts +263 -0
- package/src/plugins/zone-guard.ts +110 -0
- package/src/prerender.ts +49 -3
- package/src/public-dir.ts +135 -49
- package/src/pwa-manifest.ts +226 -0
- package/src/query.ts +4 -4
- package/src/render-seam.ts +46 -1
- package/src/route-manifest.ts +15 -3
- package/src/route-types.ts +264 -0
- package/src/router.ts +163 -63
- package/src/trusted-html.ts +44 -0
- package/src/vitals.ts +44 -0
- package/src/vite.ts +207 -36
- package/src/zones.ts +430 -0
- package/dist/plugins/vite-server-only.d.ts +0 -49
- package/dist/plugins/vite-server-only.d.ts.map +0 -1
- package/dist/plugins/vite-server-only.js +0 -57
- package/dist/plugins/vite-server-only.js.map +0 -1
- package/dist/server-only.d.ts +0 -21
- package/dist/server-only.d.ts.map +0 -1
- package/dist/server-only.js +0 -25
- package/dist/server-only.js.map +0 -1
- package/src/plugins/vite-server-only.ts +0 -75
- package/src/server-only.ts +0 -25
package/src/build-plan.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { basename as pathBasename } from "node:path"
|
|
2
|
+
import type { CssLoadingMode } from "./css-contract.ts"
|
|
3
|
+
import { codeUnitOrder } from "./internal/code-unit-order.ts"
|
|
4
|
+
import { isBareNodeBuiltin } from "./internal/node-builtins.ts"
|
|
5
|
+
import type { SecretExemption } from "./internal/secret-scan.ts"
|
|
2
6
|
import type { ClientModuleGraph } from "./module-graph.ts"
|
|
3
7
|
|
|
4
8
|
export interface BuildManifest {
|
|
@@ -19,6 +23,10 @@ export interface BuildManifest {
|
|
|
19
23
|
* imported the CSS; the always-safe fallback `createWebApp` links when a route has no per-route entry
|
|
20
24
|
* in {@link routeStyles}. Omitted when the app imports no CSS. */
|
|
21
25
|
readonly css?: readonly string[]
|
|
26
|
+
/** Whether the Vite client build kept CSS split by entry/chunk. Absent for the native Bun build. */
|
|
27
|
+
readonly cssCodeSplit?: boolean
|
|
28
|
+
/** How framework-owned SSR stylesheet links should become active. Defaults to `blocking`. */
|
|
29
|
+
readonly cssLoading?: CssLoadingMode
|
|
22
30
|
/** `routeId → [chain CSS URLs]` - only the stylesheets the matched route's layout chain + own file
|
|
23
31
|
* actually use (Bun emits a per-entrypoint CSS bundle per route/layout, with shared-component CSS
|
|
24
32
|
* inlined into each consumer). `createWebApp` links these instead of the aggregate, so a page ships
|
|
@@ -46,7 +54,7 @@ export interface ServerBuild {
|
|
|
46
54
|
// ===================================================================================================
|
|
47
55
|
|
|
48
56
|
/** A deploy target `nifra build --target <t>` can emit. `static` is pure SSG (no server). */
|
|
49
|
-
export const BUILD_TARGETS = ["bun", "node", "deno", "
|
|
57
|
+
export const BUILD_TARGETS = ["bun", "node", "deno", "cloudflare", "vercel", "static"] as const
|
|
50
58
|
export type BuildTarget = (typeof BUILD_TARGETS)[number]
|
|
51
59
|
|
|
52
60
|
/** A type guard narrowing an arbitrary string to a {@link BuildTarget}. */
|
|
@@ -54,6 +62,16 @@ export function isBuildTarget(value: string): value is BuildTarget {
|
|
|
54
62
|
return (BUILD_TARGETS as readonly string[]).includes(value)
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
/** `value` as a {@link BuildTarget}, or throw naming the valid ones (and the renamed `cf-pages`). */
|
|
66
|
+
export function parseBuildTarget(value: string, label = "target"): BuildTarget {
|
|
67
|
+
if (isBuildTarget(value)) return value
|
|
68
|
+
throw new Error(
|
|
69
|
+
value === "cf-pages"
|
|
70
|
+
? `[nifra] the ${label} "cf-pages" is now "cloudflare"`
|
|
71
|
+
: `[nifra] unknown ${label} "${value}". Valid: ${BUILD_TARGETS.join(", ")}`,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
export type ServerBuildTarget = "browser" | "node" | "bun"
|
|
58
76
|
|
|
59
77
|
export interface StaticBuildTargetPlan {
|
|
@@ -61,6 +79,7 @@ export interface StaticBuildTargetPlan {
|
|
|
61
79
|
readonly kind: "static"
|
|
62
80
|
readonly serverTarget: undefined
|
|
63
81
|
readonly outputFile: undefined
|
|
82
|
+
readonly staticDir: ""
|
|
64
83
|
readonly run: string
|
|
65
84
|
}
|
|
66
85
|
|
|
@@ -68,8 +87,10 @@ export interface ServerBuildTargetPlan {
|
|
|
68
87
|
readonly target: Exclude<BuildTarget, "static">
|
|
69
88
|
readonly kind: "server"
|
|
70
89
|
readonly serverTarget: ServerBuildTarget
|
|
71
|
-
/** The worker's final
|
|
72
|
-
readonly outputFile: "_worker.js" | "index.js" | "server.js"
|
|
90
|
+
/** The worker's final path inside the assembled deploy directory. */
|
|
91
|
+
readonly outputFile: "_worker.js" | "functions/index.func/index.js" | "server.js"
|
|
92
|
+
/** Where the host serves static files from, relative to the deploy directory (`""` = its root). */
|
|
93
|
+
readonly staticDir: "" | "static"
|
|
73
94
|
readonly run: string
|
|
74
95
|
}
|
|
75
96
|
|
|
@@ -101,15 +122,17 @@ export function planBuildTarget(target: BuildTarget, outDir: string): BuildTarge
|
|
|
101
122
|
kind: "static",
|
|
102
123
|
serverTarget: undefined,
|
|
103
124
|
outputFile: undefined,
|
|
125
|
+
staticDir: "",
|
|
104
126
|
run: `static site → ${outDir} (serve the directory with any static host)`,
|
|
105
127
|
}
|
|
106
128
|
}
|
|
107
|
-
if (target === "
|
|
129
|
+
if (target === "cloudflare") {
|
|
108
130
|
return {
|
|
109
131
|
target,
|
|
110
132
|
kind: "server",
|
|
111
133
|
serverTarget: "browser",
|
|
112
134
|
outputFile: "_worker.js",
|
|
135
|
+
staticDir: "",
|
|
113
136
|
run: `Cloudflare Pages → ${outDir} (deploy: wrangler pages deploy ${finalPathSegment(outDir)})`,
|
|
114
137
|
}
|
|
115
138
|
}
|
|
@@ -118,8 +141,10 @@ export function planBuildTarget(target: BuildTarget, outDir: string): BuildTarge
|
|
|
118
141
|
target,
|
|
119
142
|
kind: "server",
|
|
120
143
|
serverTarget: "browser",
|
|
121
|
-
|
|
122
|
-
|
|
144
|
+
// Vercel's Build Output API: `vercel deploy --prebuilt` uploads this tree as it is.
|
|
145
|
+
outputFile: "functions/index.func/index.js",
|
|
146
|
+
staticDir: "static",
|
|
147
|
+
run: `Vercel (Build Output API) → ${outDir} (deploy: vercel deploy --prebuilt)`,
|
|
123
148
|
}
|
|
124
149
|
}
|
|
125
150
|
return {
|
|
@@ -127,6 +152,7 @@ export function planBuildTarget(target: BuildTarget, outDir: string): BuildTarge
|
|
|
127
152
|
kind: "server",
|
|
128
153
|
serverTarget: target === "node" ? "node" : target === "bun" ? "bun" : "browser",
|
|
129
154
|
outputFile: "server.js",
|
|
155
|
+
staticDir: "",
|
|
130
156
|
run: `${target} server → ${outDir} (run: ${target === "node" ? "node" : target} ${finalPathSegment(outDir)}/server.js)`,
|
|
131
157
|
}
|
|
132
158
|
}
|
|
@@ -150,8 +176,14 @@ export interface Bundler {
|
|
|
150
176
|
readonly define?: Readonly<Record<string, string>>
|
|
151
177
|
readonly publicDir?: string | false
|
|
152
178
|
readonly publicEnvPrefix?: string
|
|
179
|
+
/** Vite CSS splitting policy; the Bun strategy rejects `false` and otherwise preserves its native behavior. */
|
|
180
|
+
readonly cssCodeSplit?: boolean
|
|
181
|
+
/** Framework-owned stylesheet activation policy; supported by both server strategies. */
|
|
182
|
+
readonly cssLoading?: CssLoadingMode
|
|
153
183
|
/** Project root (Vite needs it; the Bun strategy ignores it). */
|
|
154
184
|
readonly root?: string
|
|
185
|
+
/** Reviewed false positives of the secret scan. */
|
|
186
|
+
readonly secretExemptions?: readonly SecretExemption[]
|
|
155
187
|
}): Promise<BuildManifest>
|
|
156
188
|
/** Build the server worker → the shared {@link ServerBuild}. */
|
|
157
189
|
buildServer(input: {
|
|
@@ -160,15 +192,26 @@ export interface Bundler {
|
|
|
160
192
|
readonly outDir: string
|
|
161
193
|
readonly clientEntry: string
|
|
162
194
|
readonly target: "browser" | "node" | "bun"
|
|
195
|
+
/** The client build's aggregate stylesheet URLs to bake into the server manifest. */
|
|
196
|
+
readonly styles?: readonly string[]
|
|
197
|
+
/** The client build's per-route stylesheet map to bake into the server manifest. */
|
|
198
|
+
readonly routeStyles?: Readonly<Record<string, readonly string[]>>
|
|
163
199
|
readonly plugins?: readonly unknown[]
|
|
164
200
|
readonly define?: Readonly<Record<string, string>>
|
|
201
|
+
/** CSS activation policy to bake into the generated server manifest. */
|
|
202
|
+
readonly cssLoading?: CssLoadingMode
|
|
165
203
|
readonly root?: string
|
|
204
|
+
/** Modules the zone rules treat as generated: the adapter module the generated entry imports. */
|
|
205
|
+
readonly generatedFiles?: readonly string[]
|
|
206
|
+
/** The public-env prefix browser code may read (default `"PUBLIC_"`). */
|
|
207
|
+
readonly publicEnvPrefix?: string
|
|
166
208
|
}): Promise<ServerBuild>
|
|
167
209
|
}
|
|
168
210
|
|
|
169
211
|
interface GraphImport {
|
|
170
212
|
readonly path?: string
|
|
171
213
|
readonly original?: string
|
|
214
|
+
readonly external?: boolean
|
|
172
215
|
}
|
|
173
216
|
|
|
174
217
|
interface GraphInput {
|
|
@@ -180,7 +223,11 @@ const basename = (path: string): string => pathBasename(path)
|
|
|
180
223
|
const nodeBuiltinOf = (im: GraphImport): string | undefined => {
|
|
181
224
|
if (im.original?.startsWith("node:")) return im.original
|
|
182
225
|
if (im.path?.startsWith("node:")) return im.path
|
|
183
|
-
|
|
226
|
+
// A bare built-in only counts when the bundler left it external: a bundled `fs` resolved to an
|
|
227
|
+
// installed package (or a polyfill keyed `node:fs`) is handled above or is not a built-in at all.
|
|
228
|
+
// Labeled with the prefix, so `fs/promises` reads the same from either bundler.
|
|
229
|
+
const spec = im.external === true ? (im.original ?? im.path) : undefined
|
|
230
|
+
return spec !== undefined && isBareNodeBuiltin(spec) ? `node:${spec}` : undefined
|
|
184
231
|
}
|
|
185
232
|
|
|
186
233
|
/** One `node:`-builtin-in-the-client finding: the offending builtin, the emitted chunk it landed in,
|
|
@@ -287,29 +334,43 @@ export function detectNodeBuiltinsInClient(
|
|
|
287
334
|
chains.set(builtin, chain)
|
|
288
335
|
return chain
|
|
289
336
|
}
|
|
337
|
+
// (1b) Builtins the bundler left EXTERNAL: the import ships in the importer's chunk but the module
|
|
338
|
+
// is in no output, so (2) can't find it as a chunk member. Bun does this for a dynamic
|
|
339
|
+
// `import("node:fs")` in a browser build; the browser then fails to load it.
|
|
340
|
+
const externalsOf = new Map<string, string[]>()
|
|
341
|
+
for (const [inputKey, input] of Object.entries(inputs)) {
|
|
342
|
+
if (inputKey.startsWith("node:")) continue
|
|
343
|
+
for (const im of input.imports ?? []) {
|
|
344
|
+
if (im.external !== true) continue
|
|
345
|
+
const builtin = nodeBuiltinOf(im)
|
|
346
|
+
if (builtin === undefined) continue
|
|
347
|
+
const list = externalsOf.get(inputKey) ?? []
|
|
348
|
+
if (!list.includes(builtin)) list.push(builtin)
|
|
349
|
+
externalsOf.set(inputKey, list)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
290
352
|
// (2) Locate which emitted chunk each user-imported builtin reached, via the per-output `inputs`.
|
|
291
353
|
const findings = new Map<string, NodeBuiltinFinding>()
|
|
354
|
+
const report = (builtin: string, outputPath: string): void => {
|
|
355
|
+
const chunk = basename(outputPath)
|
|
356
|
+
findings.set(`${builtin}\0${chunk}`, { builtin, chunk, chain: chainFor(builtin) })
|
|
357
|
+
}
|
|
292
358
|
for (const [outputPath, output] of Object.entries(graph.chunks)) {
|
|
293
359
|
for (const inputKey of output.modules) {
|
|
294
|
-
if (
|
|
295
|
-
const
|
|
296
|
-
findings.set(`${inputKey}\0${chunk}`, {
|
|
297
|
-
builtin: inputKey,
|
|
298
|
-
chunk,
|
|
299
|
-
chain: chainFor(inputKey),
|
|
300
|
-
})
|
|
360
|
+
if (userImported.has(inputKey)) report(inputKey, outputPath)
|
|
361
|
+
for (const builtin of externalsOf.get(inputKey) ?? []) report(builtin, outputPath)
|
|
301
362
|
}
|
|
302
363
|
}
|
|
303
364
|
return [...findings.values()].sort((a, b) =>
|
|
304
|
-
a.builtin === b.builtin ? a.chunk
|
|
365
|
+
a.builtin === b.builtin ? codeUnitOrder(a.chunk, b.chunk) : codeUnitOrder(a.builtin, b.builtin),
|
|
305
366
|
)
|
|
306
367
|
}
|
|
307
368
|
|
|
308
369
|
// ---------------------------------------------------------------------------------------------------
|
|
309
|
-
// `
|
|
370
|
+
// `backend-only` poison-import guard (§3.3/§5.1). The complement to the zones + the
|
|
310
371
|
// node-builtin guard: a module of PURE server logic with NO `node:` import (a secret-bearing constant,
|
|
311
372
|
// a server-only API call) that an author wants to FAIL LOUD if it reaches the client opts in with a
|
|
312
|
-
// side-effect `import "@nifrajs/web/
|
|
373
|
+
// side-effect `import "@nifrajs/web/backend-only"` (Next's `import "server-only"`). On the SERVER build
|
|
313
374
|
// the marker is an empty no-op; the CLIENT build detects - via the SAME Bun metafile graph the
|
|
314
375
|
// node-builtin guard walks - any module that imports the marker AND lands in a client chunk, and fails
|
|
315
376
|
// the build with the import chain. Graph-based (never the emitted text), so it survives minification.
|
|
@@ -317,18 +378,18 @@ export function detectNodeBuiltinsInClient(
|
|
|
317
378
|
|
|
318
379
|
/** The marker specifier an author imports to opt a module into the client-leak guard. Matched on the
|
|
319
380
|
* import edge's *as-written* `original` first (the robust signal: it's exactly what the author typed,
|
|
320
|
-
* before Bun resolves it to `src/
|
|
321
|
-
export const SERVER_ONLY_MARKER = "@nifrajs/web/
|
|
381
|
+
* before Bun resolves it to `src/backend-only.ts` / `dist/backend-only.js`). */
|
|
382
|
+
export const SERVER_ONLY_MARKER = "@nifrajs/web/backend-only"
|
|
322
383
|
|
|
323
384
|
const isServerOnlyMarkerImport = (im: GraphImport): boolean => {
|
|
324
385
|
if (im.original === SERVER_ONLY_MARKER) return true
|
|
325
|
-
return im.path !== undefined && /(^|\/)
|
|
386
|
+
return im.path !== undefined && /(^|\/)backend-only\.[cm]?[jt]s$/.test(im.path)
|
|
326
387
|
}
|
|
327
388
|
|
|
328
|
-
/** True when an INPUT graph key is the marker module file itself (`…/
|
|
389
|
+
/** True when an INPUT graph key is the marker module file itself (`…/backend-only.{ts,js}` under web).
|
|
329
390
|
* Excluded from the "marked" set - the marker is the import target, not an opt-in module. */
|
|
330
391
|
const isServerOnlyMarkerModule = (inputKey: string): boolean =>
|
|
331
|
-
/(^|\/)
|
|
392
|
+
/(^|\/)backend-only\.[cm]?[jt]s$/.test(inputKey)
|
|
332
393
|
|
|
333
394
|
/** One `server-only`-module-in-the-client finding: the offending module (the as-written marker-import
|
|
334
395
|
* chain's tail before the marker), the emitted chunk it landed in, and the shortest USER-module import
|
|
@@ -383,7 +444,7 @@ function shortestServerOnlyChain(
|
|
|
383
444
|
): string[] {
|
|
384
445
|
// The label for the marked module's tail: its as-written specifier (filled when we cross the edge
|
|
385
446
|
// that reaches it) suffixed with `(marked server-only)`; the entry case uses the entry key itself.
|
|
386
|
-
const tail = (label: string): string => `${label} (marked
|
|
447
|
+
const tail = (label: string): string => `${label} (marked backend-only)`
|
|
387
448
|
// An entry that is ITSELF the marked module - the chain is just that one node.
|
|
388
449
|
if (entryInputs.includes(markedModule)) return [tail(markedModule)]
|
|
389
450
|
const seen = new Set(entryInputs)
|
|
@@ -412,7 +473,7 @@ function shortestServerOnlyChain(
|
|
|
412
473
|
|
|
413
474
|
/**
|
|
414
475
|
* Scan a build's metafile for any module that opts into the `server-only` marker (a side-effect
|
|
415
|
-
* `import "@nifrajs/web/
|
|
476
|
+
* `import "@nifrajs/web/backend-only"`) yet landed in a CLIENT output chunk, returning a sorted, deduped
|
|
416
477
|
* list of {@link ServerOnlyFinding}s. Mirrors {@link detectNodeBuiltinsInClient}: it reads the SAME
|
|
417
478
|
* graph facts - which inputs import the marker (the "marked" modules), which chunk each landed in (the
|
|
418
479
|
* per-output `inputs`), and the shortest import chain from a user entry to it. The marker module ITSELF
|
|
@@ -451,17 +512,28 @@ export function detectServerOnlyInClient(
|
|
|
451
512
|
}
|
|
452
513
|
// (2) Locate which emitted chunk each marked module reached, via the per-output `inputs`.
|
|
453
514
|
const findings = new Map<string, ServerOnlyFinding>()
|
|
515
|
+
const placed = new Set<string>()
|
|
454
516
|
for (const [outputPath, output] of Object.entries(graph.chunks)) {
|
|
455
517
|
for (const inputKey of output.modules) {
|
|
456
518
|
if (!marked.has(inputKey)) continue
|
|
457
519
|
const chunk = basename(outputPath)
|
|
520
|
+
placed.add(inputKey)
|
|
458
521
|
findings.set(`${inputKey}\0${chunk}`, { chunk, chain: chainFor(inputKey) })
|
|
459
522
|
}
|
|
460
523
|
}
|
|
524
|
+
// A marked module the build loaded but no chunk lists: the bundler inlined what it exports (a
|
|
525
|
+
// constant ships without its module) or dropped it. The import crossed the boundary either way.
|
|
526
|
+
for (const inputKey of marked) {
|
|
527
|
+
if (placed.has(inputKey)) continue
|
|
528
|
+
findings.set(`${inputKey}\0`, {
|
|
529
|
+
chunk: "none, inlined or shaken out",
|
|
530
|
+
chain: chainFor(inputKey),
|
|
531
|
+
})
|
|
532
|
+
}
|
|
461
533
|
return [...findings.values()].sort((a, b) => {
|
|
462
534
|
const am = a.chain[a.chain.length - 1] ?? ""
|
|
463
535
|
const bm = b.chain[b.chain.length - 1] ?? ""
|
|
464
|
-
return am === bm ? a.chunk
|
|
536
|
+
return am === bm ? codeUnitOrder(a.chunk, b.chunk) : codeUnitOrder(am, bm)
|
|
465
537
|
})
|
|
466
538
|
}
|
|
467
539
|
|
|
@@ -483,9 +555,8 @@ export function formatNodeBuiltinLeak(
|
|
|
483
555
|
: ` - ${finding.builtin} reached the client bundle via ${finding.chunk}`,
|
|
484
556
|
)
|
|
485
557
|
return (
|
|
486
|
-
`[nifra/web] Node built-in(s) in the client bundle -
|
|
487
|
-
`
|
|
488
|
-
`top level):\n${lines.join("\n")}`
|
|
558
|
+
`[nifra/web] Node built-in(s) in the client bundle - built-ins run on the server only: import ` +
|
|
559
|
+
`them in a route's backend half (x.backend.ts) or under backend/, never in browser code:\n${lines.join("\n")}`
|
|
489
560
|
)
|
|
490
561
|
}
|
|
491
562
|
|
|
@@ -496,14 +567,13 @@ export function formatServerOnlyLeak(
|
|
|
496
567
|
if (findings.length === 0) return undefined
|
|
497
568
|
const lines = findings.map((finding) =>
|
|
498
569
|
finding.chain.length > 1
|
|
499
|
-
? ` -
|
|
500
|
-
: ` -
|
|
570
|
+
? ` - backend-only module reached the client bundle via ${finding.chain.join(" → ")} (chunk: ${finding.chunk})`
|
|
571
|
+
: ` - backend-only module reached the client bundle via ${finding.chunk}`,
|
|
501
572
|
)
|
|
502
573
|
return (
|
|
503
|
-
`[nifra/web]
|
|
504
|
-
`\`import "${SERVER_ONLY_MARKER}"\` reached the browser. Move it
|
|
505
|
-
`
|
|
506
|
-
`to the client:\n${lines.join("\n")}`
|
|
574
|
+
`[nifra/web] backend-only module(s) in the client bundle - a module marked ` +
|
|
575
|
+
`\`import "${SERVER_ONLY_MARKER}"\` reached the browser. Move it under backend/ and reach it ` +
|
|
576
|
+
`from a route's backend half (x.backend.ts) or a *.fn.ts server function:\n${lines.join("\n")}`
|
|
507
577
|
)
|
|
508
578
|
}
|
|
509
579
|
|
|
@@ -540,7 +610,7 @@ export interface SizeReport {
|
|
|
540
610
|
*/
|
|
541
611
|
export function aggregateSizeReport(chunks: readonly ChunkSize[]): SizeReport {
|
|
542
612
|
const sorted = [...chunks].sort(
|
|
543
|
-
(a, b) => b.gzip - a.gzip || b.bytes - a.bytes || a.name
|
|
613
|
+
(a, b) => b.gzip - a.gzip || b.bytes - a.bytes || codeUnitOrder(a.name, b.name),
|
|
544
614
|
)
|
|
545
615
|
let totalBytes = 0
|
|
546
616
|
let totalGzip = 0
|
|
@@ -618,6 +688,8 @@ export interface ManifestDrift {
|
|
|
618
688
|
const MANIFEST_ROUTE_ENTRY = /^[ \t]+(["'])([^"'\n]+)\1\s*:\s*(?:m\d+\b|\(\s*\)\s*=>\s*import\b)/gm
|
|
619
689
|
// The baked client-entry line: `export const clientEntry = "…"`.
|
|
620
690
|
const MANIFEST_CLIENT_ENTRY = /export\s+const\s+clientEntry\s*=\s*["']([^"']+)["']/
|
|
691
|
+
// The baked stylesheet activation line: `generateServerManifest` emits this after the style maps.
|
|
692
|
+
const MANIFEST_CSS_LOADING = /export\s+const\s+cssLoading\s*=\s*["'](blocking|deferred)["']/
|
|
621
693
|
|
|
622
694
|
/**
|
|
623
695
|
* Extract the route-relative file list a committed server-manifest declares, as the same
|
|
@@ -642,6 +714,11 @@ export function parseManifestClientEntry(source: string): string | undefined {
|
|
|
642
714
|
return MANIFEST_CLIENT_ENTRY.exec(source)?.[1]
|
|
643
715
|
}
|
|
644
716
|
|
|
717
|
+
/** The baked framework stylesheet activation mode, or `undefined` for an older manifest. Pure. */
|
|
718
|
+
export function parseManifestCssLoading(source: string): CssLoadingMode | undefined {
|
|
719
|
+
return MANIFEST_CSS_LOADING.exec(source)?.[1] as CssLoadingMode | undefined
|
|
720
|
+
}
|
|
721
|
+
|
|
645
722
|
// The baked asset lines `generateServerManifest` emits: `export const styles = […]` then
|
|
646
723
|
// `export const routeStyles = {…}`, each a JSON literal running to the next `export const`. Extracted
|
|
647
724
|
// with `indexOf` slicing rather than a lazy `[\s\S]*?` regex: the slice is multi-line by nature (a
|
|
@@ -653,7 +730,7 @@ function bakedManifestLiteral(source: string, name: string): string | undefined
|
|
|
653
730
|
if (start === -1) return undefined
|
|
654
731
|
const from = start + opener.length
|
|
655
732
|
const end = source.indexOf("\nexport const ", from)
|
|
656
|
-
return end === -1 ?
|
|
733
|
+
return source.slice(from, end === -1 ? source.length : end).trim()
|
|
657
734
|
}
|
|
658
735
|
|
|
659
736
|
/** Parse a baked JSON literal captured from a committed manifest, tolerating a formatter's TRAILING COMMAS
|
package/src/build-vite.ts
CHANGED
|
@@ -28,6 +28,13 @@ import {
|
|
|
28
28
|
publicEnvDefines,
|
|
29
29
|
} from "./build.ts"
|
|
30
30
|
import type { BuildManifest, BuildTarget, Bundler, ServerBuild } from "./build-plan.ts"
|
|
31
|
+
import {
|
|
32
|
+
assertCssLoadingCompatible,
|
|
33
|
+
type CssLoadingMode,
|
|
34
|
+
DEFAULT_CSS_LOADING,
|
|
35
|
+
normalizeCssCodeSplit,
|
|
36
|
+
normalizeCssLoading,
|
|
37
|
+
} from "./css-contract.ts"
|
|
31
38
|
import { discoverRoutes } from "./fs.ts"
|
|
32
39
|
import { generateClientEntry, generateServerManifest } from "./index.ts"
|
|
33
40
|
import { viteDedupePackages } from "./internal/identity-policy.ts"
|
|
@@ -37,12 +44,17 @@ import {
|
|
|
37
44
|
collectDevelopmentParityInput,
|
|
38
45
|
} from "./internal/parity.ts"
|
|
39
46
|
import { vitePublicEnvPrefix } from "./internal/server-boundary.ts"
|
|
47
|
+
import { unsupportedBuiltin } from "./internal/target-compat.ts"
|
|
40
48
|
import { importVite, isViteUnresolved } from "./internal/vite-import.ts"
|
|
41
49
|
import { scopedName } from "./plugins/css-modules.ts"
|
|
42
50
|
import { reproduciblePath } from "./plugins/kit.ts"
|
|
43
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
viteAssetUrlGuard,
|
|
53
|
+
viteBareBuiltinExternal,
|
|
54
|
+
viteLeakGuard,
|
|
55
|
+
viteServerZoneGuard,
|
|
56
|
+
} from "./plugins/vite-leak-guard.ts"
|
|
44
57
|
import { viteServerFnStub } from "./plugins/vite-server-fn.ts"
|
|
45
|
-
import { viteServerOnlyEmpty } from "./plugins/vite-server-only.ts"
|
|
46
58
|
|
|
47
59
|
// ---------------------------------------------------------------------------------------------------
|
|
48
60
|
// Structural Vite typings - no hard `vite` dependency (mirrors vite.ts). Only the build API is used.
|
|
@@ -117,6 +129,17 @@ export interface BuildClientViteOptions extends Omit<BuildClientOptions, "plugin
|
|
|
117
129
|
readonly vitePlugins?: readonly unknown[]
|
|
118
130
|
/** Vite project root (default: the parent of `routesDir`). Manifest keys are relative to it. */
|
|
119
131
|
readonly root?: string
|
|
132
|
+
/**
|
|
133
|
+
* Vite's CSS output strategy (default `true`). Set to `false` to emit one aggregate stylesheet for
|
|
134
|
+
* the whole client, preventing lazy-route prefetch from attaching additional stylesheets.
|
|
135
|
+
*/
|
|
136
|
+
readonly cssCodeSplit?: boolean
|
|
137
|
+
/**
|
|
138
|
+
* Activation policy for framework-owned SSR stylesheet links (default `"blocking"`). `"deferred"`
|
|
139
|
+
* fetches with `media="print"` and the generated client promotes the links before mounting. It must
|
|
140
|
+
* be paired with `cssCodeSplit: false`; split lazy chunks can otherwise attach CSS during prefetch.
|
|
141
|
+
*/
|
|
142
|
+
readonly cssLoading?: CssLoadingMode
|
|
120
143
|
}
|
|
121
144
|
|
|
122
145
|
/**
|
|
@@ -130,6 +153,9 @@ export interface BuildClientViteOptions extends Omit<BuildClientOptions, "plugin
|
|
|
130
153
|
*/
|
|
131
154
|
export async function buildClientVite(options: BuildClientViteOptions): Promise<BuildManifest> {
|
|
132
155
|
const { routesDir, outDir, clientModule } = options
|
|
156
|
+
const cssCodeSplit = normalizeCssCodeSplit(options.cssCodeSplit)
|
|
157
|
+
const cssLoading = normalizeCssLoading(options.cssLoading ?? DEFAULT_CSS_LOADING)
|
|
158
|
+
assertCssLoadingCompatible(cssCodeSplit, cssLoading)
|
|
133
159
|
const root = resolvePath(options.root ?? dirname(routesDir))
|
|
134
160
|
await assertIdentityParity(root)
|
|
135
161
|
const resolve = options.resolve ?? ((file: string) => `${routesDir}/${file}`)
|
|
@@ -153,6 +179,8 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
153
179
|
...routeManifest.routes.map((r) => r.file),
|
|
154
180
|
...Object.values(routeManifest.layouts).map((l) => l.file),
|
|
155
181
|
...(routeManifest.notFound ? [routeManifest.notFound.file] : []),
|
|
182
|
+
// A nested `_404` is an entry only for its stylesheet: it renders on the server, unhydrated.
|
|
183
|
+
...Object.values(routeManifest.notFounds ?? {}).map((page) => page.file),
|
|
156
184
|
]),
|
|
157
185
|
].sort()
|
|
158
186
|
|
|
@@ -180,7 +208,28 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
180
208
|
const buildEnv = (typeof Bun !== "undefined" ? Bun.env : undefined) ?? process.env
|
|
181
209
|
const publicDefines = publicEnvDefines(options.publicEnvPrefix ?? "PUBLIC_", buildEnv)
|
|
182
210
|
const vite = await loadVite()
|
|
183
|
-
|
|
211
|
+
// A worker is bundled by its own sub-build the main graph never sees, so it gets its own guard; the
|
|
212
|
+
// main guard then accepts the worker chunks that guard verified.
|
|
213
|
+
const verified = new Set<string>()
|
|
214
|
+
const exemptions = options.secretExemptions
|
|
215
|
+
const guardOptions = {
|
|
216
|
+
appRoot: dirname(routesDir),
|
|
217
|
+
routesDir,
|
|
218
|
+
root,
|
|
219
|
+
outDir: resolvePath(outDir),
|
|
220
|
+
generatedFiles: [entryFile],
|
|
221
|
+
verified,
|
|
222
|
+
...(options.publicEnvPrefix !== undefined ? { publicEnvPrefix: options.publicEnvPrefix } : {}),
|
|
223
|
+
secrets: { env: buildEnv, ...(exemptions ? { exemptions } : {}) },
|
|
224
|
+
}
|
|
225
|
+
// nifra copies `public/` after the build; the main guard scans it with the bundle.
|
|
226
|
+
const publicDir = options.publicDir === false ? undefined : (options.publicDir ?? "public")
|
|
227
|
+
const leakGuard = viteLeakGuard({
|
|
228
|
+
...guardOptions,
|
|
229
|
+
secrets: { ...guardOptions.secrets, ...(publicDir !== undefined ? { publicDir } : {}) },
|
|
230
|
+
})
|
|
231
|
+
const assetUrlGuard = viteAssetUrlGuard(guardOptions)
|
|
232
|
+
const workerGuards: { readonly leak?: string | undefined }[] = []
|
|
184
233
|
try {
|
|
185
234
|
await withSerializedNodeEnv(mode, () =>
|
|
186
235
|
vite.build({
|
|
@@ -214,11 +263,22 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
214
263
|
scopedName(reproduciblePath(filename), name),
|
|
215
264
|
},
|
|
216
265
|
},
|
|
217
|
-
|
|
266
|
+
// First, so a bare built-in is named before Vite turns it into an anonymous stub.
|
|
267
|
+
// The asset URL guard last, so it reads each module after any framework plugin made it JavaScript.
|
|
268
|
+
plugins: [viteBareBuiltinExternal(), ...(options.vitePlugins ?? []), assetUrlGuard],
|
|
269
|
+
worker: {
|
|
270
|
+
plugins: () => {
|
|
271
|
+
const guard = viteLeakGuard(guardOptions)
|
|
272
|
+
const urlGuard = viteAssetUrlGuard(guardOptions)
|
|
273
|
+
workerGuards.push(guard, urlGuard)
|
|
274
|
+
return [viteBareBuiltinExternal(), viteServerFnStub(), guard, urlGuard]
|
|
275
|
+
},
|
|
276
|
+
},
|
|
218
277
|
build: {
|
|
219
278
|
outDir,
|
|
220
279
|
emptyOutDir: false, // buildTargetWith owns outDir lifecycle; never let Vite wipe sibling files
|
|
221
280
|
manifest: true,
|
|
281
|
+
cssCodeSplit,
|
|
222
282
|
minify: options.minify !== false,
|
|
223
283
|
rollupOptions: {
|
|
224
284
|
// Keep `node:` builtins as external specifiers so the leak guard sees `node:crypto` and can
|
|
@@ -229,7 +289,7 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
229
289
|
external: [/^node:/],
|
|
230
290
|
input,
|
|
231
291
|
// The leak guard is a Rollup plugin - last, so it sees the final graph.
|
|
232
|
-
plugins: [viteServerFnStub(),
|
|
292
|
+
plugins: [viteServerFnStub(), leakGuard],
|
|
233
293
|
output: {
|
|
234
294
|
entryFileNames: "[name]-[hash].js",
|
|
235
295
|
chunkFileNames: "[name]-[hash].js",
|
|
@@ -245,7 +305,11 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
245
305
|
// that construction fails, replacing a precise "node:crypto reached the client bundle" with an
|
|
246
306
|
// internal complaint naming nothing. Raising it here puts the message beyond anything that can
|
|
247
307
|
// rewrite it; the bundler's error is kept as `cause` for the rest of the context.
|
|
248
|
-
|
|
308
|
+
const leak =
|
|
309
|
+
assetUrlGuard.leak ??
|
|
310
|
+
leakGuard.leak ??
|
|
311
|
+
workerGuards.find((guard) => guard.leak !== undefined)?.leak
|
|
312
|
+
if (leak !== undefined) throw new Error(leak, { cause: error })
|
|
249
313
|
throw error
|
|
250
314
|
} finally {
|
|
251
315
|
rmSync(entryDir, { recursive: true, force: true })
|
|
@@ -298,16 +362,28 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
298
362
|
}
|
|
299
363
|
return [...urls]
|
|
300
364
|
}
|
|
301
|
-
// CSS aggregate - every stylesheet the app emits, the always-safe fallback.
|
|
365
|
+
// CSS aggregate - every stylesheet the app emits, the always-safe fallback. With native Vite
|
|
366
|
+
// `cssCodeSplit: false`, the aggregate is a standalone manifest asset and is not listed in any
|
|
367
|
+
// JavaScript entry's `css` array. Collect both shapes so the framework manifest never loses the only
|
|
368
|
+
// stylesheet in a single-file build.
|
|
302
369
|
const allCss = new Set<string>()
|
|
303
370
|
for (const entry of Object.values(viteManifest)) {
|
|
371
|
+
if (entry.file.endsWith(".css")) allCss.add(url(entry.file))
|
|
304
372
|
for (const css of entry.css ?? []) allCss.add(url(css))
|
|
305
373
|
}
|
|
306
374
|
const css = [...allCss]
|
|
307
375
|
const routeStyles: Record<string, readonly string[]> = {}
|
|
308
|
-
|
|
376
|
+
// A single aggregate stylesheet is deliberately the route fallback. A per-route `[]` map would
|
|
377
|
+
// suppress it in `createWebApp` and produce unstyled SSR on every route.
|
|
378
|
+
if (css.length > 0 && cssCodeSplit) {
|
|
309
379
|
for (const route of routeManifest.routes) routeStyles[route.id] = stylesFor(chainFiles(route))
|
|
310
380
|
if (routeManifest.notFound) routeStyles._404 = stylesFor([routeManifest.notFound.file])
|
|
381
|
+
for (const [id, page] of Object.entries(routeManifest.notFounds ?? {})) {
|
|
382
|
+
routeStyles[id] = stylesFor([
|
|
383
|
+
...page.layoutIds.map((layoutId) => routeManifest.layouts[layoutId]?.file ?? ""),
|
|
384
|
+
page.file,
|
|
385
|
+
])
|
|
386
|
+
}
|
|
311
387
|
}
|
|
312
388
|
|
|
313
389
|
// assets - every emitted file + stylesheet across the manifest (chunks, entries, css).
|
|
@@ -318,7 +394,6 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
318
394
|
for (const asset of entry.assets ?? []) assets.add(url(asset))
|
|
319
395
|
}
|
|
320
396
|
|
|
321
|
-
const publicDir = options.publicDir === false ? undefined : (options.publicDir ?? "public")
|
|
322
397
|
const publicFiles =
|
|
323
398
|
publicDir !== undefined && existsSync(publicDir) ? await copyPublicDir(publicDir, outDir) : []
|
|
324
399
|
|
|
@@ -326,6 +401,8 @@ export async function buildClientVite(options: BuildClientViteOptions): Promise<
|
|
|
326
401
|
entry: url(bootstrap.file),
|
|
327
402
|
assets: [...assets],
|
|
328
403
|
routes,
|
|
404
|
+
cssCodeSplit,
|
|
405
|
+
cssLoading,
|
|
329
406
|
...(publicFiles.length > 0 ? { publicFiles } : {}),
|
|
330
407
|
...(css.length > 0 ? { css } : {}),
|
|
331
408
|
...(Object.keys(routeStyles).length > 0 ? { routeStyles } : {}),
|
|
@@ -343,6 +420,8 @@ export interface BuildServerViteOptions {
|
|
|
343
420
|
readonly clientEntry: string
|
|
344
421
|
readonly styles?: readonly string[] | undefined
|
|
345
422
|
readonly routeStyles?: Readonly<Record<string, readonly string[]>> | undefined
|
|
423
|
+
/** How framework-owned stylesheet links should activate before hydration (default `"blocking"`). */
|
|
424
|
+
readonly cssLoading?: CssLoadingMode
|
|
346
425
|
readonly resolve?: (file: string) => string
|
|
347
426
|
readonly manifestFile?: string
|
|
348
427
|
readonly vitePlugins?: readonly unknown[]
|
|
@@ -353,6 +432,10 @@ export interface BuildServerViteOptions {
|
|
|
353
432
|
readonly target?: "browser" | "node" | "bun"
|
|
354
433
|
/** Vite project root (default: the parent of `routesDir`). */
|
|
355
434
|
readonly root?: string
|
|
435
|
+
/** Modules the build generated besides the entry and manifest (a target's adapter import). */
|
|
436
|
+
readonly generatedFiles?: readonly string[]
|
|
437
|
+
/** The public-env prefix browser code may read (default `"PUBLIC_"`). */
|
|
438
|
+
readonly publicEnvPrefix?: string
|
|
356
439
|
}
|
|
357
440
|
|
|
358
441
|
interface EdgeBundleChunk {
|
|
@@ -399,12 +482,13 @@ function edgeBuiltinGuard(): EdgeGuardPlugin {
|
|
|
399
482
|
for (const output of Object.values(bundle)) {
|
|
400
483
|
if (output.type !== "chunk") continue
|
|
401
484
|
for (const specifier of [...(output.imports ?? []), ...(output.dynamicImports ?? [])]) {
|
|
402
|
-
if (specifier
|
|
485
|
+
if (unsupportedBuiltin(specifier, "browser")) builtins.add(specifier)
|
|
403
486
|
}
|
|
404
487
|
for (const id of output.moduleIds ?? []) {
|
|
405
488
|
const info = this.getModuleInfo(id)
|
|
406
489
|
const imports = [...(info?.importedIds ?? []), ...(info?.dynamicallyImportedIds ?? [])]
|
|
407
|
-
if (imports.some((specifier) => specifier
|
|
490
|
+
if (imports.some((specifier) => unsupportedBuiltin(specifier, "browser")))
|
|
491
|
+
importers.add(id)
|
|
408
492
|
}
|
|
409
493
|
}
|
|
410
494
|
if (builtins.size === 0) return
|
|
@@ -434,7 +518,7 @@ function edgeBuiltinGuard(): EdgeGuardPlugin {
|
|
|
434
518
|
* The client-leak guards are deliberately NOT run here: a server bundle's `node:` imports are legitimate.
|
|
435
519
|
*/
|
|
436
520
|
export async function buildServerVite(options: BuildServerViteOptions): Promise<ServerBuild> {
|
|
437
|
-
const { routesDir, serverEntry, outDir, clientEntry, styles, routeStyles } = options
|
|
521
|
+
const { routesDir, serverEntry, outDir, clientEntry, styles, routeStyles, cssLoading } = options
|
|
438
522
|
const root = resolvePath(options.root ?? dirname(routesDir))
|
|
439
523
|
const entryDir = dirname(serverEntry)
|
|
440
524
|
const manifestFile = options.manifestFile ?? "server-manifest.ts"
|
|
@@ -451,7 +535,13 @@ export async function buildServerVite(options: BuildServerViteOptions): Promise<
|
|
|
451
535
|
const routeManifest = discoverRoutes(routesDir)
|
|
452
536
|
writeFileSync(
|
|
453
537
|
`${entryDir}/${manifestFile}`,
|
|
454
|
-
generateServerManifest(routeManifest, {
|
|
538
|
+
generateServerManifest(routeManifest, {
|
|
539
|
+
resolve,
|
|
540
|
+
clientEntry,
|
|
541
|
+
styles,
|
|
542
|
+
routeStyles,
|
|
543
|
+
...(cssLoading !== undefined ? { cssLoading } : {}),
|
|
544
|
+
}),
|
|
455
545
|
)
|
|
456
546
|
|
|
457
547
|
// Pin the mode to match NODE_ENV - the react plugin's JSX runtime (jsx vs jsxDEV) follows it, and a
|
|
@@ -459,6 +549,16 @@ export async function buildServerVite(options: BuildServerViteOptions): Promise<
|
|
|
459
549
|
const mode = options.minify === false ? "development" : "production"
|
|
460
550
|
const vite = await loadVite()
|
|
461
551
|
const edgeGuard = edgeBuiltinGuard()
|
|
552
|
+
const zoneGuard = viteServerZoneGuard({
|
|
553
|
+
appRoot: root,
|
|
554
|
+
routesDir: resolvePath(routesDir),
|
|
555
|
+
generatedFiles: [
|
|
556
|
+
serverEntry,
|
|
557
|
+
join(entryDir, manifestFile),
|
|
558
|
+
...(options.generatedFiles ?? []),
|
|
559
|
+
].map((file) => resolvePath(file)),
|
|
560
|
+
...(options.publicEnvPrefix !== undefined ? { publicEnvPrefix: options.publicEnvPrefix } : {}),
|
|
561
|
+
})
|
|
462
562
|
try {
|
|
463
563
|
await withSerializedNodeEnv(mode, () =>
|
|
464
564
|
vite.build({
|
|
@@ -487,7 +587,7 @@ export async function buildServerVite(options: BuildServerViteOptions): Promise<
|
|
|
487
587
|
// Keep builtins external so Node/Bun use their native implementations. Edge builds add a
|
|
488
588
|
// generateBundle guard below, so an external specifier can never silently ship to workerd.
|
|
489
589
|
external: [/^node:/],
|
|
490
|
-
...(edge ?
|
|
590
|
+
plugins: [...(edge ? [edgeGuard] : []), zoneGuard],
|
|
491
591
|
// ONE self-contained `server.js`. `inlineDynamicImports` forces the ENTRY chunk to absorb
|
|
492
592
|
// every module - the app, the adapter, react/react-dom, @nifrajs/* - so no second chunk is
|
|
493
593
|
// emitted. It is NOT redundant with `ssr.noExternal`: `noExternal` decides what gets bundled,
|
|
@@ -512,7 +612,8 @@ export async function buildServerVite(options: BuildServerViteOptions): Promise<
|
|
|
512
612
|
} catch (error) {
|
|
513
613
|
// Re-raise the guard's own message from here, where no bundler error-reporting path can rewrite it
|
|
514
614
|
// (see the same handling in buildClientVite).
|
|
515
|
-
|
|
615
|
+
const leak = zoneGuard.leak ?? edgeGuard.leak
|
|
616
|
+
if (leak !== undefined) throw new Error(leak, { cause: error })
|
|
516
617
|
throw error
|
|
517
618
|
}
|
|
518
619
|
|
|
@@ -536,8 +637,11 @@ export const viteBundler: Bundler = {
|
|
|
536
637
|
...(input.plugins ? { vitePlugins: input.plugins } : {}),
|
|
537
638
|
...(input.conditions ? { conditions: input.conditions } : {}),
|
|
538
639
|
...(input.define ? { define: input.define } : {}),
|
|
640
|
+
...(input.cssCodeSplit !== undefined ? { cssCodeSplit: input.cssCodeSplit } : {}),
|
|
641
|
+
...(input.cssLoading !== undefined ? { cssLoading: input.cssLoading } : {}),
|
|
539
642
|
...(input.publicDir !== undefined ? { publicDir: input.publicDir } : {}),
|
|
540
643
|
...(input.publicEnvPrefix !== undefined ? { publicEnvPrefix: input.publicEnvPrefix } : {}),
|
|
644
|
+
...(input.secretExemptions !== undefined ? { secretExemptions: input.secretExemptions } : {}),
|
|
541
645
|
...(input.root ? { root: input.root } : {}),
|
|
542
646
|
}),
|
|
543
647
|
buildServer: (input) =>
|
|
@@ -547,9 +651,14 @@ export const viteBundler: Bundler = {
|
|
|
547
651
|
outDir: input.outDir,
|
|
548
652
|
clientEntry: input.clientEntry,
|
|
549
653
|
target: input.target,
|
|
654
|
+
...(input.styles !== undefined ? { styles: input.styles } : {}),
|
|
655
|
+
...(input.routeStyles !== undefined ? { routeStyles: input.routeStyles } : {}),
|
|
550
656
|
...(input.plugins ? { vitePlugins: input.plugins } : {}),
|
|
551
657
|
...(input.define ? { define: input.define } : {}),
|
|
658
|
+
...(input.cssLoading !== undefined ? { cssLoading: input.cssLoading } : {}),
|
|
552
659
|
...(input.root ? { root: input.root } : {}),
|
|
660
|
+
...(input.generatedFiles !== undefined ? { generatedFiles: input.generatedFiles } : {}),
|
|
661
|
+
...(input.publicEnvPrefix !== undefined ? { publicEnvPrefix: input.publicEnvPrefix } : {}),
|
|
553
662
|
}),
|
|
554
663
|
}
|
|
555
664
|
|