Differentiate the http-server's creation function with future create server functions that create other kinds of servers.
135 lines
3.6 KiB
TypeScript
135 lines
3.6 KiB
TypeScript
import { type ChildProcess, fork } from "node:child_process";
|
|
import * as fs from "node:fs";
|
|
import * as posix from "node:path/posix";
|
|
import { prettify, htmlDocument } from "antihtml";
|
|
import { pages } from "./pages.js";
|
|
import type { Page } from "./types.js";
|
|
import { resolveRefs } from "./utils/resolve-refs.js";
|
|
import { createHttpServer } from "./utils/http-server.js";
|
|
|
|
const srcDir = "build/node";
|
|
const webDir = "web";
|
|
|
|
function pageToHtml(page: Page) {
|
|
if (!page.ref.startsWith("/")) {
|
|
throw new Error(`ref "${page.ref}" for "${page.title}" is not absolute.`);
|
|
}
|
|
return htmlDocument(
|
|
prettify(
|
|
resolveRefs(
|
|
page.content,
|
|
posix.dirname(page.ref),
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
function assembleResources() {
|
|
const resources = new Map<string, string | Page>();
|
|
for (const entry of fs.readdirSync(webDir, { recursive: true, withFileTypes: true })) {
|
|
if (!entry.isFile())
|
|
continue;
|
|
const parentPath = entry.parentPath.replace(/\\/g, "/");
|
|
const ref = `${parentPath.slice(webDir.length)}/${entry.name}`;
|
|
resources.set(ref, `${parentPath}/${entry.name}`);
|
|
}
|
|
for (const page of pages) {
|
|
if (resources.has(page.ref)) {
|
|
const existing = resources.get(page.ref)!;
|
|
const other = typeof existing === "string" ? "a static resource" : `"${existing.title}"`;
|
|
throw new Error(`ref "${page.ref}" is taken up by both "${page.title}" and ${other}`)
|
|
}
|
|
resources.set(page.ref, page);
|
|
}
|
|
return resources;
|
|
}
|
|
|
|
function build() {
|
|
const outDir = "build/web";
|
|
const dirsCreated = new Set<string>()
|
|
for (const [ref, resource] of assembleResources()) {
|
|
const refDir = `${outDir}${posix.dirname(ref)}`;
|
|
if (!dirsCreated.has(refDir)) {
|
|
fs.mkdirSync(refDir, { recursive: true });
|
|
}
|
|
console.log(`writing ${outDir}${ref}`);
|
|
let content;
|
|
if (typeof resource === "string") {
|
|
content = fs.readFileSync(resource);
|
|
} else {
|
|
content = pageToHtml(resource);
|
|
}
|
|
fs.writeFileSync(`${outDir}${ref}`, content);
|
|
}
|
|
}
|
|
|
|
function serve() {
|
|
const resources = assembleResources();
|
|
const server = createHttpServer(
|
|
(ref) => {
|
|
const resource = resources.get(ref);
|
|
if (resource === undefined)
|
|
return undefined;
|
|
if (typeof resource === "string")
|
|
return fs.readFileSync(resource);
|
|
return Buffer.from(pageToHtml(resource));
|
|
}
|
|
);
|
|
|
|
server.listen(8080);
|
|
console.log("Listening on http://localhost:8080");
|
|
}
|
|
|
|
function watch(script: string) {
|
|
let child: ChildProcess;
|
|
function start() {
|
|
child = fork(script, ["serve"]);
|
|
}
|
|
function restart() {
|
|
if (child.exitCode === null) {
|
|
child.kill();
|
|
child.once("close", start);
|
|
} else {
|
|
start();
|
|
}
|
|
}
|
|
let restartTimeout: ReturnType<typeof setTimeout>;
|
|
function onChange() {
|
|
clearTimeout(restartTimeout);
|
|
restartTimeout = setTimeout(restart, 100)
|
|
}
|
|
fs.watch(webDir, { recursive: true }, onChange);
|
|
fs.watch(srcDir, { recursive: true }, onChange);
|
|
start();
|
|
}
|
|
|
|
function printUsage() {
|
|
console.log("Usage: cli.js <cmd>");
|
|
console.log(" build - Copy resources and generated pages to build directory.");
|
|
console.log(" serve - Host website on localhost:8080 for development purposes.");
|
|
console.log(" watch - Run serve and automatically restart it on changes.");
|
|
}
|
|
|
|
function main(runtime: string, script: string, args: string[]) {
|
|
if (!args.length) {
|
|
printUsage();
|
|
process.exitCode = 1;
|
|
return;
|
|
}
|
|
|
|
const [command, ...commandArgs] = args;
|
|
if (command === "build") {
|
|
build();
|
|
} else if (command === "serve") {
|
|
serve();
|
|
} else if (command === "watch") {
|
|
watch(script);
|
|
} else {
|
|
console.log(`Error: Unkown sub-command ${command}`);
|
|
printUsage();
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
const [runtime, script, ...args] = process.argv;
|
|
main(runtime, script, args)
|