@nifrajs/web 4.0.0 → 5.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/README.md +11 -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 +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +49 -26
- 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-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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- 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/page-execution.d.ts +6 -0
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +377 -108
- package/dist/internal/page-execution.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 +1 -0
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +28 -5
- 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 +8 -0
- package/dist/internal/route-path.d.ts.map +1 -0
- package/dist/internal/route-path.js +8 -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 +105 -19
- 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 +21 -16
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +73 -21
- 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/zone-graph.d.ts +4 -0
- package/dist/internal/zone-graph.d.ts.map +1 -1
- package/dist/internal/zone-graph.js +54 -14
- package/dist/internal/zone-graph.js.map +1 -1
- package/dist/isr.d.ts +58 -2
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +296 -39
- 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 +28 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +14 -3
- 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 +3 -1
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +4 -0
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +10 -2
- package/dist/plugins/zone-guard.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 +2 -0
- 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 +39 -46
- package/dist/router.js.map +1 -1
- 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 +77 -17
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +40 -0
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +170 -6
- package/dist/zones.js.map +1 -1
- package/package.json +97 -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 -23
- package/src/deferred.ts +27 -9
- package/src/dev-feed.ts +24 -5
- 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 +3 -2
- 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/page-execution.ts +506 -108
- package/src/internal/progressive-cookies.ts +60 -0
- package/src/internal/public-env.ts +14 -0
- package/src/internal/render-document.ts +26 -11
- package/src/internal/response-controls.ts +31 -14
- package/src/internal/revive-dates.ts +35 -0
- package/src/internal/route-path.ts +8 -0
- package/src/internal/secret-scan.ts +104 -17
- package/src/internal/server-boundary.ts +29 -5
- package/src/internal/web-app.ts +116 -37
- package/src/internal/wire-date.ts +16 -0
- package/src/internal/zone-graph.ts +59 -19
- package/src/isr.ts +351 -43
- package/src/llms-txt.ts +32 -3
- package/src/manifest.ts +43 -7
- package/src/open-resolved.ts +64 -0
- package/src/plugins/kit.ts +31 -11
- package/src/plugins/vite-leak-guard.ts +5 -1
- package/src/plugins/zone-guard.ts +11 -2
- package/src/public-dir.ts +27 -8
- package/src/query.ts +107 -26
- package/src/render-seam.ts +2 -0
- package/src/route-types.ts +34 -7
- package/src/router.ts +58 -55
- 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 +68 -17
- package/src/zones.ts +208 -7
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createRequire } from "node:module"
|
|
2
|
+
import type * as TS from "typescript"
|
|
3
|
+
|
|
4
|
+
/** The code of the error a TypeScript without the in-process compiler API raises here. */
|
|
5
|
+
export const UNSUPPORTED_TYPESCRIPT = "NIFRA_TS_UNSUPPORTED"
|
|
6
|
+
|
|
7
|
+
function isCompiler(value: unknown): value is typeof TS {
|
|
8
|
+
return (
|
|
9
|
+
typeof value === "object" &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
"createSourceFile" in value &&
|
|
12
|
+
typeof value.createSourceFile === "function"
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The TypeScript compiler API, resolved from `from` the way `import "typescript"` there would be.
|
|
18
|
+
* TypeScript 7's package ships no in-process compiler API (its root export is only `version`), so
|
|
19
|
+
* it raises an error that names the fix instead of a TypeError on first use.
|
|
20
|
+
*/
|
|
21
|
+
export function loadTypeScript(from: string = import.meta.url): typeof TS {
|
|
22
|
+
const loaded: unknown = createRequire(from)("typescript")
|
|
23
|
+
if (isCompiler(loaded)) return loaded
|
|
24
|
+
const version =
|
|
25
|
+
typeof loaded === "object" &&
|
|
26
|
+
loaded !== null &&
|
|
27
|
+
"version" in loaded &&
|
|
28
|
+
typeof loaded.version === "string"
|
|
29
|
+
? loaded.version
|
|
30
|
+
: "unknown"
|
|
31
|
+
throw Object.assign(
|
|
32
|
+
new Error(
|
|
33
|
+
`${UNSUPPORTED_TYPESCRIPT}: server components read component modules with the TypeScript compiler API, which TypeScript ${version} does not ship. Pin the project to TypeScript 6 (for example, \`bun add -d typescript@^6\`).`,
|
|
34
|
+
),
|
|
35
|
+
{ code: UNSUPPORTED_TYPESCRIPT, version },
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const ts: typeof TS = loadTypeScript()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ServerComponentError } from "./error.ts"
|
|
2
|
+
import type { ComponentValue } from "./types.ts"
|
|
3
|
+
|
|
4
|
+
/** Property order does not identify a resource; callers supply already-validated DTO inputs. */
|
|
5
|
+
export function componentInputKey(value: ComponentValue): string {
|
|
6
|
+
if (Array.isArray(value)) return `[${value.map(componentInputKey).join(",")}]`
|
|
7
|
+
if (typeof value === "object" && value !== null)
|
|
8
|
+
return `{${Object.keys(value)
|
|
9
|
+
.sort()
|
|
10
|
+
.map((key) => `${JSON.stringify(key)}:${componentInputKey(Reflect.get(value, key))}`)
|
|
11
|
+
.join(",")}}`
|
|
12
|
+
return JSON.stringify(value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const encoder = new TextEncoder()
|
|
16
|
+
const forbidden = new Set(["__proto__", "prototype", "constructor"])
|
|
17
|
+
|
|
18
|
+
export function componentValue(value: unknown, maxBytes: number = 65536): ComponentValue {
|
|
19
|
+
const seen = new Set<object>()
|
|
20
|
+
let nodes = 0
|
|
21
|
+
let bytes = 0
|
|
22
|
+
const charge = (text: string): void => {
|
|
23
|
+
bytes += encoder.encode(text).byteLength
|
|
24
|
+
if (bytes > maxBytes) throw new ServerComponentError("component_limit", 413)
|
|
25
|
+
}
|
|
26
|
+
const visit = (item: unknown, depth: number): ComponentValue => {
|
|
27
|
+
if (++nodes > 8192 || depth > 16) throw new ServerComponentError("component_limit", 413)
|
|
28
|
+
if (
|
|
29
|
+
item === null ||
|
|
30
|
+
typeof item === "string" ||
|
|
31
|
+
typeof item === "boolean" ||
|
|
32
|
+
(typeof item === "number" && Number.isFinite(item))
|
|
33
|
+
) {
|
|
34
|
+
charge(JSON.stringify(item))
|
|
35
|
+
return item
|
|
36
|
+
}
|
|
37
|
+
if (typeof item !== "object" || item === null || seen.has(item))
|
|
38
|
+
throw new ServerComponentError("component_contract")
|
|
39
|
+
const array = Array.isArray(item)
|
|
40
|
+
const prototype = Object.getPrototypeOf(item)
|
|
41
|
+
if (
|
|
42
|
+
array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null
|
|
43
|
+
) {
|
|
44
|
+
throw new ServerComponentError("component_contract")
|
|
45
|
+
}
|
|
46
|
+
// Reject custom serializers without invoking them, including inherited serializers.
|
|
47
|
+
if ("toJSON" in item || Object.getOwnPropertySymbols(item).length > 0)
|
|
48
|
+
throw new ServerComponentError("component_contract")
|
|
49
|
+
const descriptors = Object.getOwnPropertyDescriptors(item)
|
|
50
|
+
const names = Object.keys(descriptors)
|
|
51
|
+
if (array && (item.length > 8192 || names.length !== item.length + 1))
|
|
52
|
+
throw new ServerComponentError("component_contract")
|
|
53
|
+
const result: ComponentValue[] | Record<string, ComponentValue> = array
|
|
54
|
+
? []
|
|
55
|
+
: Object.create(null)
|
|
56
|
+
seen.add(item)
|
|
57
|
+
charge(array ? "[]" : "{}")
|
|
58
|
+
let count = 0
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
if (array && name === "length") continue
|
|
61
|
+
const property = descriptors[name]!
|
|
62
|
+
if (
|
|
63
|
+
forbidden.has(name) ||
|
|
64
|
+
!property.enumerable ||
|
|
65
|
+
!Object.hasOwn(property, "value") ||
|
|
66
|
+
(array && (!/^(0|[1-9][0-9]*)$/.test(name) || Number(name) >= item.length))
|
|
67
|
+
) {
|
|
68
|
+
throw new ServerComponentError("component_contract")
|
|
69
|
+
}
|
|
70
|
+
charge((count++ === 0 ? "" : ",") + (array ? "" : `${JSON.stringify(name)}:`))
|
|
71
|
+
Object.defineProperty(result, name, {
|
|
72
|
+
value: visit(property.value, depth + 1),
|
|
73
|
+
enumerable: true,
|
|
74
|
+
writable: true,
|
|
75
|
+
configurable: true,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
seen.delete(item)
|
|
79
|
+
return result
|
|
80
|
+
}
|
|
81
|
+
return visit(value, 0)
|
|
82
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { createRequire } from "node:module"
|
|
3
|
+
import { resolve } from "node:path"
|
|
4
|
+
import type * as TS from "typescript"
|
|
5
|
+
import { type Plugin, version as viteVersion } from "vite"
|
|
6
|
+
import {
|
|
7
|
+
compileComponentModule,
|
|
8
|
+
componentReference,
|
|
9
|
+
directive,
|
|
10
|
+
exportedValues,
|
|
11
|
+
guardCreateElementCalls,
|
|
12
|
+
sourceFile,
|
|
13
|
+
} from "./source.ts"
|
|
14
|
+
import { ts } from "./typescript.ts"
|
|
15
|
+
|
|
16
|
+
const componentFile = /\.component\.server\.[cm]?[jt]sx?$/
|
|
17
|
+
const islandFile = /\.island\.[cm]?[jt]sx?$/
|
|
18
|
+
const referencePrefix = "\0nifra-component-reference:"
|
|
19
|
+
const nativeQuery = "nifra-native-client-reference"
|
|
20
|
+
const reactRuntime = "\0nifra-component-react-runtime"
|
|
21
|
+
// Vite uses forward slashes for module IDs even when the filesystem uses backslashes.
|
|
22
|
+
const bare = (id: string): string => (id.split("?")[0] ?? id).replaceAll("\\", "/")
|
|
23
|
+
|
|
24
|
+
export interface ServerComponentsPluginOptions {
|
|
25
|
+
readonly root: string
|
|
26
|
+
readonly adapter: "react" | "vue" | "svelte" | "solid" | "preact"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function nativeFacade(source: string, file: string): string {
|
|
30
|
+
const prefix = file.replace(/\.[cm]?[jt]sx?$/, ".props.")
|
|
31
|
+
const extensions = /\.[cm]?jsx?$/.test(file)
|
|
32
|
+
? ["js", "mjs", "cjs", "ts"]
|
|
33
|
+
: ["ts", "mts", "cts", "js"]
|
|
34
|
+
const companion =
|
|
35
|
+
extensions.map((extension) => prefix + extension).find(existsSync) ?? prefix + extensions[0]
|
|
36
|
+
if (!existsSync(companion))
|
|
37
|
+
throw new Error(
|
|
38
|
+
`[nifra/components] ${file} requires ${companion}: export a closed props schema for every native client component`,
|
|
39
|
+
)
|
|
40
|
+
const schemas = sourceFile(readFileSync(companion, "utf8"), companion)
|
|
41
|
+
const registered = new Set(exportedValues(schemas))
|
|
42
|
+
const slots = new Map<string, readonly string[]>()
|
|
43
|
+
for (const statement of schemas.statements) {
|
|
44
|
+
if (!ts.isVariableStatement(statement)) continue
|
|
45
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
46
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.name.text.endsWith("Slots")) continue
|
|
47
|
+
let value = declaration.initializer
|
|
48
|
+
if (value !== undefined && ts.isAsExpression(value)) value = value.expression
|
|
49
|
+
if (
|
|
50
|
+
value === undefined ||
|
|
51
|
+
!ts.isArrayLiteralExpression(value) ||
|
|
52
|
+
!value.elements.every(ts.isStringLiteral)
|
|
53
|
+
)
|
|
54
|
+
throw new Error("[nifra/components] Managed slot names must be a literal array")
|
|
55
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The compiler inspects trusted AST/package metadata and its own populated stub map; application data never supplies these values.
|
|
56
|
+
const names = value.elements.map((node) => (node as TS.StringLiteral).text)
|
|
57
|
+
if (
|
|
58
|
+
names.some(
|
|
59
|
+
(name) =>
|
|
60
|
+
!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(name) ||
|
|
61
|
+
name === "ref" ||
|
|
62
|
+
name === "key" ||
|
|
63
|
+
name === "constructor",
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
throw new Error("[nifra/components] Invalid managed slot name")
|
|
67
|
+
slots.set(declaration.name.text.slice(0, -5), names)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const names = exportedValues(sourceFile(source, file))
|
|
71
|
+
const exports = names.map((name, index) => {
|
|
72
|
+
if (!registered.has(name))
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[nifra/components] Native client export ${name} in ${file} has no registered props schema`,
|
|
75
|
+
)
|
|
76
|
+
if (name !== "default" && !/^[$A-Z_a-z][$\w]*$/.test(name))
|
|
77
|
+
throw new Error("[nifra/components] Native client export names must be identifiers")
|
|
78
|
+
const local = `__nifra_client_${index}`
|
|
79
|
+
const value = `__guard(__native[${JSON.stringify(name)}], __schemas[${JSON.stringify(name)}], ${JSON.stringify(slots.get(name) ?? [])})`
|
|
80
|
+
return name === "default"
|
|
81
|
+
? `const ${local} = ${value}; export default ${local};`
|
|
82
|
+
: `export const ${name} = ${value};`
|
|
83
|
+
})
|
|
84
|
+
return [
|
|
85
|
+
'import { guardedClientReference as __guard } from "@nifrajs/web-react/server-components/guard";',
|
|
86
|
+
`import * as __native from ${JSON.stringify(`${file}?${nativeQuery}`)};`,
|
|
87
|
+
`import * as __schemas from ${JSON.stringify(companion)};`,
|
|
88
|
+
...exports,
|
|
89
|
+
].join("\n")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Compiler bridge. The ordinary build must continue rejecting arbitrary server imports. */
|
|
93
|
+
export function serverComponentsPlugin(
|
|
94
|
+
options: ServerComponentsPluginOptions,
|
|
95
|
+
): Plugin & { readonly api: { readonly componentModules: ReadonlySet<string> } } {
|
|
96
|
+
const stubs = new Map<string, string>()
|
|
97
|
+
const internal = new Set<string>()
|
|
98
|
+
const nativeReferences = new Set<string>()
|
|
99
|
+
const componentModules = new Set<string>()
|
|
100
|
+
return {
|
|
101
|
+
api: { componentModules },
|
|
102
|
+
name: "nifra:server-components",
|
|
103
|
+
enforce: "pre",
|
|
104
|
+
configResolved(config) {
|
|
105
|
+
if (viteVersion !== "8.2.1")
|
|
106
|
+
throw new Error(
|
|
107
|
+
"[nifra/components] Use the certified Vite 8.2.1 component pipeline; Bun remains a supported server runtime",
|
|
108
|
+
)
|
|
109
|
+
if (options.adapter === "react") {
|
|
110
|
+
const require = createRequire(resolve(config.root, "package.json"))
|
|
111
|
+
const versions = ["react", "react-dom", "react-server-dom-webpack"].map((name) => {
|
|
112
|
+
try {
|
|
113
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The compiler inspects trusted AST/package metadata and its own populated stub map; application data never supplies these values.
|
|
114
|
+
return (require(`${name}/package.json`) as { version: string }).version
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`[nifra/components] Install ${name}@19.2.8 for the React component pipeline`,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
if (versions.some((version) => version !== "19.2.8"))
|
|
122
|
+
throw new Error(
|
|
123
|
+
"[nifra/components] React, React DOM, and Flight must all use the matching 19.2.8 version",
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
async resolveId(source, importer) {
|
|
128
|
+
const environment = this.environment.name
|
|
129
|
+
if (source === "react" && environment === "rsc" && !internal.has(bare(importer ?? ""))) {
|
|
130
|
+
internal.add(reactRuntime)
|
|
131
|
+
const guard = await this.resolve("@nifrajs/web-react/server-components/guard", importer, {
|
|
132
|
+
skipSelf: true,
|
|
133
|
+
})
|
|
134
|
+
if (guard !== null) internal.add(bare(guard.id))
|
|
135
|
+
return reactRuntime
|
|
136
|
+
}
|
|
137
|
+
if (source.startsWith("\0virtual:vite-rsc/") && environment !== "rsc") return null
|
|
138
|
+
if (source.startsWith("@nifrajs/web-react/server-components/")) {
|
|
139
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true })
|
|
140
|
+
if (resolved !== null) internal.add(bare(resolved.id))
|
|
141
|
+
return resolved
|
|
142
|
+
}
|
|
143
|
+
if (source.includes(nativeQuery)) {
|
|
144
|
+
const file = source.startsWith("/@fs/")
|
|
145
|
+
? source.slice("/@fs".length).replace(/^\/(?=[A-Za-z]:[\\/])/, "")
|
|
146
|
+
: existsSync(bare(source))
|
|
147
|
+
? source
|
|
148
|
+
: resolve(options.root, `.${source}`)
|
|
149
|
+
if (
|
|
150
|
+
!nativeReferences.has(bare(file)) ||
|
|
151
|
+
(environment === "rsc" && (importer === undefined || bare(importer) !== bare(file)))
|
|
152
|
+
)
|
|
153
|
+
throw new Error(
|
|
154
|
+
"[nifra/components] Raw native client references are compiler-owned; import the registered client component",
|
|
155
|
+
)
|
|
156
|
+
return file
|
|
157
|
+
}
|
|
158
|
+
if (source.startsWith(referencePrefix) || source.startsWith("\0")) return null
|
|
159
|
+
if (
|
|
160
|
+
environment === "rsc" &&
|
|
161
|
+
options.adapter === "react" &&
|
|
162
|
+
(source === "react/jsx-runtime" || source === "react/jsx-dev-runtime") &&
|
|
163
|
+
!internal.has(bare(importer ?? ""))
|
|
164
|
+
) {
|
|
165
|
+
const resolved = await this.resolve(
|
|
166
|
+
`@nifrajs/web-react/server-components/${source.endsWith("dev-runtime") ? "jsx-dev-runtime" : "jsx-runtime"}`,
|
|
167
|
+
importer,
|
|
168
|
+
{ skipSelf: true },
|
|
169
|
+
)
|
|
170
|
+
if (resolved === null)
|
|
171
|
+
throw new Error("[nifra/components] Install the matching @nifrajs/web-react adapter")
|
|
172
|
+
internal.add(bare(resolved.id))
|
|
173
|
+
const guard = await this.resolve("@nifrajs/web-react/server-components/guard", importer, {
|
|
174
|
+
skipSelf: true,
|
|
175
|
+
})
|
|
176
|
+
if (guard !== null) internal.add(bare(guard.id))
|
|
177
|
+
return resolved
|
|
178
|
+
}
|
|
179
|
+
if (environment !== "client") return null
|
|
180
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true })
|
|
181
|
+
if (resolved === null || !componentFile.test(bare(resolved.id))) return null
|
|
182
|
+
if (resolved.id !== bare(resolved.id))
|
|
183
|
+
throw new Error("[nifra/components] Server component source cannot be imported as an asset")
|
|
184
|
+
const compiled = compileComponentModule(
|
|
185
|
+
readFileSync(resolved.id, "utf8"),
|
|
186
|
+
resolved.id,
|
|
187
|
+
options.root,
|
|
188
|
+
"client",
|
|
189
|
+
)
|
|
190
|
+
const id = referencePrefix + componentReference(resolved.id, options.root, "module")
|
|
191
|
+
stubs.set(id, compiled.code)
|
|
192
|
+
return id
|
|
193
|
+
},
|
|
194
|
+
load(id) {
|
|
195
|
+
if (id === reactRuntime)
|
|
196
|
+
return {
|
|
197
|
+
code: 'import * as Native from "react"; import { guardedCreateElement as createElement, guardedCloneElement as cloneElement } from "@nifrajs/web-react/server-components/guard"; export { Children, Fragment, Profiler, StrictMode, Suspense, __SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, cache, cacheSignal, captureOwnerStack, createRef, forwardRef, isValidElement, lazy, memo, use, useCallback, useDebugValue, useId, useMemo, version } from "react"; export { createElement, cloneElement }; export default { ...Native, createElement, cloneElement };',
|
|
198
|
+
map: null,
|
|
199
|
+
}
|
|
200
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The compiler inspects trusted AST/package metadata and its own populated stub map; application data never supplies these values.
|
|
201
|
+
if (stubs.has(id)) return { code: stubs.get(id) as string, map: null }
|
|
202
|
+
const path = bare(id)
|
|
203
|
+
if (id.endsWith(`?${nativeQuery}`)) return { code: readFileSync(path, "utf8"), map: null }
|
|
204
|
+
if (!/\.[cm]?[jt]sx?$/.test(path) || !existsSync(path)) return null
|
|
205
|
+
const source = readFileSync(path, "utf8")
|
|
206
|
+
const ast = sourceFile(source, path)
|
|
207
|
+
if (directive(ast, "use server"))
|
|
208
|
+
throw new Error(
|
|
209
|
+
`[nifra/components] Native use-server actions in ${path} are unsupported; use a validated serverFn in a *.fn.ts module`,
|
|
210
|
+
)
|
|
211
|
+
if (
|
|
212
|
+
this.environment.name === "rsc" &&
|
|
213
|
+
options.adapter === "react" &&
|
|
214
|
+
directive(ast, "use client")
|
|
215
|
+
) {
|
|
216
|
+
nativeReferences.add(path)
|
|
217
|
+
return { code: nativeFacade(source, path), map: null }
|
|
218
|
+
}
|
|
219
|
+
return null
|
|
220
|
+
},
|
|
221
|
+
transform(source, id) {
|
|
222
|
+
if (id.includes(nativeQuery) || internal.has(bare(id))) return null
|
|
223
|
+
const path = bare(id)
|
|
224
|
+
let code = source
|
|
225
|
+
if (componentFile.test(path)) {
|
|
226
|
+
code = compileComponentModule(source, path, options.root, this.environment.name).code
|
|
227
|
+
componentModules.add(path)
|
|
228
|
+
}
|
|
229
|
+
if (islandFile.test(path))
|
|
230
|
+
code = compileComponentModule(
|
|
231
|
+
source,
|
|
232
|
+
path,
|
|
233
|
+
options.root,
|
|
234
|
+
this.environment.name,
|
|
235
|
+
"island",
|
|
236
|
+
).code
|
|
237
|
+
if (this.environment.name === "rsc" && options.adapter === "react")
|
|
238
|
+
code = guardCreateElementCalls(code, path)
|
|
239
|
+
return code === source ? null : { code, map: null }
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export {
|
|
2
|
+
defineServerComponent,
|
|
3
|
+
inheritOwnerAuthorization,
|
|
4
|
+
isServerComponent,
|
|
5
|
+
prepareServerComponent,
|
|
6
|
+
} from "./server-components/definition.ts"
|
|
7
|
+
export { type ComponentErrorCode, ServerComponentError } from "./server-components/error.ts"
|
|
8
|
+
export {
|
|
9
|
+
createServerComponents,
|
|
10
|
+
defineComponentOwner,
|
|
11
|
+
type ServerComponentsOptions,
|
|
12
|
+
serverComponentIdentity,
|
|
13
|
+
serverComponentInstance,
|
|
14
|
+
} from "./server-components/runtime.ts"
|
|
15
|
+
export type {
|
|
16
|
+
ComponentInstance,
|
|
17
|
+
ComponentIsland,
|
|
18
|
+
ComponentKey,
|
|
19
|
+
ComponentOwner,
|
|
20
|
+
ComponentRecord,
|
|
21
|
+
ComponentRender,
|
|
22
|
+
ComponentRenderContext,
|
|
23
|
+
ComponentState,
|
|
24
|
+
ComponentValue,
|
|
25
|
+
PreparedServerComponent,
|
|
26
|
+
ServerComponent,
|
|
27
|
+
ServerComponentContext,
|
|
28
|
+
ServerComponentDefinition,
|
|
29
|
+
ServerComponentHandle,
|
|
30
|
+
ServerComponentRenderAdapter,
|
|
31
|
+
ServerComponentRuntime,
|
|
32
|
+
} from "./server-components/types.ts"
|
package/src/service-worker.ts
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* - **GET, same-origin, `ok`, and not `no-store`.** Anything else goes straight to the network.
|
|
14
14
|
* - **The cache name carries the build id.** A deploy that changed the assets gets a new cache, and
|
|
15
15
|
* activation deletes every older one, so a stale worker cannot pin an old build indefinitely.
|
|
16
|
+
* Only caches named `<cacheName>:<buildId>` are the worker's: the origin's other caches - another
|
|
17
|
+
* app's, or data the page stored itself - are never read, written or deleted.
|
|
18
|
+
* - **Only this build's precached URLs are answered.** Every other request, an API call included,
|
|
19
|
+
* goes to the network untouched.
|
|
16
20
|
*
|
|
17
21
|
* It is opt-in and generated at build time; an app that does not call this ships nothing.
|
|
18
22
|
*/
|
|
@@ -36,7 +40,10 @@ export interface ServiceWorkerOptions {
|
|
|
36
40
|
* bytes. Omit it and failed navigations simply fail, which is the honest default.
|
|
37
41
|
*/
|
|
38
42
|
readonly offlineUrl?: string
|
|
39
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Cache name prefix, and the namespace activation cleans: every cache named `<cacheName>:<id>` other
|
|
45
|
+
* than this build's is deleted. Give each worker sharing an origin its own. Default `nifra`.
|
|
46
|
+
*/
|
|
40
47
|
readonly cacheName?: string
|
|
41
48
|
/** Extra same-origin URLs to precache. Only pass immutable ones. */
|
|
42
49
|
readonly additionalPrecache?: readonly string[]
|
|
@@ -101,8 +108,11 @@ export function generateServiceWorker(
|
|
|
101
108
|
const urls = [...new Set(offline === undefined ? precache : [...precache, offline])].sort()
|
|
102
109
|
|
|
103
110
|
return `// Generated by @nifrajs/web/service-worker. Do not edit.
|
|
104
|
-
const
|
|
111
|
+
const OWNED = ${JSON.stringify(`${prefix}:`)}
|
|
112
|
+
const CACHE = OWNED + ${JSON.stringify(options.buildId)}
|
|
105
113
|
const PRECACHE = ${JSON.stringify(urls)}
|
|
114
|
+
const OWN_URLS = new Set(PRECACHE)
|
|
115
|
+
const OWN = { cacheName: CACHE }
|
|
106
116
|
const OFFLINE = ${offline === undefined ? "null" : JSON.stringify(offline)}
|
|
107
117
|
|
|
108
118
|
self.addEventListener("install", (event) => {
|
|
@@ -115,7 +125,9 @@ self.addEventListener("activate", (event) => {
|
|
|
115
125
|
event.waitUntil(
|
|
116
126
|
caches
|
|
117
127
|
.keys()
|
|
118
|
-
|
|
128
|
+
// Neither the prefix nor a build id can hold a colon, so the first one ends the namespace.
|
|
129
|
+
.then((keys) => keys.filter((k) => k !== CACHE && k.startsWith(OWNED)))
|
|
130
|
+
.then((old) => Promise.all(old.map((k) => caches.delete(k))))
|
|
119
131
|
.then(() => self.clients.claim()),
|
|
120
132
|
)
|
|
121
133
|
})
|
|
@@ -130,20 +142,25 @@ self.addEventListener("fetch", (event) => {
|
|
|
130
142
|
// how one user gets served the page rendered for another, so nothing is stored here.
|
|
131
143
|
if (request.mode === "navigate") {
|
|
132
144
|
if (OFFLINE === null) return
|
|
133
|
-
event.respondWith(
|
|
145
|
+
event.respondWith(
|
|
146
|
+
fetch(request).catch(() => caches.match(OFFLINE, OWN).then((r) => r ?? Response.error())),
|
|
147
|
+
)
|
|
134
148
|
return
|
|
135
149
|
}
|
|
136
150
|
|
|
137
|
-
// Assets: cache first, because every precached URL is content-hashed and therefore immutable.
|
|
151
|
+
// Assets: cache first, because every precached URL is content-hashed and therefore immutable. Only
|
|
152
|
+
// this build's URLs are answered, and only from this build's cache: every other request, an API
|
|
153
|
+
// call included, goes to the network, and another cache on the origin is never read.
|
|
154
|
+
if (!OWN_URLS.has(url.pathname)) return
|
|
138
155
|
event.respondWith(
|
|
139
|
-
caches.match(request).then((hit) => {
|
|
156
|
+
caches.match(request, OWN).then((hit) => {
|
|
140
157
|
if (hit !== undefined) return hit
|
|
141
158
|
return fetch(request).then((response) => {
|
|
142
159
|
const cacheable =
|
|
143
160
|
response.ok &&
|
|
144
161
|
response.type === "basic" &&
|
|
145
162
|
!(response.headers.get("cache-control") ?? "").includes("no-store")
|
|
146
|
-
if (cacheable
|
|
163
|
+
if (cacheable) {
|
|
147
164
|
const copy = response.clone()
|
|
148
165
|
void caches.open(CACHE).then((c) => c.put(request, copy))
|
|
149
166
|
}
|
package/src/vite.ts
CHANGED
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
* injects its HMR client + the framework's refresh preamble. Node `http` (not `Bun.serve`) because
|
|
11
11
|
* Vite's `middlewares` are Connect-style - it runs fine under Bun.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
import { type Dirent, readdirSync, writeFileSync } from "node:fs"
|
|
14
15
|
import {
|
|
15
16
|
createServer as createHttpServer,
|
|
16
17
|
type IncomingMessage,
|
|
17
18
|
type ServerResponse,
|
|
18
19
|
} from "node:http"
|
|
19
20
|
import { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path"
|
|
21
|
+
import { singleCopyPackageNames } from "@nifrajs/core/single-copy"
|
|
20
22
|
import { CLIENT_BATCH_MAX_BYTES } from "./dev-client.ts"
|
|
21
23
|
import { DEV_REQUEST_ID_HEADER, DEV_SERVER_FILES } from "./dev-feed.ts"
|
|
22
24
|
import { listenOrExplain } from "./dev-port.ts"
|
|
@@ -170,6 +172,7 @@ export { LAST_ERROR_PATH } from "./diagnostic.ts"
|
|
|
170
172
|
// The codegen'd client entry is written here (at the Vite root) so Vite serves + HMRs it.
|
|
171
173
|
const DEV_ENTRY = ".nifra-vite-entry.tsx"
|
|
172
174
|
const VITE_CLOSE_BOUND_MS = 2000
|
|
175
|
+
const POLL_INTERVAL_MS = 80
|
|
173
176
|
|
|
174
177
|
/** Candidate spellings for Vite's module-graph map. Vite has used native and slash-normalized
|
|
175
178
|
* absolute Windows paths across versions, while watcher events can additionally arrive as a file URL. */
|
|
@@ -184,6 +187,22 @@ const pathInside = (root: string, path: string): boolean => {
|
|
|
184
187
|
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))
|
|
185
188
|
}
|
|
186
189
|
|
|
190
|
+
/** Every file below `dir` as an absolute path. A directory removed mid-walk contributes nothing. */
|
|
191
|
+
const filesUnder = (dir: string, into = new Set<string>()): Set<string> => {
|
|
192
|
+
let entries: Dirent[]
|
|
193
|
+
try {
|
|
194
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
195
|
+
} catch {
|
|
196
|
+
return into
|
|
197
|
+
}
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
const path = join(dir, entry.name)
|
|
200
|
+
if (entry.isDirectory()) filesUnder(path, into)
|
|
201
|
+
else into.add(path)
|
|
202
|
+
}
|
|
203
|
+
return into
|
|
204
|
+
}
|
|
205
|
+
|
|
187
206
|
const readNodeBody = async (req: IncomingMessage): Promise<Buffer | undefined> => {
|
|
188
207
|
if (req.method === "GET" || req.method === "HEAD") return undefined
|
|
189
208
|
const chunks: Buffer[] = []
|
|
@@ -415,6 +434,9 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
415
434
|
generateClientEntry(manifest, { clientModule: options.clientModule, resolve: toUrl }),
|
|
416
435
|
)
|
|
417
436
|
}
|
|
437
|
+
const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
|
|
438
|
+
// Listed before the entry is generated, so a file added in between is reconciled again, not lost.
|
|
439
|
+
const routeFiles = usePolling ? filesUnder(routesDir) : new Set<string>()
|
|
418
440
|
writeClientEntry()
|
|
419
441
|
const entryUrl = `/${DEV_ENTRY}`
|
|
420
442
|
|
|
@@ -578,7 +600,6 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
578
600
|
// path-shaped `clientModule` (tests) names no package; there is then nothing to externalize.
|
|
579
601
|
const adapterPackage = packageNameOf(options.clientModule)
|
|
580
602
|
const ssrExternal = [...(adapterPackage !== undefined ? [adapterPackage] : []), "@nifrajs/web"]
|
|
581
|
-
const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
|
|
582
603
|
// The feed writes its record and log under `.nifra/`: watching them would turn every log flush into
|
|
583
604
|
// a "file change" that re-creates the app and marks every entry stale.
|
|
584
605
|
const watch: { ignored: RegExp[]; usePolling?: boolean; interval?: number } = {
|
|
@@ -587,7 +608,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
587
608
|
// Poll when native fs events aren't delivered (containers/sandboxes).
|
|
588
609
|
if (usePolling) {
|
|
589
610
|
watch.usePolling = true
|
|
590
|
-
watch.interval =
|
|
611
|
+
watch.interval = POLL_INTERVAL_MS
|
|
591
612
|
}
|
|
592
613
|
// Never `import("vite")` directly here: the guard in `importVite` has to run first, and the dev
|
|
593
614
|
// server importing vite unguarded is precisely what poisoned the module for the whole process.
|
|
@@ -613,6 +634,10 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
613
634
|
// Ahead of the user's plugins: the zone guard refuses backend code before anything serves or
|
|
614
635
|
// transforms it, and a `*.fn` module must be replaced before anything else reads it.
|
|
615
636
|
plugins: [zoneGuard, viteServerFnStub(), ...plugins],
|
|
637
|
+
// Vite pre-bundles what it finds by crawling `index.html`, and this app has none: every dependency
|
|
638
|
+
// would be found mid-session instead, each one re-optimizing and reloading the page. The client
|
|
639
|
+
// entry imports every route's frontend module, so crawling it covers the whole route graph.
|
|
640
|
+
optimizeDeps: { entries: [DEV_ENTRY] },
|
|
616
641
|
resolve: {
|
|
617
642
|
conditions: resolveConditions,
|
|
618
643
|
// Dedupe each framework runtime to ONE copy. In a multi-root workspace a shared package can pull
|
|
@@ -621,7 +646,8 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
621
646
|
// on any hook-using route (the error points at the component, not the resolution - brutal to
|
|
622
647
|
// diagnose). Same policy table the production builds read, so dev matches prod. No-op per
|
|
623
648
|
// framework the app does not use (the package simply isn't present to dedupe).
|
|
624
|
-
|
|
649
|
+
// The app's single-copy declaration pins its packages the same way.
|
|
650
|
+
dedupe: [...viteDedupePackages(), ...singleCopyPackageNames(root)],
|
|
625
651
|
},
|
|
626
652
|
ssr: {
|
|
627
653
|
// Explicit listing forces external even for workspace-LINKED packages (Vite's default keeps
|
|
@@ -722,20 +748,43 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
722
748
|
session.markChange()
|
|
723
749
|
refreshApp(normalizeFilePath(path))
|
|
724
750
|
})
|
|
751
|
+
const onAddUnlink = (event: "add" | "unlink", path: string): void => {
|
|
752
|
+
const filePath = normalizeFilePath(path)
|
|
753
|
+
const inRoutes = pathInside(routesDir, filePath)
|
|
754
|
+
if (inRoutes && usePolling) {
|
|
755
|
+
const file = resolvePath(filePath)
|
|
756
|
+
// Whichever of the watcher and the rescan below reports a route file first handles it.
|
|
757
|
+
if (routeFiles.has(file) === (event === "add")) return
|
|
758
|
+
if (event === "add") routeFiles.add(file)
|
|
759
|
+
else routeFiles.delete(file)
|
|
760
|
+
}
|
|
761
|
+
session.markChange()
|
|
762
|
+
if (inRoutes) {
|
|
763
|
+
writeClientEntry()
|
|
764
|
+
// A newly added route is not in Vite's module graph yet, so invalidating only the
|
|
765
|
+
// route path cannot evict the cached generated entry that imports the route. Evict the
|
|
766
|
+
// generated entry after rewriting it before recreating the SSR app; this also prevents
|
|
767
|
+
// an unlink refresh from briefly loading an entry that still references the deleted file.
|
|
768
|
+
invalidateImporterClosure(resolvePath(root, DEV_ENTRY))
|
|
769
|
+
}
|
|
770
|
+
refreshApp(filePath)
|
|
771
|
+
}
|
|
725
772
|
for (const event of ["add", "unlink"] as const) {
|
|
726
|
-
vite.watcher.on(event, (path) =>
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
773
|
+
vite.watcher.on(event, (path) => onAddUnlink(event, path))
|
|
774
|
+
}
|
|
775
|
+
// Chokidar reports ready before the poller has taken its baseline stat of a directory (Bun's
|
|
776
|
+
// fs.watchFile takes it asynchronously), and a route added or removed in that gap is folded into the
|
|
777
|
+
// baseline and never reported. The manifest comes from a directory scan, so a scan diffed against the
|
|
778
|
+
// route files already handled recovers those events.
|
|
779
|
+
const reconcileRouteFiles = (): void => {
|
|
780
|
+
const onDisk = filesUnder(routesDir)
|
|
781
|
+
try {
|
|
782
|
+
for (const file of onDisk) if (!routeFiles.has(file)) onAddUnlink("add", file)
|
|
783
|
+
for (const file of [...routeFiles]) if (!onDisk.has(file)) onAddUnlink("unlink", file)
|
|
784
|
+
} catch (err) {
|
|
785
|
+
console.error("[nifra/web/vite] route rescan failed:", err)
|
|
786
|
+
session.buildFailed(err, "route rescan failed")
|
|
787
|
+
}
|
|
739
788
|
}
|
|
740
789
|
await watcherReady
|
|
741
790
|
|
|
@@ -756,9 +805,11 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
756
805
|
const address = server.address()
|
|
757
806
|
const boundPort = typeof address === "object" && address !== null ? address.port : port
|
|
758
807
|
session.listening(boundPort)
|
|
808
|
+
const routeRescan = usePolling ? setInterval(reconcileRouteFiles, POLL_INTERVAL_MS) : undefined
|
|
759
809
|
return {
|
|
760
810
|
port: boundPort,
|
|
761
811
|
stop: async () => {
|
|
812
|
+
clearInterval(routeRescan)
|
|
762
813
|
session.stop()
|
|
763
814
|
// Cleared with the server that owns it: the slot is process-global, so a stopped server leaving
|
|
764
815
|
// its loader behind would hand the next one's adapter a graph that is closed (tests start and
|