@modern-js/server-core
Version:
A Progressive React Framework for modern web development.
186 lines (185 loc) • 6.36 kB
JavaScript
import { time } from "@modern-js/runtime-utils/time";
import { isArray, isFunction } from "@modern-js/utils";
import { ServerTimings } from "../../constants";
import { getLoaderCtx } from "../../helper";
import { transformResponse } from "../../utils";
import { createBaseHookContext } from "./base";
import { createAfterStreamingRenderContext, createCustomMiddlewaresCtx, getAfterMatchCtx, getAfterRenderCtx } from "./context";
const noop = () => {
};
const isHtmlResponse = (response) => {
const contentType = response.headers.get("content-type");
return contentType === null || contentType === void 0 ? void 0 : contentType.includes("text/html");
};
class CustomServer {
getHookMiddleware(entryName, routes) {
return async (c, next) => {
const routeInfo = routes.find((route) => route.entryName === entryName);
const monitors = c.get("monitors");
const baseHookCtx = createBaseHookContext(c);
const afterMatchCtx = getAfterMatchCtx(entryName, baseHookCtx);
const getCost = time();
await this.hooks.afterMatch.call(afterMatchCtx);
const cost = getCost();
cost && (monitors === null || monitors === void 0 ? void 0 : monitors.timing(ServerTimings.SERVER_HOOK_AFTER_MATCH, cost));
const { url, status } = afterMatchCtx.router;
if (url) {
return c.redirect(url, status);
}
const { current } = afterMatchCtx.router;
if (current !== entryName) {
const rewriteRoute = routes.find((route) => route.entryName === current);
if (rewriteRoute) {
c.set("matchPathname", rewriteRoute.urlPath);
c.set("matchEntryName", current);
}
}
if (c.finalized) {
return void 0;
}
await next();
if (c.finalized && (!c.res.body || !isHtmlResponse(c.res) || [
404,
500
].includes(c.res.status))) {
return void 0;
}
if (c.res) {
c.status(c.res.status);
}
if (routeInfo.isStream) {
const afterStreamingRenderContext = createAfterStreamingRenderContext(baseHookCtx, routeInfo);
c.res = transformResponse(c.res, async (chunk) => {
const context = afterStreamingRenderContext(chunk);
const result = await this.hooks.afterStreamingRender.call(context);
if (typeof result === "string") {
return result;
}
return result.chunk;
});
} else {
const afterRenderCtx = await getAfterRenderCtx(c, baseHookCtx, routeInfo);
const getCost2 = time();
await this.hooks.afterRender.call(afterRenderCtx);
const cost2 = getCost2();
cost2 && (monitors === null || monitors === void 0 ? void 0 : monitors.timing(ServerTimings.SERVER_HOOK_AFTER_RENDER, cost2));
if (afterRenderCtx.response.private_overrided) {
return void 0;
}
const newBody = afterRenderCtx.template.get();
c.res = c.body(newBody);
}
};
}
async getServerMiddleware() {
const serverMiddleware = await this.serverMiddlewarePromise;
if (!serverMiddleware || !isFunction(serverMiddleware) && !isArray(serverMiddleware)) {
return;
}
if (Array.isArray(serverMiddleware)) {
const unstableMiddlewares = getServerMidFromUnstableMid(serverMiddleware);
return unstableMiddlewares;
}
return async (c, next) => {
var _c_env_node_res, _c_env_node, _c_env;
const monitors = c.get("monitors");
const locals = {};
const resArgs = {
headers: new Headers()
};
const customMiddlewareCtx = createCustomMiddlewaresCtx(c, locals, resArgs);
const getCost = time();
await serverMiddleware(customMiddlewareCtx);
const cost = getCost();
cost && (monitors === null || monitors === void 0 ? void 0 : monitors.timing(ServerTimings.SERVER_MIDDLEWARE, cost));
c.set("locals", locals);
if (isRedirect(resArgs.headers, resArgs.status)) {
return c.redirect(resArgs.headers.get("Location") || "", resArgs.status || 302);
}
if ((_c_env = c.env) === null || _c_env === void 0 ? void 0 : (_c_env_node = _c_env.node) === null || _c_env_node === void 0 ? void 0 : (_c_env_node_res = _c_env_node.res) === null || _c_env_node_res === void 0 ? void 0 : _c_env_node_res.headersSent) {
return void 0;
}
if (!c.finalized) {
return next();
}
};
}
constructor(hooks, pwd) {
this.hooks = hooks;
const webExtension = [];
this.serverMiddlewarePromise = hooks.prepareWebServer.call({
pwd,
config: {
middleware: webExtension
}
});
}
}
function getServerMidFromUnstableMid(serverMiddleware) {
return serverMiddleware.map((middleware) => {
return async (c, next) => {
const context = await createMiddlewareContextFromHono(c);
return middleware(context, next);
};
});
}
function isRedirect(headers, code) {
return [
301,
302,
307,
308
].includes(code || 0) || headers.get("Location");
}
async function createMiddlewareContextFromHono(c) {
const loaderContext = getLoaderCtx(c);
const rawRequest = c.req.raw;
const method = rawRequest.method.toUpperCase();
if (![
"GET",
"HEAD"
].includes(method) && !rawRequest.body && c.env.node.req) {
const streamModulePath = "../../adapters/node/polyfills/stream.js";
const { createReadableStreamFromReadable } = await import(streamModulePath);
const init = {
body: createReadableStreamFromReadable(c.env.node.req),
headers: rawRequest.headers,
signal: rawRequest.signal,
method: rawRequest.method
};
init.duplex = "half";
c.req.raw = new Request(rawRequest.url, init);
}
return {
get request() {
return c.req.raw;
},
set request(request) {
c.req.raw = request;
},
get response() {
return c.res;
},
set response(newRes) {
c.res = newRes;
},
get route() {
return c.get("route");
},
get(key) {
return loaderContext.get(key);
},
set(key, value) {
return loaderContext.set(key, value);
},
status: c.status.bind(c),
header: c.header.bind(c),
body: c.body.bind(c),
html: c.html.bind(c),
redirect: c.redirect.bind(c)
};
}
export {
CustomServer,
getServerMidFromUnstableMid
};