@mcansh/remix-fastify
Version:
Fastify server request handler for Remix and React Router
127 lines (124 loc) • 4.32 kB
JavaScript
import { Readable } from "node:stream";
import path from "node:path";
import url from "node:url";
import fastifyStatic from "@fastify/static";
import { cacheHeader } from "pretty-cache-header";
//#region src/shared.ts
function createHeaders(requestHeaders) {
let headers = new Headers();
for (let [key, values] of Object.entries(requestHeaders)) if (values) if (Array.isArray(values)) for (let value of values) headers.append(key, value);
else headers.set(key, values);
return headers;
}
function getUrl(request) {
let origin = `${request.protocol}://${request.host}`;
let url$1 = `${origin}${request.originalUrl}`;
return url$1;
}
function createRequest(request, reply, createReadableStreamFromReadable) {
let url$1 = getUrl(request);
let controller = new AbortController();
let init = {
method: request.method,
headers: createHeaders(request.headers),
signal: controller.signal
};
reply.raw.on("finish", () => controller = null);
reply.raw.on("close", () => controller?.abort());
if (request.method !== "GET" && request.method !== "HEAD") {
init.body = createReadableStreamFromReadable(request.raw);
init.duplex = "half";
}
return new Request(url$1, init);
}
async function sendResponse(reply, nodeResponse) {
reply.status(nodeResponse.status);
for (let [key, values] of nodeResponse.headers.entries()) reply.headers({ [key]: values });
if (nodeResponse.body) {
let stream = responseToReadable(nodeResponse);
return reply.send(stream);
}
return reply.send(await nodeResponse.text());
}
function responseToReadable(response) {
if (!response.body) return null;
let reader = response.body.getReader();
let readable = new Readable();
readable._read = async () => {
try {
let result = await reader.read();
if (!result.done) readable.push(Buffer.from(result.value));
else readable.push(null);
} catch (error) {
readable.destroy(error);
throw error;
}
};
return readable;
}
//#endregion
//#region src/plugins/index.ts
function createPlugin(fastify, { basename = "/", buildDirectory = "build", serverBuildFile = "index.js", getLoadContext, mode = process.env.NODE_ENV, viteOptions, fastifyStaticOptions, assetCacheControl = {
public: true,
maxAge: "1 year",
immutable: true
}, defaultCacheControl = {
public: true,
maxAge: "1 hour"
}, productionServerBuild, childServerOptions }, virtualModule, createRequestHandler) {
return async () => {
let cwd = process.env.REMIX_ROOT ?? process.cwd();
let vite;
if (mode !== "production") vite = await import("vite").then((mod) => {
return mod.createServer({
...viteOptions,
server: {
...viteOptions?.server,
middlewareMode: true
}
});
});
let resolvedBuildDirectory = path.resolve(cwd, buildDirectory);
let SERVER_BUILD = path.join(resolvedBuildDirectory, "server", serverBuildFile);
let SERVER_BUILD_URL = url.pathToFileURL(SERVER_BUILD).href;
let handler = createRequestHandler({
mode,
getLoadContext,
build: vite ? () => vite.ssrLoadModule(virtualModule) : productionServerBuild ?? await import(SERVER_BUILD_URL)
});
if (vite) {
let middie = await import("@fastify/middie").then((mod) => mod.default);
await fastify.register(middie);
fastify.use(vite.middlewares);
} else {
let BUILD_DIR = path.join(resolvedBuildDirectory, "client");
let ASSET_DIR = path.join(BUILD_DIR, "assets");
await fastify.register(fastifyStatic, {
root: BUILD_DIR,
prefix: basename,
wildcard: false,
cacheControl: false,
dotfiles: "allow",
etag: true,
serveDotFiles: true,
lastModified: true,
setHeaders(res, filepath) {
let isAsset = filepath.startsWith(ASSET_DIR);
res.setHeader("cache-control", isAsset ? cacheHeader(assetCacheControl) : cacheHeader(defaultCacheControl));
},
...fastifyStaticOptions
});
}
fastify.register(async function createRemixRequestHandler(childServer) {
childServer.removeAllContentTypeParsers();
childServer.addContentTypeParser("*", (_request, payload, done) => {
done(null, payload);
});
if (childServerOptions) childServer.all("*", childServerOptions, handler);
else childServer.all("*", handler);
}, { prefix: basename });
};
}
//#endregion
export { createPlugin, createRequest, sendResponse };
//# sourceMappingURL=plugins-CszfyhUI.js.map