ruru
Version:
Grafast-flavoured GraphiQL distribution
167 lines • 6.07 kB
JavaScript
;
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