@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,194 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises"
|
|
3
|
+
import { relative, resolve } from "node:path"
|
|
4
|
+
import { build } from "vite"
|
|
5
|
+
import { unsupportedBuiltin } from "../internal/target-compat.ts"
|
|
6
|
+
import type { ComponentPipelineOptions } from "./build.ts"
|
|
7
|
+
import { componentBundleImports } from "./bundle-proof.ts"
|
|
8
|
+
import { componentServerEntry } from "./server-entry.ts"
|
|
9
|
+
|
|
10
|
+
export interface ComponentBuildResult {
|
|
11
|
+
readonly outDir: string
|
|
12
|
+
readonly build: string
|
|
13
|
+
readonly target: NonNullable<ComponentPipelineOptions["target"]>
|
|
14
|
+
readonly entry: string
|
|
15
|
+
readonly styles: readonly string[]
|
|
16
|
+
readonly assets: readonly string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Asset {
|
|
20
|
+
readonly file: string
|
|
21
|
+
readonly isEntry?: boolean
|
|
22
|
+
readonly css?: readonly string[]
|
|
23
|
+
}
|
|
24
|
+
const assetPath = /^assets\/[A-Za-z0-9_./-]+$/
|
|
25
|
+
function readAssets(value: unknown): readonly Asset[] {
|
|
26
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
27
|
+
throw new Error("[nifra/components] Missing client build manifest")
|
|
28
|
+
return Object.values(value).map((item: unknown) => {
|
|
29
|
+
if (typeof item !== "object" || item === null || Array.isArray(item))
|
|
30
|
+
throw new Error("[nifra/components] Invalid client manifest")
|
|
31
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Manifest object shape, asset paths and optional field types are checked before constructing the target manifest.
|
|
32
|
+
const record = item as Record<string, unknown>
|
|
33
|
+
if (
|
|
34
|
+
typeof record.file !== "string" ||
|
|
35
|
+
!assetPath.test(record.file) ||
|
|
36
|
+
record.file.split("/").includes("..") ||
|
|
37
|
+
(record.isEntry !== undefined && typeof record.isEntry !== "boolean") ||
|
|
38
|
+
(record.css !== undefined &&
|
|
39
|
+
(!Array.isArray(record.css) ||
|
|
40
|
+
record.css.some(
|
|
41
|
+
(css) =>
|
|
42
|
+
typeof css !== "string" || !assetPath.test(css) || css.split("/").includes(".."),
|
|
43
|
+
)))
|
|
44
|
+
)
|
|
45
|
+
throw new Error("[nifra/components] Invalid build asset URL")
|
|
46
|
+
const asset: Asset = { file: record.file }
|
|
47
|
+
if (record.isEntry !== undefined) Object.assign(asset, { isEntry: record.isEntry })
|
|
48
|
+
if (record.css !== undefined) Object.assign(asset, { css: record.css })
|
|
49
|
+
return asset
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function files(directory: string): Promise<string[]> {
|
|
54
|
+
const result: string[] = []
|
|
55
|
+
for (const item of await readdir(directory, { withFileTypes: true })) {
|
|
56
|
+
if (item.isSymbolicLink())
|
|
57
|
+
throw new Error("[nifra/components] Build artifacts cannot be symbolic links")
|
|
58
|
+
const path = resolve(directory, item.name)
|
|
59
|
+
if (item.isDirectory()) result.push(...(await files(path)))
|
|
60
|
+
else if (item.isFile()) result.push(path)
|
|
61
|
+
}
|
|
62
|
+
return result.sort()
|
|
63
|
+
}
|
|
64
|
+
const digest = (value: string | Uint8Array): string =>
|
|
65
|
+
createHash("sha256").update(value).digest("hex")
|
|
66
|
+
|
|
67
|
+
/** Assemble one complete generation before the build orchestrator publishes its directory. */
|
|
68
|
+
export async function assembleComponentTarget(
|
|
69
|
+
options: ComponentPipelineOptions,
|
|
70
|
+
generation: string,
|
|
71
|
+
): Promise<ComponentBuildResult> {
|
|
72
|
+
const out = resolve(options.root, options.outDir ?? "dist")
|
|
73
|
+
const target = options.target ?? "bun"
|
|
74
|
+
const records = readAssets(
|
|
75
|
+
JSON.parse(await readFile(resolve(out, "client/.vite/manifest.json"), "utf8")),
|
|
76
|
+
)
|
|
77
|
+
const entries = records.filter((record) => record.isEntry)
|
|
78
|
+
if (entries.length !== 1)
|
|
79
|
+
throw new Error("[nifra/components] Exactly one client entry is required")
|
|
80
|
+
const assets = (await files(resolve(out, "client/assets"))).map(
|
|
81
|
+
(file) => `/${relative(resolve(out, "client"), file).replaceAll("\\", "/")}`,
|
|
82
|
+
)
|
|
83
|
+
const entry = `/${entries[0]!.file}`
|
|
84
|
+
const styles = [...new Set(records.flatMap((record) => record.css ?? []))]
|
|
85
|
+
.map((file) => `/${file}`)
|
|
86
|
+
.sort()
|
|
87
|
+
if (!assets.includes(entry) || styles.some((file) => !assets.includes(file)))
|
|
88
|
+
throw new Error("[nifra/components] Missing client generation artifacts")
|
|
89
|
+
const serverDirectory = options.adapter === "react" ? "ssr" : "server"
|
|
90
|
+
const serverEntry = `${serverDirectory}/index.js`
|
|
91
|
+
const staticRoot = target === "vercel" ? resolve(out, "static") : out
|
|
92
|
+
await cp(resolve(out, "client/assets"), resolve(staticRoot, "assets"), { recursive: true })
|
|
93
|
+
await rm(resolve(out, "client"), { recursive: true })
|
|
94
|
+
const metadata = {
|
|
95
|
+
v: 1,
|
|
96
|
+
build: generation,
|
|
97
|
+
adapter: options.adapter,
|
|
98
|
+
target,
|
|
99
|
+
entry,
|
|
100
|
+
styles,
|
|
101
|
+
assets,
|
|
102
|
+
}
|
|
103
|
+
if (target === "cloudflare") {
|
|
104
|
+
const source = componentServerEntry({ ...metadata, serverEntry, platform: "cloudflare" })
|
|
105
|
+
const input = resolve(out, ".nifra-entry.backend.js")
|
|
106
|
+
await writeFile(input, source)
|
|
107
|
+
await build({
|
|
108
|
+
root: out,
|
|
109
|
+
configFile: false,
|
|
110
|
+
publicDir: false,
|
|
111
|
+
ssr: { noExternal: true },
|
|
112
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
113
|
+
build: {
|
|
114
|
+
ssr: input,
|
|
115
|
+
outDir: resolve(out, ".nifra-worker"),
|
|
116
|
+
emptyOutDir: true,
|
|
117
|
+
minify: true,
|
|
118
|
+
rollupOptions: { output: { inlineDynamicImports: true, entryFileNames: "index.js" } },
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
await cp(resolve(out, ".nifra-worker/index.js"), resolve(out, "_worker.js"))
|
|
122
|
+
const worker = await readFile(resolve(out, "_worker.js"), "utf8")
|
|
123
|
+
if (
|
|
124
|
+
componentBundleImports(worker).some((specifier) => unsupportedBuiltin(specifier, "browser"))
|
|
125
|
+
)
|
|
126
|
+
throw new Error("[nifra/components] Unsupported built-in in the assembled edge worker")
|
|
127
|
+
await rm(input)
|
|
128
|
+
await rm(resolve(out, ".nifra-worker"), { recursive: true })
|
|
129
|
+
await rm(resolve(out, serverDirectory), { recursive: true })
|
|
130
|
+
if (options.adapter === "react") await rm(resolve(out, "rsc"), { recursive: true })
|
|
131
|
+
await writeFile(
|
|
132
|
+
resolve(out, "_routes.json"),
|
|
133
|
+
`${JSON.stringify({ version: 1, include: ["/*"], exclude: ["/assets/*"] })}\n`,
|
|
134
|
+
)
|
|
135
|
+
await writeFile(resolve(out, ".assetsignore"), "*\n!assets/\n!assets/**\n")
|
|
136
|
+
} else {
|
|
137
|
+
const runtimeRoot = target === "vercel" ? resolve(out, "functions/index.func") : out
|
|
138
|
+
await mkdir(runtimeRoot, { recursive: true })
|
|
139
|
+
if (runtimeRoot !== out) {
|
|
140
|
+
await cp(resolve(out, serverDirectory), resolve(runtimeRoot, serverDirectory), {
|
|
141
|
+
recursive: true,
|
|
142
|
+
})
|
|
143
|
+
await rm(resolve(out, serverDirectory), { recursive: true })
|
|
144
|
+
if (options.adapter === "react") {
|
|
145
|
+
await cp(resolve(out, "rsc"), resolve(runtimeRoot, "rsc"), { recursive: true })
|
|
146
|
+
await rm(resolve(out, "rsc"), { recursive: true })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const privateFiles = await files(resolve(runtimeRoot, serverDirectory))
|
|
150
|
+
if (options.adapter === "react")
|
|
151
|
+
privateFiles.push(...(await files(resolve(runtimeRoot, "rsc"))))
|
|
152
|
+
if (target !== "vercel") privateFiles.push(...(await files(resolve(runtimeRoot, "assets"))))
|
|
153
|
+
const hashes = Object.fromEntries(
|
|
154
|
+
await Promise.all(
|
|
155
|
+
privateFiles.map(async (file) => [
|
|
156
|
+
relative(runtimeRoot, file).replaceAll("\\", "/"),
|
|
157
|
+
digest(await readFile(file)),
|
|
158
|
+
]),
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
const manifest = `${JSON.stringify({ ...metadata, files: hashes })}\n`
|
|
162
|
+
await writeFile(resolve(runtimeRoot, "component-manifest.json"), manifest)
|
|
163
|
+
await writeFile(
|
|
164
|
+
resolve(runtimeRoot, target === "vercel" ? "index.js" : "server.js"),
|
|
165
|
+
componentServerEntry({
|
|
166
|
+
...metadata,
|
|
167
|
+
serverEntry,
|
|
168
|
+
platform: target,
|
|
169
|
+
manifestDigest: digest(manifest),
|
|
170
|
+
}),
|
|
171
|
+
)
|
|
172
|
+
await writeFile(resolve(runtimeRoot, "package.json"), '{"type":"module"}\n')
|
|
173
|
+
if (target === "vercel") {
|
|
174
|
+
await writeFile(
|
|
175
|
+
resolve(runtimeRoot, ".vc-config.json"),
|
|
176
|
+
`${JSON.stringify({
|
|
177
|
+
runtime: "nodejs22.x",
|
|
178
|
+
handler: "index.js",
|
|
179
|
+
launcherType: "Nodejs",
|
|
180
|
+
shouldAddHelpers: false,
|
|
181
|
+
})}\n`,
|
|
182
|
+
)
|
|
183
|
+
await writeFile(
|
|
184
|
+
resolve(out, "config.json"),
|
|
185
|
+
`${JSON.stringify({
|
|
186
|
+
version: 3,
|
|
187
|
+
routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/index" }],
|
|
188
|
+
})}\n`,
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
await writeFile(resolve(out, "component-build.json"), `${JSON.stringify(metadata)}\n`)
|
|
193
|
+
return { outDir: out, build: generation, target, entry, styles, assets }
|
|
194
|
+
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto"
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs"
|
|
3
|
+
import { mkdtemp, rename, rm } from "node:fs/promises"
|
|
4
|
+
import { relative, resolve } from "node:path"
|
|
5
|
+
import { fileURLToPath } from "node:url"
|
|
6
|
+
import {
|
|
7
|
+
createBuilder,
|
|
8
|
+
createServer,
|
|
9
|
+
loadEnv,
|
|
10
|
+
type Plugin,
|
|
11
|
+
type PluginOption,
|
|
12
|
+
type UserConfig,
|
|
13
|
+
version as viteVersion,
|
|
14
|
+
} from "vite"
|
|
15
|
+
import { codeUnitOrder } from "../internal/code-unit-order.ts"
|
|
16
|
+
import { assertDisposableDir } from "../internal/disposable-dir.ts"
|
|
17
|
+
import { publicEnvDefines } from "../internal/public-env.ts"
|
|
18
|
+
import { buildEnvironment } from "../internal/secret-scan.ts"
|
|
19
|
+
import { vitePublicEnvPrefix } from "../internal/server-boundary.ts"
|
|
20
|
+
import { unsupportedBuiltin } from "../internal/target-compat.ts"
|
|
21
|
+
import {
|
|
22
|
+
viteAssetUrlGuard,
|
|
23
|
+
viteBareBuiltinExternal,
|
|
24
|
+
viteLeakGuard,
|
|
25
|
+
viteServerZoneGuard,
|
|
26
|
+
} from "../plugins/vite-leak-guard.ts"
|
|
27
|
+
import { viteServerFnStub } from "../plugins/vite-server-fn.ts"
|
|
28
|
+
import { viteZoneGuard } from "../plugins/vite-zone-guard.ts"
|
|
29
|
+
import { assembleComponentTarget, type ComponentBuildResult } from "./build-target.ts"
|
|
30
|
+
import { componentBundleImports } from "./bundle-proof.ts"
|
|
31
|
+
import { installComponentDevHandler } from "./dev-http.ts"
|
|
32
|
+
import { serverComponentsPlugin } from "./vite.ts"
|
|
33
|
+
|
|
34
|
+
const sourceProofs = new WeakMap<UserConfig, ReadonlyMap<string, string>>()
|
|
35
|
+
|
|
36
|
+
function compilerGeneration(): string {
|
|
37
|
+
const extension = import.meta.url.endsWith(".ts") ? "ts" : "js"
|
|
38
|
+
const hash = createHash("sha256").update(viteVersion)
|
|
39
|
+
// Compiler-only modules do not enter the application graph, but their changes invalidate its ABI.
|
|
40
|
+
for (const name of ["build", "vite", "source", "build-target", "bundle-proof", "server-entry"])
|
|
41
|
+
hash.update(readFileSync(fileURLToPath(new URL(`./${name}.${extension}`, import.meta.url))))
|
|
42
|
+
return hash.digest("hex")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ComponentPipelineOptions {
|
|
46
|
+
readonly root: string
|
|
47
|
+
readonly adapter: "react" | "vue" | "svelte" | "solid" | "preact"
|
|
48
|
+
readonly entries: { readonly client: string; readonly ssr: string; readonly rsc?: string }
|
|
49
|
+
readonly plugins?: readonly PluginOption[]
|
|
50
|
+
readonly outDir?: string
|
|
51
|
+
readonly port?: number
|
|
52
|
+
readonly publicEnvPrefix?: string
|
|
53
|
+
/** The deployment target (default `"bun"`, as `nifra build`'s): compiled for and assembled as one. */
|
|
54
|
+
readonly target?: "bun" | "node" | "deno" | "cloudflare" | "vercel"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** One content-derived generation for every environment; no payloads or paths are published. */
|
|
58
|
+
export function componentBuildId(root: string, outDir = "dist"): string {
|
|
59
|
+
const digest = createHash("sha256").update("nifra-components-v1")
|
|
60
|
+
const output = resolve(root, outDir)
|
|
61
|
+
let count = 0
|
|
62
|
+
const walk = (directory: string): void => {
|
|
63
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) =>
|
|
64
|
+
codeUnitOrder(a.name, b.name),
|
|
65
|
+
)) {
|
|
66
|
+
if (entry.name.startsWith(".") || ["node_modules", "dist", "build"].includes(entry.name))
|
|
67
|
+
continue
|
|
68
|
+
const file = resolve(directory, entry.name)
|
|
69
|
+
if (file === output) continue
|
|
70
|
+
if (entry.isDirectory()) walk(file)
|
|
71
|
+
else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|vue|svelte|css|json|lock)$/.test(entry.name)) {
|
|
72
|
+
if (++count > 10000)
|
|
73
|
+
throw new Error("[nifra/components] Component source graph exceeds 10000 files")
|
|
74
|
+
digest
|
|
75
|
+
.update(relative(root, file).replaceAll("\\", "/"))
|
|
76
|
+
.update("\0")
|
|
77
|
+
.update(readFileSync(file))
|
|
78
|
+
.update("\0")
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
walk(resolve(root))
|
|
83
|
+
return `c_${digest.digest("hex")}`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Coordinated Vite 8 build/dev configuration with the ordinary Nifra boundary guards. */
|
|
87
|
+
export async function serverComponentsConfig(
|
|
88
|
+
options: ComponentPipelineOptions,
|
|
89
|
+
generationId?: string,
|
|
90
|
+
): Promise<UserConfig> {
|
|
91
|
+
const root = resolve(options.root)
|
|
92
|
+
if (
|
|
93
|
+
!["react", "vue", "svelte", "solid", "preact"].includes(options.adapter) ||
|
|
94
|
+
(options.target !== undefined &&
|
|
95
|
+
!["bun", "node", "deno", "cloudflare", "vercel"].includes(options.target))
|
|
96
|
+
)
|
|
97
|
+
throw new Error("[nifra/components] Unsupported component adapter or deployment target")
|
|
98
|
+
const out = resolve(root, options.outDir ?? "dist")
|
|
99
|
+
if (out === root)
|
|
100
|
+
throw new Error("[nifra/components] The output directory must differ from the application root")
|
|
101
|
+
const entries = Object.fromEntries(
|
|
102
|
+
Object.entries(options.entries).map(([name, file]) => [name, resolve(root, file)]),
|
|
103
|
+
)
|
|
104
|
+
if (
|
|
105
|
+
typeof entries.client !== "string" ||
|
|
106
|
+
typeof entries.ssr !== "string" ||
|
|
107
|
+
Object.values(entries).some((file) => !existsSync(file))
|
|
108
|
+
)
|
|
109
|
+
throw new Error(
|
|
110
|
+
"[nifra/components] Declare existing client and SSR entries; the SSR entry must export createApp(clientEntry, assets)",
|
|
111
|
+
)
|
|
112
|
+
let build = generationId ?? componentBuildId(root, options.outDir)
|
|
113
|
+
const sources = new Map<string, string>()
|
|
114
|
+
const target = options.target ?? "bun"
|
|
115
|
+
const edge = target === "cloudflare"
|
|
116
|
+
const generatedFiles = Object.values(entries)
|
|
117
|
+
const guards = {
|
|
118
|
+
appRoot: root,
|
|
119
|
+
root,
|
|
120
|
+
generatedFiles: [entries.client!],
|
|
121
|
+
publicEnvPrefix: options.publicEnvPrefix ?? "PUBLIC_",
|
|
122
|
+
}
|
|
123
|
+
const compiler = serverComponentsPlugin({ root, adapter: options.adapter })
|
|
124
|
+
// Vite reads `.env.production` too; a value only there is still the app's secret to keep.
|
|
125
|
+
const client = viteLeakGuard({
|
|
126
|
+
...guards,
|
|
127
|
+
outDir: resolve(out, "client"),
|
|
128
|
+
secrets: { env: { ...loadEnv("production", root, ""), ...buildEnvironment() } },
|
|
129
|
+
})
|
|
130
|
+
const server = viteServerZoneGuard({
|
|
131
|
+
...guards,
|
|
132
|
+
generatedFiles,
|
|
133
|
+
componentModules: compiler.api.componentModules,
|
|
134
|
+
})
|
|
135
|
+
const generation: Plugin & { api: { readonly componentSources: ReadonlyMap<string, string> } } = {
|
|
136
|
+
api: { componentSources: sources },
|
|
137
|
+
name: "nifra:component-generation",
|
|
138
|
+
config(_config, env) {
|
|
139
|
+
if (env.command === "serve") build = `d_${randomBytes(16).toString("hex")}`
|
|
140
|
+
const config: UserConfig = {
|
|
141
|
+
define: {
|
|
142
|
+
"process.env.NODE_ENV": JSON.stringify(
|
|
143
|
+
env.command === "build" ? "production" : "development",
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
// Vue's Node entry reexports CommonJS; native loading preserves its development SSR runtime.
|
|
148
|
+
if (env.command === "serve" && options.adapter === "vue")
|
|
149
|
+
config.ssr = { external: ["vue", "@vue/server-renderer"] }
|
|
150
|
+
return config
|
|
151
|
+
},
|
|
152
|
+
configEnvironment(name) {
|
|
153
|
+
return {
|
|
154
|
+
define: {
|
|
155
|
+
__NIFRA_COMPONENT_BROWSER__: JSON.stringify(name === "client"),
|
|
156
|
+
__NIFRA_COMPONENT_BUILD__: JSON.stringify(build),
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
resolveId(source) {
|
|
161
|
+
if (source === "virtual:nifra-component-build") return "\0nifra-component-build"
|
|
162
|
+
return null
|
|
163
|
+
},
|
|
164
|
+
load(id) {
|
|
165
|
+
if (id === "\0nifra-component-build")
|
|
166
|
+
return `export const componentBuild = ${JSON.stringify(build)}`
|
|
167
|
+
return null
|
|
168
|
+
},
|
|
169
|
+
transform(source, id) {
|
|
170
|
+
const file = id.split("?")[0]!
|
|
171
|
+
if (!file.startsWith("\0") && existsSync(file)) {
|
|
172
|
+
if (sources.size >= 10000 && !sources.has(file))
|
|
173
|
+
throw new Error("[nifra/components] Component source graph exceeds 10000 modules")
|
|
174
|
+
sources.set(file, createHash("sha256").update(readFileSync(file)).digest("hex"))
|
|
175
|
+
}
|
|
176
|
+
if (id.split("?")[0] === entries.ssr && this.environment.name === "ssr")
|
|
177
|
+
return {
|
|
178
|
+
code:
|
|
179
|
+
source +
|
|
180
|
+
'\nexport { componentBuild as __nifraComponentBuild } from "@nifrajs/web/server-components/build-info";\n',
|
|
181
|
+
map: null,
|
|
182
|
+
}
|
|
183
|
+
return null
|
|
184
|
+
},
|
|
185
|
+
async handleHotUpdate(context) {
|
|
186
|
+
// Recreate all environments together so no warm module retains an earlier generation.
|
|
187
|
+
context.server.ws.send({ type: "full-reload" })
|
|
188
|
+
await context.server.restart()
|
|
189
|
+
return []
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
const proof: Plugin = {
|
|
193
|
+
name: "nifra:component-build-proof",
|
|
194
|
+
generateBundle(output, bundle) {
|
|
195
|
+
const guard = this.environment.name === "client" ? client : server
|
|
196
|
+
guard.generateBundle.call(this, output, bundle)
|
|
197
|
+
if (this.environment.name !== "client")
|
|
198
|
+
for (const chunk of Object.values(bundle)) {
|
|
199
|
+
if (chunk.type === "chunk")
|
|
200
|
+
for (const specifier of componentBundleImports(chunk.code)) {
|
|
201
|
+
if (
|
|
202
|
+
unsupportedBuiltin(specifier, edge ? "browser" : target === "bun" ? "bun" : "node")
|
|
203
|
+
) {
|
|
204
|
+
const importers = [...this.getModuleIds()].filter((id) =>
|
|
205
|
+
this.getModuleInfo(id)?.importedIds.includes(specifier),
|
|
206
|
+
)
|
|
207
|
+
throw new Error(
|
|
208
|
+
`[nifra/components] ${specifier} is unsupported on ${target}; imported by ${importers.join(", ") || chunk.fileName}`,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
const builtinGuard: Plugin = {
|
|
216
|
+
name: "nifra:component-client-builtins",
|
|
217
|
+
enforce: "pre",
|
|
218
|
+
resolveId(source, importer) {
|
|
219
|
+
if (this.environment.name === "client" && source.startsWith("node:"))
|
|
220
|
+
throw new Error(
|
|
221
|
+
`[nifra/components] ${source} may not reach the browser; imported by ${importer ?? "the client entry"}`,
|
|
222
|
+
)
|
|
223
|
+
return null
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
const plugins: UserConfig["plugins"] = [
|
|
227
|
+
compiler,
|
|
228
|
+
generation,
|
|
229
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared compiler guards implement Vite hooks; the shim differs only in optional hook metadata.
|
|
230
|
+
viteServerFnStub() as Plugin,
|
|
231
|
+
builtinGuard,
|
|
232
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared compiler guards implement Vite hooks; the shim differs only in optional hook metadata.
|
|
233
|
+
viteBareBuiltinExternal() as Plugin,
|
|
234
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared compiler guards implement Vite hooks; the shim differs only in optional hook metadata.
|
|
235
|
+
viteAssetUrlGuard(guards) as Plugin,
|
|
236
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The shared compiler guards implement Vite hooks; the shim differs only in optional hook metadata.
|
|
237
|
+
viteZoneGuard(guards) as Plugin,
|
|
238
|
+
...(options.plugins ?? []),
|
|
239
|
+
proof,
|
|
240
|
+
]
|
|
241
|
+
if (options.adapter === "react") {
|
|
242
|
+
if (entries.rsc === undefined) throw new Error("[nifra/components] React requires an RSC entry")
|
|
243
|
+
const { default: rsc } = await import("@vitejs/plugin-rsc")
|
|
244
|
+
plugins.push(rsc({ entries: { client: entries.client!, ssr: entries.ssr!, rsc: entries.rsc } }))
|
|
245
|
+
} else if (entries.rsc !== undefined)
|
|
246
|
+
throw new Error("[nifra/components] Only React uses an RSC environment")
|
|
247
|
+
const serverConditions = edge
|
|
248
|
+
? ["module", "workerd", "edge-light", "node", "development|production"]
|
|
249
|
+
: ["module", "node", "development|production"]
|
|
250
|
+
const config: UserConfig = {
|
|
251
|
+
root,
|
|
252
|
+
plugins,
|
|
253
|
+
publicDir: false,
|
|
254
|
+
cacheDir: resolve(root, ".nifra/cache/components-vite"),
|
|
255
|
+
// Without it Vite inlines every `VITE_*` value from the app's .env files into client code.
|
|
256
|
+
envPrefix: vitePublicEnvPrefix(options.publicEnvPrefix),
|
|
257
|
+
define: {
|
|
258
|
+
...publicEnvDefines(options.publicEnvPrefix ?? "PUBLIC_", process.env),
|
|
259
|
+
"process.env.NIFRA_SSR_BUNDLED": '"1"',
|
|
260
|
+
"globalThis.__NIFRA_EDGE_RUNTIME__": edge ? "true" : "false",
|
|
261
|
+
__NIFRA_COMPONENT_BUILD__: JSON.stringify(build),
|
|
262
|
+
},
|
|
263
|
+
resolve: {
|
|
264
|
+
dedupe: [
|
|
265
|
+
"react",
|
|
266
|
+
"react-dom",
|
|
267
|
+
"react-server-dom-webpack",
|
|
268
|
+
"vue",
|
|
269
|
+
"svelte",
|
|
270
|
+
"solid-js",
|
|
271
|
+
"preact",
|
|
272
|
+
"@nifrajs/web",
|
|
273
|
+
"@nifrajs/core",
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
ssr: {
|
|
277
|
+
noExternal: true,
|
|
278
|
+
target: edge ? "webworker" : "node",
|
|
279
|
+
resolve: { conditions: serverConditions },
|
|
280
|
+
},
|
|
281
|
+
...(options.adapter === "preact"
|
|
282
|
+
? { oxc: { jsx: { runtime: "automatic", importSource: "preact" } } }
|
|
283
|
+
: { esbuild: { jsx: "automatic" } }),
|
|
284
|
+
build: { outDir: out, sourcemap: false, manifest: true, minify: true },
|
|
285
|
+
environments: {
|
|
286
|
+
client: {
|
|
287
|
+
consumer: "client",
|
|
288
|
+
build: { outDir: resolve(out, "client"), manifest: true, emptyOutDir: true },
|
|
289
|
+
},
|
|
290
|
+
ssr: {
|
|
291
|
+
consumer: "server",
|
|
292
|
+
build: {
|
|
293
|
+
outDir: resolve(out, options.adapter === "react" ? "ssr" : "server"),
|
|
294
|
+
emptyOutDir: true,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
server: { host: "127.0.0.1", port: options.port ?? 3000, strictPort: true },
|
|
299
|
+
}
|
|
300
|
+
// These environments were constructed above; only the compiler's selected entry lane adds inputs.
|
|
301
|
+
const environments = config.environments!
|
|
302
|
+
if (edge) environments.ssr!.resolve = { conditions: serverConditions }
|
|
303
|
+
// Rune modules need the native TS transform before Svelte compiles them; prebundling skips it.
|
|
304
|
+
if (options.adapter === "svelte") config.optimizeDeps = { exclude: ["@nifrajs/web-svelte"] }
|
|
305
|
+
if (options.adapter === "react") {
|
|
306
|
+
environments.rsc = { build: { outDir: resolve(out, "rsc"), emptyOutDir: true } }
|
|
307
|
+
} else {
|
|
308
|
+
environments.client!.build!.rollupOptions = { input: entries.client! }
|
|
309
|
+
Object.assign(environments.ssr!.build!, {
|
|
310
|
+
ssr: entries.ssr!,
|
|
311
|
+
rollupOptions: { output: { entryFileNames: "index.js" } },
|
|
312
|
+
})
|
|
313
|
+
config.builder = {
|
|
314
|
+
async buildApp(builder) {
|
|
315
|
+
await builder.build(builder.environments.client!)
|
|
316
|
+
await builder.build(builder.environments.ssr!)
|
|
317
|
+
},
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
sourceProofs.set(config, sources)
|
|
321
|
+
return config
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function buildServerComponents(
|
|
325
|
+
options: ComponentPipelineOptions,
|
|
326
|
+
): Promise<ComponentBuildResult> {
|
|
327
|
+
const out = resolve(options.root, options.outDir ?? "dist")
|
|
328
|
+
const root = resolve(options.root)
|
|
329
|
+
assertDisposableDir("outDir", out, [
|
|
330
|
+
root,
|
|
331
|
+
process.cwd(),
|
|
332
|
+
...Object.values(options.entries).map((file) => resolve(root, file)),
|
|
333
|
+
])
|
|
334
|
+
const parent = resolve(out, "..")
|
|
335
|
+
const { mkdir } = await import("node:fs/promises")
|
|
336
|
+
await mkdir(parent, { recursive: true })
|
|
337
|
+
const stage = await mkdtemp(resolve(parent, ".nifra-components-"))
|
|
338
|
+
const backup = `${stage}-previous`
|
|
339
|
+
let previous = false
|
|
340
|
+
try {
|
|
341
|
+
const buildId = componentBuildId(options.root, options.outDir)
|
|
342
|
+
const compilerId = compilerGeneration()
|
|
343
|
+
let config = await serverComponentsConfig({ ...options, outDir: stage }, buildId)
|
|
344
|
+
await (await createBuilder({ ...config, configFile: false })).buildApp()
|
|
345
|
+
const snapshot = (value: UserConfig): ReadonlyMap<string, string> => {
|
|
346
|
+
const sources = sourceProofs.get(value)
|
|
347
|
+
if (sources === undefined) throw new Error("[nifra/components] Missing generation proof")
|
|
348
|
+
return sources
|
|
349
|
+
}
|
|
350
|
+
const graphId = (modules: ReadonlyMap<string, string>): string => {
|
|
351
|
+
const digest = createHash("sha256")
|
|
352
|
+
.update(buildId)
|
|
353
|
+
.update(compilerId)
|
|
354
|
+
.update(
|
|
355
|
+
JSON.stringify({
|
|
356
|
+
adapter: options.adapter,
|
|
357
|
+
target: options.target ?? "bun",
|
|
358
|
+
env: publicEnvDefines(options.publicEnvPrefix ?? "PUBLIC_", process.env),
|
|
359
|
+
}),
|
|
360
|
+
)
|
|
361
|
+
for (const [file, hash] of [...modules].sort(([a], [b]) => codeUnitOrder(a, b)))
|
|
362
|
+
digest.update(relative(options.root, file).replaceAll("\\", "/")).update("\0").update(hash)
|
|
363
|
+
return `c_${digest.digest("hex")}`
|
|
364
|
+
}
|
|
365
|
+
const modules = snapshot(config)
|
|
366
|
+
const generationId = graphId(modules)
|
|
367
|
+
// The analysis output is never published. Compile again with the complete dependency graph's generation.
|
|
368
|
+
config = await serverComponentsConfig({ ...options, outDir: stage }, generationId)
|
|
369
|
+
await (await createBuilder({ ...config, configFile: false })).buildApp()
|
|
370
|
+
if (
|
|
371
|
+
graphId(snapshot(config)) !== generationId ||
|
|
372
|
+
[...modules].some(
|
|
373
|
+
([file, hash]) => createHash("sha256").update(readFileSync(file)).digest("hex") !== hash,
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
throw new Error("[nifra/components] Sources changed during the coordinated build")
|
|
377
|
+
if (buildId !== componentBuildId(options.root, options.outDir))
|
|
378
|
+
throw new Error(
|
|
379
|
+
"[nifra/components] Sources changed during the build; rebuild one complete generation",
|
|
380
|
+
)
|
|
381
|
+
if (compilerId !== compilerGeneration())
|
|
382
|
+
throw new Error(
|
|
383
|
+
"[nifra/components] Compiler changed during the build; rebuild one complete generation",
|
|
384
|
+
)
|
|
385
|
+
const result = await assembleComponentTarget({ ...options, outDir: stage }, generationId)
|
|
386
|
+
if (existsSync(out)) {
|
|
387
|
+
await rename(out, backup)
|
|
388
|
+
previous = true
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
await rename(stage, out)
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (previous) await rename(backup, out)
|
|
394
|
+
throw error
|
|
395
|
+
}
|
|
396
|
+
if (previous) await rm(backup, { recursive: true, force: true })
|
|
397
|
+
return { ...result, outDir: out }
|
|
398
|
+
} catch (error) {
|
|
399
|
+
await rm(stage, { recursive: true, force: true })
|
|
400
|
+
throw error
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
export async function devServerComponents(
|
|
404
|
+
options: ComponentPipelineOptions,
|
|
405
|
+
): Promise<Awaited<ReturnType<typeof createServer>>> {
|
|
406
|
+
const config = await serverComponentsConfig(options)
|
|
407
|
+
config.plugins = [
|
|
408
|
+
...(config.plugins ?? []),
|
|
409
|
+
{
|
|
410
|
+
name: "nifra:component-dev-handler",
|
|
411
|
+
configureServer(server) {
|
|
412
|
+
installComponentDevHandler(server, options)
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
]
|
|
416
|
+
const server = await createServer({ ...config, configFile: false })
|
|
417
|
+
await server.listen()
|
|
418
|
+
return server
|
|
419
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type * as TS from "typescript"
|
|
2
|
+
import { ts } from "./typescript.ts"
|
|
3
|
+
|
|
4
|
+
/** RSC rewrites chunks after linking; inspect emitted imports rather than stale chunk metadata. */
|
|
5
|
+
export function componentBundleImports(code: string): readonly string[] {
|
|
6
|
+
const imports = new Set<string>()
|
|
7
|
+
const source = ts.createSourceFile(
|
|
8
|
+
"component-bundle.js",
|
|
9
|
+
code,
|
|
10
|
+
ts.ScriptTarget.Latest,
|
|
11
|
+
true,
|
|
12
|
+
ts.ScriptKind.JS,
|
|
13
|
+
)
|
|
14
|
+
const visit = (node: TS.Node): void => {
|
|
15
|
+
if (
|
|
16
|
+
(ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
|
|
17
|
+
node.moduleSpecifier !== undefined &&
|
|
18
|
+
ts.isStringLiteral(node.moduleSpecifier)
|
|
19
|
+
)
|
|
20
|
+
imports.add(node.moduleSpecifier.text)
|
|
21
|
+
if (
|
|
22
|
+
ts.isCallExpression(node) &&
|
|
23
|
+
(node.expression.kind === ts.SyntaxKind.ImportKeyword ||
|
|
24
|
+
(ts.isIdentifier(node.expression) && node.expression.text === "require"))
|
|
25
|
+
) {
|
|
26
|
+
const argument = node.arguments[0]
|
|
27
|
+
if (argument !== undefined && ts.isStringLiteralLike(argument)) imports.add(argument.text)
|
|
28
|
+
}
|
|
29
|
+
ts.forEachChild(node, visit)
|
|
30
|
+
}
|
|
31
|
+
visit(source)
|
|
32
|
+
return [...imports]
|
|
33
|
+
}
|