154 lines
3.4 KiB
TypeScript
154 lines
3.4 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* crawl(dir: string): AsyncIterable<string> {
|
|
for await (const file of Deno.readDir(dir)) {
|
|
const fullPath = dir + "/" + file.name;
|
|
|
|
if (file.isDirectory) {
|
|
yield* crawl(fullPath);
|
|
} else {
|
|
yield fullPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function dev() {
|
|
const paths = [];
|
|
const ignoredFiles = ["bundler", "bundle", "dev", "test"];
|
|
|
|
for await (const path of crawl("./")) {
|
|
if (
|
|
path.endsWith(".ts") &&
|
|
!ignoredFiles.find((file) => path.includes(file))
|
|
) {
|
|
paths.push(path);
|
|
}
|
|
}
|
|
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();
|
|
}
|