import { path, emit, graph } from "./deps.ts";import { createEtagHash, should304 } from "./_etag.ts";
function parseCwd(cwd: string): string { if (cwd.startsWith("https://") || cwd.startsWith("http://")) { cwd = path.join(cwd, ".."); } else if (cwd.startsWith("file://")) { cwd = path.join(path.fromFileUrl(cwd), ".."); } return cwd;}
export interface ServeBundleOptions { cwd?: string; url: string;}
interface BundleCache { code: string; headers: Headers; etag: string; modified: Date;}
const bundleCache = new Map<string, Promise<BundleCache>>();
async function bundle(url: string): Promise<string> { const code = (await emit.bundle(path.fromFileUrl(url), { allowRemote: true, type: "module", compilerOptions: { inlineSources: false, inlineSourceMap: false, sourceMap: false, }, load: async (specifier) => { const res = await fetch(specifier); const headers: Record<string, string> = {}; for (const [k, v] of res.headers.entries()) { if (!headers[k]) { headers[k] = v; } } return { kind: "module", specifier: res.url, headers: headers, content: await res.text(), }; }, })).code;
return code.split("//# sourceMappingURL=")[0];}
export async function serveBundle( req: Request, opt: ServeBundleOptions,): Promise<Response> { const cwd = opt.cwd ? parseCwd(opt.cwd) : Deno.cwd(); let url = opt.url; if ( !url.startsWith("file://") && !url.startsWith("https://") && !url.startsWith("http://") ) { url = path.toFileUrl(path.resolve(cwd, url)).href; }
let cache = bundleCache.get(url); if (cache) { const { code, headers, etag, modified } = await cache; if (should304({ req, etag, modified })) { return new Response(null, { status: 304, headers }); } return new Response(code, { headers }); }
const createCache = async (): Promise<BundleCache> => { try { const code = await bundle(url); const etag = await createEtagHash(code); const modified = new Date(); const headers = new Headers(); headers.set("content-type", "text/javascript; charset=UTF-8"); headers.set("etag", etag); headers.set("last-modified", modified.toUTCString()); return { code, headers, etag, modified }; } catch (err) { bundleCache.delete(url); throw err; } };
cache = createCache(); bundleCache.set(url, cache);
(async () => { try { while (true) { await cache;
const deps = (await graph.createGraph(url)).modules .filter(m => m.specifier.startsWith("file://")) .map(m => path.fromFileUrl(m.specifier)); for await (const _ of Deno.watchFs(deps)) { break; }
cache = createCache(); bundleCache.set(url, cache); } } catch { return; } })();
const { code, headers } = await cache; return new Response(code, { headers });}