@bleedingdev/modern-js-server-core 3.8.2-ultramodern.9 → 3.8.3-ultramodern.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/adapters/node/node.js +3 -1
- package/dist/cjs/adapters/node/plugins/staticModuleFederation.js +5 -1
- package/dist/cjs/adapters/node/plugins/staticPrecompressed.js +39 -19
- package/dist/cjs/adapters/node/plugins/staticServing.js +27 -4
- package/dist/cjs/plugins/compat/hooks.js +0 -1
- package/dist/cjs/plugins/compat/index.js +0 -1
- package/dist/cjs/plugins/render/inject.js +3 -13
- package/dist/cjs/plugins/render/render.js +9 -8
- package/dist/cjs/plugins/render/serverActionHandler.js +6 -0
- package/dist/cjs/utils/error.js +4 -56
- package/dist/esm/adapters/node/node.mjs +3 -1
- package/dist/esm/adapters/node/plugins/staticModuleFederation.mjs +5 -1
- package/dist/esm/adapters/node/plugins/staticPrecompressed.mjs +39 -19
- package/dist/esm/adapters/node/plugins/staticServing.mjs +27 -4
- package/dist/esm/plugins/compat/hooks.mjs +0 -1
- package/dist/esm/plugins/compat/index.mjs +1 -2
- package/dist/esm/plugins/render/inject.mjs +3 -13
- package/dist/esm/plugins/render/render.mjs +9 -8
- package/dist/esm/plugins/render/serverActionHandler.mjs +6 -0
- package/dist/esm/utils/error.mjs +2 -54
- package/dist/esm-node/adapters/node/node.mjs +3 -1
- package/dist/esm-node/adapters/node/plugins/staticModuleFederation.mjs +5 -1
- package/dist/esm-node/adapters/node/plugins/staticPrecompressed.mjs +39 -19
- package/dist/esm-node/adapters/node/plugins/staticServing.mjs +27 -4
- package/dist/esm-node/plugins/compat/hooks.mjs +0 -1
- package/dist/esm-node/plugins/compat/index.mjs +1 -2
- package/dist/esm-node/plugins/render/inject.mjs +3 -13
- package/dist/esm-node/plugins/render/render.mjs +9 -8
- package/dist/esm-node/plugins/render/serverActionHandler.mjs +6 -0
- package/dist/esm-node/utils/error.mjs +2 -54
- package/dist/types/adapters/node/plugins/staticPrecompressed.d.ts +1 -0
- package/dist/types/adapters/node/plugins/staticServing.d.ts +4 -4
- package/dist/types/plugins/render/inject.d.ts +1 -1
- package/dist/types/plugins/render/render.d.ts +2 -3
- package/dist/types/types/plugins/base.d.ts +1 -13
- package/dist/types/types/plugins/index.d.ts +1 -1
- package/dist/types/types/plugins/plugin.d.ts +2 -4
- package/dist/types/utils/error.d.ts +2 -16
- package/package.json +7 -6
|
@@ -5,23 +5,14 @@ const injectRenderHandlerPlugin = ({ staticGenerate, cacheConfig })=>({
|
|
|
5
5
|
api.onPrepare(async ()=>{
|
|
6
6
|
const { distDirectory: pwd, routes, metaName } = api.getServerContext();
|
|
7
7
|
const config = api.getServerConfig();
|
|
8
|
-
const hooks = api.getHooks();
|
|
9
8
|
if (!routes) return;
|
|
10
|
-
const onFallback = async (reason, error, context)=>{
|
|
11
|
-
await hooks.fallback.call({
|
|
12
|
-
reason,
|
|
13
|
-
error,
|
|
14
|
-
context
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
9
|
const getRenderHandlerOptions = {
|
|
18
10
|
pwd: pwd,
|
|
19
11
|
routes,
|
|
20
12
|
config,
|
|
21
13
|
metaName,
|
|
22
14
|
cacheConfig: cacheConfig,
|
|
23
|
-
staticGenerate
|
|
24
|
-
onFallback
|
|
15
|
+
staticGenerate
|
|
25
16
|
};
|
|
26
17
|
const render = await getRenderHandler(getRenderHandlerOptions);
|
|
27
18
|
api.updateServerContext({
|
|
@@ -31,7 +22,7 @@ const injectRenderHandlerPlugin = ({ staticGenerate, cacheConfig })=>({
|
|
|
31
22
|
});
|
|
32
23
|
}
|
|
33
24
|
});
|
|
34
|
-
async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate
|
|
25
|
+
async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate }) {
|
|
35
26
|
const ssrConfig = config.server?.ssr;
|
|
36
27
|
const ssrByEntries = config.server?.ssrByEntries;
|
|
37
28
|
const forceCSR = 'object' == typeof ssrConfig ? ssrConfig.forceCSR : false;
|
|
@@ -48,8 +39,7 @@ async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, st
|
|
|
48
39
|
forceCSR,
|
|
49
40
|
forceCSRMap,
|
|
50
41
|
nonce: config.security?.nonce,
|
|
51
|
-
metaName: metaName || 'modern-js'
|
|
52
|
-
onFallback
|
|
42
|
+
metaName: metaName || 'modern-js'
|
|
53
43
|
});
|
|
54
44
|
return render;
|
|
55
45
|
}
|
|
@@ -67,7 +67,7 @@ function getHeadersWithoutCookie(headers) {
|
|
|
67
67
|
delete _headers.cookie;
|
|
68
68
|
return _headers;
|
|
69
69
|
}
|
|
70
|
-
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config
|
|
70
|
+
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config }) {
|
|
71
71
|
const router = getRouter(routes);
|
|
72
72
|
return async (req, { monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext, serverContext, contextForceCSR, reporter })=>{
|
|
73
73
|
const forMatchpathname = matchPathname ?? getPathname(req);
|
|
@@ -75,13 +75,14 @@ async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig
|
|
|
75
75
|
const framework = cutNameByHyphen(metaName || 'modern-js');
|
|
76
76
|
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
77
77
|
let fallbackReason = null;
|
|
78
|
-
const
|
|
79
|
-
request: req,
|
|
80
|
-
monitors
|
|
81
|
-
};
|
|
82
|
-
const fallbackWrapper = async (reason, error)=>{
|
|
78
|
+
const fallbackWrapper = (reason)=>{
|
|
83
79
|
fallbackReason = reason;
|
|
84
|
-
|
|
80
|
+
monitors?.warn('fallback to CSR reason: %o', {
|
|
81
|
+
type: reason
|
|
82
|
+
});
|
|
83
|
+
monitors?.counter('ssr-fallback', {
|
|
84
|
+
type: reason
|
|
85
|
+
});
|
|
85
86
|
};
|
|
86
87
|
if (!routeInfo) return new Response(createErrorHtml(404), {
|
|
87
88
|
status: 404,
|
|
@@ -177,7 +178,7 @@ async function renderHandler(request, options, mode, fallbackWrapper, framework)
|
|
|
177
178
|
response = await ssrRender(request, options);
|
|
178
179
|
} catch (e) {
|
|
179
180
|
options.onError(e, ErrorDigest.ERENDER);
|
|
180
|
-
|
|
181
|
+
fallbackWrapper('error');
|
|
181
182
|
response = await csrRender(request, {
|
|
182
183
|
...options,
|
|
183
184
|
html: injectFallbackReasonToHtml({
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { MAIN_ENTRY_NAME } from "@modern-js/utils/universal/constants";
|
|
2
2
|
const serverActionHandler = async (req, { serverManifest, routeInfo, rscClientManifest })=>{
|
|
3
|
+
if ('POST' !== req.method) return new Response('Method not allowed', {
|
|
4
|
+
status: 405,
|
|
5
|
+
headers: {
|
|
6
|
+
Allow: 'POST'
|
|
7
|
+
}
|
|
8
|
+
});
|
|
3
9
|
const serverBundle = serverManifest?.renderBundles?.[routeInfo.entryName || MAIN_ENTRY_NAME];
|
|
4
10
|
if (!serverBundle) return new Response('Cannot find server bundle for server action', {
|
|
5
11
|
status: 500
|
package/dist/esm/utils/error.mjs
CHANGED
|
@@ -3,59 +3,6 @@ const ERROR_PAGE_TEXT = {
|
|
|
3
3
|
404: 'This page could not be found.',
|
|
4
4
|
500: 'Internal Server Error.'
|
|
5
5
|
};
|
|
6
|
-
const SAFE_FAILURE_MESSAGES = {
|
|
7
|
-
500: 'Internal Server Error',
|
|
8
|
-
503: 'Service Unavailable'
|
|
9
|
-
};
|
|
10
|
-
const SAFE_FAILURE_CODES = {
|
|
11
|
-
500: 'INTERNAL_SERVER_ERROR',
|
|
12
|
-
503: 'SERVICE_UNAVAILABLE'
|
|
13
|
-
};
|
|
14
|
-
const readErrorProperty = (error, key)=>{
|
|
15
|
-
if ('object' != typeof error || null === error || !(key in error)) return;
|
|
16
|
-
return error[key];
|
|
17
|
-
};
|
|
18
|
-
const normalizeFailureStatus = (value)=>{
|
|
19
|
-
if ('number' != typeof value || !Number.isInteger(value)) return;
|
|
20
|
-
return value >= 400 && value <= 599 ? value : void 0;
|
|
21
|
-
};
|
|
22
|
-
const normalizeRetryAfter = (value)=>{
|
|
23
|
-
if ('number' == typeof value && Number.isFinite(value) && value >= 0) return String(Math.ceil(value));
|
|
24
|
-
if ('string' == typeof value) {
|
|
25
|
-
const trimmed = value.trim();
|
|
26
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
27
|
-
}
|
|
28
|
-
if (value instanceof Date) return value.toUTCString();
|
|
29
|
-
};
|
|
30
|
-
const getSafeFailureStatus = (error)=>normalizeFailureStatus(readErrorProperty(error, 'status')) ?? normalizeFailureStatus(readErrorProperty(error, 'statusCode')) ?? 500;
|
|
31
|
-
const createSafeFailureHttpResult = (error)=>{
|
|
32
|
-
const status = getSafeFailureStatus(error);
|
|
33
|
-
const retryAfter = 503 === status ? normalizeRetryAfter(readErrorProperty(error, 'retryAfter')) ?? normalizeRetryAfter(readErrorProperty(error, 'retryAfterSeconds')) ?? normalizeRetryAfter('number' == typeof readErrorProperty(error, 'retryAfterMs') ? readErrorProperty(error, 'retryAfterMs') / 1000 : void 0) : void 0;
|
|
34
|
-
return {
|
|
35
|
-
status,
|
|
36
|
-
body: {
|
|
37
|
-
success: false,
|
|
38
|
-
error: {
|
|
39
|
-
code: SAFE_FAILURE_CODES[status] ?? 'REQUEST_FAILED',
|
|
40
|
-
message: SAFE_FAILURE_MESSAGES[status] ?? 'Request failed',
|
|
41
|
-
status
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
headers: {
|
|
45
|
-
'content-type': 'application/json; charset=utf-8',
|
|
46
|
-
...retryAfter ? {
|
|
47
|
-
'Retry-After': retryAfter
|
|
48
|
-
} : {}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
const createSafeJsonFailureResponse = (error)=>{
|
|
53
|
-
const result = createSafeFailureHttpResult(error);
|
|
54
|
-
return new Response(JSON.stringify(result.body), {
|
|
55
|
-
status: result.status,
|
|
56
|
-
headers: result.headers
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
6
|
const createErrorHtml = (status)=>{
|
|
60
7
|
const text = ERROR_PAGE_TEXT[status] || '';
|
|
61
8
|
const title = `${status}: ${text}`;
|
|
@@ -103,4 +50,5 @@ function onError(digest, error, monitors, req) {
|
|
|
103
50
|
else if (req) console.error(`Server Error - ${digest}, error = ${error instanceof Error ? error.stack || error.message : error}, req.url = ${req.url}, req.headers = ${JSON.stringify(headerData)}`);
|
|
104
51
|
else console.error(`Server Error - ${digest}, error = ${error instanceof Error ? error.stack || error.message : error} `);
|
|
105
52
|
}
|
|
106
|
-
export {
|
|
53
|
+
export { createSafeFailureHttpResult, createSafeJsonFailureResponse, getSafeFailureStatus } from "@modern-js/runtime-extensions/safe-failure";
|
|
54
|
+
export { createErrorHtml, error_ErrorDigest as ErrorDigest, onError };
|
|
@@ -26,7 +26,9 @@ const createWebRequest = (req, res, body)=>{
|
|
|
26
26
|
signal: controller.signal
|
|
27
27
|
};
|
|
28
28
|
res.on('close', ()=>controller.abort('res closed'));
|
|
29
|
-
const
|
|
29
|
+
const protocol = 'encrypted' in req.socket && req.socket.encrypted ? 'https' : 'http';
|
|
30
|
+
const authority = req.headers[':authority'] ?? req.headers.host;
|
|
31
|
+
const url = `${protocol}://${authority}${req.url}`;
|
|
30
32
|
const needsRequestBody = body || !('GET' === method || 'HEAD' === method);
|
|
31
33
|
const cloneableReq = needsRequestBody ? cloneable_readable(req) : null;
|
|
32
34
|
if (needsRequestBody) {
|
|
@@ -12,7 +12,11 @@ const MODULE_FEDERATION_OPTIONAL_FILES = [
|
|
|
12
12
|
'mf-stats.json'
|
|
13
13
|
];
|
|
14
14
|
const trimLeadingSlash = (value)=>value.replace(/^\/+/, '');
|
|
15
|
-
const getModuleFederationRequestPath = (pathname, pathPrefix)=>
|
|
15
|
+
const getModuleFederationRequestPath = (pathname, pathPrefix)=>{
|
|
16
|
+
const normalizedPrefix = `/${trimLeadingSlash(pathPrefix)}`.replace(/\/+$/u, '');
|
|
17
|
+
const requestPath = normalizedPrefix && (pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`)) ? pathname.slice(normalizedPrefix.length) : pathname;
|
|
18
|
+
return trimLeadingSlash(requestPath);
|
|
19
|
+
};
|
|
16
20
|
const isModuleFederationManifestRequest = (requestPath)=>MODULE_FEDERATION_MANIFEST_FILES.includes(requestPath);
|
|
17
21
|
const isBackendModuleFederationManifestRequest = (requestPath)=>requestPath === BACKEND_MODULE_FEDERATION_MANIFEST_FILE;
|
|
18
22
|
const applyModuleFederationAssetHeaders = (c)=>{
|
|
@@ -8,23 +8,32 @@ const PRE_COMPRESSED_SUPPORTED_ENCODINGS = [
|
|
|
8
8
|
'br',
|
|
9
9
|
'gzip'
|
|
10
10
|
];
|
|
11
|
+
const QUALITY_VALUE_PATTERN = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/u;
|
|
11
12
|
const parseAcceptEncoding = (value)=>value.split(',').map((item)=>item.trim()).filter(Boolean).map((item)=>{
|
|
12
13
|
const [rawName, ...params] = item.split(';');
|
|
13
14
|
const name = rawName.trim().toLowerCase();
|
|
14
15
|
let q = 1;
|
|
16
|
+
let qualitySeen = false;
|
|
15
17
|
for (const param of params){
|
|
16
18
|
const [key, rawValue] = param.split('=').map((v)=>v.trim());
|
|
17
|
-
if ('q'
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
if ('q' === key.toLowerCase()) {
|
|
20
|
+
if (qualitySeen || null == rawValue || !QUALITY_VALUE_PATTERN.test(rawValue)) {
|
|
21
|
+
q = 0;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
qualitySeen = true;
|
|
25
|
+
q = Number(rawValue);
|
|
26
|
+
}
|
|
20
27
|
}
|
|
21
28
|
return {
|
|
22
29
|
name,
|
|
23
30
|
q
|
|
24
31
|
};
|
|
25
32
|
});
|
|
26
|
-
const
|
|
27
|
-
if (!value) return [
|
|
33
|
+
const getAcceptedRepresentations = (value)=>{
|
|
34
|
+
if (!value) return [
|
|
35
|
+
'identity'
|
|
36
|
+
];
|
|
28
37
|
const parsed = parseAcceptEncoding(value);
|
|
29
38
|
const qualityByEncoding = new Map();
|
|
30
39
|
let wildcardQuality;
|
|
@@ -40,10 +49,17 @@ const getAcceptedEncodings = (value)=>{
|
|
|
40
49
|
if (void 0 !== explicit) return explicit;
|
|
41
50
|
return wildcardQuality ?? 0;
|
|
42
51
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
const identityQuality = qualityByEncoding.get('identity') ?? (0 === wildcardQuality ? 0 : 1);
|
|
53
|
+
return [
|
|
54
|
+
...PRE_COMPRESSED_SUPPORTED_ENCODINGS.map((encoding)=>({
|
|
55
|
+
encoding,
|
|
56
|
+
quality: getQuality(encoding)
|
|
57
|
+
})),
|
|
58
|
+
{
|
|
59
|
+
encoding: 'identity',
|
|
60
|
+
quality: identityQuality
|
|
61
|
+
}
|
|
62
|
+
].filter((item)=>item.quality > 0).sort((a, b)=>b.quality - a.quality).map((item)=>item.encoding);
|
|
47
63
|
};
|
|
48
64
|
const appendVaryHeader = (c, value)=>{
|
|
49
65
|
const current = c.res.headers.get('Vary');
|
|
@@ -59,34 +75,38 @@ const resolvePreCompressedAsset = async (c, filepath)=>{
|
|
|
59
75
|
fs.pathExists(gzipPath)
|
|
60
76
|
]);
|
|
61
77
|
const hasVariant = hasBr || hasGzip;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
const acceptedRepresentations = getAcceptedRepresentations(c.req.header('accept-encoding'));
|
|
79
|
+
for (const encoding of acceptedRepresentations){
|
|
80
|
+
if ('identity' === encoding) return {
|
|
81
|
+
selected: null,
|
|
82
|
+
hasVariant,
|
|
83
|
+
acceptable: true
|
|
84
|
+
};
|
|
68
85
|
if ('br' === encoding && hasBr) return {
|
|
69
86
|
selected: {
|
|
70
87
|
filepath: brPath,
|
|
71
88
|
encoding
|
|
72
89
|
},
|
|
73
|
-
hasVariant: true
|
|
90
|
+
hasVariant: true,
|
|
91
|
+
acceptable: true
|
|
74
92
|
};
|
|
75
93
|
if ('gzip' === encoding && hasGzip) return {
|
|
76
94
|
selected: {
|
|
77
95
|
filepath: gzipPath,
|
|
78
96
|
encoding
|
|
79
97
|
},
|
|
80
|
-
hasVariant: true
|
|
98
|
+
hasVariant: true,
|
|
99
|
+
acceptable: true
|
|
81
100
|
};
|
|
82
101
|
}
|
|
83
102
|
return {
|
|
84
103
|
selected: null,
|
|
85
|
-
hasVariant
|
|
104
|
+
hasVariant,
|
|
105
|
+
acceptable: false
|
|
86
106
|
};
|
|
87
107
|
};
|
|
88
108
|
const applyPreCompressedAssetHeaders = (c, preCompressedAsset)=>{
|
|
89
|
-
if (preCompressedAsset.hasVariant) appendVaryHeader(c, 'Accept-Encoding');
|
|
109
|
+
if (preCompressedAsset.hasVariant || !preCompressedAsset.acceptable) appendVaryHeader(c, 'Accept-Encoding');
|
|
90
110
|
if (preCompressedAsset.selected) c.header('Content-Encoding', preCompressedAsset.selected.encoding);
|
|
91
111
|
};
|
|
92
112
|
export { applyPreCompressedAssetHeaders, resolvePreCompressedAsset };
|
|
@@ -5,11 +5,16 @@ import { getMimeType } from "hono/utils/mime";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { applyModuleFederationAssetHeaders, getModuleFederationAssetList, getModuleFederationRequestPath, isBackendModuleFederationManifestRequest, isModuleFederationManifestRequest, patchModuleFederationManifestPublicPath, patchModuleFederationRemoteEntryPublicPath } from "./staticModuleFederation.mjs";
|
|
7
7
|
import { applyPreCompressedAssetHeaders, resolvePreCompressedAsset } from "./staticPrecompressed.mjs";
|
|
8
|
+
const MODULE_FEDERATION_ASSET_REFRESH_INTERVAL_MS = 1000;
|
|
8
9
|
const getStaticMimeType = (filename)=>getMimeType(filename) ?? ('.cjs' === path.extname(filename).toLowerCase() ? "text/javascript; charset=UTF-8" : void 0);
|
|
9
10
|
const servePreCompressedPublicRouteAsset = async (c, pwd, route)=>{
|
|
10
11
|
const { entryPath } = route;
|
|
11
12
|
const originFilename = path.join(pwd, entryPath);
|
|
12
13
|
const preCompressedAsset = await resolvePreCompressedAsset(c, originFilename);
|
|
14
|
+
if (!preCompressedAsset.acceptable) {
|
|
15
|
+
applyPreCompressedAssetHeaders(c, preCompressedAsset);
|
|
16
|
+
return c.body(null, 406);
|
|
17
|
+
}
|
|
13
18
|
const filename = preCompressedAsset.selected?.filepath ?? originFilename;
|
|
14
19
|
const data = await fileReader.readFile(filename, 'buffer');
|
|
15
20
|
const mimeType = getStaticMimeType(originFilename);
|
|
@@ -56,6 +61,10 @@ const servePublicDirectoryAsset = async (c, pwd)=>{
|
|
|
56
61
|
const originFilename = await resolvePublicDirectoryAsset(pwd, c.req.path);
|
|
57
62
|
if (null === originFilename) return null;
|
|
58
63
|
const preCompressedAsset = await resolvePreCompressedAsset(c, originFilename);
|
|
64
|
+
if (!preCompressedAsset.acceptable) {
|
|
65
|
+
applyPreCompressedAssetHeaders(c, preCompressedAsset);
|
|
66
|
+
return c.body(null, 406);
|
|
67
|
+
}
|
|
59
68
|
const selectedFilename = preCompressedAsset.selected?.filepath ?? originFilename;
|
|
60
69
|
const publicDirectory = await fs.realpath(path.join(pwd, 'public'));
|
|
61
70
|
let realSelectedFilename;
|
|
@@ -76,10 +85,19 @@ const servePublicDirectoryAsset = async (c, pwd)=>{
|
|
|
76
85
|
return c.body(body, 200);
|
|
77
86
|
};
|
|
78
87
|
const createModuleFederationStaticServing = ({ pwd, pathPrefix })=>{
|
|
79
|
-
let
|
|
88
|
+
let moduleFederationAssets = null;
|
|
89
|
+
let moduleFederationAssetsExpiresAt = 0;
|
|
90
|
+
let moduleFederationAssetsRefresh = null;
|
|
80
91
|
const getModuleFederationAssets = async ()=>{
|
|
81
|
-
if (
|
|
82
|
-
|
|
92
|
+
if (moduleFederationAssets && Date.now() < moduleFederationAssetsExpiresAt) return moduleFederationAssets;
|
|
93
|
+
if (!moduleFederationAssetsRefresh) moduleFederationAssetsRefresh = getModuleFederationAssetList(pwd).then((assets)=>{
|
|
94
|
+
moduleFederationAssets = assets;
|
|
95
|
+
moduleFederationAssetsExpiresAt = Date.now() + MODULE_FEDERATION_ASSET_REFRESH_INTERVAL_MS;
|
|
96
|
+
return assets;
|
|
97
|
+
}).finally(()=>{
|
|
98
|
+
moduleFederationAssetsRefresh = null;
|
|
99
|
+
});
|
|
100
|
+
return moduleFederationAssetsRefresh;
|
|
83
101
|
};
|
|
84
102
|
const resolveRequest = async (pathname)=>{
|
|
85
103
|
const requestPath = getModuleFederationRequestPath(pathname, pathPrefix);
|
|
@@ -100,8 +118,13 @@ const createModuleFederationStaticServing = ({ pwd, pathPrefix })=>{
|
|
|
100
118
|
const canUsePreCompressed = !shouldPatchManifest && !shouldPatchRemoteEntry;
|
|
101
119
|
const preCompressedAsset = canUsePreCompressed ? await resolvePreCompressedAsset(c, filepath) : {
|
|
102
120
|
selected: null,
|
|
103
|
-
hasVariant: false
|
|
121
|
+
hasVariant: false,
|
|
122
|
+
acceptable: true
|
|
104
123
|
};
|
|
124
|
+
if (!preCompressedAsset.acceptable) {
|
|
125
|
+
applyPreCompressedAssetHeaders(c, preCompressedAsset);
|
|
126
|
+
return c.body(null, 406);
|
|
127
|
+
}
|
|
105
128
|
const targetFilepath = preCompressedAsset.selected?.filepath ?? filepath;
|
|
106
129
|
const chunk = await fileReader.readFileFromSystem(targetFilepath, 'buffer');
|
|
107
130
|
if (null === chunk) return null;
|
|
@@ -5,7 +5,6 @@ function getHookRunners(context) {
|
|
|
5
5
|
config: (params)=>hooks.modifyConfig.call(params),
|
|
6
6
|
prepare: ()=>hooks.onPrepare.call(),
|
|
7
7
|
reset: (params)=>hooks.onReset.call(params),
|
|
8
|
-
fallback: (input)=>hooks.fallback.call(input),
|
|
9
8
|
prepareWebServer: (input)=>hooks.prepareWebServer.call(input),
|
|
10
9
|
prepareApiServer: (input)=>hooks.prepareApiServer.call(input),
|
|
11
10
|
afterMatch: (ctx)=>hooks.afterMatch.call(ctx),
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
-
import {
|
|
2
|
+
import { createAsyncPipelineHook } from "@modern-js/plugin";
|
|
3
3
|
import { getHookRunners, handleSetupResult } from "./hooks.mjs";
|
|
4
4
|
const compatPlugin = ()=>({
|
|
5
5
|
name: '@modern-js/server-compat',
|
|
6
6
|
registryHooks: {
|
|
7
|
-
fallback: createAsyncHook(),
|
|
8
7
|
prepareWebServer: createAsyncPipelineHook(),
|
|
9
8
|
prepareApiServer: createAsyncPipelineHook(),
|
|
10
9
|
afterMatch: createAsyncPipelineHook(),
|
|
@@ -6,23 +6,14 @@ const injectRenderHandlerPlugin = ({ staticGenerate, cacheConfig })=>({
|
|
|
6
6
|
api.onPrepare(async ()=>{
|
|
7
7
|
const { distDirectory: pwd, routes, metaName } = api.getServerContext();
|
|
8
8
|
const config = api.getServerConfig();
|
|
9
|
-
const hooks = api.getHooks();
|
|
10
9
|
if (!routes) return;
|
|
11
|
-
const onFallback = async (reason, error, context)=>{
|
|
12
|
-
await hooks.fallback.call({
|
|
13
|
-
reason,
|
|
14
|
-
error,
|
|
15
|
-
context
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
10
|
const getRenderHandlerOptions = {
|
|
19
11
|
pwd: pwd,
|
|
20
12
|
routes,
|
|
21
13
|
config,
|
|
22
14
|
metaName,
|
|
23
15
|
cacheConfig: cacheConfig,
|
|
24
|
-
staticGenerate
|
|
25
|
-
onFallback
|
|
16
|
+
staticGenerate
|
|
26
17
|
};
|
|
27
18
|
const render = await getRenderHandler(getRenderHandlerOptions);
|
|
28
19
|
api.updateServerContext({
|
|
@@ -32,7 +23,7 @@ const injectRenderHandlerPlugin = ({ staticGenerate, cacheConfig })=>({
|
|
|
32
23
|
});
|
|
33
24
|
}
|
|
34
25
|
});
|
|
35
|
-
async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate
|
|
26
|
+
async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate }) {
|
|
36
27
|
const ssrConfig = config.server?.ssr;
|
|
37
28
|
const ssrByEntries = config.server?.ssrByEntries;
|
|
38
29
|
const forceCSR = 'object' == typeof ssrConfig ? ssrConfig.forceCSR : false;
|
|
@@ -49,8 +40,7 @@ async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, st
|
|
|
49
40
|
forceCSR,
|
|
50
41
|
forceCSRMap,
|
|
51
42
|
nonce: config.security?.nonce,
|
|
52
|
-
metaName: metaName || 'modern-js'
|
|
53
|
-
onFallback
|
|
43
|
+
metaName: metaName || 'modern-js'
|
|
54
44
|
});
|
|
55
45
|
return render;
|
|
56
46
|
}
|
|
@@ -68,7 +68,7 @@ function getHeadersWithoutCookie(headers) {
|
|
|
68
68
|
delete _headers.cookie;
|
|
69
69
|
return _headers;
|
|
70
70
|
}
|
|
71
|
-
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config
|
|
71
|
+
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config }) {
|
|
72
72
|
const router = getRouter(routes);
|
|
73
73
|
return async (req, { monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext, serverContext, contextForceCSR, reporter })=>{
|
|
74
74
|
const forMatchpathname = matchPathname ?? getPathname(req);
|
|
@@ -76,13 +76,14 @@ async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig
|
|
|
76
76
|
const framework = cutNameByHyphen(metaName || 'modern-js');
|
|
77
77
|
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
78
78
|
let fallbackReason = null;
|
|
79
|
-
const
|
|
80
|
-
request: req,
|
|
81
|
-
monitors
|
|
82
|
-
};
|
|
83
|
-
const fallbackWrapper = async (reason, error)=>{
|
|
79
|
+
const fallbackWrapper = (reason)=>{
|
|
84
80
|
fallbackReason = reason;
|
|
85
|
-
|
|
81
|
+
monitors?.warn('fallback to CSR reason: %o', {
|
|
82
|
+
type: reason
|
|
83
|
+
});
|
|
84
|
+
monitors?.counter('ssr-fallback', {
|
|
85
|
+
type: reason
|
|
86
|
+
});
|
|
86
87
|
};
|
|
87
88
|
if (!routeInfo) return new Response(createErrorHtml(404), {
|
|
88
89
|
status: 404,
|
|
@@ -178,7 +179,7 @@ async function renderHandler(request, options, mode, fallbackWrapper, framework)
|
|
|
178
179
|
response = await ssrRender(request, options);
|
|
179
180
|
} catch (e) {
|
|
180
181
|
options.onError(e, ErrorDigest.ERENDER);
|
|
181
|
-
|
|
182
|
+
fallbackWrapper('error');
|
|
182
183
|
response = await csrRender(request, {
|
|
183
184
|
...options,
|
|
184
185
|
html: injectFallbackReasonToHtml({
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { MAIN_ENTRY_NAME } from "@modern-js/utils/universal/constants";
|
|
3
3
|
const serverActionHandler = async (req, { serverManifest, routeInfo, rscClientManifest })=>{
|
|
4
|
+
if ('POST' !== req.method) return new Response('Method not allowed', {
|
|
5
|
+
status: 405,
|
|
6
|
+
headers: {
|
|
7
|
+
Allow: 'POST'
|
|
8
|
+
}
|
|
9
|
+
});
|
|
4
10
|
const serverBundle = serverManifest?.renderBundles?.[routeInfo.entryName || MAIN_ENTRY_NAME];
|
|
5
11
|
if (!serverBundle) return new Response('Cannot find server bundle for server action', {
|
|
6
12
|
status: 500
|
|
@@ -4,59 +4,6 @@ const ERROR_PAGE_TEXT = {
|
|
|
4
4
|
404: 'This page could not be found.',
|
|
5
5
|
500: 'Internal Server Error.'
|
|
6
6
|
};
|
|
7
|
-
const SAFE_FAILURE_MESSAGES = {
|
|
8
|
-
500: 'Internal Server Error',
|
|
9
|
-
503: 'Service Unavailable'
|
|
10
|
-
};
|
|
11
|
-
const SAFE_FAILURE_CODES = {
|
|
12
|
-
500: 'INTERNAL_SERVER_ERROR',
|
|
13
|
-
503: 'SERVICE_UNAVAILABLE'
|
|
14
|
-
};
|
|
15
|
-
const readErrorProperty = (error, key)=>{
|
|
16
|
-
if ('object' != typeof error || null === error || !(key in error)) return;
|
|
17
|
-
return error[key];
|
|
18
|
-
};
|
|
19
|
-
const normalizeFailureStatus = (value)=>{
|
|
20
|
-
if ('number' != typeof value || !Number.isInteger(value)) return;
|
|
21
|
-
return value >= 400 && value <= 599 ? value : void 0;
|
|
22
|
-
};
|
|
23
|
-
const normalizeRetryAfter = (value)=>{
|
|
24
|
-
if ('number' == typeof value && Number.isFinite(value) && value >= 0) return String(Math.ceil(value));
|
|
25
|
-
if ('string' == typeof value) {
|
|
26
|
-
const trimmed = value.trim();
|
|
27
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
28
|
-
}
|
|
29
|
-
if (value instanceof Date) return value.toUTCString();
|
|
30
|
-
};
|
|
31
|
-
const getSafeFailureStatus = (error)=>normalizeFailureStatus(readErrorProperty(error, 'status')) ?? normalizeFailureStatus(readErrorProperty(error, 'statusCode')) ?? 500;
|
|
32
|
-
const createSafeFailureHttpResult = (error)=>{
|
|
33
|
-
const status = getSafeFailureStatus(error);
|
|
34
|
-
const retryAfter = 503 === status ? normalizeRetryAfter(readErrorProperty(error, 'retryAfter')) ?? normalizeRetryAfter(readErrorProperty(error, 'retryAfterSeconds')) ?? normalizeRetryAfter('number' == typeof readErrorProperty(error, 'retryAfterMs') ? readErrorProperty(error, 'retryAfterMs') / 1000 : void 0) : void 0;
|
|
35
|
-
return {
|
|
36
|
-
status,
|
|
37
|
-
body: {
|
|
38
|
-
success: false,
|
|
39
|
-
error: {
|
|
40
|
-
code: SAFE_FAILURE_CODES[status] ?? 'REQUEST_FAILED',
|
|
41
|
-
message: SAFE_FAILURE_MESSAGES[status] ?? 'Request failed',
|
|
42
|
-
status
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
headers: {
|
|
46
|
-
'content-type': 'application/json; charset=utf-8',
|
|
47
|
-
...retryAfter ? {
|
|
48
|
-
'Retry-After': retryAfter
|
|
49
|
-
} : {}
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
const createSafeJsonFailureResponse = (error)=>{
|
|
54
|
-
const result = createSafeFailureHttpResult(error);
|
|
55
|
-
return new Response(JSON.stringify(result.body), {
|
|
56
|
-
status: result.status,
|
|
57
|
-
headers: result.headers
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
7
|
const createErrorHtml = (status)=>{
|
|
61
8
|
const text = ERROR_PAGE_TEXT[status] || '';
|
|
62
9
|
const title = `${status}: ${text}`;
|
|
@@ -104,4 +51,5 @@ function onError(digest, error, monitors, req) {
|
|
|
104
51
|
else if (req) console.error(`Server Error - ${digest}, error = ${error instanceof Error ? error.stack || error.message : error}, req.url = ${req.url}, req.headers = ${JSON.stringify(headerData)}`);
|
|
105
52
|
else console.error(`Server Error - ${digest}, error = ${error instanceof Error ? error.stack || error.message : error} `);
|
|
106
53
|
}
|
|
107
|
-
export {
|
|
54
|
+
export { createSafeFailureHttpResult, createSafeJsonFailureResponse, getSafeFailureStatus } from "@modern-js/runtime-extensions/safe-failure";
|
|
55
|
+
export { createErrorHtml, error_ErrorDigest as ErrorDigest, onError };
|
|
@@ -6,6 +6,7 @@ type ResolvePreCompressedAssetResult = {
|
|
|
6
6
|
encoding: SupportedEncoding;
|
|
7
7
|
} | null;
|
|
8
8
|
hasVariant: boolean;
|
|
9
|
+
acceptable: boolean;
|
|
9
10
|
};
|
|
10
11
|
export declare const resolvePreCompressedAsset: (c: Parameters<Middleware>[0], filepath: string) => Promise<ResolvePreCompressedAssetResult>;
|
|
11
12
|
export declare const applyPreCompressedAssetHeaders: (c: Parameters<Middleware>[0], preCompressedAsset: ResolvePreCompressedAssetResult) => void;
|
|
@@ -10,16 +10,16 @@ type StaticServingOptions = {
|
|
|
10
10
|
pwd: string;
|
|
11
11
|
pathPrefix: string;
|
|
12
12
|
};
|
|
13
|
-
export declare const servePreCompressedPublicRouteAsset: (c: MiddlewareContext, pwd: string, route: ServerRoute) => Promise<(Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null>;
|
|
13
|
+
export declare const servePreCompressedPublicRouteAsset: (c: MiddlewareContext, pwd: string, route: ServerRoute) => Promise<(Response & import("hono").TypedResponse<null, 406, "body">) | (Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null>;
|
|
14
14
|
/**
|
|
15
15
|
* Serves post-build convention assets generated under dist/public at their
|
|
16
16
|
* root URL. This is intentionally independent of config/public and route.json:
|
|
17
17
|
* the generator runs after the route manifest is built.
|
|
18
18
|
*/
|
|
19
|
-
export declare const servePublicDirectoryAsset: (c: MiddlewareContext, pwd: string) => Promise<(Response & import("hono").TypedResponse<null, 200, "body">) | (Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null>;
|
|
19
|
+
export declare const servePublicDirectoryAsset: (c: MiddlewareContext, pwd: string) => Promise<(Response & import("hono").TypedResponse<null, 200, "body">) | (Response & import("hono").TypedResponse<null, 406, "body">) | (Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null>;
|
|
20
20
|
export declare const createModuleFederationStaticServing: ({ pwd, pathPrefix, }: StaticServingOptions) => {
|
|
21
21
|
resolveRequest: (pathname: string) => Promise<StaticServingRequest | null>;
|
|
22
|
-
serveStaticHit: (c: MiddlewareContext, request: StaticServingRequest) => Promise<(Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null>;
|
|
23
|
-
serveModuleFederationAsset: (c: MiddlewareContext, request: StaticServingRequest) => Promise<(Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null> | null;
|
|
22
|
+
serveStaticHit: (c: MiddlewareContext, request: StaticServingRequest) => Promise<(Response & import("hono").TypedResponse<null, 406, "body">) | (Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null>;
|
|
23
|
+
serveModuleFederationAsset: (c: MiddlewareContext, request: StaticServingRequest) => Promise<(Response & import("hono").TypedResponse<null, 406, "body">) | (Response & import("hono").TypedResponse<Uint8Array<ArrayBuffer>, 200, "body">) | null> | null;
|
|
24
24
|
};
|
|
25
25
|
export {};
|
|
@@ -4,4 +4,4 @@ export interface InjectRenderHandlerOptions {
|
|
|
4
4
|
cacheConfig?: CacheConfig;
|
|
5
5
|
}
|
|
6
6
|
export declare const injectRenderHandlerPlugin: ({ staticGenerate, cacheConfig, }: InjectRenderHandlerOptions) => ServerPlugin;
|
|
7
|
-
export declare function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate,
|
|
7
|
+
export declare function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate, }: GetRenderHandlerOptions): Promise<Render>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ServerRoute } from '@modern-js/types';
|
|
2
2
|
import type { Router } from 'hono/router';
|
|
3
|
-
import type { CacheConfig,
|
|
3
|
+
import type { CacheConfig, Render, UserConfig } from '../../types';
|
|
4
4
|
import type { Params } from '../../types/requestHandler';
|
|
5
5
|
interface CreateRenderOptions {
|
|
6
6
|
pwd: string;
|
|
@@ -8,7 +8,6 @@ interface CreateRenderOptions {
|
|
|
8
8
|
config: UserConfig;
|
|
9
9
|
cacheConfig?: CacheConfig;
|
|
10
10
|
staticGenerate?: boolean;
|
|
11
|
-
onFallback?: OnFallback;
|
|
12
11
|
metaName?: string;
|
|
13
12
|
forceCSR?: boolean;
|
|
14
13
|
forceCSRMap?: Map<string, boolean>;
|
|
@@ -16,5 +15,5 @@ interface CreateRenderOptions {
|
|
|
16
15
|
}
|
|
17
16
|
type MatchedRoute = [ServerRoute | undefined, Params];
|
|
18
17
|
export declare function matchRoute(router: Router<ServerRoute>, pathname: string, entryName?: string): MatchedRoute;
|
|
19
|
-
export declare function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config,
|
|
18
|
+
export declare function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config, }: CreateRenderOptions): Promise<Render>;
|
|
20
19
|
export {};
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import type { CacheOption, Container, HttpMethodDecider, MiddlewareContext,
|
|
1
|
+
import type { CacheOption, Container, HttpMethodDecider, MiddlewareContext, ServerRoute } from '@modern-js/types';
|
|
2
2
|
import type { Context, MiddlewareHandler } from 'hono';
|
|
3
3
|
import type { UserConfig } from '../config';
|
|
4
4
|
import type { Render } from '../render';
|
|
5
5
|
import type { ServerPlugin } from './plugin';
|
|
6
6
|
export type { FileChangeEvent, ResetEvent } from '@modern-js/plugin';
|
|
7
|
-
export type FallbackReason = 'error' | 'header' | 'query' | `header,${string}`;
|
|
8
|
-
export type FallbackContext = {
|
|
9
|
-
request: Request;
|
|
10
|
-
monitors?: Monitors;
|
|
11
|
-
};
|
|
12
|
-
export type FallbackInput = {
|
|
13
|
-
reason: FallbackReason;
|
|
14
|
-
error: unknown;
|
|
15
|
-
context?: FallbackContext;
|
|
16
|
-
};
|
|
17
|
-
export type OnFallback = (reason: FallbackReason, error?: unknown, context?: FallbackContext) => Promise<void>;
|
|
18
7
|
export type APIServerStartInput = {
|
|
19
8
|
pwd: string;
|
|
20
9
|
prefix?: string;
|
|
@@ -44,7 +33,6 @@ export interface GetRenderHandlerOptions {
|
|
|
44
33
|
pwd: string;
|
|
45
34
|
routes: ServerRoute[];
|
|
46
35
|
config: UserConfig;
|
|
47
|
-
onFallback?: OnFallback;
|
|
48
36
|
cacheConfig?: CacheConfig;
|
|
49
37
|
staticGenerate?: boolean;
|
|
50
38
|
metaName?: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { APIServerStartInput, CacheConfig,
|
|
1
|
+
export type { APIServerStartInput, CacheConfig, FileChangeEvent, GetRenderHandlerOptions, MiddlewareObj, ServerConfig, ServerMiddleware, WebServerStartInput, } from './base';
|
|
2
2
|
export * from './plugin';
|