trainsim/dev.ts
2025-02-02 23:29:40 -07:00

142 lines
3.2 KiB
TypeScript

/// <reference lib="deno.ns" />
import * as esbuild from "npm:esbuild";
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader";
import { serveDir } from "jsr:@std/http";
async function dev() {
const paths = [];
const ignoredFiles = ["bundler", "bundle", "dev"];
for (const path of Deno.readDirSync("./")) {
if (
path.name.endsWith(".ts") &&
!ignoredFiles.find((file) => path.name.includes(file))
) {
paths.push(path.name);
}
}
await build();
const watcher = Deno.watchFs(paths);
for await (const event of watcher) {
if (event.kind === "modify") {
console.log("File modified, bundling...");
await build();
}
}
}
async function build() {
const cfg = await import("./deno.json", {
with: { type: "json" },
});
const importMap = {
imports: cfg.default.imports,
};
const importMapURL = "data:application/json," + JSON.stringify(importMap);
console.log("File modified, bundling...");
try {
const result = await esbuild.build({
entryPoints: ["./main.ts"],
bundle: true,
outfile: "bundle.js",
plugins: [...denoPlugins({
importMapURL,
lockPath: "./deno.lock",
})],
loader: {
".ts": "ts",
".js": "js",
".jsx": "jsx",
".tsx": "tsx",
},
});
esbuild.stop();
console.log("Bundled successfully!");
sendSSE("data: build\n\n");
} catch (e) {
console.error(e);
// Deno.exit(1);
}
}
let sseStreams: ReadableStreamDefaultController[] = [];
function sendSSE(message: string) {
sseStreams.filter((stream) => {
try {
stream.enqueue(new TextEncoder().encode(message));
return true;
} catch {
return false;
}
});
}
function sse(r: Request) {
let controller: ReadableStreamDefaultController;
const body = new ReadableStream({
start(controller) {
sseStreams.push(controller);
},
cancel() {
sseStreams = sseStreams.filter((stream) => stream !== controller);
},
});
return new Response(body, {
status: 200,
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
if (Deno.args.includes("dev")) {
dev();
Deno.serve(async (r) => {
if (r.url.endsWith("sse")) {
return sse(r);
}
const d = await serveDir(r, {
fsRoot: ".",
showIndex: true,
});
if (d.headers.get("content-type")?.startsWith("text/html")) {
const body = await d.text();
return new Response(
body.replace(
"</body>",
`<script defer>
const source = new EventSource("/sse");
source.onmessage = (event) => {
if (event.data === "build") {
location.reload();
}
};
</script></body>`,
),
{
status: 200,
headers: {
"Content-Type": "text/html",
"Cache-Control": "no-cache",
},
},
);
}
if (d.url.endsWith(".js")) {
d.headers.set("Cache-Control", "no-cache");
d.headers.set("Content-Type", "application/javascript");
}
return d;
});
} else {
await build();
}