@nifrajs/web 4.0.0 → 4.0.2
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/client.d.ts.map +1 -1
- package/dist/client.js +25 -20
- package/dist/client.js.map +1 -1
- package/dist/internal/secret-scan.d.ts.map +1 -1
- package/dist/internal/secret-scan.js +20 -2
- package/dist/internal/secret-scan.js.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +11 -3
- package/dist/manifest.js.map +1 -1
- package/dist/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +10 -2
- package/dist/plugins/zone-guard.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +70 -16
- package/dist/vite.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +24 -18
- package/src/internal/secret-scan.ts +17 -2
- package/src/manifest.ts +15 -3
- package/src/plugins/zone-guard.ts +11 -2
- package/src/vite.ts +60 -16
package/src/vite.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* injects its HMR client + the framework's refresh preamble. Node `http` (not `Bun.serve`) because
|
|
11
11
|
* Vite's `middlewares` are Connect-style - it runs fine under Bun.
|
|
12
12
|
*/
|
|
13
|
-
import { writeFileSync } from "node:fs"
|
|
13
|
+
import { type Dirent, readdirSync, writeFileSync } from "node:fs"
|
|
14
14
|
import {
|
|
15
15
|
createServer as createHttpServer,
|
|
16
16
|
type IncomingMessage,
|
|
@@ -170,6 +170,7 @@ export { LAST_ERROR_PATH } from "./diagnostic.ts"
|
|
|
170
170
|
// The codegen'd client entry is written here (at the Vite root) so Vite serves + HMRs it.
|
|
171
171
|
const DEV_ENTRY = ".nifra-vite-entry.tsx"
|
|
172
172
|
const VITE_CLOSE_BOUND_MS = 2000
|
|
173
|
+
const POLL_INTERVAL_MS = 80
|
|
173
174
|
|
|
174
175
|
/** Candidate spellings for Vite's module-graph map. Vite has used native and slash-normalized
|
|
175
176
|
* absolute Windows paths across versions, while watcher events can additionally arrive as a file URL. */
|
|
@@ -184,6 +185,22 @@ const pathInside = (root: string, path: string): boolean => {
|
|
|
184
185
|
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
/** Every file below `dir` as an absolute path. A directory removed mid-walk contributes nothing. */
|
|
189
|
+
const filesUnder = (dir: string, into = new Set<string>()): Set<string> => {
|
|
190
|
+
let entries: Dirent[]
|
|
191
|
+
try {
|
|
192
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
193
|
+
} catch {
|
|
194
|
+
return into
|
|
195
|
+
}
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
const path = join(dir, entry.name)
|
|
198
|
+
if (entry.isDirectory()) filesUnder(path, into)
|
|
199
|
+
else into.add(path)
|
|
200
|
+
}
|
|
201
|
+
return into
|
|
202
|
+
}
|
|
203
|
+
|
|
187
204
|
const readNodeBody = async (req: IncomingMessage): Promise<Buffer | undefined> => {
|
|
188
205
|
if (req.method === "GET" || req.method === "HEAD") return undefined
|
|
189
206
|
const chunks: Buffer[] = []
|
|
@@ -415,6 +432,9 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
415
432
|
generateClientEntry(manifest, { clientModule: options.clientModule, resolve: toUrl }),
|
|
416
433
|
)
|
|
417
434
|
}
|
|
435
|
+
const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
|
|
436
|
+
// Listed before the entry is generated, so a file added in between is reconciled again, not lost.
|
|
437
|
+
const routeFiles = usePolling ? filesUnder(routesDir) : new Set<string>()
|
|
418
438
|
writeClientEntry()
|
|
419
439
|
const entryUrl = `/${DEV_ENTRY}`
|
|
420
440
|
|
|
@@ -578,7 +598,6 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
578
598
|
// path-shaped `clientModule` (tests) names no package; there is then nothing to externalize.
|
|
579
599
|
const adapterPackage = packageNameOf(options.clientModule)
|
|
580
600
|
const ssrExternal = [...(adapterPackage !== undefined ? [adapterPackage] : []), "@nifrajs/web"]
|
|
581
|
-
const usePolling = options.poll ?? process.env.CHOKIDAR_USEPOLLING === "1"
|
|
582
601
|
// The feed writes its record and log under `.nifra/`: watching them would turn every log flush into
|
|
583
602
|
// a "file change" that re-creates the app and marks every entry stale.
|
|
584
603
|
const watch: { ignored: RegExp[]; usePolling?: boolean; interval?: number } = {
|
|
@@ -587,7 +606,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
587
606
|
// Poll when native fs events aren't delivered (containers/sandboxes).
|
|
588
607
|
if (usePolling) {
|
|
589
608
|
watch.usePolling = true
|
|
590
|
-
watch.interval =
|
|
609
|
+
watch.interval = POLL_INTERVAL_MS
|
|
591
610
|
}
|
|
592
611
|
// Never `import("vite")` directly here: the guard in `importVite` has to run first, and the dev
|
|
593
612
|
// server importing vite unguarded is precisely what poisoned the module for the whole process.
|
|
@@ -722,20 +741,43 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
722
741
|
session.markChange()
|
|
723
742
|
refreshApp(normalizeFilePath(path))
|
|
724
743
|
})
|
|
744
|
+
const onAddUnlink = (event: "add" | "unlink", path: string): void => {
|
|
745
|
+
const filePath = normalizeFilePath(path)
|
|
746
|
+
const inRoutes = pathInside(routesDir, filePath)
|
|
747
|
+
if (inRoutes && usePolling) {
|
|
748
|
+
const file = resolvePath(filePath)
|
|
749
|
+
// Whichever of the watcher and the rescan below reports a route file first handles it.
|
|
750
|
+
if (routeFiles.has(file) === (event === "add")) return
|
|
751
|
+
if (event === "add") routeFiles.add(file)
|
|
752
|
+
else routeFiles.delete(file)
|
|
753
|
+
}
|
|
754
|
+
session.markChange()
|
|
755
|
+
if (inRoutes) {
|
|
756
|
+
writeClientEntry()
|
|
757
|
+
// A newly added route is not in Vite's module graph yet, so invalidating only the
|
|
758
|
+
// route path cannot evict the cached generated entry that imports the route. Evict the
|
|
759
|
+
// generated entry after rewriting it before recreating the SSR app; this also prevents
|
|
760
|
+
// an unlink refresh from briefly loading an entry that still references the deleted file.
|
|
761
|
+
invalidateImporterClosure(resolvePath(root, DEV_ENTRY))
|
|
762
|
+
}
|
|
763
|
+
refreshApp(filePath)
|
|
764
|
+
}
|
|
725
765
|
for (const event of ["add", "unlink"] as const) {
|
|
726
|
-
vite.watcher.on(event, (path) =>
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
766
|
+
vite.watcher.on(event, (path) => onAddUnlink(event, path))
|
|
767
|
+
}
|
|
768
|
+
// Chokidar reports ready before the poller has taken its baseline stat of a directory (Bun's
|
|
769
|
+
// fs.watchFile takes it asynchronously), and a route added or removed in that gap is folded into the
|
|
770
|
+
// baseline and never reported. The manifest comes from a directory scan, so a scan diffed against the
|
|
771
|
+
// route files already handled recovers those events.
|
|
772
|
+
const reconcileRouteFiles = (): void => {
|
|
773
|
+
const onDisk = filesUnder(routesDir)
|
|
774
|
+
try {
|
|
775
|
+
for (const file of onDisk) if (!routeFiles.has(file)) onAddUnlink("add", file)
|
|
776
|
+
for (const file of [...routeFiles]) if (!onDisk.has(file)) onAddUnlink("unlink", file)
|
|
777
|
+
} catch (err) {
|
|
778
|
+
console.error("[nifra/web/vite] route rescan failed:", err)
|
|
779
|
+
session.buildFailed(err, "route rescan failed")
|
|
780
|
+
}
|
|
739
781
|
}
|
|
740
782
|
await watcherReady
|
|
741
783
|
|
|
@@ -756,9 +798,11 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
756
798
|
const address = server.address()
|
|
757
799
|
const boundPort = typeof address === "object" && address !== null ? address.port : port
|
|
758
800
|
session.listening(boundPort)
|
|
801
|
+
const routeRescan = usePolling ? setInterval(reconcileRouteFiles, POLL_INTERVAL_MS) : undefined
|
|
759
802
|
return {
|
|
760
803
|
port: boundPort,
|
|
761
804
|
stop: async () => {
|
|
805
|
+
clearInterval(routeRescan)
|
|
762
806
|
session.stop()
|
|
763
807
|
// Cleared with the server that owns it: the slot is process-global, so a stopped server leaving
|
|
764
808
|
// its loader behind would hand the next one's adapter a graph that is closed (tests start and
|