@nifrajs/web 2.1.0 → 2.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/README.md +3 -3
- package/dist/build-vite.d.ts +68 -0
- package/dist/build-vite.d.ts.map +1 -0
- package/dist/build-vite.js +433 -0
- package/dist/build-vite.js.map +1 -0
- package/dist/build.d.ts +174 -65
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +376 -135
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +83 -0
- package/dist/bun-dev-entry.d.ts.map +1 -0
- package/dist/bun-dev-entry.js +136 -0
- package/dist/bun-dev-entry.js.map +1 -0
- package/dist/chunk-names.js +1 -1
- package/dist/client.d.ts +3 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +209 -27
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +2 -2
- package/dist/conformance.js +3 -3
- package/dist/deferred.d.ts +11 -11
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +16 -16
- package/dist/dev-error.d.ts +2 -2
- package/dist/dev-error.js +3 -3
- package/dist/dev-port.d.ts +36 -0
- package/dist/dev-port.d.ts.map +1 -0
- package/dist/dev-port.js +48 -0
- package/dist/dev-port.js.map +1 -0
- package/dist/dev.d.ts +62 -5
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +419 -60
- package/dist/dev.js.map +1 -1
- package/dist/draft.d.ts +44 -7
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +92 -9
- package/dist/draft.js.map +1 -1
- package/dist/fn-state.d.ts +56 -0
- package/dist/fn-state.d.ts.map +1 -0
- package/dist/fn-state.js +77 -0
- package/dist/fn-state.js.map +1 -0
- package/dist/fn.d.ts +111 -0
- package/dist/fn.d.ts.map +1 -0
- package/dist/fn.js +125 -0
- package/dist/fn.js.map +1 -0
- package/dist/fonts-google.d.ts +9 -9
- package/dist/fonts-google.js +10 -10
- package/dist/fonts.d.ts +7 -7
- package/dist/fonts.js +1 -1
- package/dist/forms.d.ts +8 -8
- package/dist/fs.d.ts +15 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +8 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +266 -74
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +869 -179
- package/dist/index.js.map +1 -1
- package/dist/internal/head-attributes.d.ts +15 -0
- package/dist/internal/head-attributes.d.ts.map +1 -0
- package/dist/internal/head-attributes.js +99 -0
- package/dist/internal/head-attributes.js.map +1 -0
- package/dist/internal/script-types.d.ts +14 -0
- package/dist/internal/script-types.d.ts.map +1 -0
- package/dist/internal/script-types.js +17 -0
- package/dist/internal/script-types.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +60 -0
- package/dist/internal/server-boundary.d.ts.map +1 -0
- package/dist/internal/server-boundary.js +174 -0
- package/dist/internal/server-boundary.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +16 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -0
- package/dist/internal/timing-safe-equal.js +26 -0
- package/dist/internal/timing-safe-equal.js.map +1 -0
- package/dist/internal/vite-import.d.ts +63 -0
- package/dist/internal/vite-import.d.ts.map +1 -0
- package/dist/internal/vite-import.js +94 -0
- package/dist/internal/vite-import.js.map +1 -0
- package/dist/islands.d.ts +5 -5
- package/dist/islands.js +5 -5
- package/dist/isr.d.ts +15 -15
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +8 -21
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +12 -6
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +121 -40
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +136 -26
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +91 -0
- package/dist/module-graph.d.ts.map +1 -0
- package/dist/module-graph.js +92 -0
- package/dist/module-graph.js.map +1 -0
- package/dist/navigation.d.ts +120 -5
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +37 -5
- package/dist/navigation.js.map +1 -1
- package/dist/plugins/css-modules.d.ts +3 -3
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +0 -0
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +8 -8
- package/dist/plugins/kit.js +9 -9
- package/dist/plugins/scss.js +3 -3
- package/dist/plugins/vite-leak-guard.d.ts +45 -0
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -0
- package/dist/plugins/vite-leak-guard.js +67 -0
- package/dist/plugins/vite-leak-guard.js.map +1 -0
- package/dist/plugins/vite-server-fn.d.ts +23 -0
- package/dist/plugins/vite-server-fn.d.ts.map +1 -0
- package/dist/plugins/vite-server-fn.js +51 -0
- package/dist/plugins/vite-server-fn.js.map +1 -0
- package/dist/plugins/vite-server-only.d.ts +49 -0
- package/dist/plugins/vite-server-only.d.ts.map +1 -0
- package/dist/plugins/vite-server-only.js +57 -0
- package/dist/plugins/vite-server-only.js.map +1 -0
- package/dist/prerender.d.ts +5 -5
- package/dist/prerender.js +7 -7
- package/dist/public-dir.d.ts +36 -0
- package/dist/public-dir.d.ts.map +1 -0
- package/dist/public-dir.js +89 -0
- package/dist/public-dir.js.map +1 -0
- package/dist/query.d.ts +10 -10
- package/dist/query.js +15 -15
- package/dist/route-manifest.d.ts +91 -0
- package/dist/route-manifest.d.ts.map +1 -0
- package/dist/route-manifest.js +128 -0
- package/dist/route-manifest.js.map +1 -0
- package/dist/router.d.ts +94 -22
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +170 -58
- package/dist/router.js.map +1 -1
- package/dist/search.d.ts +94 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +232 -0
- package/dist/search.js.map +1 -0
- package/dist/server-only.d.ts +6 -6
- package/dist/server-only.js +7 -7
- package/dist/service-worker.d.ts +56 -0
- package/dist/service-worker.d.ts.map +1 -0
- package/dist/service-worker.js +127 -0
- package/dist/service-worker.js.map +1 -0
- package/dist/vite.d.ts +25 -17
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +75 -33
- package/dist/vite.js.map +1 -1
- package/package.json +48 -4
- package/server-manifest.d.ts +31 -0
- package/src/build-vite.ts +553 -0
- package/src/build.ts +563 -172
- package/src/bun-dev-entry.ts +184 -0
- package/src/chunk-names.ts +1 -1
- package/src/client.ts +221 -30
- package/src/conformance.ts +3 -3
- package/src/deferred.ts +17 -17
- package/src/dev-error.ts +3 -3
- package/src/dev-port.ts +63 -0
- package/src/dev.ts +468 -66
- package/src/draft.ts +124 -12
- package/src/fn-state.ts +107 -0
- package/src/fn.ts +199 -0
- package/src/fonts-google.ts +13 -13
- package/src/fonts.ts +7 -7
- package/src/forms.ts +8 -8
- package/src/fs.ts +24 -6
- package/src/index.ts +1174 -225
- package/src/internal/head-attributes.ts +109 -0
- package/src/internal/script-types.ts +18 -0
- package/src/internal/server-boundary.ts +187 -0
- package/src/internal/timing-safe-equal.ts +24 -0
- package/src/internal/vite-import.ts +92 -0
- package/src/islands.ts +7 -7
- package/src/isr.ts +18 -30
- package/src/llms-txt.ts +13 -5
- package/src/manifest.ts +264 -48
- package/src/module-graph.ts +146 -0
- package/src/navigation.ts +151 -5
- package/src/plugins/css-modules.ts +0 -0
- package/src/plugins/kit.ts +11 -11
- package/src/plugins/scss.ts +3 -3
- package/src/plugins/vite-leak-guard.ts +110 -0
- package/src/plugins/vite-server-fn.ts +67 -0
- package/src/plugins/vite-server-only.ts +75 -0
- package/src/prerender.ts +10 -10
- package/src/public-dir.ts +108 -0
- package/src/query.ts +22 -22
- package/src/route-manifest.ts +240 -0
- package/src/router.ts +288 -74
- package/src/search.ts +297 -0
- package/src/server-only.ts +7 -7
- package/src/service-worker.ts +165 -0
- package/src/vite.ts +103 -49
package/src/vite.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@nifrajs/web/vite`
|
|
2
|
+
* `@nifrajs/web/vite` - a **dev-only** server with true HMR, backed by Vite in middleware mode. nifra's
|
|
3
3
|
* production pipeline stays Bun-native (`@nifrajs/web/build` → `Bun.build`); this is purely for the dev
|
|
4
4
|
* loop. Bun's own HMR is dev-server-only and DCE's `import.meta.hot` under `Bun.build` (nifra's bundler),
|
|
5
5
|
* so HMR comes from Vite + the framework's official Vite plugin (React Fast Refresh, Vue/Svelte/Solid
|
|
6
|
-
* HMR)
|
|
6
|
+
* HMR) - which you inject via `plugins` (the same structural-injection idiom as the Bun/codec plugins).
|
|
7
7
|
*
|
|
8
8
|
* Flow: Vite serves + HMR-swaps the client modules (nifra's codegen'd entry + the route files); nifra
|
|
9
9
|
* still **SSRs** each request, and the rendered HTML is run through `vite.transformIndexHtml` so Vite
|
|
10
10
|
* injects its HMR client + the framework's refresh preamble. Node `http` (not `Bun.serve`) because
|
|
11
|
-
* Vite's `middlewares` are Connect-style
|
|
11
|
+
* Vite's `middlewares` are Connect-style - it runs fine under Bun.
|
|
12
12
|
*/
|
|
13
13
|
import { writeFileSync } from "node:fs"
|
|
14
14
|
import {
|
|
@@ -18,20 +18,29 @@ import {
|
|
|
18
18
|
} from "node:http"
|
|
19
19
|
import { relative, resolve as resolvePath } from "node:path"
|
|
20
20
|
import { renderDevErrorOverlay } from "./dev-error.ts"
|
|
21
|
+
import { listenOrExplain } from "./dev-port.ts"
|
|
21
22
|
import { discoverRoutes } from "./fs.ts"
|
|
22
23
|
import { DEFAULT_DEV_PORT, generateClientEntry } from "./index.ts"
|
|
24
|
+
import { vitePublicEnvPrefix } from "./internal/server-boundary.ts"
|
|
25
|
+
import { importVite } from "./internal/vite-import.ts"
|
|
26
|
+
import { viteServerFnStub } from "./plugins/vite-server-fn.ts"
|
|
27
|
+
import { viteServerOnlyEmpty } from "./plugins/vite-server-only.ts"
|
|
23
28
|
|
|
24
|
-
/** Minimal app surface
|
|
29
|
+
/** Minimal app surface - `createWebApp(...)` satisfies it. */
|
|
25
30
|
interface FetchApp {
|
|
26
31
|
fetch(request: Request): Response | Promise<Response>
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
// Structural slice of the Vite dev server this module drives (avoids a hard type dep on `vite`).
|
|
30
35
|
interface ViteLike {
|
|
36
|
+
/** Load a module through VITE's graph - the seam that makes the Vite pipeline own SSR too. */
|
|
37
|
+
ssrLoadModule(url: string): Promise<Record<string, unknown>>
|
|
31
38
|
readonly middlewares: (req: IncomingMessage, res: ServerResponse, next: () => void) => void
|
|
32
39
|
transformIndexHtml(url: string, html: string): Promise<string>
|
|
33
40
|
ssrFixStacktrace(err: Error): void
|
|
34
|
-
readonly watcher: {
|
|
41
|
+
readonly watcher: {
|
|
42
|
+
on(event: "change" | "add" | "unlink", cb: (path: string) => void): void
|
|
43
|
+
}
|
|
35
44
|
close(): Promise<void>
|
|
36
45
|
}
|
|
37
46
|
interface ViteModule {
|
|
@@ -40,7 +49,7 @@ interface ViteModule {
|
|
|
40
49
|
readonly rolldownVersion?: string
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
// `node:http` server type the request handler runs on (also the HMR WebSocket host
|
|
52
|
+
// `node:http` server type the request handler runs on (also the HMR WebSocket host - see below).
|
|
44
53
|
type NodeHttpServer = ReturnType<typeof createHttpServer>
|
|
45
54
|
|
|
46
55
|
export interface ViteDevServerOptions {
|
|
@@ -49,15 +58,22 @@ export interface ViteDevServerOptions {
|
|
|
49
58
|
/** Client runtime module providing `mountRouter` (e.g. `"@nifrajs/web-react/client"`). */
|
|
50
59
|
readonly clientModule: string
|
|
51
60
|
/**
|
|
52
|
-
* Build the nifra app for the given dev client-entry URL.
|
|
53
|
-
*
|
|
54
|
-
*
|
|
61
|
+
* Build the nifra app for the given dev client-entry URL.
|
|
62
|
+
*
|
|
63
|
+
* `load` resolves a route module through **Vite's** graph (`ssrLoadModule`). Pass it to
|
|
64
|
+
* `discoverRoutes(routesDir, { load })` so SSR and the client are resolved by the SAME toolchain.
|
|
65
|
+
* Without it SSR resolves through Bun while the client resolves through Vite - two resolvers, one
|
|
66
|
+
* process - which is what makes `resolve.dedupe` fail to reach SSR and produces the dual-React
|
|
67
|
+
* crash. Vite re-evaluates on change, so no `importQuery` cache-buster is needed alongside it.
|
|
55
68
|
*/
|
|
56
|
-
readonly createApp: (
|
|
57
|
-
|
|
69
|
+
readonly createApp: (
|
|
70
|
+
clientEntry: string,
|
|
71
|
+
load: (absolutePath: string) => Promise<unknown>,
|
|
72
|
+
) => FetchApp | Promise<FetchApp>
|
|
73
|
+
/** Vite plugins - inject your framework's official plugin, e.g. `[react()]`. */
|
|
58
74
|
readonly plugins?: readonly unknown[]
|
|
59
75
|
/**
|
|
60
|
-
* Extra `resolve.conditions` prepended ahead of nifra's defaults
|
|
76
|
+
* Extra `resolve.conditions` prepended ahead of nifra's defaults - some frameworks need their own
|
|
61
77
|
* (e.g. Solid's `"solid"` condition routes `solid-js` to its source/JSX-dev build).
|
|
62
78
|
*/
|
|
63
79
|
readonly conditions?: readonly string[]
|
|
@@ -72,10 +88,14 @@ export interface ViteDevServerOptions {
|
|
|
72
88
|
readonly port?: number
|
|
73
89
|
/**
|
|
74
90
|
* Use polling for the file watcher. Native fs events (fsevents/inotify) are unreliable inside
|
|
75
|
-
* containers, networked filesystems, and some sandboxes
|
|
91
|
+
* containers, networked filesystems, and some sandboxes - there, HMR silently never fires. Set
|
|
76
92
|
* `true` (or the env var `CHOKIDAR_USEPOLLING=1`) to poll instead. Default: off (native events).
|
|
77
93
|
*/
|
|
78
94
|
readonly poll?: boolean
|
|
95
|
+
/** Vite public directory. Defaults to `<root>/public`; `false` disables it. */
|
|
96
|
+
readonly publicDir?: string | false
|
|
97
|
+
/** Client-visible environment prefix (default `"PUBLIC_"`; empty disables exposure). */
|
|
98
|
+
readonly publicEnvPrefix?: string
|
|
79
99
|
}
|
|
80
100
|
|
|
81
101
|
export interface ViteDevServer {
|
|
@@ -114,11 +134,11 @@ function toWebRequest(req: IncomingMessage, body: Buffer | undefined): Request {
|
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
// A Vite `config` hook: a plain function, OR the object form `{ handler, order }` Vite accepts for hook
|
|
117
|
-
// ordering (`order: "pre" | "post"`). We wrap the handler in either shape
|
|
137
|
+
// ordering (`order: "pre" | "post"`). We wrap the handler in either shape - see normalizeRolldownPlugins.
|
|
118
138
|
type ConfigFn = (config: unknown, env: unknown) => unknown
|
|
119
139
|
type ConfigHook = ConfigFn | { readonly handler: ConfigFn; readonly order?: unknown }
|
|
120
140
|
|
|
121
|
-
// A Vite plugin
|
|
141
|
+
// A Vite plugin - the only hook we wrap is `config`. Typed structurally (no `vite` type dep). Everything
|
|
122
142
|
// else on the plugin object is preserved by spread, so wrapping is transparent to Vite.
|
|
123
143
|
interface VitePluginLike {
|
|
124
144
|
readonly name?: string
|
|
@@ -126,7 +146,7 @@ interface VitePluginLike {
|
|
|
126
146
|
readonly [key: string]: unknown
|
|
127
147
|
}
|
|
128
148
|
|
|
129
|
-
/** The bits of a Node ServerResponse `pipeWebBodyToNode` touches
|
|
149
|
+
/** The bits of a Node ServerResponse `pipeWebBodyToNode` touches - structural, to avoid a node:http dep here. */
|
|
130
150
|
interface NodeResLike {
|
|
131
151
|
flushHeaders?(): void
|
|
132
152
|
on(event: "close", cb: () => void): void
|
|
@@ -141,7 +161,7 @@ interface NodeHeaderSink {
|
|
|
141
161
|
|
|
142
162
|
/**
|
|
143
163
|
* Copy a Web `Response`'s headers onto a Node response, emitting EACH `Set-Cookie` as its own header. The
|
|
144
|
-
* `Headers` iterator (and `.get`) join multiple set-cookie values with ", ", which corrupts cookies
|
|
164
|
+
* `Headers` iterator (and `.get`) join multiple set-cookie values with ", ", which corrupts cookies - e.g.
|
|
145
165
|
* better-auth's `session_token` + `session_data` collapse into one unparseable cookie and the session is
|
|
146
166
|
* silently lost. `getSetCookie()` returns them split; Node's `setHeader` emits one header per array element.
|
|
147
167
|
*/
|
|
@@ -156,7 +176,7 @@ export function applyResponseHeaders(headers: Headers, res: NodeHeaderSink): voi
|
|
|
156
176
|
|
|
157
177
|
/**
|
|
158
178
|
* Stream a Web `Response` body to a Node response chunk-by-chunk. Buffering the whole body (e.g.
|
|
159
|
-
* `arrayBuffer()`) waits for the stream to END
|
|
179
|
+
* `arrayBuffer()`) waits for the stream to END - which an open-ended SSE (`text/event-stream`) body never
|
|
160
180
|
* does, so it hung `nifra dev` (the Bun production server streamed it fine). This flushes each chunk as it
|
|
161
181
|
* arrives and cancels the reader if the client disconnects; a finite body just streams its chunk(s) + ends.
|
|
162
182
|
*/
|
|
@@ -178,23 +198,23 @@ export async function pipeWebBodyToNode(
|
|
|
178
198
|
res.write(value)
|
|
179
199
|
}
|
|
180
200
|
} catch {
|
|
181
|
-
// client disconnected mid-stream
|
|
201
|
+
// client disconnected mid-stream - the `close` handler already cancelled the reader
|
|
182
202
|
}
|
|
183
203
|
res.end()
|
|
184
204
|
}
|
|
185
205
|
|
|
186
206
|
/**
|
|
187
207
|
* Strip `optimizeDeps.rollupOptions.jsx` from a plugin's `config` hook output when running under
|
|
188
|
-
* rolldown-vite
|
|
208
|
+
* rolldown-vite - the source of the scary, harmless `Warning: Invalid input options … "jsx" Invalid
|
|
189
209
|
* key: Expected never but received "jsx"` on `nifra dev`.
|
|
190
210
|
*
|
|
191
211
|
* Why it happens: `@vitejs/plugin-react@4.x` (and peers) target an *older* rolldown-vite optimizeDeps
|
|
192
|
-
* API
|
|
212
|
+
* API - they inject `optimizeDeps.rollupOptions.jsx` to tell the dep pre-bundler to transform JSX. Vite
|
|
193
213
|
* 8's rolldown dep-optimizer renamed that surface to `optimizeDeps.rolldownOptions` (and moved jsx under
|
|
194
214
|
* `transform.jsx`), so the stale `rollupOptions.jsx` is an unrecognized input option → the warning. It's
|
|
195
215
|
* a version-skew artifact, not a real misconfig: the route source JSX transform runs through the
|
|
196
216
|
* plugin's own `transform` hook (untouched here), and node_modules deps that get pre-bundled almost
|
|
197
|
-
* never contain raw JSX
|
|
217
|
+
* never contain raw JSX - so dropping the dead key changes no behavior and keeps HMR/Fast Refresh
|
|
198
218
|
* intact. We *strip* (rather than translate to `rolldownOptions`) so the fix is version-agnostic: a
|
|
199
219
|
* plugin already emitting the correct `rolldownOptions` is left untouched, and a future plugin bump that
|
|
200
220
|
* stops emitting `rollupOptions.jsx` makes this a no-op.
|
|
@@ -202,12 +222,12 @@ export async function pipeWebBodyToNode(
|
|
|
202
222
|
* Scoped narrowly: only the `optimizeDeps.rollupOptions.jsx` key is removed, only under rolldown-vite,
|
|
203
223
|
* and only from the value a plugin's `config` hook returns. Non-rolldown Vite is passed through verbatim.
|
|
204
224
|
*
|
|
205
|
-
* FLATTEN FIRST: a Vite plugin factory may return an ARRAY of plugins
|
|
225
|
+
* FLATTEN FIRST: a Vite plugin factory may return an ARRAY of plugins - `@vitejs/plugin-react`'s `react()`
|
|
206
226
|
* returns `[vite:react-babel, vite:react-refresh]`, and it's `react:react-babel`'s `config` hook that emits
|
|
207
227
|
* the offending `optimizeDeps.rollupOptions.jsx`. `nifra.config.ts` writes `vitePlugins = [react()]`, so the
|
|
208
228
|
* plugin list arrives NESTED (`[[babel, refresh]]`). Without flattening, `.map` sees the inner array (which
|
|
209
|
-
* has no `config`), leaves it untouched, and Vite
|
|
210
|
-
*
|
|
229
|
+
* has no `config`), leaves it untouched, and Vite - which flattens plugin arrays itself before running them
|
|
230
|
+
* - then executes the un-stripped babel hook, so the warning survives. Flattening here (Vite accepts a flat
|
|
211
231
|
* list identically) is what lets the strip reach every real plugin.
|
|
212
232
|
*/
|
|
213
233
|
export function normalizeRolldownPlugins(
|
|
@@ -237,7 +257,7 @@ export function normalizeRolldownPlugins(
|
|
|
237
257
|
if (typeof handler !== "function") return plugin
|
|
238
258
|
const wrappedHandler: ConfigFn = (config, env) => {
|
|
239
259
|
const returned = handler(config, env)
|
|
240
|
-
// The hook may return a promise
|
|
260
|
+
// The hook may return a promise - normalize both shapes.
|
|
241
261
|
return returned instanceof Promise ? returned.then(stripJsxKey) : stripJsxKey(returned)
|
|
242
262
|
}
|
|
243
263
|
// Preserve the ORIGINAL shape: a function stays a function; the object form keeps its `order`
|
|
@@ -259,21 +279,23 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
259
279
|
const port = options.port ?? DEFAULT_DEV_PORT
|
|
260
280
|
|
|
261
281
|
// Codegen the client entry with Vite-servable, root-relative specifiers (e.g. `/routes/index.tsx`).
|
|
262
|
-
const manifest = discoverRoutes(routesDir)
|
|
263
282
|
const toUrl = (file: string): string => `/${relative(root, `${routesDir}/${file}`)}`
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
const writeClientEntry = (): void => {
|
|
284
|
+
const manifest = discoverRoutes(routesDir)
|
|
285
|
+
writeFileSync(
|
|
286
|
+
resolvePath(root, DEV_ENTRY),
|
|
287
|
+
generateClientEntry(manifest, { clientModule: options.clientModule, resolve: toUrl }),
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
writeClientEntry()
|
|
268
291
|
const entryUrl = `/${DEV_ENTRY}`
|
|
269
292
|
|
|
270
293
|
// Create our HTTP server FIRST, then hand it to Vite as the HMR WebSocket host (`hmr.server`). In
|
|
271
|
-
// middleware mode Vite would otherwise open a *separate* ws port (24678)
|
|
294
|
+
// middleware mode Vite would otherwise open a *separate* ws port (24678) - fragile: it conflicts
|
|
272
295
|
// across restarts ("Port undefined is already in use" → the client never connects). Sharing one
|
|
273
296
|
// port means HMR rides the same origin as the app, robust across restarts. The handler closes over
|
|
274
297
|
// `vite`, which is assigned just below before the server starts listening.
|
|
275
298
|
let vite!: ViteLike
|
|
276
|
-
let version = 0
|
|
277
299
|
let app: FetchApp
|
|
278
300
|
|
|
279
301
|
const server: NodeHttpServer = createHttpServer((req, res) => {
|
|
@@ -286,7 +308,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
286
308
|
const contentType = nifraRes.headers.get("content-type") ?? ""
|
|
287
309
|
res.statusCode = nifraRes.status
|
|
288
310
|
if (!contentType.includes("text/html")) {
|
|
289
|
-
// Data / redirect / asset response
|
|
311
|
+
// Data / redirect / asset response - pass through untouched, streamed (SSE-safe). Set-Cookie is
|
|
290
312
|
// emitted per-header so multi-cookie responses (e.g. better-auth sessions) aren't collapsed.
|
|
291
313
|
applyResponseHeaders(nifraRes.headers, res)
|
|
292
314
|
await pipeWebBodyToNode(nifraRes.body, res)
|
|
@@ -298,7 +320,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
298
320
|
res.end(html)
|
|
299
321
|
} catch (err) {
|
|
300
322
|
// Source-map the stack first (Vite maps the bundled frames back to your `.ts`), then render
|
|
301
|
-
// the readable dev overlay instead of a bare text dump. Dev-only
|
|
323
|
+
// the readable dev overlay instead of a bare text dump. Dev-only - production maps to `_error`.
|
|
302
324
|
if (err instanceof Error) vite.ssrFixStacktrace(err)
|
|
303
325
|
res.statusCode = 500
|
|
304
326
|
res.setHeader("content-type", "text/html; charset=utf-8")
|
|
@@ -309,18 +331,23 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
309
331
|
})
|
|
310
332
|
|
|
311
333
|
// `conditions: ["bun"]` makes Vite resolve nifra's workspace packages (`@nifrajs/web-react/client`, …) to
|
|
312
|
-
// their TS **source**
|
|
334
|
+
// their TS **source** - so the dev server needs no prior `dist` build of the adapter packages.
|
|
313
335
|
const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
|
|
314
|
-
|
|
336
|
+
// Never `import("vite")` directly here: the guard in `importVite` has to run first, and the dev
|
|
337
|
+
// server importing vite unguarded is precisely what poisoned the module for the whole process.
|
|
338
|
+
const viteModule = await importVite<ViteModule>()
|
|
315
339
|
const { createServer } = viteModule
|
|
316
340
|
// Under rolldown-vite (Vite 8+), strip the stale `optimizeDeps.rollupOptions.jsx` some framework
|
|
317
|
-
// plugins still emit
|
|
341
|
+
// plugins still emit - it triggers a noisy "Invalid key … jsx" warning but does nothing useful here.
|
|
318
342
|
const plugins = normalizeRolldownPlugins(
|
|
319
343
|
options.plugins ?? [],
|
|
320
344
|
viteModule.rolldownVersion !== undefined,
|
|
321
345
|
)
|
|
322
346
|
vite = await createServer({
|
|
323
347
|
root,
|
|
348
|
+
// Dev must not inherit Vite's default `VITE_*` exposure beside Nifra's documented boundary.
|
|
349
|
+
envPrefix: vitePublicEnvPrefix(options.publicEnvPrefix),
|
|
350
|
+
...(options.publicDir !== undefined ? { publicDir: options.publicDir } : {}),
|
|
324
351
|
appType: "custom",
|
|
325
352
|
server: {
|
|
326
353
|
middlewareMode: true,
|
|
@@ -328,34 +355,61 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
328
355
|
// Explicit watch config; poll when native fs events aren't delivered (containers/sandboxes).
|
|
329
356
|
watch: usePolling ? { usePolling: true, interval: 80 } : {},
|
|
330
357
|
},
|
|
331
|
-
plugins:
|
|
358
|
+
// Ahead of the user's plugins: a `*.fn` module must be replaced before anything else
|
|
359
|
+
// reads it, and the dev server is a client bundler like any other.
|
|
360
|
+
plugins: [viteServerFnStub(), viteServerOnlyEmpty(), ...plugins],
|
|
332
361
|
resolve: {
|
|
333
362
|
conditions: [...(options.conditions ?? []), "bun", "module", "browser", "development"],
|
|
334
363
|
// Dedupe React to ONE copy. In a multi-root workspace a shared package can pull react/react-dom
|
|
335
364
|
// from a SIBLING app's node_modules, so the dev server loads two React cores → a second hook
|
|
336
365
|
// dispatcher → `resolveDispatcher().useState` null on any hook-using route (the error points at the
|
|
337
|
-
// component, not the resolution
|
|
366
|
+
// component, not the resolution - brutal to diagnose). Mirrors the build-time reactDedupePlugin so
|
|
338
367
|
// dev matches prod. No-op for non-React apps (the package simply isn't present to dedupe).
|
|
339
368
|
dedupe: ["react", "react-dom"],
|
|
340
369
|
},
|
|
341
370
|
...(options.define ? { define: options.define } : {}),
|
|
342
371
|
})
|
|
343
372
|
|
|
344
|
-
|
|
373
|
+
const ssrLoad = (absolutePath: string): Promise<unknown> => vite.ssrLoadModule(absolutePath)
|
|
374
|
+
app = await options.createApp(entryUrl, ssrLoad)
|
|
345
375
|
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
376
|
+
// Re-create the app on change so a hard reload picks up a route ADD/REMOVE (the manifest comes
|
|
377
|
+
// from a directory scan, which `ssrLoadModule` cannot invalidate). Module CONTENT no longer needs a
|
|
378
|
+
// version counter: Vite owns SSR resolution now and re-evaluates changed modules itself, which is
|
|
379
|
+
// the cache-busting the old `importQuery` was emulating against Bun's import cache.
|
|
380
|
+
let refreshVersion = 0
|
|
381
|
+
const refreshApp = (): void => {
|
|
382
|
+
const version = ++refreshVersion
|
|
383
|
+
Promise.resolve(options.createApp(entryUrl, ssrLoad))
|
|
350
384
|
.then((next) => {
|
|
351
|
-
app = next
|
|
385
|
+
if (version === refreshVersion) app = next
|
|
352
386
|
})
|
|
353
387
|
.catch((err) => console.error("[nifra/web/vite] app re-create failed:", err))
|
|
354
|
-
}
|
|
388
|
+
}
|
|
389
|
+
vite.watcher.on("change", refreshApp)
|
|
390
|
+
for (const event of ["add", "unlink"] as const) {
|
|
391
|
+
vite.watcher.on(event, (path) => {
|
|
392
|
+
if (path.startsWith(`${routesDir}/`) || path.startsWith(`${routesDir}\\`)) writeClientEntry()
|
|
393
|
+
refreshApp()
|
|
394
|
+
})
|
|
395
|
+
}
|
|
355
396
|
|
|
356
|
-
|
|
397
|
+
try {
|
|
398
|
+
await listenOrExplain(server, port)
|
|
399
|
+
} catch (err) {
|
|
400
|
+
// Nothing is listening, but Vite is fully up by now - watchers, the dep optimizer, its own sockets -
|
|
401
|
+
// and every one of those keeps the event loop alive. Left open, the process prints the diagnosis and
|
|
402
|
+
// then HANGS on it, which is worse than the raw crash this guard replaced: the user sees a dev server
|
|
403
|
+
// that appears to be starting. Tear Vite down so the failure is terminal.
|
|
404
|
+
await vite.close().catch(() => {})
|
|
405
|
+
throw err
|
|
406
|
+
}
|
|
407
|
+
// Report the port actually BOUND, not the one requested. They differ for `port: 0`, which is how you
|
|
408
|
+
// ask the OS for a free one - the correct thing to do in a test or when running several apps at once.
|
|
409
|
+
// Echoing the request back would return a literal 0, which connects to nothing.
|
|
410
|
+
const address = server.address()
|
|
357
411
|
return {
|
|
358
|
-
port,
|
|
412
|
+
port: typeof address === "object" && address !== null ? address.port : port,
|
|
359
413
|
stop: async () => {
|
|
360
414
|
server.close()
|
|
361
415
|
await vite.close()
|