-
Notifications
You must be signed in to change notification settings - Fork 6
/
serve.ts
110 lines (90 loc) · 2.86 KB
/
serve.ts
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
99
100
101
102
103
104
105
106
107
108
109
110
import { httpServe, join, readableStreamFromReader, relative } from "./deps.ts";
import { GenerateSiteOpts } from "./main.ts";
import { RE_HIDDEN_OR_UNDERSCORED } from "./constants.ts";
import type { BuildConfig } from "./types.d.ts";
interface WatchOpts {
runner: (opts: GenerateSiteOpts) => Promise<void>;
config: BuildConfig;
logLevel: 0 | 1 | 2;
}
interface ServeOpts extends WatchOpts {
port: number | null;
}
const sockets: Set<WebSocket> = new Set();
let servePath: string;
async function watch(opts: WatchOpts) {
const paths = [opts.config.inputPath, join(Deno.cwd(), ".ter")];
const watcher = Deno.watchFs(paths);
let timer = 0;
const isInOutputDir = (path: string): boolean =>
relative(opts.config.outputPath, path).startsWith("..");
// const isInConfigDir = (path: string): boolean =>
// relative(join(Deno.cwd(), ".ter"), path).startsWith("..") === false;
eventLoop:
for await (const event of watcher) {
if (["any", "access"].includes(event.kind)) {
continue;
}
for (const eventPath of event.paths) {
if (
eventPath.match(RE_HIDDEN_OR_UNDERSCORED) || !isInOutputDir(eventPath)
) {
continue eventLoop;
}
}
console.log(
`>>> ${event.kind}: ${relative(Deno.cwd(), event.paths[0])}`,
);
await opts.runner({
config: opts.config,
includeRefresh: true,
logLevel: opts.logLevel,
});
sockets.forEach((socket) => {
clearTimeout(timer);
timer = setTimeout(() => socket.send("refresh"), 1000);
});
}
}
function refreshMiddleware(req: Request): Response | null {
if (req.url.endsWith("/refresh")) {
const { response, socket } = Deno.upgradeWebSocket(req);
sockets.add(socket);
socket.onclose = () => {
sockets.delete(socket);
};
return response;
}
return null;
}
async function requestHandler(request: Request) {
const response = refreshMiddleware(request);
if (response) return response;
const url = new URL(request.url);
const filepath = decodeURIComponent(url.pathname);
let file;
try {
file = await Deno.open(join(servePath, filepath), { read: true });
const stat = await file.stat();
if (stat.isDirectory) {
file.close();
const filePath = join(servePath, filepath, "index.html");
file = await Deno.open(filePath, { read: true });
}
} catch {
// TODO: serve the 404.html
const resp = new Response("404 Not Found", { status: 404 });
console.log(`[${resp.status}]\t${url.pathname}`);
return resp;
}
const readableStream = readableStreamFromReader(file);
const resp = new Response(readableStream);
console.log(`[${resp.status}]\t${url.pathname}`);
return resp;
}
export function serve(opts: ServeOpts) {
servePath = opts.config.outputPath;
watch(opts);
if (opts.port) httpServe(requestHandler, { port: opts.port });
else httpServe(requestHandler);
}