UNPKG

ruru

Version:

Grafast-flavoured GraphiQL distribution

167 lines 6.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getStaticFile = getStaticFile; exports.serveStatic = serveStatic; const node_util_1 = require("node:util"); const types_1 = require("node:util/types"); const node_zlib_1 = require("node:zlib"); const brotliDecompress = (0, node_util_1.promisify)(node_zlib_1.brotliDecompress); const MIME_TYPES = { txt: "text/plain; charset=utf-8", ts: "text/plain; charset=utf-8", js: "text/javascript; charset=utf-8", ttf: "font/ttf", map: "application/json", css: "text/css; charset=utf-8", }; function makeStaticFile(filename, entry) { const { buffer: content, etag } = entry; const i = filename.lastIndexOf("."); if (i < 0) throw new Error(`${filename} has no extension`); const ext = filename.substring(i + 1); const contentType = MIME_TYPES[ext]; if (!contentType) { throw new Error(`Unknown extension ${ext}`); } return { content, headers: { "content-type": contentType, "content-encoding": "br", "content-length": String(content.length), etag, }, }; } function createStaticFileLoader(loadFile) { let cache = null; return () => { if (cache === null) { cache = (async () => { const { bundleData } = await loadFile(); const files = Object.create(null); for (const filename of Object.keys(bundleData)) { const content = bundleData[filename]; files[filename] = makeStaticFile(filename, content); } cache = files; return files; })(); cache.catch((e) => { console.error(`Failed to load static files: ${e}`); cache = null; }); } return cache; }; } /** * Returns an object containing all of the static files needed by Ruru; calling * this will increase memory consumption by ~4MB */ const getStaticFiles = createStaticFileLoader(() => import("./bundleCode.js")); /** * Returns an object containing all of the source maps for ruru source; calling * this will increase memory consumption by ~10MB */ const getStaticMaps = createStaticFileLoader(() => import("./bundleMeta.js")); /** * Given the `staticPath` (which must end in a `/`) from which Ruru's static * assets are served over HTTP, and the `urlPath` that the user has requested, * return the file and its associated headers to be served in response, or null * if not found. * * IMPORTANT: `staticPath` is the URL path, not the filesystem path. It will be * pruned from the beginning of `urlPath` before looking up the file. */ function getStaticFile({ staticPath, urlPath, acceptEncoding, disallowDevAssets, }) { const i = urlPath.indexOf("?", staticPath.length); const path = urlPath.substring(staticPath.length, i >= 0 ? i : undefined); const files = path.endsWith(".map") && !disallowDevAssets ? getStaticMaps() : getStaticFiles(); return (0, types_1.isPromise)(files) ? files.then((files) => getStaticFileInner(files, path, acceptEncoding)) : getStaticFileInner(files, path, acceptEncoding); } const BROTLI_REGEXP = /\bbr\b/i; function hasBrotli(acceptEncoding) { return typeof acceptEncoding === "string" ? BROTLI_REGEXP.test(acceptEncoding) : false; } function getStaticFileInner(files, path, acceptEncoding) { const file = files[path]; if (!file) return null; if (hasBrotli(acceptEncoding)) { // Already compressed return file; } else { // We need to decompress for the client const { content: compressed, headers: { "content-encoding": _delete, ...otherHeaders }, } = file; return brotliDecompress(compressed).then((content) => ({ content, headers: { ...otherHeaders, "content-length": String(content.length), }, })); } } /** * Returns a middleware compatible with Node, Connect, Express and similar that * will serve Ruru's static files after trimming off the initial `staticPath` * (which represents the URL path under which static assets are served, and * _must_ end in a slash). */ function serveStatic(staticPath) { return (req, res, next) => void staticMiddleware(req, res, next); async function staticMiddleware(req, res, next) { try { if (req.url?.startsWith(staticPath)) { const file = await getStaticFile({ staticPath, urlPath: req.url, acceptEncoding: req.headers["accept-encoding"], }); if (file) { const etag = file.headers.etag; const reqEtag = req.headers["if-none-match"]; if (reqEtag === etag) { res.writeHead(304, "Not Modified", { etag }); res.end(); } else { // As per RFC9112 Section 4.2, a client SHOULD ignore the // reason-phrase; it's even phased out in HTTP/2+ res.writeHead(200, "LGTM", file.headers); res.end(file.content); } return; } } // Not found if (typeof next === "function") { return next(); } else { res.writeHead(404, { "content-type": "text/plain" }); res.end("Not found"); } } catch (e) { if (typeof next === "function") { return next(e); } else { res.writeHead(500); res.end("Failed to setup static middleware"); return; } } } } //# sourceMappingURL=static.js.map