release: WRNexusJS 0.4.0
This commit is contained in:
@@ -8,13 +8,16 @@
|
||||
* process and HMR socket stay alive.
|
||||
*/
|
||||
|
||||
import { watch, type FSWatcher } from "node:fs";
|
||||
import { existsSync, statSync, watch, type FSWatcher } from "node:fs";
|
||||
import { isAbsolute, join, relative, resolve } from "node:path";
|
||||
import type { HmrHub } from "./hmr.ts";
|
||||
import type { DevAssetServer } from "./assets.ts";
|
||||
import type { DevToolbarCollector } from "@wrnexus/dev-toolbar/server";
|
||||
|
||||
export interface WatchOptions {
|
||||
appDir: string;
|
||||
/** Additional package component/runtime/style directories watched for HMR. */
|
||||
extraDirs?: string[];
|
||||
hub: HmrHub;
|
||||
assets: DevAssetServer;
|
||||
devToolbarCollector?: DevToolbarCollector;
|
||||
@@ -39,8 +42,12 @@ function classify(rel: string): Kind {
|
||||
return "server";
|
||||
}
|
||||
|
||||
/** Returns the watcher so the running server can close it during shutdown. */
|
||||
export function startWatcher(opts: WatchOptions): FSWatcher | undefined {
|
||||
export interface WatchHandle {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
/** Returns a composite watcher so the running server can close every source root. */
|
||||
export function startWatcher(opts: WatchOptions): WatchHandle | undefined {
|
||||
const { appDir, hub, assets } = opts;
|
||||
const pending = new Set<Kind>();
|
||||
const pendingFiles = new Set<string>();
|
||||
@@ -72,18 +79,45 @@ export function startWatcher(opts: WatchOptions): FSWatcher | undefined {
|
||||
pendingFiles.clear();
|
||||
};
|
||||
|
||||
try {
|
||||
return watch(appDir, { recursive: true }, (_event, filename) => {
|
||||
if (!filename) return;
|
||||
const rel = filename.toString().replace(/\\/g, "/");
|
||||
if (isIgnored(rel)) return;
|
||||
pendingFiles.add(rel);
|
||||
pending.add(classify(rel));
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(flush, 200); // debounce editor write bursts
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("[wrnexus] file watching unavailable; HMR disabled", err);
|
||||
return undefined;
|
||||
const appRoot = resolve(appDir);
|
||||
const candidates = [appRoot, ...(opts.extraDirs ?? []).map((dir) => resolve(dir))];
|
||||
const roots = [...new Set(candidates)]
|
||||
.filter((dir) => existsSync(dir) && statSync(dir).isDirectory())
|
||||
.filter(
|
||||
(dir, index, values) =>
|
||||
!values.some((other, otherIndex) => {
|
||||
if (otherIndex >= index) return false;
|
||||
const nested = relative(other, dir);
|
||||
return nested === "" || (!nested.startsWith("..") && !isAbsolute(nested));
|
||||
}),
|
||||
);
|
||||
const watchers: FSWatcher[] = [];
|
||||
|
||||
for (const root of roots) {
|
||||
try {
|
||||
const external = root !== appRoot;
|
||||
watchers.push(
|
||||
watch(root, { recursive: true }, (_event, filename) => {
|
||||
if (!filename) return;
|
||||
const relativeFile = filename.toString().replace(/\\/g, "/");
|
||||
if (isIgnored(relativeFile)) return;
|
||||
const file = external ? join(root, relativeFile).replace(/\\/g, "/") : relativeFile;
|
||||
pendingFiles.add(file);
|
||||
pending.add(classify(file));
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = setTimeout(flush, 200); // debounce editor write bursts
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn(`[wrnexus] file watching unavailable for ${root}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!watchers.length) return undefined;
|
||||
return {
|
||||
close() {
|
||||
if (timer) clearTimeout(timer);
|
||||
for (const watcher of watchers) watcher.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user