79 lines
2.3 KiB
JavaScript
79 lines
2.3 KiB
JavaScript
import path from "node:path"
|
|
import {promises as fs} from "node:fs"
|
|
import {fileURLToPath} from "node:url"
|
|
|
|
import "dotenv/config"
|
|
import {serve} from "@hono/node-server"
|
|
import {serveStatic} from "@hono/node-server/serve-static"
|
|
import {Hono} from "hono"
|
|
|
|
import {createPreview} from "./server/preview.js"
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = path.dirname(__filename)
|
|
|
|
const BUILD_DIR = path.join(__dirname, "build")
|
|
const INDEX_PATH = path.join(BUILD_DIR, "index.html")
|
|
|
|
const PORT = parseInt(process.env.PORT || "", 10) || 3000
|
|
const HOST = process.env.HOST || "0.0.0.0"
|
|
|
|
let TEMPLATE_HTML = ""
|
|
try {
|
|
TEMPLATE_HTML = await fs.readFile(INDEX_PATH, "utf8")
|
|
} catch (error) {
|
|
console.error(`Unable to read ${INDEX_PATH}. Run "pnpm run build" first.`)
|
|
process.exit(1)
|
|
}
|
|
|
|
const app = new Hono()
|
|
|
|
// Only allow GET and HEAD requests
|
|
app.use("*", async (context, next) => {
|
|
const method = context.req.method
|
|
if (method !== "GET" && method !== "HEAD") {
|
|
return context.text("Method Not Allowed", 405, {Allow: "GET, HEAD"})
|
|
}
|
|
await next()
|
|
})
|
|
|
|
// Serve static assets with appropriate caching
|
|
app.use(
|
|
"*",
|
|
serveStatic({
|
|
root: BUILD_DIR,
|
|
onFound: (filePath, context) => {
|
|
const isImmutable = filePath.split(path.sep).join("/").includes("/_app/immutable/")
|
|
const cacheControl =
|
|
path.basename(filePath) === "index.html"
|
|
? "no-cache"
|
|
: isImmutable
|
|
? "public, max-age=31536000, immutable"
|
|
: "public, max-age=3600"
|
|
|
|
context.header("Cache-Control", cacheControl)
|
|
|
|
// Immutable assets are content-hashed by Vite, so the filename is itself a
|
|
// stable content identifier. Exposing it as an ETag lets clients that
|
|
// revalidate explicitly (e.g. emoji-picker-element checks its data source
|
|
// on every load) skip re-downloading large files when nothing changed.
|
|
if (isImmutable) {
|
|
context.header("ETag", `"${path.basename(filePath)}"`)
|
|
}
|
|
},
|
|
}),
|
|
)
|
|
|
|
// A tld looks like a file extension, so a client route can arrive looking like a missing asset.
|
|
app.route("/", createPreview(TEMPLATE_HTML))
|
|
|
|
serve(
|
|
{
|
|
fetch: app.fetch,
|
|
hostname: HOST,
|
|
port: PORT,
|
|
},
|
|
() => {
|
|
console.log(`Flotilla server running on http://${HOST}:${PORT}`)
|
|
},
|
|
)
|