@nifrajs/web 3.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend-only.d.ts +15 -0
- package/dist/backend-only.d.ts.map +1 -0
- package/dist/backend-only.js +16 -0
- package/dist/backend-only.js.map +1 -0
- package/dist/boundary.d.ts +5 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +11 -2
- package/dist/boundary.js.map +1 -1
- package/dist/build-plan.d.ts +35 -6
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +89 -35
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +18 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +97 -17
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +72 -23
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +411 -98
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +1 -1
- package/dist/bun-dev-entry.js +1 -1
- package/dist/cdn.d.ts +74 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +132 -0
- package/dist/cdn.js.map +1 -0
- package/dist/client.d.ts +21 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +226 -15
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +4 -3
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +12 -3
- package/dist/conformance.js.map +1 -1
- package/dist/csp.d.ts +93 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +122 -0
- package/dist/csp.js.map +1 -0
- package/dist/css-contract.d.ts +21 -0
- package/dist/css-contract.d.ts.map +1 -0
- package/dist/css-contract.js +28 -0
- package/dist/css-contract.js.map +1 -0
- package/dist/dev-client.d.ts +76 -0
- package/dist/dev-client.d.ts.map +1 -0
- package/dist/dev-client.js +280 -0
- package/dist/dev-client.js.map +1 -0
- package/dist/dev-diagnostics.d.ts +26 -2
- package/dist/dev-diagnostics.d.ts.map +1 -1
- package/dist/dev-diagnostics.js +38 -7
- package/dist/dev-diagnostics.js.map +1 -1
- package/dist/dev-error.d.ts +4 -13
- package/dist/dev-error.d.ts.map +1 -1
- package/dist/dev-error.js +46 -4
- package/dist/dev-error.js.map +1 -1
- package/dist/dev-feed.d.ts +298 -0
- package/dist/dev-feed.d.ts.map +1 -0
- package/dist/dev-feed.js +797 -0
- package/dist/dev-feed.js.map +1 -0
- package/dist/dev-indicator.d.ts +38 -0
- package/dist/dev-indicator.d.ts.map +1 -0
- package/dist/dev-indicator.js +93 -0
- package/dist/dev-indicator.js.map +1 -0
- package/dist/dev-session.d.ts +77 -0
- package/dist/dev-session.d.ts.map +1 -0
- package/dist/dev-session.js +580 -0
- package/dist/dev-session.js.map +1 -0
- package/dist/dev-sourcemap.d.ts +27 -0
- package/dist/dev-sourcemap.d.ts.map +1 -0
- package/dist/dev-sourcemap.js +235 -0
- package/dist/dev-sourcemap.js.map +1 -0
- package/dist/dev-ssr-graph.d.ts.map +1 -1
- package/dist/dev-ssr-graph.js +13 -5
- package/dist/dev-ssr-graph.js.map +1 -1
- package/dist/dev.d.ts +15 -2
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +69 -44
- package/dist/dev.js.map +1 -1
- package/dist/diagnostic-catalog.d.ts +35 -0
- package/dist/diagnostic-catalog.d.ts.map +1 -0
- package/dist/diagnostic-catalog.js +203 -0
- package/dist/diagnostic-catalog.js.map +1 -0
- package/dist/diagnostic-prompt.d.ts +49 -0
- package/dist/diagnostic-prompt.d.ts.map +1 -0
- package/dist/diagnostic-prompt.js +303 -0
- package/dist/diagnostic-prompt.js.map +1 -0
- package/dist/diagnostic.d.ts +17 -23
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +31 -39
- package/dist/diagnostic.js.map +1 -1
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +4 -1
- package/dist/draft.js.map +1 -1
- package/dist/fn.d.ts +5 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +13 -3
- package/dist/fn.js.map +1 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +4 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +15 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cdn/cloudflare.d.ts +48 -0
- package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
- package/dist/internal/cdn/cloudflare.js +94 -0
- package/dist/internal/cdn/cloudflare.js.map +1 -0
- package/dist/internal/cdn/core.d.ts +91 -0
- package/dist/internal/cdn/core.d.ts.map +1 -0
- package/dist/internal/cdn/core.js +220 -0
- package/dist/internal/cdn/core.js.map +1 -0
- package/dist/internal/cdn/fastly.d.ts +17 -0
- package/dist/internal/cdn/fastly.d.ts.map +1 -0
- package/dist/internal/cdn/fastly.js +43 -0
- package/dist/internal/cdn/fastly.js.map +1 -0
- package/dist/internal/cdn/vercel.d.ts +31 -0
- package/dist/internal/cdn/vercel.d.ts.map +1 -0
- package/dist/internal/cdn/vercel.js +56 -0
- package/dist/internal/cdn/vercel.js.map +1 -0
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/codegen.d.ts +3 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +151 -30
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/dev-csp.d.ts +24 -0
- package/dist/internal/dev-csp.d.ts.map +1 -0
- package/dist/internal/dev-csp.js +160 -0
- package/dist/internal/dev-csp.js.map +1 -0
- package/dist/internal/dev-reserved.d.ts +25 -0
- package/dist/internal/dev-reserved.d.ts.map +1 -0
- package/dist/internal/dev-reserved.js +39 -0
- package/dist/internal/dev-reserved.js.map +1 -0
- package/dist/internal/html-spans.d.ts +10 -0
- package/dist/internal/html-spans.d.ts.map +1 -0
- package/dist/internal/html-spans.js +46 -0
- package/dist/internal/html-spans.js.map +1 -0
- package/dist/internal/loading-runtime.d.ts +44 -0
- package/dist/internal/loading-runtime.d.ts.map +1 -0
- package/dist/internal/loading-runtime.js +206 -0
- package/dist/internal/loading-runtime.js.map +1 -0
- package/dist/internal/matches-runtime.d.ts +8 -0
- package/dist/internal/matches-runtime.d.ts.map +1 -0
- package/dist/internal/matches-runtime.js +61 -0
- package/dist/internal/matches-runtime.js.map +1 -0
- package/dist/internal/mount-shadow.d.ts +36 -0
- package/dist/internal/mount-shadow.d.ts.map +1 -0
- package/dist/internal/mount-shadow.js +65 -0
- package/dist/internal/mount-shadow.js.map +1 -0
- package/dist/internal/node-builtins.d.ts +3 -0
- package/dist/internal/node-builtins.d.ts.map +1 -0
- package/dist/internal/node-builtins.js +79 -0
- package/dist/internal/node-builtins.js.map +1 -0
- package/dist/internal/output-guard.d.ts +65 -0
- package/dist/internal/output-guard.d.ts.map +1 -0
- package/dist/internal/output-guard.js +630 -0
- package/dist/internal/output-guard.js.map +1 -0
- package/dist/internal/page-execution.d.ts +69 -0
- package/dist/internal/page-execution.d.ts.map +1 -0
- package/dist/internal/page-execution.js +1021 -0
- package/dist/internal/page-execution.js.map +1 -0
- package/dist/internal/parity.d.ts +21 -0
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +104 -22
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +12 -0
- package/dist/internal/private-env.d.ts.map +1 -0
- package/dist/internal/private-env.js +335 -0
- package/dist/internal/private-env.js.map +1 -0
- package/dist/internal/public-url.d.ts +3 -0
- package/dist/internal/public-url.d.ts.map +1 -0
- package/dist/internal/public-url.js +18 -0
- package/dist/internal/public-url.js.map +1 -0
- package/dist/internal/render-document.d.ts +37 -21
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +104 -65
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/request-url.d.ts +3 -0
- package/dist/internal/request-url.d.ts.map +1 -0
- package/dist/internal/request-url.js +14 -0
- package/dist/internal/request-url.js.map +1 -0
- package/dist/internal/response-controls.d.ts +75 -0
- package/dist/internal/response-controls.d.ts.map +1 -0
- package/dist/internal/response-controls.js +256 -0
- package/dist/internal/response-controls.js.map +1 -0
- package/dist/internal/runtime-contract.d.ts +11 -16
- package/dist/internal/runtime-contract.d.ts.map +1 -1
- package/dist/internal/runtime-contract.js.map +1 -1
- package/dist/internal/secret-scan.d.ts +104 -0
- package/dist/internal/secret-scan.d.ts.map +1 -0
- package/dist/internal/secret-scan.js +485 -0
- package/dist/internal/secret-scan.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +6 -29
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +6 -66
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/source-map.d.ts +47 -0
- package/dist/internal/source-map.d.ts.map +1 -0
- package/dist/internal/source-map.js +225 -0
- package/dist/internal/source-map.js.map +1 -0
- package/dist/internal/target-compat.d.ts +27 -0
- package/dist/internal/target-compat.d.ts.map +1 -0
- package/dist/internal/target-compat.js +77 -0
- package/dist/internal/target-compat.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +6 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -1
- package/dist/internal/timing-safe-equal.js +10 -0
- package/dist/internal/timing-safe-equal.js.map +1 -1
- package/dist/internal/vitals-runtime.d.ts +18 -0
- package/dist/internal/vitals-runtime.d.ts.map +1 -0
- package/dist/internal/vitals-runtime.js +38 -0
- package/dist/internal/vitals-runtime.js.map +1 -0
- package/dist/internal/web-app.d.ts +56 -8
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +215 -772
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/zone-graph.d.ts +89 -0
- package/dist/internal/zone-graph.d.ts.map +1 -0
- package/dist/internal/zone-graph.js +492 -0
- package/dist/internal/zone-graph.js.map +1 -0
- package/dist/isr.d.ts +83 -6
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +301 -39
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +3 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +37 -9
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +221 -14
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +386 -9
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +26 -5
- package/dist/module-graph.d.ts.map +1 -1
- package/dist/module-graph.js +17 -7
- package/dist/module-graph.js.map +1 -1
- package/dist/navigation.d.ts +17 -3
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +11 -5
- package/dist/navigation.js.map +1 -1
- package/dist/nonce.d.ts +47 -0
- package/dist/nonce.d.ts.map +1 -0
- package/dist/nonce.js +63 -0
- package/dist/nonce.js.map +1 -0
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +4 -3
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +9 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +18 -0
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/svg.d.ts +22 -3
- package/dist/plugins/svg.d.ts.map +1 -1
- package/dist/plugins/svg.js +112 -1
- package/dist/plugins/svg.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +107 -3
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +378 -17
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts +61 -0
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
- package/dist/plugins/vite-zone-guard.js +208 -0
- package/dist/plugins/vite-zone-guard.js.map +1 -0
- package/dist/plugins/zone-guard.d.ts +27 -0
- package/dist/plugins/zone-guard.d.ts.map +1 -0
- package/dist/plugins/zone-guard.js +72 -0
- package/dist/plugins/zone-guard.js.map +1 -0
- package/dist/prerender.d.ts +10 -0
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +35 -3
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +9 -0
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +131 -48
- package/dist/public-dir.js.map +1 -1
- package/dist/pwa-manifest.d.ts +68 -0
- package/dist/pwa-manifest.d.ts.map +1 -0
- package/dist/pwa-manifest.js +178 -0
- package/dist/pwa-manifest.js.map +1 -0
- package/dist/query.js +4 -4
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +42 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +6 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-manifest.d.ts +3 -0
- package/dist/route-manifest.d.ts.map +1 -1
- package/dist/route-manifest.js +26 -3
- package/dist/route-manifest.js.map +1 -1
- package/dist/route-types.d.ts +24 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +195 -0
- package/dist/route-types.js.map +1 -0
- package/dist/router.d.ts +28 -6
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +129 -40
- package/dist/router.js.map +1 -1
- package/dist/trusted-html.d.ts +34 -0
- package/dist/trusted-html.d.ts.map +1 -0
- package/dist/trusted-html.js +35 -0
- package/dist/trusted-html.js.map +1 -0
- package/dist/vitals.d.ts +33 -0
- package/dist/vitals.d.ts.map +1 -0
- package/dist/vitals.js +16 -0
- package/dist/vitals.js.map +1 -0
- package/dist/vite.d.ts +16 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +187 -36
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +70 -0
- package/dist/zones.d.ts.map +1 -0
- package/dist/zones.js +365 -0
- package/dist/zones.js.map +1 -0
- package/package.json +67 -14
- package/src/backend-only.ts +16 -0
- package/src/boundary.ts +18 -5
- package/src/build-plan.ts +113 -36
- package/src/build-vite.ts +124 -15
- package/src/build.ts +535 -102
- package/src/bun-dev-entry.ts +1 -1
- package/src/cdn.ts +216 -0
- package/src/client.ts +251 -18
- package/src/conformance.ts +16 -3
- package/src/csp.ts +194 -0
- package/src/css-contract.ts +44 -0
- package/src/dev-client.ts +349 -0
- package/src/dev-diagnostics.ts +60 -7
- package/src/dev-error.ts +49 -5
- package/src/dev-feed.ts +1140 -0
- package/src/dev-indicator.ts +113 -0
- package/src/dev-session.ts +729 -0
- package/src/dev-sourcemap.ts +281 -0
- package/src/dev-ssr-graph.ts +16 -5
- package/src/dev.ts +86 -43
- package/src/diagnostic-catalog.ts +243 -0
- package/src/diagnostic-prompt.ts +355 -0
- package/src/diagnostic.ts +63 -66
- package/src/draft.ts +4 -1
- package/src/fn.ts +19 -3
- package/src/fs.ts +13 -2
- package/src/index.ts +57 -10
- package/src/internal/cdn/cloudflare.ts +146 -0
- package/src/internal/cdn/core.ts +316 -0
- package/src/internal/cdn/fastly.ts +62 -0
- package/src/internal/cdn/vercel.ts +95 -0
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/codegen.ts +180 -29
- package/src/internal/dev-csp.ts +176 -0
- package/src/internal/dev-reserved.ts +52 -0
- package/src/internal/html-spans.ts +50 -0
- package/src/internal/loading-runtime.ts +244 -0
- package/src/internal/matches-runtime.ts +62 -0
- package/src/internal/mount-shadow.ts +88 -0
- package/src/internal/node-builtins.ts +79 -0
- package/src/internal/output-guard.ts +701 -0
- package/src/internal/page-execution.ts +1371 -0
- package/src/internal/parity.ts +150 -36
- package/src/internal/private-env.ts +334 -0
- package/src/internal/public-url.ts +22 -0
- package/src/internal/render-document.ts +153 -73
- package/src/internal/request-url.ts +14 -0
- package/src/internal/response-controls.ts +284 -0
- package/src/internal/runtime-contract.ts +10 -15
- package/src/internal/secret-scan.ts +623 -0
- package/src/internal/server-boundary.ts +6 -69
- package/src/internal/source-map.ts +256 -0
- package/src/internal/target-compat.ts +92 -0
- package/src/internal/timing-safe-equal.ts +13 -0
- package/src/internal/vitals-runtime.ts +53 -0
- package/src/internal/web-app.ts +290 -879
- package/src/internal/zone-graph.ts +593 -0
- package/src/isr.ts +366 -39
- package/src/llms-txt.ts +44 -8
- package/src/manifest.ts +671 -17
- package/src/module-graph.ts +53 -9
- package/src/navigation.ts +29 -6
- package/src/nonce.ts +104 -0
- package/src/plugins/css-modules.ts +5 -3
- package/src/plugins/kit.ts +24 -0
- package/src/plugins/svg.ts +135 -4
- package/src/plugins/vite-leak-guard.ts +529 -16
- package/src/plugins/vite-zone-guard.ts +263 -0
- package/src/plugins/zone-guard.ts +110 -0
- package/src/prerender.ts +49 -3
- package/src/public-dir.ts +135 -49
- package/src/pwa-manifest.ts +226 -0
- package/src/query.ts +4 -4
- package/src/render-seam.ts +46 -1
- package/src/route-manifest.ts +15 -3
- package/src/route-types.ts +264 -0
- package/src/router.ts +163 -63
- package/src/trusted-html.ts +44 -0
- package/src/vitals.ts +44 -0
- package/src/vite.ts +207 -36
- package/src/zones.ts +430 -0
- package/dist/plugins/vite-server-only.d.ts +0 -49
- package/dist/plugins/vite-server-only.d.ts.map +0 -1
- package/dist/plugins/vite-server-only.js +0 -57
- package/dist/plugins/vite-server-only.js.map +0 -1
- package/dist/server-only.d.ts +0 -21
- package/dist/server-only.d.ts.map +0 -1
- package/dist/server-only.js +0 -25
- package/dist/server-only.js.map +0 -1
- package/src/plugins/vite-server-only.ts +0 -75
- package/src/server-only.ts +0 -25
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a browser stack trace back to source files, using the source maps the dev server itself
|
|
3
|
+
* serves (Bun links `/_bun/client/<gen>.js.map`; Vite inlines a data URL per module).
|
|
4
|
+
*
|
|
5
|
+
* Every fetch goes to the dev server's own origin, whatever host a frame names, so a crafted frame
|
|
6
|
+
* cannot make the server request anything else. Browser stacks in any engine's format come out as
|
|
7
|
+
* V8 `at` frames, which is what {@link parseFrames} reads.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isAbsolute, resolve } from "node:path"
|
|
11
|
+
import { fileURLToPath } from "node:url"
|
|
12
|
+
import {
|
|
13
|
+
decodeMappings,
|
|
14
|
+
inlineSourceMap,
|
|
15
|
+
lookupPosition,
|
|
16
|
+
type MappingLine,
|
|
17
|
+
originalPosition,
|
|
18
|
+
type RawSourceMap,
|
|
19
|
+
} from "./internal/source-map.ts"
|
|
20
|
+
|
|
21
|
+
export interface SourceMapperOptions {
|
|
22
|
+
readonly root: string
|
|
23
|
+
/** The dev server's own origin (`http://127.0.0.1:<port>`), or undefined before it listens. */
|
|
24
|
+
readonly origin: () => string | undefined
|
|
25
|
+
readonly fetch?: (url: string, init: { signal: AbortSignal }) => Promise<Response>
|
|
26
|
+
/** Decoded maps kept, most recently used first. */
|
|
27
|
+
readonly maxMaps?: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SourceMapper {
|
|
31
|
+
/** `stack` with each frame of a script this server served pointed at its source file. */
|
|
32
|
+
mapStack(stack: string): Promise<string>
|
|
33
|
+
/** Forget every map: the code they describe changed. */
|
|
34
|
+
clear(): void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface Frame {
|
|
38
|
+
readonly name: string | undefined
|
|
39
|
+
readonly url: string
|
|
40
|
+
readonly line: number
|
|
41
|
+
readonly column: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// V8: ` at fn (url:1:2)` / ` at url:1:2` / ` at async fn (url:1:2)`.
|
|
45
|
+
const V8_FRAME = /^\s*at\s+(?:(.*?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/
|
|
46
|
+
// SpiderMonkey / JavaScriptCore: `fn@url:1:2` / `@url:1:2`.
|
|
47
|
+
const AT_FRAME = /^\s*(.*?)@(.+?):(\d+):(\d+)\s*$/
|
|
48
|
+
const MAX_FRAMES = 50
|
|
49
|
+
const SCRIPT_PATH = /\.(?:[cm]?[jt]sx?|vue|svelte|astro|mdx)$/i
|
|
50
|
+
const MAX_SCRIPT_BYTES = 64 * 1024 * 1024
|
|
51
|
+
const FETCH_TIMEOUT_MS = 5_000
|
|
52
|
+
|
|
53
|
+
function parseFrame(line: string): Frame | undefined {
|
|
54
|
+
const match = V8_FRAME.exec(line) ?? AT_FRAME.exec(line)
|
|
55
|
+
if (match === null) return undefined
|
|
56
|
+
const [, name, url = "", lineText = "", columnText = ""] = match
|
|
57
|
+
return {
|
|
58
|
+
name: name === undefined || name === "" ? undefined : name.replace(/^async\s+/, ""),
|
|
59
|
+
url,
|
|
60
|
+
line: Number(lineText),
|
|
61
|
+
column: Number(columnText),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------------------------------
|
|
66
|
+
// Lookup
|
|
67
|
+
// ---------------------------------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
export { decodeMappings } from "./internal/source-map.ts"
|
|
70
|
+
|
|
71
|
+
interface DecodedMap {
|
|
72
|
+
readonly sources: readonly string[]
|
|
73
|
+
readonly lines: readonly (MappingLine | undefined)[]
|
|
74
|
+
readonly sourcesContent: readonly unknown[]
|
|
75
|
+
/** Per source, the map its content carries inline (a plugin's compile map), read on first use. */
|
|
76
|
+
readonly inner: Map<number, RawSourceMap | undefined>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The source position for a 0-based generated line/column. When the source is a plugin's output that
|
|
81
|
+
* carries its own map inline (a compiled `.svelte` or `.vue` file), that map is followed too: Bun's
|
|
82
|
+
* bundler keeps the plugin's output as the source, not the file it compiled.
|
|
83
|
+
*/
|
|
84
|
+
function lookup(
|
|
85
|
+
map: DecodedMap,
|
|
86
|
+
line: number,
|
|
87
|
+
column: number,
|
|
88
|
+
): { source: string; line: number; column: number } | undefined {
|
|
89
|
+
const found = lookupPosition(map.lines, line, column)
|
|
90
|
+
if (found === undefined) return undefined
|
|
91
|
+
const source = map.sources[found.source]
|
|
92
|
+
if (source === undefined) return undefined
|
|
93
|
+
if (!map.inner.has(found.source)) {
|
|
94
|
+
const content = map.sourcesContent[found.source]
|
|
95
|
+
map.inner.set(found.source, typeof content === "string" ? inlineSourceMap(content) : undefined)
|
|
96
|
+
}
|
|
97
|
+
const inner = map.inner.get(found.source)
|
|
98
|
+
const original =
|
|
99
|
+
inner === undefined ? undefined : originalPosition(inner, found.line, found.column)
|
|
100
|
+
return original === undefined
|
|
101
|
+
? { source, line: found.line, column: found.column }
|
|
102
|
+
: { source, line: original.line, column: original.column }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------------------------------
|
|
106
|
+
// Fetching
|
|
107
|
+
// ---------------------------------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
110
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const SOURCE_MAPPING_URL = /[#@]\s*sourceMappingURL=([^\s'"]+)\s*$/
|
|
114
|
+
|
|
115
|
+
function sourceMappingUrl(script: string): string | undefined {
|
|
116
|
+
// The comment is the last one in the file; a data URL can be megabytes, so look from the end.
|
|
117
|
+
const at = script.lastIndexOf("sourceMappingURL=")
|
|
118
|
+
if (at === -1) return undefined
|
|
119
|
+
const lineStart = script.lastIndexOf("\n", at) + 1
|
|
120
|
+
return SOURCE_MAPPING_URL.exec(script.slice(lineStart))?.[1]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function decodeDataUrl(url: string): string | undefined {
|
|
124
|
+
const comma = url.indexOf(",")
|
|
125
|
+
if (comma === -1) return undefined
|
|
126
|
+
const meta = url.slice(5, comma)
|
|
127
|
+
const payload = url.slice(comma + 1)
|
|
128
|
+
return meta.endsWith(";base64")
|
|
129
|
+
? Buffer.from(payload, "base64").toString("utf8")
|
|
130
|
+
: decodeURIComponent(payload)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The directory a served script lives in on disk. Vite serves files outside the root as
|
|
135
|
+
* `/@fs/<absolute path>`; everything else maps onto the project root.
|
|
136
|
+
*/
|
|
137
|
+
function scriptDirectory(scriptPath: string, root: string): string {
|
|
138
|
+
const dir = decodeURIComponent(scriptPath.replace(/[^/]*$/, ""))
|
|
139
|
+
// `/@fs/C:/x/` names `C:/x/` on Windows: the slash before a drive letter is URL syntax only.
|
|
140
|
+
return dir.startsWith("/@fs/")
|
|
141
|
+
? dir.slice("/@fs".length).replace(/^\/([a-zA-Z]:)/, "$1")
|
|
142
|
+
: resolve(root, `.${dir}`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** The filesystem path a map's source names, when it is a file. */
|
|
146
|
+
function sourcePath(source: string, sourceRoot: string, scriptPath: string, root: string): string {
|
|
147
|
+
const joined = sourceRoot === "" ? source : `${sourceRoot.replace(/\/?$/, "/")}${source}`
|
|
148
|
+
if (joined.startsWith("file://")) {
|
|
149
|
+
try {
|
|
150
|
+
return fileURLToPath(joined)
|
|
151
|
+
} catch {
|
|
152
|
+
return joined
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(joined)) return joined
|
|
156
|
+
if (isAbsolute(joined)) return joined
|
|
157
|
+
// Relative to the script's own location, as a map's sources always are.
|
|
158
|
+
return resolve(scriptDirectory(scriptPath, root), joined)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createSourceMapper(options: SourceMapperOptions): SourceMapper {
|
|
162
|
+
const root = resolve(options.root)
|
|
163
|
+
const fetchImpl = options.fetch ?? ((url, init) => fetch(url, init))
|
|
164
|
+
const maxMaps = options.maxMaps ?? 8
|
|
165
|
+
const maps = new Map<string, Promise<DecodedMap | undefined>>()
|
|
166
|
+
|
|
167
|
+
const fetchText = async (url: string): Promise<string | undefined> => {
|
|
168
|
+
const response = await fetchImpl(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) })
|
|
169
|
+
if (!response.ok) return undefined
|
|
170
|
+
const length = Number(response.headers.get("content-length") ?? "0")
|
|
171
|
+
if (length > MAX_SCRIPT_BYTES) return undefined
|
|
172
|
+
const text = await response.text()
|
|
173
|
+
return text.length > MAX_SCRIPT_BYTES ? undefined : text
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const load = async (origin: string, pathAndQuery: string): Promise<DecodedMap | undefined> => {
|
|
177
|
+
const scriptUrl = new URL(pathAndQuery, origin)
|
|
178
|
+
const script = await fetchText(scriptUrl.href)
|
|
179
|
+
if (script === undefined) return undefined
|
|
180
|
+
const reference = sourceMappingUrl(script)
|
|
181
|
+
if (reference === undefined) return undefined
|
|
182
|
+
let text: string | undefined
|
|
183
|
+
if (reference.startsWith("data:")) text = decodeDataUrl(reference)
|
|
184
|
+
else {
|
|
185
|
+
const mapUrl = new URL(reference, scriptUrl)
|
|
186
|
+
// Same server only: the map's URL comes from a script body, and a body is data.
|
|
187
|
+
if (mapUrl.origin !== scriptUrl.origin) return undefined
|
|
188
|
+
text = await fetchText(mapUrl.href)
|
|
189
|
+
}
|
|
190
|
+
if (text === undefined) return undefined
|
|
191
|
+
const raw: unknown = JSON.parse(text)
|
|
192
|
+
if (!isRecord(raw) || typeof raw.mappings !== "string" || !Array.isArray(raw.sources))
|
|
193
|
+
return undefined
|
|
194
|
+
const sourceRoot = typeof raw.sourceRoot === "string" ? raw.sourceRoot : ""
|
|
195
|
+
return {
|
|
196
|
+
sources: raw.sources.map((source) =>
|
|
197
|
+
typeof source === "string" ? sourcePath(source, sourceRoot, scriptUrl.pathname, root) : "",
|
|
198
|
+
),
|
|
199
|
+
lines: decodeMappings(raw.mappings),
|
|
200
|
+
sourcesContent: Array.isArray(raw.sourcesContent) ? raw.sourcesContent : [],
|
|
201
|
+
inner: new Map(),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const mapFor = (origin: string, pathAndQuery: string): Promise<DecodedMap | undefined> => {
|
|
206
|
+
const cached = maps.get(pathAndQuery)
|
|
207
|
+
if (cached !== undefined) {
|
|
208
|
+
maps.delete(pathAndQuery)
|
|
209
|
+
maps.set(pathAndQuery, cached)
|
|
210
|
+
return cached
|
|
211
|
+
}
|
|
212
|
+
const loading = load(origin, pathAndQuery).catch(() => undefined)
|
|
213
|
+
maps.set(pathAndQuery, loading)
|
|
214
|
+
while (maps.size > maxMaps) {
|
|
215
|
+
const oldest = maps.keys().next().value
|
|
216
|
+
if (oldest === undefined) break
|
|
217
|
+
maps.delete(oldest)
|
|
218
|
+
}
|
|
219
|
+
return loading
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The script's path on this server, when a frame names this server at all. */
|
|
223
|
+
const ownPath = (url: string, origin: string): string | undefined => {
|
|
224
|
+
let parsed: URL
|
|
225
|
+
try {
|
|
226
|
+
parsed = new URL(url)
|
|
227
|
+
} catch {
|
|
228
|
+
return undefined
|
|
229
|
+
}
|
|
230
|
+
const own = new URL(origin)
|
|
231
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return undefined
|
|
232
|
+
// `localhost:<port>` and `127.0.0.1:<port>` both reach this server; another port is another server.
|
|
233
|
+
if (parsed.port !== own.port) return undefined
|
|
234
|
+
const host = parsed.hostname
|
|
235
|
+
if (
|
|
236
|
+
host !== "localhost" &&
|
|
237
|
+
host !== "127.0.0.1" &&
|
|
238
|
+
host !== "[::1]" &&
|
|
239
|
+
!host.endsWith(".localhost")
|
|
240
|
+
)
|
|
241
|
+
return undefined
|
|
242
|
+
// Scripts only: fetching a page URL (an inline script's frame) would render it, loaders and all.
|
|
243
|
+
if (!SCRIPT_PATH.test(parsed.pathname)) return undefined
|
|
244
|
+
return `${parsed.pathname}${parsed.search}`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
async mapStack(stack) {
|
|
249
|
+
const origin = options.origin()
|
|
250
|
+
const lines = stack.split("\n")
|
|
251
|
+
const out: string[] = []
|
|
252
|
+
let frames = 0
|
|
253
|
+
for (const line of lines) {
|
|
254
|
+
const frame = parseFrame(line)
|
|
255
|
+
if (frame === undefined) {
|
|
256
|
+
// The message block (and engine noise between frames) stays as it was.
|
|
257
|
+
if (frames === 0) out.push(line)
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
if (frames >= MAX_FRAMES) break
|
|
261
|
+
frames += 1
|
|
262
|
+
let location = `${frame.url}:${frame.line}:${frame.column}`
|
|
263
|
+
const path = origin === undefined ? undefined : ownPath(frame.url, origin)
|
|
264
|
+
if (origin !== undefined && path !== undefined) {
|
|
265
|
+
const map = await mapFor(origin, path)
|
|
266
|
+
const position =
|
|
267
|
+
map === undefined ? undefined : lookup(map, frame.line - 1, frame.column - 1)
|
|
268
|
+
if (position !== undefined && position.source !== "")
|
|
269
|
+
location = `${position.source}:${position.line}:${position.column}`
|
|
270
|
+
}
|
|
271
|
+
out.push(
|
|
272
|
+
frame.name === undefined ? ` at ${location}` : ` at ${frame.name} (${location})`,
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
return out.join("\n")
|
|
276
|
+
},
|
|
277
|
+
clear() {
|
|
278
|
+
maps.clear()
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
}
|
package/src/dev-ssr-graph.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Bun's runtime caches every module it has evaluated, and nothing invalidates that cache on a file
|
|
5
5
|
* change. The dev server's answer used to stop at the route module: `discoverRoutes({ importQuery })`
|
|
6
6
|
* appends a changing query to each route's own dynamic import, so the route re-evaluates. Its imports
|
|
7
|
-
* do not - a component, a helper, a
|
|
7
|
+
* do not - a component, a helper, a backend module all keep resolving to the same cached specifier,
|
|
8
8
|
* so SSR kept rendering the code that was on disk when the server started while the client, which Bun
|
|
9
9
|
* rebuilds properly, rendered the edit. Every framework on this pipeline had it; the Vite pipeline does
|
|
10
10
|
* not, because Vite owns its own SSR module graph.
|
|
@@ -46,8 +46,9 @@
|
|
|
46
46
|
*
|
|
47
47
|
* So the version is applied one level earlier, in the importer's own source: on load, each app-owned
|
|
48
48
|
* module has its relative specifiers rewritten to absolute paths carrying the current version of the
|
|
49
|
-
* file they point at
|
|
50
|
-
* a third-party plugin
|
|
49
|
+
* file they point at - an alias such as `@/components/Button` too, resolved the way Bun resolves it.
|
|
50
|
+
* `onResolve` stays as well, because it is the only hook that sees modules loaded by a third-party plugin
|
|
51
|
+
* this one does not wrap.
|
|
51
52
|
*
|
|
52
53
|
* Old versions stay in Bun's registry - a bounded dev-only leak, one entry per edit, which is what the
|
|
53
54
|
* route-level query already did.
|
|
@@ -104,6 +105,14 @@ const appSourceFilter = (root: string): RegExp => {
|
|
|
104
105
|
*/
|
|
105
106
|
const SPECIFIER = /\b(from|import|require)((?:\s*\()?\s*)(["'])([^"'\n]+)\3/g
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* A specifier that can name an app file: relative, absolute, or an alias an npm package name cannot be
|
|
110
|
+
* (`@/x`, `~/x`, `#x`, `$lib/x`). A bare package name is left alone - resolving one here would pin it
|
|
111
|
+
* for the life of the dev server, and a package is never app source anyway. An alias shaped like a
|
|
112
|
+
* package (`@app/x`, `src/x`) is not recognized.
|
|
113
|
+
*/
|
|
114
|
+
const APP_SPECIFIER = /^(?:[^a-z0-9@]|@[^a-z0-9])/i
|
|
115
|
+
|
|
107
116
|
export interface SsrGraphOptions {
|
|
108
117
|
/** The app root. Only files under it (and outside `node_modules`) are tracked. */
|
|
109
118
|
readonly root: string
|
|
@@ -262,7 +271,9 @@ export function createSsrGraph(options: SsrGraphOptions): SsrGraph {
|
|
|
262
271
|
// through untouched rather than adding a second, worse one.
|
|
263
272
|
return contents
|
|
264
273
|
}
|
|
265
|
-
const candidates = new Set(
|
|
274
|
+
const candidates = new Set(
|
|
275
|
+
scanned.map((entry) => entry.path).filter((path) => APP_SPECIFIER.test(path)),
|
|
276
|
+
)
|
|
266
277
|
if (candidates.size === 0) return contents
|
|
267
278
|
const keys = new Map<string, string>()
|
|
268
279
|
for (const specifier of candidates) {
|
|
@@ -289,7 +300,7 @@ export function createSsrGraph(options: SsrGraphOptions): SsrGraph {
|
|
|
289
300
|
})
|
|
290
301
|
// The resolver half covers what the loader half cannot see: a module compiled by a plugin that
|
|
291
302
|
// does not call `rewriteSsrImports`. Extension-bearing specifiers only, which is all Bun offers.
|
|
292
|
-
build.onResolve({ filter:
|
|
303
|
+
build.onResolve({ filter: APP_SPECIFIER }, (args) => {
|
|
293
304
|
if (resolving) return undefined
|
|
294
305
|
// A specifier that already carries a query is either this module's own rewrite or a plugin's
|
|
295
306
|
// virtual sub-request (`?vue-css` and friends) - both are already keyed.
|
package/src/dev.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* not, and saving it does a clean full reload. Plus no Vite dependency and ONE bundler across dev and
|
|
15
15
|
* production, which is the real prize - the dev/prod seam disappears.
|
|
16
16
|
*
|
|
17
|
-
* CSS Modules, server functions, and
|
|
17
|
+
* CSS Modules, server functions, and the zone guard use the same production transforms through the
|
|
18
18
|
* generated Bun config. The CLI owns that config because Bun's HTML dev server accepts plugins only from
|
|
19
19
|
* `[serve.static] plugins`; direct callers should pass the equivalent plugin through their Bun config.
|
|
20
20
|
*
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*
|
|
40
40
|
* SSR invalidation is Bun's import cache rather than Vite's module graph, so route modules are re-imported
|
|
41
41
|
* under a changing query on each change - which is what `discoverRoutes({ importQuery })` exists for. That
|
|
42
|
-
* query stops at the route file, so everything BELOW it - components, helpers,
|
|
42
|
+
* query stops at the route file, so everything BELOW it - components, helpers, backend modules - is
|
|
43
43
|
* tracked and re-keyed per module by `./dev-ssr-graph.ts`, or SSR would render the code that was on disk
|
|
44
44
|
* when the server started.
|
|
45
45
|
*
|
|
@@ -57,14 +57,16 @@ import {
|
|
|
57
57
|
import { dirname, relative, resolve } from "node:path"
|
|
58
58
|
import { type BuildClientOptions, buildClient } from "./build.ts"
|
|
59
59
|
import { type DevEntryMatch, resolveDevEntry } from "./bun-dev-entry.ts"
|
|
60
|
-
import { createDevDiagnostics } from "./dev-diagnostics.ts"
|
|
61
60
|
import { explainBindFailure } from "./dev-port.ts"
|
|
61
|
+
import { createDevSession, type DevAppHooks, type DevSession } from "./dev-session.ts"
|
|
62
62
|
import { createSsrGraph, type SsrGraph } from "./dev-ssr-graph.ts"
|
|
63
63
|
import { discoverRoutes } from "./fs.ts"
|
|
64
64
|
import { DEFAULT_DEV_PORT, generateClientEntry } from "./index.ts"
|
|
65
|
+
import { DEV_ENTRY_FILE } from "./internal/dev-reserved.ts"
|
|
65
66
|
import { DEV_HMR_ENV, DEV_ROOT_ENV, DEV_ROUTES_ENV } from "./plugins/kit.ts"
|
|
66
67
|
import { servePublicDir } from "./public-dir.ts"
|
|
67
68
|
|
|
69
|
+
export type { DevAppHooks } from "./dev-session.ts"
|
|
68
70
|
export { LAST_ERROR_PATH } from "./diagnostic.ts"
|
|
69
71
|
|
|
70
72
|
/** Minimal app surface the dev server needs - `createWebApp(...)` satisfies it. */
|
|
@@ -76,9 +78,14 @@ export interface DevServerOptions extends Omit<BuildClientOptions, "minify"> {
|
|
|
76
78
|
/**
|
|
77
79
|
* Build the nifra app for the current client entry. `importQuery` changes on every reload - pass it to
|
|
78
80
|
* `discoverRoutes(routesDir, { importQuery })` so SSR re-imports edited route modules instead of Bun's
|
|
79
|
-
* cached copies.
|
|
81
|
+
* cached copies. Pass `dev.onLoaderError` to `createWebApp` so failures an `_error` boundary renders
|
|
82
|
+
* still reach the dev feed (`nifra errors`).
|
|
80
83
|
*/
|
|
81
|
-
readonly createApp: (
|
|
84
|
+
readonly createApp: (
|
|
85
|
+
clientEntry: string,
|
|
86
|
+
importQuery: string,
|
|
87
|
+
dev: DevAppHooks,
|
|
88
|
+
) => FetchApp | Promise<FetchApp>
|
|
82
89
|
/** Directories to watch (default: `[routesDir]`). */
|
|
83
90
|
readonly watch?: readonly string[]
|
|
84
91
|
/** Port to listen on (default {@link DEFAULT_DEV_PORT}). */
|
|
@@ -95,6 +102,16 @@ export interface DevServerOptions extends Omit<BuildClientOptions, "minify"> {
|
|
|
95
102
|
* pass runs in the background off the hot path, so HMR is never waiting on it, and only reports.
|
|
96
103
|
*/
|
|
97
104
|
readonly guardLeaks?: boolean
|
|
105
|
+
/**
|
|
106
|
+
* Write `.nifra/dev-server.json` (how `nifra errors`/`nifra logs` and the MCP tools find this server)
|
|
107
|
+
* and the persisted dev log beside it (default `true`).
|
|
108
|
+
*/
|
|
109
|
+
readonly record?: boolean
|
|
110
|
+
/**
|
|
111
|
+
* Show the browser errors a dev page reports in a badge on that page, with a Copy prompt button per
|
|
112
|
+
* fix (default `true`). Off, the errors still reach the feed.
|
|
113
|
+
*/
|
|
114
|
+
readonly indicator?: boolean
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
export interface DevServer {
|
|
@@ -293,10 +310,17 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
293
310
|
// (`devHotComponent`). Announced here rather than passed, for the same reason the flag above is.
|
|
294
311
|
process.env[DEV_ROOT_ENV] = root
|
|
295
312
|
process.env[DEV_ROUTES_ENV] = resolve(routesDir)
|
|
296
|
-
//
|
|
297
|
-
// LAST_ERROR_PATH
|
|
298
|
-
//
|
|
299
|
-
const
|
|
313
|
+
// Everything this server sees, for the agent driving it: failures (with the overlay's Diagnostic and
|
|
314
|
+
// LAST_ERROR_PATH riding on the same capture), console output, request traces and the discovery
|
|
315
|
+
// record. Shared with the Vite adapter so the two servers report identically.
|
|
316
|
+
const session = createDevSession({
|
|
317
|
+
root,
|
|
318
|
+
pipeline: "bun",
|
|
319
|
+
publicEnvPrefix: options.publicEnvPrefix,
|
|
320
|
+
record: options.record,
|
|
321
|
+
indicator: options.indicator,
|
|
322
|
+
})
|
|
323
|
+
const devHooks: DevAppHooks = { onLoaderError: session.onLoaderError }
|
|
300
324
|
// SSR freshness BELOW the route module. The route-level `importQuery` only ever reloaded the route
|
|
301
325
|
// itself; everything it imports is tracked here and re-keyed when it changes. Registered now because a
|
|
302
326
|
// Bun runtime plugin only affects modules loaded after it, and the first route import is `appFor`
|
|
@@ -307,7 +331,7 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
307
331
|
const ssrGraph = createSsrGraph({ root })
|
|
308
332
|
;(await import("bun")).plugin(ssrGraph.plugin)
|
|
309
333
|
const devDir = resolve(root, DEV_DIR)
|
|
310
|
-
const entryPath = resolve(devDir,
|
|
334
|
+
const entryPath = resolve(devDir, DEV_ENTRY_FILE)
|
|
311
335
|
const htmlPath = resolve(devDir, "entry.html")
|
|
312
336
|
const publicDir = options.publicDir === false ? undefined : (options.publicDir ?? "public")
|
|
313
337
|
// Route dev's `public/` through the SAME handler production uses. Dev previously inherited this
|
|
@@ -378,15 +402,17 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
378
402
|
* client leaks. It is no longer what keeps SSR correct.
|
|
379
403
|
*
|
|
380
404
|
* The entry hash is not the whole marker, though, because it only covers the CLIENT graph. A module
|
|
381
|
-
* the browser never receives - a
|
|
405
|
+
* the browser never receives - a backend module, a loader's helper - can change without moving it, so
|
|
382
406
|
* the key also carries {@link SsrGraph} generation, which counts changes on the SERVER side. Either
|
|
383
407
|
* one moving rebuilds the app, which is what re-imports the route modules under a fresh query.
|
|
384
408
|
*/
|
|
385
409
|
const appFor = (key: string): Promise<FetchApp> => {
|
|
386
410
|
if (built?.key === key) return Promise.resolve(built.app)
|
|
387
411
|
if (building?.key === key) return building.promise
|
|
412
|
+
// A new key means the code moved since the last build: what the feed recorded before is stale.
|
|
413
|
+
if (version > 0) session.markChange()
|
|
388
414
|
version += 1
|
|
389
|
-
const promise = Promise.resolve(createApp(CLIENT_ENTRY_PATH, `v=${version}
|
|
415
|
+
const promise = Promise.resolve(createApp(CLIENT_ENTRY_PATH, `v=${version}`, devHooks))
|
|
390
416
|
building = { key, promise }
|
|
391
417
|
void promise
|
|
392
418
|
.then((next) => {
|
|
@@ -408,6 +434,37 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
408
434
|
return appFor(`${entrySrc}#${ssrGraph.generation()}`)
|
|
409
435
|
}
|
|
410
436
|
|
|
437
|
+
const renderApp = async (req: Request, url: URL): Promise<Response> => {
|
|
438
|
+
try {
|
|
439
|
+
// One fresh probe per request: it is both the freshness check for SSR and the stylesheet list,
|
|
440
|
+
// so the page cannot be rendered against a build the browser is not about to load.
|
|
441
|
+
const entry = await currentEntry(true)
|
|
442
|
+
const res = await (await appForRequest(entry.src)).fetch(req)
|
|
443
|
+
if (!(res.headers.get("content-type") ?? "").includes("text/html")) return res
|
|
444
|
+
if (entry.styles.length === 0) return session.decoratePage(req, res)
|
|
445
|
+
const headers = new Headers(res.headers)
|
|
446
|
+
headers.delete("content-length") // the body grows with the injected stylesheet links
|
|
447
|
+
return session.decoratePage(
|
|
448
|
+
req,
|
|
449
|
+
new Response(injectStyles(await res.text(), entry.styles), {
|
|
450
|
+
status: res.status,
|
|
451
|
+
headers,
|
|
452
|
+
}),
|
|
453
|
+
)
|
|
454
|
+
} catch (err) {
|
|
455
|
+
// One Diagnostic drives every surface: the overlay returned here, the JSON at LAST_ERROR_PATH and
|
|
456
|
+
// the feed's `ssr` entry.
|
|
457
|
+
const html = session.failure(err, {
|
|
458
|
+
method: req.method,
|
|
459
|
+
url: `${url.pathname}${url.search}`,
|
|
460
|
+
})
|
|
461
|
+
return new Response(html, {
|
|
462
|
+
status: 500,
|
|
463
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
411
468
|
try {
|
|
412
469
|
server = serve({
|
|
413
470
|
port,
|
|
@@ -427,11 +484,9 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
427
484
|
const entry = await currentEntry()
|
|
428
485
|
return new Response(null, { status: 307, headers: { location: entry.src } })
|
|
429
486
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return new Response(body, { headers })
|
|
434
|
-
}
|
|
487
|
+
// The agent endpoints (errors, logs, requests, identity, last-error): shared surface.
|
|
488
|
+
const agent = await session.handle(req)
|
|
489
|
+
if (agent !== undefined) return agent
|
|
435
490
|
// Static probe before routing; a miss returns undefined and falls through, so no route is
|
|
436
491
|
// shadowed. Every GET/HEAD pays the probe, including page renders and API routes: the handler
|
|
437
492
|
// serves extension-less files too (an ACME challenge token is the reason), so there is no
|
|
@@ -439,33 +494,11 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
439
494
|
// mounting this handler itself can pass `files` to answer a miss without touching the disk.
|
|
440
495
|
const staticFile = await servePublic(req)
|
|
441
496
|
if (staticFile !== undefined) return staticFile
|
|
442
|
-
|
|
443
|
-
// One fresh probe per request: it is both the freshness check for SSR and the stylesheet list,
|
|
444
|
-
// so the page cannot be rendered against a build the browser is not about to load.
|
|
445
|
-
const entry = await currentEntry(true)
|
|
446
|
-
const res = await (await appForRequest(entry.src)).fetch(req)
|
|
447
|
-
if (!(res.headers.get("content-type") ?? "").includes("text/html")) return res
|
|
448
|
-
if (entry.styles.length === 0) return res
|
|
449
|
-
const headers = new Headers(res.headers)
|
|
450
|
-
headers.delete("content-length") // the body grows with the injected stylesheet links
|
|
451
|
-
return new Response(injectStyles(await res.text(), entry.styles), {
|
|
452
|
-
status: res.status,
|
|
453
|
-
headers,
|
|
454
|
-
})
|
|
455
|
-
} catch (err) {
|
|
456
|
-
// One Diagnostic drives both surfaces: the overlay returned here and the JSON at LAST_ERROR_PATH.
|
|
457
|
-
const html = devDiagnostics.capture(err, {
|
|
458
|
-
method: req.method,
|
|
459
|
-
url: `${url.pathname}${url.search}`,
|
|
460
|
-
})
|
|
461
|
-
return new Response(html, {
|
|
462
|
-
status: 500,
|
|
463
|
-
headers: { "content-type": "text/html; charset=utf-8" },
|
|
464
|
-
})
|
|
465
|
-
}
|
|
497
|
+
return session.track(req, () => renderApp(req, url))
|
|
466
498
|
},
|
|
467
499
|
})
|
|
468
500
|
} catch (err) {
|
|
501
|
+
session.stop()
|
|
469
502
|
// `Bun.serve` throws synchronously on a bind failure; the Vite path's equivalent arrives as an async
|
|
470
503
|
// `error` event. Same explanation either way - see ./dev-port.ts for why it is worth spelling out.
|
|
471
504
|
throw explainBindFailure(err, port)
|
|
@@ -479,10 +512,12 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
479
512
|
// Leaving the server up would answer 500s forever, which presents as a running server rather than as
|
|
480
513
|
// the startup failure it is.
|
|
481
514
|
server.stop(true)
|
|
515
|
+
session.stop()
|
|
482
516
|
throw err
|
|
483
517
|
}
|
|
518
|
+
session.listening(server.port ?? port)
|
|
484
519
|
|
|
485
|
-
const guard = options.guardLeaks !== false ? leakGuard(options) : undefined
|
|
520
|
+
const guard = options.guardLeaks !== false ? leakGuard(options, session) : undefined
|
|
486
521
|
guard?.()
|
|
487
522
|
|
|
488
523
|
// What is left for the watcher, now that request ordering keeps SSR fresh: regenerate the client entry
|
|
@@ -494,11 +529,16 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
494
529
|
const onChange = (): void => {
|
|
495
530
|
if (timer) clearTimeout(timer)
|
|
496
531
|
timer = setTimeout(() => {
|
|
532
|
+
// A deletion its own watcher reported is unwatched now, or the next topology poll reports it
|
|
533
|
+
// again as a second change: another regeneration and another leak-guard build.
|
|
534
|
+
syncWatchedFiles(false)
|
|
535
|
+
session.markChange()
|
|
497
536
|
try {
|
|
498
537
|
writeDevFiles({ routesDir, clientModule, entryPath, htmlPath })
|
|
499
538
|
} catch (err) {
|
|
500
539
|
// A half-saved file can fail the scan mid-edit; the next change re-runs this.
|
|
501
540
|
console.error("[nifra/web/dev] client entry regeneration failed:", err)
|
|
541
|
+
session.buildFailed(err, "client entry regeneration failed")
|
|
502
542
|
}
|
|
503
543
|
guard?.()
|
|
504
544
|
}, 60)
|
|
@@ -552,6 +592,7 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
552
592
|
for (const file of watched) unwatchFile(file)
|
|
553
593
|
ssrGraph.dispose()
|
|
554
594
|
server.stop(true)
|
|
595
|
+
session.stop()
|
|
555
596
|
rmSync(devDir, { recursive: true, force: true })
|
|
556
597
|
// Put the dev-phase env flags back the way this server found them (see `priorDevEnv`).
|
|
557
598
|
for (const [key, prior] of Object.entries(priorDevEnv)) {
|
|
@@ -585,7 +626,7 @@ export function buildFailureDetail(err: unknown): string {
|
|
|
585
626
|
: ` ${err instanceof Error ? err.message : String(err)}`
|
|
586
627
|
}
|
|
587
628
|
|
|
588
|
-
function leakGuard(options: DevServerOptions): () => void {
|
|
629
|
+
function leakGuard(options: DevServerOptions, session: DevSession): () => void {
|
|
589
630
|
let running = false
|
|
590
631
|
let queued = false
|
|
591
632
|
const run = (): void => {
|
|
@@ -595,11 +636,13 @@ function leakGuard(options: DevServerOptions): () => void {
|
|
|
595
636
|
}
|
|
596
637
|
running = true
|
|
597
638
|
buildClient({ ...options, minify: false })
|
|
639
|
+
.then(() => session.buildPassed())
|
|
598
640
|
.catch((err: unknown) => {
|
|
599
641
|
console.error(
|
|
600
642
|
`\n[nifra/web/dev] client-leak guard failed:\n${buildFailureDetail(err)}\n` +
|
|
601
643
|
" The dev server is still running. This will fail `nifra build`.\n",
|
|
602
644
|
)
|
|
645
|
+
session.buildFailed(err, "client build failed (this will fail `nifra build`)")
|
|
603
646
|
})
|
|
604
647
|
.finally(() => {
|
|
605
648
|
running = false
|