gale/dev_server.ts

95 lines
3.1 KiB
TypeScript
Raw Normal View History

2025-02-22 16:38:11 -05:00
import { contentType } from "jsr:@std/media-types";
// Parse the port from the command-line arguments, defaulting to 8000
const port = parseInt(Deno.args[0] || "8000", 10);
const sockets: WebSocket[] = [];
const connect = `<script>
const ws = new WebSocket('ws://localhost:${port}/ws');
ws.addEventListener('message', (event) => {
if (event.data === 'reload') {
window.location.reload();
}
});
ws.addEventListener('error', console.error);
ws.addEventListener('close', console.warn);
</script>`;
const bundle = await fetch(import.meta.resolve("./bundle.js")).then(r=>r.text());
let html: string;
try {
html = Deno.readTextFileSync("./index.html").replace("<head>", "<head>"+connect);
} catch (_) {
html = `
<!DOCTYPE html>
<html lang="en">
<head>
${connect}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style> * {margin: 0;padding: 0;box-sizing: border-box;}html, body {height: 100%;width: 100%;font-family: Arial, sans-serif;line-height: 1.6;}body {-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}img, video {max-width: 100%;height: auto;}a {text-decoration: none;color: inherit;}ul, ol {list-style: none;}button, input, textarea {font-family: inherit;font-size: inherit;line-height: inherit;border: none;background: none;padding: 0;margin: 0;outline: none;}table {border-collapse: collapse;width: 100%;}</style>
<script>${bundle}</script>
</head>
<body></body>
</html>`;
}
function extension(path: string): string {
// Remove trailing slash if it exists
const normalizedPath = path.endsWith("/") ? path.slice(0, -1) : path;
// Get the last part of the path
const lastPart = normalizedPath.split("/").pop() || "";
// Check if the last part contains a "."
return lastPart.split(".")[1] || "";
}
// Start the HTTP server using Deno.serve
Deno.serve({ port }, async (req: Request) => {
const url = new URL(req.url);
// Handle WebSocket connections
if (url.pathname === "/ws") {
const { socket, response } = Deno.upgradeWebSocket(req);
sockets.push(socket);
return response;
}
// Serve static files or the predefined HTML for non-file routes
const path = new URL(req.url).pathname;
const ext = extension(path);
// Serve the predefined HTML for non-file routes
if (!ext) {
return new Response(html, {
headers: { "Content-Type": "text/html" },
});
}
try {
const file = await Deno.open("." + path, { read: true });
return new Response(file.readable, {
headers: { "Content-Type": contentType(ext) || "application/javascript" },
});
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return new Response("File not found", { status: 404 });
} else {
return new Response("Internal server error", { status: 500 });
}
}
});
// Start watching for file changes
const watcher = Deno.watchFs(".");
for await (const event of watcher) {
if (event.kind === "modify") {
console.log("reload", event.paths);
for (const ws of sockets) {
if (ws.readyState === WebSocket.OPEN) {
ws.send("reload");
}
}
}
}