@nifrajs/web 2.3.0 → 2.5.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/dev-diagnostics.d.ts +20 -0
- package/dist/dev-diagnostics.d.ts.map +1 -0
- package/dist/dev-diagnostics.js +34 -0
- package/dist/dev-diagnostics.js.map +1 -0
- package/dist/dev-error.d.ts +10 -13
- package/dist/dev-error.d.ts.map +1 -1
- package/dist/dev-error.js +50 -36
- package/dist/dev-error.js.map +1 -1
- package/dist/dev-port.d.ts +1 -1
- package/dist/dev-port.d.ts.map +1 -1
- package/dist/dev-port.js +9 -3
- package/dist/dev-port.js.map +1 -1
- package/dist/dev.d.ts +1 -1
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +19 -3
- package/dist/dev.js.map +1 -1
- package/dist/diagnostic.d.ts +93 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +205 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +5 -4
- package/dist/router.js.map +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +78 -4
- package/dist/vite.js.map +1 -1
- package/package.json +8 -2
- package/src/dev-diagnostics.ts +47 -0
- package/src/dev-error.ts +63 -32
- package/src/dev-port.ts +15 -3
- package/src/dev.ts +24 -8
- package/src/diagnostic.ts +284 -0
- package/src/router.ts +7 -4
- package/src/vite.ts +79 -4
package/src/vite.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type ServerResponse,
|
|
18
18
|
} from "node:http"
|
|
19
19
|
import { relative, resolve as resolvePath } from "node:path"
|
|
20
|
-
import {
|
|
20
|
+
import { createDevDiagnostics } from "./dev-diagnostics.ts"
|
|
21
21
|
import { listenOrExplain } from "./dev-port.ts"
|
|
22
22
|
import { discoverRoutes } from "./fs.ts"
|
|
23
23
|
import { DEFAULT_DEV_PORT, generateClientEntry } from "./index.ts"
|
|
@@ -103,6 +103,8 @@ export interface ViteDevServer {
|
|
|
103
103
|
stop(): Promise<void>
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
export { LAST_ERROR_PATH } from "./diagnostic.ts"
|
|
107
|
+
|
|
106
108
|
// The codegen'd client entry is written here (at the Vite root) so Vite serves + HMRs it.
|
|
107
109
|
const DEV_ENTRY = ".nifra-vite-entry.tsx"
|
|
108
110
|
|
|
@@ -269,6 +271,22 @@ export function normalizeRolldownPlugins(
|
|
|
269
271
|
})
|
|
270
272
|
}
|
|
271
273
|
|
|
274
|
+
/**
|
|
275
|
+
* The npm package a bare module specifier belongs to (`@nifrajs/web-react/client` →
|
|
276
|
+
* `@nifrajs/web-react`, `some-adapter/client` → `some-adapter`), or `undefined` for a path-shaped
|
|
277
|
+
* specifier (relative/absolute), which names no package.
|
|
278
|
+
*/
|
|
279
|
+
function packageNameOf(specifier: string): string | undefined {
|
|
280
|
+
if (specifier.startsWith(".") || specifier.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(specifier))
|
|
281
|
+
return undefined
|
|
282
|
+
const parts = specifier.split("/")
|
|
283
|
+
return specifier.startsWith("@")
|
|
284
|
+
? parts.length >= 2
|
|
285
|
+
? `${parts[0]}/${parts[1]}`
|
|
286
|
+
: undefined
|
|
287
|
+
: parts[0]
|
|
288
|
+
}
|
|
289
|
+
|
|
272
290
|
/**
|
|
273
291
|
* Start the Vite-backed dev server: Vite serves/HMRs the client; nifra SSRs each request and Vite
|
|
274
292
|
* injects its HMR client + the framework refresh preamble via `transformIndexHtml`.
|
|
@@ -297,8 +315,19 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
297
315
|
// `vite`, which is assigned just below before the server starts listening.
|
|
298
316
|
let vite!: ViteLike
|
|
299
317
|
let app: FetchApp
|
|
318
|
+
// The most recent SSR failure, served as JSON at LAST_ERROR_PATH. Shared with the Bun adapter so the
|
|
319
|
+
// endpoint and its headers can't drift between the two dev servers.
|
|
320
|
+
const devDiagnostics = createDevDiagnostics(root)
|
|
300
321
|
|
|
301
322
|
const server: NodeHttpServer = createHttpServer((req, res) => {
|
|
323
|
+
// Keep this before Vite's middleware: it is an agent endpoint owned by nifra, not a file or app route.
|
|
324
|
+
if (devDiagnostics.isLastErrorPath((req.url ?? "/").split("?", 1)[0] ?? "/")) {
|
|
325
|
+
const { body, headers } = devDiagnostics.lastError()
|
|
326
|
+
res.statusCode = 200
|
|
327
|
+
for (const [key, value] of Object.entries(headers)) res.setHeader(key, value)
|
|
328
|
+
res.end(body)
|
|
329
|
+
return
|
|
330
|
+
}
|
|
302
331
|
vite.middlewares(req, res, () => {
|
|
303
332
|
// Not a Vite asset → nifra SSR. (`next` runs after Vite declines, so the body is still readable.)
|
|
304
333
|
void (async () => {
|
|
@@ -322,9 +351,13 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
322
351
|
// Source-map the stack first (Vite maps the bundled frames back to your `.ts`), then render
|
|
323
352
|
// the readable dev overlay instead of a bare text dump. Dev-only - production maps to `_error`.
|
|
324
353
|
if (err instanceof Error) vite.ssrFixStacktrace(err)
|
|
354
|
+
const html = devDiagnostics.capture(err, {
|
|
355
|
+
method: req.method ?? "GET",
|
|
356
|
+
url: req.url ?? "/",
|
|
357
|
+
})
|
|
325
358
|
res.statusCode = 500
|
|
326
359
|
res.setHeader("content-type", "text/html; charset=utf-8")
|
|
327
|
-
res.end(
|
|
360
|
+
res.end(html)
|
|
328
361
|
}
|
|
329
362
|
})()
|
|
330
363
|
})
|
|
@@ -332,6 +365,34 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
332
365
|
|
|
333
366
|
// `conditions: ["bun"]` makes Vite resolve nifra's workspace packages (`@nifrajs/web-react/client`, …) to
|
|
334
367
|
// their TS **source** - so the dev server needs no prior `dist` build of the adapter packages.
|
|
368
|
+
//
|
|
369
|
+
// That covers the CLIENT half only: Vite resolves SSR through `ssr.resolve.*`, which ignores
|
|
370
|
+
// `resolve.conditions`. Left at its defaults, SSR resolves a package's `default` export (`dist/…`)
|
|
371
|
+
// while the app's own imports of the same package run under Bun and resolve the `bun` export
|
|
372
|
+
// (`src/…`). Two consequences, both real bugs:
|
|
373
|
+
// 1. The adapter package loads TWICE - the app's Bun import (the adapter passed to `createWebApp`)
|
|
374
|
+
// and the route modules' Vite-SSR import - so `createContext` runs twice and the routing hooks
|
|
375
|
+
// (`useSearch`/`useParams`/`useLocation`) read a context the render never provided: they
|
|
376
|
+
// SSR-render their EMPTY defaults in dev while the loader (same request!) sees the real values.
|
|
377
|
+
// 2. SSR reads `dist`, which nothing in the dev loop rebuilds - a stale `dist` then 500s inside
|
|
378
|
+
// framework code and reads exactly like a framework regression.
|
|
379
|
+
// Fix, below: `ssr.resolve.{conditions,externalConditions}` mirror the client conditions (so SSR
|
|
380
|
+
// resolves the same files Bun does), and the adapter package + `@nifrajs/web` are `ssr.external`
|
|
381
|
+
// (so Bun's OWN module cache serves them to route modules - one instance, one `RouterContext`).
|
|
382
|
+
const resolveConditions = [
|
|
383
|
+
...(options.conditions ?? []),
|
|
384
|
+
"bun",
|
|
385
|
+
"module",
|
|
386
|
+
"browser",
|
|
387
|
+
"development",
|
|
388
|
+
]
|
|
389
|
+
// SSR must not pick `browser` builds; otherwise identical to the client list so both halves agree.
|
|
390
|
+
const ssrConditions = [...(options.conditions ?? []), "bun", "module", "node", "development"]
|
|
391
|
+
// The adapter package (from `clientModule`, e.g. `@nifrajs/web-react/client` → `@nifrajs/web-react`)
|
|
392
|
+
// - identity-sensitive (its `RouterContext` must be the ONE the Bun-imported adapter provides). A
|
|
393
|
+
// path-shaped `clientModule` (tests) names no package; there is then nothing to externalize.
|
|
394
|
+
const adapterPackage = packageNameOf(options.clientModule)
|
|
395
|
+
const ssrExternal = [...(adapterPackage !== undefined ? [adapterPackage] : []), "@nifrajs/web"]
|
|
335
396
|
const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
|
|
336
397
|
// Never `import("vite")` directly here: the guard in `importVite` has to run first, and the dev
|
|
337
398
|
// server importing vite unguarded is precisely what poisoned the module for the whole process.
|
|
@@ -359,7 +420,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
359
420
|
// reads it, and the dev server is a client bundler like any other.
|
|
360
421
|
plugins: [viteServerFnStub(), viteServerOnlyEmpty(), ...plugins],
|
|
361
422
|
resolve: {
|
|
362
|
-
conditions:
|
|
423
|
+
conditions: resolveConditions,
|
|
363
424
|
// Dedupe React to ONE copy. In a multi-root workspace a shared package can pull react/react-dom
|
|
364
425
|
// from a SIBLING app's node_modules, so the dev server loads two React cores → a second hook
|
|
365
426
|
// dispatcher → `resolveDispatcher().useState` null on any hook-using route (the error points at the
|
|
@@ -367,6 +428,20 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
367
428
|
// dev matches prod. No-op for non-React apps (the package simply isn't present to dedupe).
|
|
368
429
|
dedupe: ["react", "react-dom"],
|
|
369
430
|
},
|
|
431
|
+
ssr: {
|
|
432
|
+
// Explicit listing forces external even for workspace-LINKED packages (Vite's default keeps
|
|
433
|
+
// linked deps internal), so a monorepo dev run gets the same single-instance guarantee as an
|
|
434
|
+
// npm-installed one.
|
|
435
|
+
external: ssrExternal,
|
|
436
|
+
resolve: {
|
|
437
|
+
// Non-externalized deps (a linked `@platform/*`, say): Vite's runner evaluates them, but from
|
|
438
|
+
// the same files Bun would pick - `bun`-conditioned source, never a stale `dist`.
|
|
439
|
+
conditions: ssrConditions,
|
|
440
|
+
// Externalized deps: Vite resolves the specifier, Bun imports the result. `bun` first so the
|
|
441
|
+
// resolved file IS the one already in Bun's module cache (one evaluation, shared context).
|
|
442
|
+
externalConditions: ssrConditions,
|
|
443
|
+
},
|
|
444
|
+
},
|
|
370
445
|
...(options.define ? { define: options.define } : {}),
|
|
371
446
|
})
|
|
372
447
|
|
|
@@ -395,7 +470,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
395
470
|
}
|
|
396
471
|
|
|
397
472
|
try {
|
|
398
|
-
await listenOrExplain(server, port)
|
|
473
|
+
await listenOrExplain(server, port, "127.0.0.1")
|
|
399
474
|
} catch (err) {
|
|
400
475
|
// Nothing is listening, but Vite is fully up by now - watchers, the dep optimizer, its own sockets -
|
|
401
476
|
// and every one of those keeps the event loop alive. Left open, the process prints the diagnosis and
|