import { EventEmitter } from 'node:events'; import c from 'picocolors'; import createDebug from 'debug'; import { normalizeRequestId } from './utils.mjs'; import 'node:url'; import 'node:module'; import 'node:fs'; import 'pathe'; function createHmrEmitter() { const emitter = new EventEmitter(); return emitter; } function viteNodeHmrPlugin() { const emitter = createHmrEmitter(); return { name: "vite-node:hmr", config() { if (process.platform === "darwin" && process.env.VITE_TEST_WATCHER_DEBUG) { return { server: { watch: { useFsEvents: false, usePolling: false } } }; } }, configureServer(server) { const _send = server.ws.send; server.emitter = emitter; server.ws.send = function(payload) { _send(payload); emitter.emit("message", payload); }; if (process.env.VITE_TEST_WATCHER_DEBUG) { server.watcher.on("ready", () => { console.log("[debug] watcher is ready"); }); } } }; } const debugHmr = createDebug("vite-node:hmr"); const cache = /* @__PURE__ */ new WeakMap(); function getCache(runner) { if (!cache.has(runner)) { cache.set(runner, { hotModulesMap: /* @__PURE__ */ new Map(), dataMap: /* @__PURE__ */ new Map(), disposeMap: /* @__PURE__ */ new Map(), pruneMap: /* @__PURE__ */ new Map(), customListenersMap: /* @__PURE__ */ new Map(), ctxToListenersMap: /* @__PURE__ */ new Map(), messageBuffer: [], isFirstUpdate: false, pending: false, queued: [] }); } return cache.get(runner); } function sendMessageBuffer(runner, emitter) { const maps = getCache(runner); maps.messageBuffer.forEach((msg) => emitter.emit("custom", msg)); maps.messageBuffer.length = 0; } async function reload(runner, files) { Array.from(runner.moduleCache.keys()).forEach((fsPath) => { if (!fsPath.includes("node_modules")) runner.moduleCache.delete(fsPath); }); return Promise.all(files.map((file) => runner.executeId(file))); } async function notifyListeners(runner, event, data) { const maps = getCache(runner); const cbs = maps.customListenersMap.get(event); if (cbs) await Promise.all(cbs.map((cb) => cb(data))); } async function queueUpdate(runner, p) { const maps = getCache(runner); maps.queued.push(p); if (!maps.pending) { maps.pending = true; await Promise.resolve(); maps.pending = false; const loading = [...maps.queued]; maps.queued = []; (await Promise.all(loading)).forEach((fn) => fn && fn()); } } async function fetchUpdate(runner, { path, acceptedPath }) { path = normalizeRequestId(path); acceptedPath = normalizeRequestId(acceptedPath); const maps = getCache(runner); const mod = maps.hotModulesMap.get(path); if (!mod) { return; } const isSelfUpdate = path === acceptedPath; let fetchedModule; const qualifiedCallbacks = mod.callbacks.filter( ({ deps }) => deps.includes(acceptedPath) ); if (isSelfUpdate || qualifiedCallbacks.length > 0) { const disposer = maps.disposeMap.get(acceptedPath); if (disposer) await disposer(maps.dataMap.get(acceptedPath)); try { [fetchedModule] = await reload(runner, [acceptedPath]); } catch (e) { warnFailedFetch(e, acceptedPath); } } return () => { for (const { deps, fn } of qualifiedCallbacks) fn(deps.map((dep) => dep === acceptedPath ? fetchedModule : void 0)); const loggedPath = isSelfUpdate ? path : `${acceptedPath} via ${path}`; console.log(`${c.cyan("[vite-node]")} hot updated: ${loggedPath}`); }; } function warnFailedFetch(err, path) { if (!err.message.match("fetch")) console.error(err); console.error( `[hmr] Failed to reload ${path}. This could be due to syntax errors or importing non-existent modules. (see errors above)` ); } async function handleMessage(runner, emitter, files, payload) { const maps = getCache(runner); switch (payload.type) { case "connected": sendMessageBuffer(runner, emitter); break; case "update": await notifyListeners(runner, "vite:beforeUpdate", payload); await Promise.all(payload.updates.map((update) => { if (update.type === "js-update") return queueUpdate(runner, fetchUpdate(runner, update)); console.error(`${c.cyan("[vite-node]")} no support css hmr.}`); return null; })); await notifyListeners(runner, "vite:afterUpdate", payload); break; case "full-reload": await notifyListeners(runner, "vite:beforeFullReload", payload); maps.customListenersMap.delete("vite:beforeFullReload"); await reload(runner, files); break; case "custom": await notifyListeners(runner, payload.event, payload.data); break; case "prune": await notifyListeners(runner, "vite:beforePrune", payload); payload.paths.forEach((path) => { const fn = maps.pruneMap.get(path); if (fn) fn(maps.dataMap.get(path)); }); break; case "error": { await notifyListeners(runner, "vite:error", payload); const err = payload.err; console.error(`${c.cyan("[vite-node]")} Internal Server Error ${err.message} ${err.stack}`); break; } } } function createHotContext(runner, emitter, files, ownerPath) { debugHmr("createHotContext", ownerPath); const maps = getCache(runner); if (!maps.dataMap.has(ownerPath)) maps.dataMap.set(ownerPath, {}); const mod = maps.hotModulesMap.get(ownerPath); if (mod) mod.callbacks = []; const newListeners = /* @__PURE__ */ new Map(); maps.ctxToListenersMap.set(ownerPath, newListeners); function acceptDeps(deps, callback = () => { }) { const mod2 = maps.hotModulesMap.get(ownerPath) || { id: ownerPath, callbacks: [] }; mod2.callbacks.push({ deps, fn: callback }); maps.hotModulesMap.set(ownerPath, mod2); } const hot = { get data() { return maps.dataMap.get(ownerPath); }, acceptExports(_, callback) { acceptDeps([ownerPath], callback && (([mod2]) => callback(mod2))); }, accept(deps, callback) { if (typeof deps === "function" || !deps) { acceptDeps([ownerPath], ([mod2]) => deps && deps(mod2)); } else if (typeof deps === "string") { acceptDeps([deps], ([mod2]) => callback && callback(mod2)); } else if (Array.isArray(deps)) { acceptDeps(deps, callback); } else { throw new TypeError("invalid hot.accept() usage."); } }, dispose(cb) { maps.disposeMap.set(ownerPath, cb); }, prune(cb) { maps.pruneMap.set(ownerPath, cb); }, invalidate() { notifyListeners(runner, "vite:invalidate", { path: ownerPath, message: void 0 }); return reload(runner, files); }, on(event, cb) { const addToMap = (map) => { const existing = map.get(event) || []; existing.push(cb); map.set(event, existing); }; addToMap(maps.customListenersMap); addToMap(newListeners); }, off(event, cb) { const removeFromMap = (map) => { const existing = map.get(event); if (existing === void 0) return; const pruned = existing.filter((l) => l !== cb); if (pruned.length === 0) { map.delete(event); return; } map.set(event, pruned); }; removeFromMap(maps.customListenersMap); removeFromMap(newListeners); }, send(event, data) { maps.messageBuffer.push(JSON.stringify({ type: "custom", event, data })); sendMessageBuffer(runner, emitter); } }; return hot; } export { createHmrEmitter, createHotContext, getCache, handleMessage, reload, sendMessageBuffer, viteNodeHmrPlugin };