@nifrajs/web 5.1.0 → 5.3.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/CHANGELOG.md +3042 -0
- package/dist/build-plan.d.ts +7 -0
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +19 -0
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +1 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +51 -3
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +23 -3
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +224 -16
- package/dist/build.js.map +1 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -7
- package/dist/client.js.map +1 -1
- package/dist/csp.d.ts +2 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +2 -0
- package/dist/csp.js.map +1 -1
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +23 -3
- package/dist/deferred.js.map +1 -1
- package/dist/index.d.ts +4 -3
- 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 +6 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +23 -5
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/copy-split.d.ts +27 -0
- package/dist/internal/copy-split.d.ts.map +1 -0
- package/dist/internal/copy-split.js +173 -0
- package/dist/internal/copy-split.js.map +1 -0
- package/dist/internal/deferred-free.d.ts +3 -0
- package/dist/internal/deferred-free.d.ts.map +1 -0
- package/dist/internal/deferred-free.js +15 -0
- package/dist/internal/deferred-free.js.map +1 -0
- package/dist/internal/module-preparation.d.ts +7 -0
- package/dist/internal/module-preparation.d.ts.map +1 -0
- package/dist/internal/module-preparation.js +24 -0
- package/dist/internal/module-preparation.js.map +1 -0
- package/dist/internal/output-guard.d.ts +2 -0
- package/dist/internal/output-guard.d.ts.map +1 -1
- package/dist/internal/output-guard.js +51 -9
- package/dist/internal/output-guard.js.map +1 -1
- package/dist/internal/page-execution.d.ts +20 -1
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +115 -50
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +3 -1
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/prerendered-pages.d.ts +42 -0
- package/dist/internal/prerendered-pages.d.ts.map +1 -0
- package/dist/internal/prerendered-pages.js +92 -0
- package/dist/internal/prerendered-pages.js.map +1 -0
- package/dist/internal/public-url.d.ts +7 -0
- package/dist/internal/public-url.d.ts.map +1 -1
- package/dist/internal/public-url.js +13 -0
- package/dist/internal/public-url.js.map +1 -1
- package/dist/internal/render-document.d.ts +11 -2
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +131 -41
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/request-url.d.ts +4 -0
- package/dist/internal/request-url.d.ts.map +1 -1
- package/dist/internal/request-url.js +24 -0
- package/dist/internal/request-url.js.map +1 -1
- package/dist/internal/secret-scan.d.ts +4 -0
- package/dist/internal/secret-scan.d.ts.map +1 -1
- package/dist/internal/secret-scan.js +8 -1
- package/dist/internal/secret-scan.js.map +1 -1
- package/dist/internal/typescript-api.d.ts +4 -0
- package/dist/internal/typescript-api.d.ts.map +1 -0
- package/dist/internal/typescript-api.js +8 -0
- package/dist/internal/typescript-api.js.map +1 -0
- package/dist/internal/vite-plugin-compat.d.ts +28 -0
- package/dist/internal/vite-plugin-compat.d.ts.map +1 -0
- package/dist/internal/vite-plugin-compat.js +82 -0
- package/dist/internal/vite-plugin-compat.js.map +1 -0
- package/dist/internal/web-app.d.ts +45 -0
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +32 -1
- package/dist/internal/web-app.js.map +1 -1
- package/dist/isr.d.ts +1 -0
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +7 -4
- package/dist/isr.js.map +1 -1
- package/dist/plugins/import-glob.d.ts +20 -0
- package/dist/plugins/import-glob.d.ts.map +1 -0
- package/dist/plugins/import-glob.js +401 -0
- package/dist/plugins/import-glob.js.map +1 -0
- package/dist/plugins/kit.d.ts +11 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +32 -0
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/stylex.d.ts.map +1 -1
- package/dist/plugins/stylex.js +2 -2
- package/dist/plugins/stylex.js.map +1 -1
- package/dist/prerender.d.ts +4 -0
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +9 -1
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +15 -0
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +91 -12
- package/dist/public-dir.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 +33 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/router.d.ts +3 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +4 -4
- package/dist/router.js.map +1 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +2 -0
- package/dist/search.js.map +1 -1
- package/dist/server-components/typescript.d.ts.map +1 -1
- package/dist/server-components/typescript.js +2 -7
- package/dist/server-components/typescript.js.map +1 -1
- package/dist/vite.d.ts +7 -28
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +130 -123
- package/dist/vite.js.map +1 -1
- package/package.json +5 -4
- package/src/build-plan.ts +21 -0
- package/src/build-vite.ts +60 -3
- package/src/build.ts +283 -39
- package/src/client.ts +15 -7
- package/src/csp.ts +4 -0
- package/src/deferred.ts +20 -3
- package/src/index.ts +9 -0
- package/src/internal/codegen.ts +32 -3
- package/src/internal/copy-split.ts +220 -0
- package/src/internal/deferred-free.ts +15 -0
- package/src/internal/module-preparation.ts +30 -0
- package/src/internal/output-guard.ts +51 -9
- package/src/internal/page-execution.ts +137 -53
- package/src/internal/parity.ts +2 -1
- package/src/internal/prerendered-pages.ts +124 -0
- package/src/internal/public-url.ts +13 -0
- package/src/internal/render-document.ts +179 -58
- package/src/internal/request-url.ts +24 -0
- package/src/internal/secret-scan.ts +9 -1
- package/src/internal/typescript-api.ts +11 -0
- package/src/internal/vite-plugin-compat.ts +81 -0
- package/src/internal/web-app.ts +82 -2
- package/src/isr.ts +7 -4
- package/src/plugins/import-glob.ts +392 -0
- package/src/plugins/kit.ts +36 -0
- package/src/plugins/stylex.ts +2 -1
- package/src/prerender.ts +13 -1
- package/src/public-dir.ts +107 -12
- package/src/render-seam.ts +65 -2
- package/src/router.ts +7 -6
- package/src/search.ts +1 -0
- package/src/server-components/typescript.ts +2 -10
- package/src/vite.ts +133 -135
package/src/vite.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Vite's `middlewares` are Connect-style - it runs fine under Bun.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { type Dirent, readdirSync, writeFileSync } from "node:fs"
|
|
14
|
+
import { type Dirent, existsSync, readdirSync, realpathSync, writeFileSync } from "node:fs"
|
|
15
15
|
import {
|
|
16
16
|
createServer as createHttpServer,
|
|
17
17
|
type IncomingMessage,
|
|
@@ -33,8 +33,10 @@ import {
|
|
|
33
33
|
formatIdentityParityFindings,
|
|
34
34
|
identityParityHeadline,
|
|
35
35
|
} from "./internal/parity.ts"
|
|
36
|
+
import { isHiddenPublicPath } from "./internal/public-url.ts"
|
|
36
37
|
import { vitePublicEnvPrefix } from "./internal/server-boundary.ts"
|
|
37
38
|
import { importVite } from "./internal/vite-import.ts"
|
|
39
|
+
import { normalizeRolldownPlugins } from "./internal/vite-plugin-compat.ts"
|
|
38
40
|
import { scopedName } from "./plugins/css-modules.ts"
|
|
39
41
|
import {
|
|
40
42
|
DEV_ROOT_ENV,
|
|
@@ -76,6 +78,8 @@ interface ViteLike {
|
|
|
76
78
|
ssrFixStacktrace(err: Error): void
|
|
77
79
|
/** The module graph, used to invalidate a changed file AND its transitive importers before reload. */
|
|
78
80
|
readonly moduleGraph?: ViteModuleGraph
|
|
81
|
+
/** The resolved config: `publicDir` is absolute, or `""` when the public directory is off. */
|
|
82
|
+
readonly config?: { readonly publicDir?: string }
|
|
79
83
|
readonly watcher: {
|
|
80
84
|
on(event: "change" | "add" | "unlink", cb: (path: string) => void): void
|
|
81
85
|
on(event: "ready", cb: () => void): void
|
|
@@ -168,6 +172,7 @@ export interface ViteDevServer {
|
|
|
168
172
|
|
|
169
173
|
export type { DevAppHooks } from "./dev-session.ts"
|
|
170
174
|
export { LAST_ERROR_PATH } from "./diagnostic.ts"
|
|
175
|
+
export { normalizeRolldownPlugins } from "./internal/vite-plugin-compat.ts"
|
|
171
176
|
|
|
172
177
|
// The codegen'd client entry is written here (at the Vite root) so Vite serves + HMRs it.
|
|
173
178
|
const DEV_ENTRY = ".nifra-vite-entry.tsx"
|
|
@@ -247,23 +252,15 @@ function toWebRequest(req: IncomingMessage, body: Buffer | undefined): Request {
|
|
|
247
252
|
return new Request(url, init as unknown as ConstructorParameters<typeof Request>[1])
|
|
248
253
|
}
|
|
249
254
|
|
|
250
|
-
// A Vite `config` hook: a plain function, OR the object form `{ handler, order }` Vite accepts for hook
|
|
251
|
-
// ordering (`order: "pre" | "post"`). We wrap the handler in either shape - see normalizeRolldownPlugins.
|
|
252
|
-
type ConfigFn = (config: unknown, env: unknown) => unknown
|
|
253
|
-
type ConfigHook = ConfigFn | { readonly handler: ConfigFn; readonly order?: unknown }
|
|
254
|
-
|
|
255
|
-
// A Vite plugin - the only hook we wrap is `config`. Typed structurally (no `vite` type dep). Everything
|
|
256
|
-
// else on the plugin object is preserved by spread, so wrapping is transparent to Vite.
|
|
257
|
-
interface VitePluginLike {
|
|
258
|
-
readonly name?: string
|
|
259
|
-
config?: ConfigHook
|
|
260
|
-
readonly [key: string]: unknown
|
|
261
|
-
}
|
|
262
|
-
|
|
263
255
|
/** The bits of a Node ServerResponse `pipeWebBodyToNode` touches - structural, to avoid a node:http dep here. */
|
|
264
256
|
interface NodeResLike {
|
|
257
|
+
/** Set once the socket is gone: `close` has fired, and a write returns `false` for good. */
|
|
258
|
+
readonly destroyed?: boolean
|
|
265
259
|
flushHeaders?(): void
|
|
266
260
|
on(event: "close", cb: () => void): void
|
|
261
|
+
// Optional so a bare sink still fits; without them a full buffer is not waited out.
|
|
262
|
+
once?(event: "drain", cb: () => void): void
|
|
263
|
+
off?(event: "close" | "drain", cb: () => void): void
|
|
267
264
|
write(chunk: Uint8Array): boolean
|
|
268
265
|
end(): void
|
|
269
266
|
}
|
|
@@ -293,6 +290,8 @@ export function applyResponseHeaders(headers: Headers, res: NodeHeaderSink): voi
|
|
|
293
290
|
* `arrayBuffer()`) waits for the stream to END - which an open-ended SSE (`text/event-stream`) body never
|
|
294
291
|
* does, so it hung `nifra dev` (the Bun production server streamed it fine). This flushes each chunk as it
|
|
295
292
|
* arrives and cancels the reader if the client disconnects; a finite body just streams its chunk(s) + ends.
|
|
293
|
+
* A write the socket can't take yet is waited out (`drain`) before the next read, so a slow client
|
|
294
|
+
* holds one chunk rather than the whole body.
|
|
296
295
|
*/
|
|
297
296
|
export async function pipeWebBodyToNode(
|
|
298
297
|
body: ReadableStream<Uint8Array> | null,
|
|
@@ -302,87 +301,52 @@ export async function pipeWebBodyToNode(
|
|
|
302
301
|
res.end()
|
|
303
302
|
return
|
|
304
303
|
}
|
|
304
|
+
// A call, so the check after an await is not narrowed by the one before it.
|
|
305
|
+
const destroyed = (): boolean => res.destroyed === true
|
|
306
|
+
// The client left while the app ran: `close` fired before anyone listened, and no `drain` comes.
|
|
307
|
+
if (destroyed()) {
|
|
308
|
+
await body.cancel().catch(() => {})
|
|
309
|
+
return
|
|
310
|
+
}
|
|
305
311
|
const reader = body.getReader()
|
|
306
312
|
res.flushHeaders?.()
|
|
307
|
-
|
|
313
|
+
let closed = false
|
|
314
|
+
let wake: (() => void) | undefined
|
|
315
|
+
const onClose = (): void => {
|
|
316
|
+
closed = true
|
|
317
|
+
void reader.cancel().catch(() => {})
|
|
318
|
+
wake?.()
|
|
319
|
+
}
|
|
320
|
+
res.on("close", onClose)
|
|
308
321
|
try {
|
|
309
322
|
for (;;) {
|
|
310
323
|
const { done, value } = await reader.read()
|
|
311
324
|
if (done) break
|
|
312
|
-
res.write(value)
|
|
325
|
+
if (!res.write(value) && !closed && res.once !== undefined) {
|
|
326
|
+
if (destroyed()) {
|
|
327
|
+
onClose()
|
|
328
|
+
break
|
|
329
|
+
}
|
|
330
|
+
let drain!: () => void
|
|
331
|
+
const drained = new Promise<void>((resolve) => {
|
|
332
|
+
drain = resolve
|
|
333
|
+
})
|
|
334
|
+
wake = drain
|
|
335
|
+
res.once("drain", drain)
|
|
336
|
+
await drained
|
|
337
|
+
res.off?.("drain", drain)
|
|
338
|
+
wake = undefined
|
|
339
|
+
}
|
|
313
340
|
}
|
|
314
341
|
} catch {
|
|
315
342
|
// client disconnected mid-stream - the `close` handler already cancelled the reader
|
|
343
|
+
} finally {
|
|
344
|
+
res.off?.("close", onClose)
|
|
345
|
+
reader.releaseLock()
|
|
316
346
|
}
|
|
317
347
|
res.end()
|
|
318
348
|
}
|
|
319
349
|
|
|
320
|
-
/**
|
|
321
|
-
* Strip `optimizeDeps.rollupOptions.jsx` from a plugin's `config` hook output when running under
|
|
322
|
-
* rolldown-vite - the source of the scary, harmless `Warning: Invalid input options … "jsx" Invalid
|
|
323
|
-
* key: Expected never but received "jsx"` on `nifra dev`.
|
|
324
|
-
*
|
|
325
|
-
* Why it happens: `@vitejs/plugin-react@4.x` (and peers) target an *older* rolldown-vite optimizeDeps
|
|
326
|
-
* API - they inject `optimizeDeps.rollupOptions.jsx` to tell the dep pre-bundler to transform JSX. Vite
|
|
327
|
-
* 8's rolldown dep-optimizer renamed that surface to `optimizeDeps.rolldownOptions` (and moved jsx under
|
|
328
|
-
* `transform.jsx`), so the stale `rollupOptions.jsx` is an unrecognized input option → the warning. It's
|
|
329
|
-
* a version-skew artifact, not a real misconfig: the route source JSX transform runs through the
|
|
330
|
-
* plugin's own `transform` hook (untouched here), and node_modules deps that get pre-bundled almost
|
|
331
|
-
* never contain raw JSX - so dropping the dead key changes no behavior and keeps HMR/Fast Refresh
|
|
332
|
-
* intact. We *strip* (rather than translate to `rolldownOptions`) so the fix is version-agnostic: a
|
|
333
|
-
* plugin already emitting the correct `rolldownOptions` is left untouched, and a future plugin bump that
|
|
334
|
-
* stops emitting `rollupOptions.jsx` makes this a no-op.
|
|
335
|
-
*
|
|
336
|
-
* Scoped narrowly: only the `optimizeDeps.rollupOptions.jsx` key is removed, only under rolldown-vite,
|
|
337
|
-
* and only from the value a plugin's `config` hook returns. Non-rolldown Vite is passed through verbatim.
|
|
338
|
-
*
|
|
339
|
-
* FLATTEN FIRST: a Vite plugin factory may return an ARRAY of plugins - `@vitejs/plugin-react`'s `react()`
|
|
340
|
-
* returns `[vite:react-babel, vite:react-refresh]`, and it's `react:react-babel`'s `config` hook that emits
|
|
341
|
-
* the offending `optimizeDeps.rollupOptions.jsx`. `nifra.config.ts` writes `vitePlugins = [react()]`, so the
|
|
342
|
-
* plugin list arrives NESTED (`[[babel, refresh]]`). Without flattening, `.map` sees the inner array (which
|
|
343
|
-
* has no `config`), leaves it untouched, and Vite - which flattens plugin arrays itself before running them
|
|
344
|
-
* - then executes the un-stripped babel hook, so the warning survives. Flattening here (Vite accepts a flat
|
|
345
|
-
* list identically) is what lets the strip reach every real plugin.
|
|
346
|
-
*/
|
|
347
|
-
export function normalizeRolldownPlugins(
|
|
348
|
-
plugins: readonly unknown[],
|
|
349
|
-
isRolldown: boolean,
|
|
350
|
-
): readonly unknown[] {
|
|
351
|
-
if (!isRolldown) return plugins
|
|
352
|
-
const stripJsxKey = (returned: unknown): unknown => {
|
|
353
|
-
// Only touch a plain-object config carrying optimizeDeps.rollupOptions.jsx; leave anything else as-is.
|
|
354
|
-
if (returned === null || typeof returned !== "object") return returned
|
|
355
|
-
const cfg = returned as { optimizeDeps?: { rollupOptions?: Record<string, unknown> } }
|
|
356
|
-
const rollupOptions = cfg.optimizeDeps?.rollupOptions
|
|
357
|
-
if (rollupOptions === undefined || !("jsx" in rollupOptions)) return returned
|
|
358
|
-
// Clone the affected branch (never mutate the plugin's own return value) and drop the dead key.
|
|
359
|
-
const { jsx: _dropped, ...restRollup } = rollupOptions
|
|
360
|
-
return {
|
|
361
|
-
...cfg,
|
|
362
|
-
optimizeDeps: { ...cfg.optimizeDeps, rollupOptions: restRollup },
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return plugins.flat(Number.POSITIVE_INFINITY).map((plugin) => {
|
|
366
|
-
if (plugin === null || typeof plugin !== "object") return plugin
|
|
367
|
-
const p = plugin as VitePluginLike
|
|
368
|
-
// `config` may be a plain function or the object form `{ handler, order }`. Wrap the handler either way.
|
|
369
|
-
const hook = p.config
|
|
370
|
-
const handler = typeof hook === "function" ? hook : hook?.handler
|
|
371
|
-
if (typeof handler !== "function") return plugin
|
|
372
|
-
const wrappedHandler: ConfigFn = (config, env) => {
|
|
373
|
-
const returned = handler(config, env)
|
|
374
|
-
// The hook may return a promise - normalize both shapes.
|
|
375
|
-
return returned instanceof Promise ? returned.then(stripJsxKey) : stripJsxKey(returned)
|
|
376
|
-
}
|
|
377
|
-
// Preserve the ORIGINAL shape: a function stays a function; the object form keeps its `order`
|
|
378
|
-
// (collapsing `{ handler, order }` to a bare function would silently drop the hook ordering).
|
|
379
|
-
return {
|
|
380
|
-
...p,
|
|
381
|
-
config: typeof hook === "function" ? wrappedHandler : { ...hook, handler: wrappedHandler },
|
|
382
|
-
}
|
|
383
|
-
})
|
|
384
|
-
}
|
|
385
|
-
|
|
386
350
|
/**
|
|
387
351
|
* The npm package a bare module specifier belongs to (`@nifrajs/web-react/client` →
|
|
388
352
|
* `@nifrajs/web-react`, `some-adapter/client` → `some-adapter`), or `undefined` for a path-shaped
|
|
@@ -493,7 +457,38 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
493
457
|
handleWithVite(req, res)
|
|
494
458
|
})
|
|
495
459
|
|
|
460
|
+
// Vite's public middleware serves every file under the public directory, dotfiles included. A
|
|
461
|
+
// build ships no hidden file and the Bun pipeline's `servePublicDir` refuses one, so a request for
|
|
462
|
+
// `/.env` or `/.git/config` goes to the app here too, as it does in production.
|
|
463
|
+
function isHiddenPublicFile(url: string): boolean {
|
|
464
|
+
const configured = vite.config?.publicDir
|
|
465
|
+
if (configured === undefined || configured === "") return false
|
|
466
|
+
// Vite writes the path with `/`; resolved, it uses the platform's separator like `file`.
|
|
467
|
+
const publicDir = resolvePath(configured)
|
|
468
|
+
let rel: string
|
|
469
|
+
try {
|
|
470
|
+
rel = decodeURIComponent(new URL(url, "http://localhost").pathname).slice(1)
|
|
471
|
+
} catch {
|
|
472
|
+
return false
|
|
473
|
+
}
|
|
474
|
+
const file = resolvePath(publicDir, rel)
|
|
475
|
+
if (!file.startsWith(publicDir + sep) || !existsSync(file)) return false
|
|
476
|
+
if (isHiddenPublicPath(rel)) return true
|
|
477
|
+
// A visible name linking to a hidden file is refused too, as `servePublicDir` refuses it.
|
|
478
|
+
try {
|
|
479
|
+
const real = realpathSync(file)
|
|
480
|
+
const realRoot = realpathSync(publicDir)
|
|
481
|
+
return real.startsWith(realRoot + sep) && isHiddenPublicPath(real.slice(realRoot.length + 1))
|
|
482
|
+
} catch {
|
|
483
|
+
return false
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
496
487
|
function handleWithVite(req: IncomingMessage, res: ServerResponse): void {
|
|
488
|
+
if (isHiddenPublicFile(req.url ?? "/")) {
|
|
489
|
+
handleWithApp(req, res)
|
|
490
|
+
return
|
|
491
|
+
}
|
|
497
492
|
vite.middlewares(req, res, (error) => {
|
|
498
493
|
// In middleware mode Vite reports a module's transform error to the overlay and passes the
|
|
499
494
|
// request on. A request naming a source file was for a module, never a page: it gets the error,
|
|
@@ -514,60 +509,64 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
514
509
|
return
|
|
515
510
|
}
|
|
516
511
|
// Not a Vite asset → nifra SSR. (`next` runs after Vite declines, so the body is still readable.)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (requestId !== null) res.setHeader(DEV_REQUEST_ID_HEADER, requestId)
|
|
540
|
-
if (overlay || session.isOverlay(nifraRes)) {
|
|
541
|
-
res.setHeader("content-type", "text/html; charset=utf-8")
|
|
542
|
-
res.end(await nifraRes.text())
|
|
543
|
-
return
|
|
544
|
-
}
|
|
545
|
-
if (!contentType.includes("text/html")) {
|
|
546
|
-
// Data / redirect / asset response - pass through untouched, streamed (SSE-safe). Set-Cookie is
|
|
547
|
-
// emitted per-header so multi-cookie responses (e.g. better-auth sessions) aren't collapsed.
|
|
548
|
-
applyResponseHeaders(nifraRes.headers, res)
|
|
549
|
-
await pipeWebBodyToNode(nifraRes.body, res)
|
|
550
|
-
return
|
|
512
|
+
handleWithApp(req, res)
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function handleWithApp(req: IncomingMessage, res: ServerResponse): void {
|
|
517
|
+
void (async () => {
|
|
518
|
+
try {
|
|
519
|
+
const body = await readNodeBody(req)
|
|
520
|
+
const webRequest = toWebRequest(req, body)
|
|
521
|
+
let overlay = false
|
|
522
|
+
const nifraRes = await session.track(webRequest, async () => {
|
|
523
|
+
try {
|
|
524
|
+
return await app.fetch(webRequest)
|
|
525
|
+
} catch (err) {
|
|
526
|
+
// Source-map the stack first (Vite maps the bundled frames back to your `.ts`), then render
|
|
527
|
+
// the readable dev overlay instead of a bare text dump. Dev-only - production maps to `_error`.
|
|
528
|
+
if (err instanceof Error) vite.ssrFixStacktrace(err)
|
|
529
|
+
overlay = true
|
|
530
|
+
return new Response(
|
|
531
|
+
session.failure(err, { method: req.method ?? "GET", url: req.url ?? "/" }),
|
|
532
|
+
{ status: 500, headers: { "content-type": "text/html; charset=utf-8" } },
|
|
533
|
+
)
|
|
551
534
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
applyResponseHeaders(headers, res)
|
|
559
|
-
res.setHeader("content-type", "text/html; charset=utf-8")
|
|
560
|
-
res.end(html)
|
|
561
|
-
} catch (err) {
|
|
562
|
-
// A failure past the app (reading the body, Vite's HTML transform): same overlay, same feed.
|
|
563
|
-
if (err instanceof Error) vite.ssrFixStacktrace(err)
|
|
564
|
-
const html = session.failure(err, { method: req.method ?? "GET", url: req.url ?? "/" })
|
|
565
|
-
res.statusCode = 500
|
|
535
|
+
})
|
|
536
|
+
const contentType = nifraRes.headers.get("content-type") ?? ""
|
|
537
|
+
res.statusCode = nifraRes.status
|
|
538
|
+
const requestId = nifraRes.headers.get(DEV_REQUEST_ID_HEADER)
|
|
539
|
+
if (requestId !== null) res.setHeader(DEV_REQUEST_ID_HEADER, requestId)
|
|
540
|
+
if (overlay || session.isOverlay(nifraRes)) {
|
|
566
541
|
res.setHeader("content-type", "text/html; charset=utf-8")
|
|
567
|
-
res.end(
|
|
542
|
+
res.end(await nifraRes.text())
|
|
543
|
+
return
|
|
568
544
|
}
|
|
569
|
-
|
|
570
|
-
|
|
545
|
+
if (!contentType.includes("text/html")) {
|
|
546
|
+
// Data / redirect / asset response - pass through untouched, streamed (SSE-safe). Set-Cookie is
|
|
547
|
+
// emitted per-header so multi-cookie responses (e.g. better-auth sessions) aren't collapsed.
|
|
548
|
+
applyResponseHeaders(nifraRes.headers, res)
|
|
549
|
+
await pipeWebBodyToNode(nifraRes.body, res)
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
// Inject Vite's HMR client + the framework's refresh preamble into the SSR'd HTML, keeping the
|
|
553
|
+
// app's headers (cookies, CSP, cache-control) as the Bun pipeline does.
|
|
554
|
+
const headers = new Headers(nifraRes.headers)
|
|
555
|
+
headers.delete("content-length") // the body grows with Vite's tags
|
|
556
|
+
const page = session.decorateHtml(webRequest, await nifraRes.text(), headers, requestId)
|
|
557
|
+
const html = admitViteTags(await vite.transformIndexHtml(req.url ?? "/", page), headers)
|
|
558
|
+
applyResponseHeaders(headers, res)
|
|
559
|
+
res.setHeader("content-type", "text/html; charset=utf-8")
|
|
560
|
+
res.end(html)
|
|
561
|
+
} catch (err) {
|
|
562
|
+
// A failure past the app (reading the body, Vite's HTML transform): same overlay, same feed.
|
|
563
|
+
if (err instanceof Error) vite.ssrFixStacktrace(err)
|
|
564
|
+
const html = session.failure(err, { method: req.method ?? "GET", url: req.url ?? "/" })
|
|
565
|
+
res.statusCode = 500
|
|
566
|
+
res.setHeader("content-type", "text/html; charset=utf-8")
|
|
567
|
+
res.end(html)
|
|
568
|
+
}
|
|
569
|
+
})()
|
|
571
570
|
}
|
|
572
571
|
|
|
573
572
|
// `conditions: ["bun"]` makes Vite resolve nifra's workspace packages (`@nifrajs/web-react/client`, …) to
|
|
@@ -614,8 +613,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
614
613
|
// server importing vite unguarded is precisely what poisoned the module for the whole process.
|
|
615
614
|
const viteModule = await importVite<ViteModule>()
|
|
616
615
|
const { createServer } = viteModule
|
|
617
|
-
// Under rolldown-vite (Vite 8+),
|
|
618
|
-
// plugins still emit - it triggers a noisy "Invalid key … jsx" warning but does nothing useful here.
|
|
616
|
+
// Under rolldown-vite (Vite 8+), framework plugins' stale `optimizeDeps.rollupOptions` warns on every start.
|
|
619
617
|
const plugins = normalizeRolldownPlugins(
|
|
620
618
|
options.plugins ?? [],
|
|
621
619
|
viteModule.rolldownVersion !== undefined,
|