import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; import vm from "node:vm"; import * as crypto from "node:crypto"; import ts from "typescript"; const root = path.resolve(__dirname, "../.."); export function serverLoader(mocks: Record, env: Record = {}) { const modules = new Map(); const runtimeGlobal = {}; const load = (relative: string): any => { const filename = path.resolve(root, relative); if (modules.has(filename)) return modules.get(filename)!.exports; const module = { exports: {} }; modules.set(filename, module); const source = ts.transpileModule(readFileSync(filename, "utf8"), { fileName: filename, compilerOptions: { jsx: ts.JsxEmit.ReactJSX, module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, esModuleInterop: true, }, }).outputText; const nativeRequire = createRequire(filename); const requireMock = (name: string) => { if (name === "server-only") return {}; if (Object.prototype.hasOwnProperty.call(mocks, name)) return mocks[name]; if (name.startsWith("@/")) return load(`${name.slice(2)}.ts`); if (name.startsWith(".")) return load(path.relative(root, path.resolve(path.dirname(filename), `${name}.ts`))); return nativeRequire(name); }; const context = vm.createContext({ module, exports: module.exports, require: requireMock, globalThis: runtimeGlobal, process: { env, pid: process.pid, kill: process.kill }, Buffer, URL, URLSearchParams, DOMException, Response, crypto, setTimeout: mocks.setTimeout || setTimeout, clearTimeout, TextEncoder, sessionStorage: mocks.sessionStorage, setImmediate, AbortSignal, AbortController, ReadableStream, Headers, Blob, Request, Date: mocks.Date || Date, document: mocks.document, window: mocks.window, console: { log() {}, warn() {}, error() {}, info() {} }, fetch: mocks.fetch || (() => { throw new Error("Unexpected network call"); }), }); new vm.Script(source, { filename }).runInContext(context); return module.exports; }; return load; }