summary refs log tree commit diff stats
path: root/src/index.ts
blob: 790c8561445350017aa2f8cbbae4356602c6dd29 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import path from "node:path";
import fs, { Stats } from "node:fs";
import fsp from "node:fs/promises";
import { withHtmlLiveReload } from "bun-html-live-reload";

import readConfig from "./config";

const base = "../website/";
const publicDir = path.resolve(base, "public") + path.sep;

const config = readConfig(base);
const defaultHeaders = config.extra.headers;

type File = {
  filename: string;
  headers?: Record<string, string>;
  size: number;
  mtime: Date;
};

let files = new Map<string, File>();

function registerFile(pathname: string, filename: string, stat: Stats): void {
  pathname = "/" + (pathname === "." || pathname === "./" ? "" : pathname);

  if (files.get(pathname) !== undefined) {
    console.warn("File already registered:", pathname);
  }
  files.set(pathname, {
    filename,
    headers:
      pathname === "/404.html"
        ? Object.assign({}, defaultHeaders, { "cache-control": "no-cache" })
        : undefined,
    size: stat.size,
    mtime: stat.mtime,
  });
}

function walkDirectory(root: string, dir: string) {
  const absDir = path.join(root, dir);
  for (let pathname of fs.readdirSync(absDir)) {
    const relPath = path.join(dir, pathname);
    const absPath = path.join(absDir, pathname);
    const stat = fs.statSync(absPath);
    if (stat.isDirectory()) {
      walkDirectory(root, relPath + path.sep);
    } else if (stat.isFile()) {
      if (pathname === "index.html") {
        const dir = path.dirname(relPath);
        registerFile(dir, absPath, stat);
        if (dir !== ".") {
          registerFile(dir + path.sep, absPath, stat);
        }
      }
      registerFile(relPath, absPath, stat);
    }
  }
}

walkDirectory(publicDir, "");

async function serveFile(
  file: File | undefined,
  statusCode: number = 200,
): Promise<Response> {
  if (file && (await fsp.exists(file.filename))) {
    return new Response(Bun.file(file.filename), {
      headers: {
        "last-modified": file.mtime.toUTCString(),
        ...(file.headers || defaultHeaders),
      },
      status: statusCode,
    });
  } else {
    return serveFile(files.get("/404.html"), 404);
  }
}

function parseIfModifiedSinceHeader(header: string | null): number {
  return header ? new Date(header).getTime() + 999 : 0;
}

export default withHtmlLiveReload({
  fetch: async function (request) {
    const pathname = new URL(request.url).pathname;
    const file = files.get(pathname);
    if (file) {
      const ims = parseIfModifiedSinceHeader(
        request.headers.get("if-modified-since"),
      );
      if (ims >= file?.mtime.getTime()) {
        return new Response("", { status: 304 });
      }
    }
    return serveFile(file);
  },
});