import { posix } from "../deps/path.ts";import Events from "./events.ts";import { serveFile as HttpServeFile } from "../deps/http.ts";
import type { Event, EventListener, EventOptions } from "./events.ts";
export interface Options extends Deno.ServeOptions { root: string;}
export const defaults: Options = { root: `${Deno.cwd()}/_site`, port: 8000,};
export type RequestHandler = (req: Request) => Promise<Response>;export type Middleware = ( req: Request, next: RequestHandler, info: Deno.ServeHandlerInfo,) => Promise<Response>;
export interface ServerEvent extends Event { type: ServerEventType;
request?: Request;
error?: Error;}
export type ServerEventType = | "start" | "error";
export default class Server { events: Events<ServerEvent> = new Events<ServerEvent>(); options: Options; middlewares: Middleware[] = []; #server?: Deno.HttpServer;
constructor(options: Partial<Options> = {}) { this.options = { ...defaults, ...options }; }
use(...middleware: Middleware[]) { this.middlewares.push(...middleware); return this; }
addEventListener( type: ServerEventType, listener: EventListener<ServerEvent>, options?: EventOptions, ) { this.events.addEventListener(type, listener, options); return this; }
dispatchEvent(event: ServerEvent) { return this.events.dispatchEvent(event); }
start(signal?: Deno.ServeOptions["signal"]) { this.#server = Deno.serve({ ...this.options, signal, onListen: () => this.dispatchEvent({ type: "start" }), }, this.handle.bind(this)); }
stop() { try { this.#server?.shutdown(); } catch (error) { this.dispatchEvent({ type: "error", error, }); } }
async handle( request: Request, info: Deno.ServeHandlerInfo, ): Promise<Response> { const middlewares = [...this.middlewares];
const next: RequestHandler = async ( request: Request, ): Promise<Response> => { const middleware = middlewares.shift();
if (middleware) { return await middleware(request, next, info); }
return await serveFile(this.options.root, request); };
return await next(request); }}
export async function serveFile( root: string, request: Request,): Promise<Response> { const url = new URL(request.url); const pathname = decodeURIComponent(url.pathname); const path = posix.join(root, pathname);
try { const file = path.endsWith("/") ? path + "index.html" : path;
const info = await Deno.stat(file);
if (info.isDirectory) { return new Response(null, { status: 301, headers: { location: posix.join(pathname, "/"), }, }); }
return await HttpServeFile(request, file); } catch { try { if (!posix.extname(path)) { return await HttpServeFile(request, path + ".html"); } } catch { }
return new Response( "Not found", { status: 404 }, ); }}