@modern-js/server-core
Version:
A Progressive React Framework for modern web development.
288 lines (287 loc) • 11.7 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var render_exports = {};
__export(render_exports, {
createRender: () => createRender
});
module.exports = __toCommonJS(render_exports);
var import_universal = require("@modern-js/utils/universal");
var import_trie_router = require("hono/router/trie-router");
var import_constants = require("../../constants");
var import_utils = require("../../utils");
var import_utils2 = require("../../utils");
var import_csrRscRender = require("./csrRscRender");
var import_dataHandler = require("./dataHandler");
var import_renderRscHandler = require("./renderRscHandler");
var import_serverActionHandler = require("./serverActionHandler");
var import_ssrRender = require("./ssrRender");
const DYNAMIC_ROUTE_REG = /\/:./;
function getRouter(routes) {
const dynamicRoutes = [];
const normalRoutes = [];
routes.forEach((route) => {
if (DYNAMIC_ROUTE_REG.test(route.urlPath)) {
dynamicRoutes.push(route);
} else {
normalRoutes.push(route);
}
});
const finalRoutes = [
...normalRoutes.sort(import_utils2.sortRoutes),
...dynamicRoutes.sort(import_utils2.sortRoutes)
];
const router = new import_trie_router.TrieRouter();
for (const route of finalRoutes) {
const { urlPath: originUrlPath } = route;
const urlPath = originUrlPath.endsWith("/") ? `${originUrlPath}*` : `${originUrlPath}/*`;
router.add("*", urlPath, route);
}
return router;
}
function matchRoute(router, pathname, entryName) {
const matched = router.match("*", pathname);
if (entryName && matched[0].length > 1) {
const matches = matched[0];
const result = matches.find(([route]) => route.entryName === entryName);
return result || [];
} else {
const result = matched[0][0];
return result || [];
}
}
function getHeadersWithoutCookie(headers) {
const _headers = {
...headers,
cookie: void 0
};
delete _headers.cookie;
return _headers;
}
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config, onFallback }) {
const router = getRouter(routes);
return async (req, { logger, reporter, metrics, monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext }) => {
const forMatchpathname = matchPathname !== null && matchPathname !== void 0 ? matchPathname : (0, import_utils2.getPathname)(req);
const [routeInfo, params] = matchRoute(router, forMatchpathname, matchEntryName);
const framework = (0, import_universal.cutNameByHyphen)(metaName || "modern-js");
const fallbackHeader = `x-${framework}-ssr-fallback`;
let fallbackReason = null;
const fallbackWrapper = async (reason, error) => {
fallbackReason = reason;
return onFallback === null || onFallback === void 0 ? void 0 : onFallback(reason, {
logger,
reporter,
metrics
}, error);
};
if (!routeInfo) {
return new Response((0, import_utils2.createErrorHtml)(404), {
status: 404,
headers: {
"content-type": "text/html; charset=UTF-8"
}
});
}
const html = templates[(0, import_utils.uniqueKeyByRoute)(routeInfo)];
if (!html) {
return new Response((0, import_utils2.createErrorHtml)(404), {
status: 404,
headers: {
"content-type": "text/html; charset=UTF-8"
}
});
}
var _forceCSRMap_get;
const finalForceCSR = routeInfo.entryName ? (_forceCSRMap_get = forceCSRMap === null || forceCSRMap === void 0 ? void 0 : forceCSRMap.get(routeInfo.entryName)) !== null && _forceCSRMap_get !== void 0 ? _forceCSRMap_get : forceCSR : forceCSR;
const renderMode = await getRenderMode(req, fallbackHeader, routeInfo.isSSR, finalForceCSR, nodeReq, fallbackWrapper);
const headerData = (0, import_utils2.parseHeaders)(req);
const onError = (e, key) => {
monitors === null || monitors === void 0 ? void 0 : monitors.error(`SSR Error - ${key || (e instanceof Error ? e.name : e)}, error = %s, req.url = %s, req.headers = %o`, e instanceof Error ? e.stack || e.message : e, forMatchpathname, getHeadersWithoutCookie(headerData));
};
const onTiming = (name, dur) => {
monitors === null || monitors === void 0 ? void 0 : monitors.timing(name, dur, "SSR");
};
const renderOptions = {
pwd,
html,
routeInfo,
staticGenerate: staticGenerate || false,
config,
nodeReq,
cacheConfig,
reporter,
serverRoutes: routes,
params,
logger,
metrics,
monitors,
locals,
rscClientManifest,
rscSSRManifest,
rscServerManifest,
serverManifest,
loaderContext: loaderContext || /* @__PURE__ */ new Map(),
onError,
onTiming
};
if (fallbackReason) {
renderOptions.html = injectFallbackReasonToHtml({
html: renderOptions.html,
reason: fallbackReason,
framework
});
}
let response;
switch (renderMode) {
case "data":
response = await (0, import_dataHandler.dataHandler)(req, renderOptions) || await renderHandler(req, renderOptions, "ssr", fallbackWrapper, framework);
break;
case "rsc-tree":
response = await (0, import_renderRscHandler.renderRscHandler)(req, renderOptions);
break;
case "rsc-action":
response = await (0, import_serverActionHandler.serverActionHandler)(req, renderOptions);
break;
case "ssr":
case "csr":
response = await renderHandler(req, renderOptions, renderMode, fallbackWrapper, framework);
break;
default:
throw new Error(`Unknown render mode: ${renderMode}`);
}
if (fallbackReason) {
response.headers.set(fallbackHeader, `1;reason=${fallbackReason}`);
}
return response;
};
}
async function renderHandler(request, options, mode, fallbackWrapper, framework) {
var _options_config_server;
let response = null;
const { serverManifest } = options;
const ssrByRouteIds = (_options_config_server = options.config.server) === null || _options_config_server === void 0 ? void 0 : _options_config_server.ssrByRouteIds;
const runtimeEnv = (0, import_utils2.getRuntimeEnv)();
if (serverManifest.nestedRoutesJson && ssrByRouteIds && (ssrByRouteIds === null || ssrByRouteIds === void 0 ? void 0 : ssrByRouteIds.length) > 0 && runtimeEnv === "node") {
const { nestedRoutesJson } = serverManifest;
const routes = nestedRoutesJson === null || nestedRoutesJson === void 0 ? void 0 : nestedRoutesJson[options.routeInfo.entryName];
if (routes) {
const urlPath = "node:url";
const { pathToFileURL } = await import(urlPath);
const { matchRoutes } = await import(pathToFileURL(require.resolve("@modern-js/runtime-utils/remix-router")).href);
const url = new URL(request.url);
const matchedRoutes = matchRoutes(routes, url.pathname, options.routeInfo.urlPath);
if (!matchedRoutes) {
response = await csrRender(request, options);
} else {
var _lastMatch_route;
const lastMatch = matchedRoutes[matchedRoutes.length - 1];
if (!(lastMatch === null || lastMatch === void 0 ? void 0 : (_lastMatch_route = lastMatch.route) === null || _lastMatch_route === void 0 ? void 0 : _lastMatch_route.id) || !ssrByRouteIds.includes(lastMatch.route.id)) {
response = await csrRender(request, options);
}
}
}
}
if (mode === "ssr" && !response) {
try {
response = await (0, import_ssrRender.ssrRender)(request, options);
} catch (e) {
options.onError(e, import_utils2.ErrorDigest.ERENDER);
await fallbackWrapper("error", e);
response = await csrRender(request, {
...options,
html: injectFallbackReasonToHtml({
html: options.html,
reason: "error",
framework
})
});
}
} else {
response = await csrRender(request, options);
}
const { routeInfo } = options;
applyExtendHeaders(response, routeInfo);
return response;
function applyExtendHeaders(r, route) {
Object.entries(route.responseHeaders || {}).forEach(([k, v]) => {
r.headers.set(k, v);
});
}
}
async function getRenderMode(req, fallbackHeader, isSSR, forceCSR, nodeReq, onFallback) {
const query = (0, import_utils2.parseQuery)(req);
if (req.headers.get("x-rsc-action")) {
return "rsc-action";
}
if (req.headers.get("x-rsc-tree")) {
return "rsc-tree";
}
if (isSSR) {
if (query.__loader) {
return "data";
}
const fallbackHeaderValue = req.headers.get(fallbackHeader) || (nodeReq === null || nodeReq === void 0 ? void 0 : nodeReq.headers[fallbackHeader]);
if (forceCSR && (query.csr || fallbackHeaderValue)) {
if (query.csr) {
await (onFallback === null || onFallback === void 0 ? void 0 : onFallback("query"));
} else {
var _fallbackHeaderValue_split_;
const reason = fallbackHeaderValue === null || fallbackHeaderValue === void 0 ? void 0 : (_fallbackHeaderValue_split_ = fallbackHeaderValue.split(";")[1]) === null || _fallbackHeaderValue_split_ === void 0 ? void 0 : _fallbackHeaderValue_split_.split("=")[1];
await (onFallback === null || onFallback === void 0 ? void 0 : onFallback(reason ? `header,${reason}` : "header"));
}
return "csr";
}
return "ssr";
} else {
return "csr";
}
}
function injectFallbackReasonToHtml({ html, reason, framework }) {
const tag = `<script id="__${framework}_ssr_fallback_reason__" type="application/json">${JSON.stringify({
reason
})}</script>`;
return html.replace(/<\/head>/, `${tag}</head>`);
}
async function csrRender(request, options) {
const { html, rscClientManifest } = options;
if (!rscClientManifest || process.env.MODERN_DISABLE_INJECT_RSC_DATA) {
return new Response(html, {
status: 200,
headers: new Headers({
"content-type": "text/html; charset=UTF-8",
[import_constants.X_MODERNJS_RENDER]: "client"
})
});
} else {
return (0, import_csrRscRender.csrRscRender)(request, options);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createRender
});