import { existsSync, statSync } from "fs"; import path from "path"; import isDevelopment from "./is-development"; /** * File path -> mtime of the version currently held in Bun's module * registry (dev only). */ const loaded_versions = new Map(); /** * Imports a module by its stable path. * * Dev: when the file changed since it was last imported, its registry * entry is evicted first so the new version is evaluated and the old one * can be garbage collected. A cache-busting `?t=` query would instead * create a new, never-evicted module record on every call. * * Prod: plain import, so each module is loaded (and kept) once. */ export default async function importFresh(file_path) { if (!isDevelopment()) { return import(file_path); } let mtime; try { mtime = statSync(file_path).mtimeMs; } catch { } if (loaded_versions.has(file_path) && loaded_versions.get(file_path) !== mtime) { evictModule(file_path); } try { const mod = await import(file_path); loaded_versions.set(file_path, mtime); return mod; } catch (error) { // Don't keep a failed evaluation around: the next request retries. evictModule(file_path); throw error; } } /** * Drops a module from Bun's registry so its memory can be reclaimed and the * next import re-evaluates it. Only the module itself is evicted, not the * modules it imports. */ export function evictModule(file_path) { delete require.cache[file_path]; loaded_versions.delete(file_path); } /** * Evicts every registry entry under `dir` whose file no longer exists. Bundle * outputs (entries and shared chunks) are content-hashed and esbuild deletes * the superseded ones on rebuild, so a missing file means no current module * imports it anymore. */ export function evictDeletedModules(dir) { const prefix = dir.endsWith(path.sep) ? dir : dir + path.sep; for (const key of Object.keys(require.cache)) { if (key.startsWith(prefix) && !existsSync(key)) { evictModule(key); } } }