UNPKG

@astrojs/node

Version:
112 lines (111 loc) 3.77 kB
import fs from "node:fs"; import path from "node:path"; import url from "node:url"; import { hasFileExtension, isInternalPath } from "@astrojs/internal-helpers/path"; import send from "send"; function createStaticHandler(app, options) { const client = resolveClientDir(options); return (req, res, ssr) => { if (req.url) { const [urlPath, urlQuery] = req.url.split("?"); const filePath = path.join(client, app.removeBase(urlPath)); let isDirectory = false; try { isDirectory = fs.lstatSync(filePath).isDirectory(); } catch { } const { trailingSlash = "ignore" } = options; const hasSlash = urlPath.endsWith("/"); let pathname = urlPath; if (app.headersMap && app.headersMap.length > 0) { const routeData = app.match(req, true); if (routeData && routeData.prerender) { const matchedRoute = app.headersMap.find((header) => header.pathname.includes(pathname)); if (matchedRoute) { for (const header of matchedRoute.headers) { res.setHeader(header.key, header.value); } } } } switch (trailingSlash) { case "never": { if (isDirectory && urlPath !== "/" && hasSlash) { pathname = urlPath.slice(0, -1) + (urlQuery ? "?" + urlQuery : ""); res.statusCode = 301; res.setHeader("Location", pathname); return res.end(); } if (isDirectory && !hasSlash) { pathname = `${urlPath}/index.html`; } break; } case "ignore": { if (isDirectory && !hasSlash) { pathname = `${urlPath}/index.html`; } break; } case "always": { if (!hasSlash && !hasFileExtension(urlPath) && !isInternalPath(urlPath)) { pathname = urlPath + "/" + (urlQuery ? "?" + urlQuery : ""); res.statusCode = 301; res.setHeader("Location", pathname); return res.end(); } break; } } pathname = prependForwardSlash(app.removeBase(pathname)); const stream = send(req, pathname, { root: client, dotfiles: pathname.startsWith("/.well-known/") ? "allow" : "deny" }); let forwardError = false; stream.on("error", (err) => { if (forwardError) { console.error(err.toString()); res.writeHead(500); res.end("Internal server error"); return; } ssr(); }); stream.on("headers", (_res) => { if (pathname.startsWith(`/${options.assets}/`)) { _res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); } }); stream.on("file", () => { forwardError = true; }); stream.pipe(res); } else { ssr(); } }; } function resolveClientDir(options) { const clientURLRaw = new URL(options.client); const serverURLRaw = new URL(options.server); const rel = path.relative(url.fileURLToPath(serverURLRaw), url.fileURLToPath(clientURLRaw)); const serverFolder = path.basename(options.server); let serverEntryFolderURL = path.dirname(import.meta.url); while (!serverEntryFolderURL.endsWith(serverFolder)) { serverEntryFolderURL = path.dirname(serverEntryFolderURL); } const serverEntryURL = serverEntryFolderURL + "/entry.mjs"; const clientURL = new URL(appendForwardSlash(rel), serverEntryURL); const client = url.fileURLToPath(clientURL); return client; } function prependForwardSlash(pth) { return pth.startsWith("/") ? pth : "/" + pth; } function appendForwardSlash(pth) { return pth.endsWith("/") ? pth : pth + "/"; } export { createStaticHandler };