@bleedingdev/modern-js-server-core 3.8.3-ultramodern.1 → 3.8.3-ultramodern.3
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/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/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/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/utils/error.d.ts +2 -16
- package/package.json +7 -6
|
@@ -69,7 +69,9 @@ const createWebRequest = (req, res, body)=>{
|
|
|
69
69
|
signal: controller.signal
|
|
70
70
|
};
|
|
71
71
|
res.on('close', ()=>controller.abort('res closed'));
|
|
72
|
-
const
|
|
72
|
+
const protocol = 'encrypted' in req.socket && req.socket.encrypted ? 'https' : 'http';
|
|
73
|
+
const authority = req.headers[':authority'] ?? req.headers.host;
|
|
74
|
+
const url = `${protocol}://${authority}${req.url}`;
|
|
73
75
|
const needsRequestBody = body || !('GET' === method || 'HEAD' === method);
|
|
74
76
|
const cloneableReq = needsRequestBody ? external_cloneable_readable_default()(req) : null;
|
|
75
77
|
if (needsRequestBody) {
|
|
@@ -60,7 +60,11 @@ const MODULE_FEDERATION_OPTIONAL_FILES = [
|
|
|
60
60
|
'mf-stats.json'
|
|
61
61
|
];
|
|
62
62
|
const trimLeadingSlash = (value)=>value.replace(/^\/+/, '');
|
|
63
|
-
const getModuleFederationRequestPath = (pathname, pathPrefix)=>
|
|
63
|
+
const getModuleFederationRequestPath = (pathname, pathPrefix)=>{
|
|
64
|
+
const normalizedPrefix = `/${trimLeadingSlash(pathPrefix)}`.replace(/\/+$/u, '');
|
|
65
|
+
const requestPath = normalizedPrefix && (pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`)) ? pathname.slice(normalizedPrefix.length) : pathname;
|
|
66
|
+
return trimLeadingSlash(requestPath);
|
|
67
|
+
};
|
|
64
68
|
const isModuleFederationManifestRequest = (requestPath)=>MODULE_FEDERATION_MANIFEST_FILES.includes(requestPath);
|
|
65
69
|
const isBackendModuleFederationManifestRequest = (requestPath)=>requestPath === BACKEND_MODULE_FEDERATION_MANIFEST_FILE;
|
|
66
70
|
const applyModuleFederationAssetHeaders = (c)=>{
|
|
@@ -40,23 +40,32 @@ const PRE_COMPRESSED_SUPPORTED_ENCODINGS = [
|
|
|
40
40
|
'br',
|
|
41
41
|
'gzip'
|
|
42
42
|
];
|
|
43
|
+
const QUALITY_VALUE_PATTERN = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/u;
|
|
43
44
|
const parseAcceptEncoding = (value)=>value.split(',').map((item)=>item.trim()).filter(Boolean).map((item)=>{
|
|
44
45
|
const [rawName, ...params] = item.split(';');
|
|
45
46
|
const name = rawName.trim().toLowerCase();
|
|
46
47
|
let q = 1;
|
|
48
|
+
let qualitySeen = false;
|
|
47
49
|
for (const param of params){
|
|
48
50
|
const [key, rawValue] = param.split('=').map((v)=>v.trim());
|
|
49
|
-
if ('q'
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
if ('q' === key.toLowerCase()) {
|
|
52
|
+
if (qualitySeen || null == rawValue || !QUALITY_VALUE_PATTERN.test(rawValue)) {
|
|
53
|
+
q = 0;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
qualitySeen = true;
|
|
57
|
+
q = Number(rawValue);
|
|
58
|
+
}
|
|
52
59
|
}
|
|
53
60
|
return {
|
|
54
61
|
name,
|
|
55
62
|
q
|
|
56
63
|
};
|
|
57
64
|
});
|
|
58
|
-
const
|
|
59
|
-
if (!value) return [
|
|
65
|
+
const getAcceptedRepresentations = (value)=>{
|
|
66
|
+
if (!value) return [
|
|
67
|
+
'identity'
|
|
68
|
+
];
|
|
60
69
|
const parsed = parseAcceptEncoding(value);
|
|
61
70
|
const qualityByEncoding = new Map();
|
|
62
71
|
let wildcardQuality;
|
|
@@ -72,10 +81,17 @@ const getAcceptedEncodings = (value)=>{
|
|
|
72
81
|
if (void 0 !== explicit) return explicit;
|
|
73
82
|
return wildcardQuality ?? 0;
|
|
74
83
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
const identityQuality = qualityByEncoding.get('identity') ?? (0 === wildcardQuality ? 0 : 1);
|
|
85
|
+
return [
|
|
86
|
+
...PRE_COMPRESSED_SUPPORTED_ENCODINGS.map((encoding)=>({
|
|
87
|
+
encoding,
|
|
88
|
+
quality: getQuality(encoding)
|
|
89
|
+
})),
|
|
90
|
+
{
|
|
91
|
+
encoding: 'identity',
|
|
92
|
+
quality: identityQuality
|
|
93
|
+
}
|
|
94
|
+
].filter((item)=>item.quality > 0).sort((a, b)=>b.quality - a.quality).map((item)=>item.encoding);
|
|
79
95
|
};
|
|
80
96
|
const appendVaryHeader = (c, value)=>{
|
|
81
97
|
const current = c.res.headers.get('Vary');
|
|
@@ -91,34 +107,38 @@ const resolvePreCompressedAsset = async (c, filepath)=>{
|
|
|
91
107
|
utils_namespaceObject.fs.pathExists(gzipPath)
|
|
92
108
|
]);
|
|
93
109
|
const hasVariant = hasBr || hasGzip;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
const acceptedRepresentations = getAcceptedRepresentations(c.req.header('accept-encoding'));
|
|
111
|
+
for (const encoding of acceptedRepresentations){
|
|
112
|
+
if ('identity' === encoding) return {
|
|
113
|
+
selected: null,
|
|
114
|
+
hasVariant,
|
|
115
|
+
acceptable: true
|
|
116
|
+
};
|
|
100
117
|
if ('br' === encoding && hasBr) return {
|
|
101
118
|
selected: {
|
|
102
119
|
filepath: brPath,
|
|
103
120
|
encoding
|
|
104
121
|
},
|
|
105
|
-
hasVariant: true
|
|
122
|
+
hasVariant: true,
|
|
123
|
+
acceptable: true
|
|
106
124
|
};
|
|
107
125
|
if ('gzip' === encoding && hasGzip) return {
|
|
108
126
|
selected: {
|
|
109
127
|
filepath: gzipPath,
|
|
110
128
|
encoding
|
|
111
129
|
},
|
|
112
|
-
hasVariant: true
|
|
130
|
+
hasVariant: true,
|
|
131
|
+
acceptable: true
|
|
113
132
|
};
|
|
114
133
|
}
|
|
115
134
|
return {
|
|
116
135
|
selected: null,
|
|
117
|
-
hasVariant
|
|
136
|
+
hasVariant,
|
|
137
|
+
acceptable: false
|
|
118
138
|
};
|
|
119
139
|
};
|
|
120
140
|
const applyPreCompressedAssetHeaders = (c, preCompressedAsset)=>{
|
|
121
|
-
if (preCompressedAsset.hasVariant) appendVaryHeader(c, 'Accept-Encoding');
|
|
141
|
+
if (preCompressedAsset.hasVariant || !preCompressedAsset.acceptable) appendVaryHeader(c, 'Accept-Encoding');
|
|
122
142
|
if (preCompressedAsset.selected) c.header('Content-Encoding', preCompressedAsset.selected.encoding);
|
|
123
143
|
};
|
|
124
144
|
exports.applyPreCompressedAssetHeaders = __webpack_exports__.applyPreCompressedAssetHeaders;
|
|
@@ -48,11 +48,16 @@ const external_path_namespaceObject = require("path");
|
|
|
48
48
|
var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
|
|
49
49
|
const external_staticModuleFederation_js_namespaceObject = require("./staticModuleFederation.js");
|
|
50
50
|
const external_staticPrecompressed_js_namespaceObject = require("./staticPrecompressed.js");
|
|
51
|
+
const MODULE_FEDERATION_ASSET_REFRESH_INTERVAL_MS = 1000;
|
|
51
52
|
const getStaticMimeType = (filename)=>(0, mime_namespaceObject.getMimeType)(filename) ?? ('.cjs' === external_path_default().extname(filename).toLowerCase() ? "text/javascript; charset=UTF-8" : void 0);
|
|
52
53
|
const servePreCompressedPublicRouteAsset = async (c, pwd, route)=>{
|
|
53
54
|
const { entryPath } = route;
|
|
54
55
|
const originFilename = external_path_default().join(pwd, entryPath);
|
|
55
56
|
const preCompressedAsset = await (0, external_staticPrecompressed_js_namespaceObject.resolvePreCompressedAsset)(c, originFilename);
|
|
57
|
+
if (!preCompressedAsset.acceptable) {
|
|
58
|
+
(0, external_staticPrecompressed_js_namespaceObject.applyPreCompressedAssetHeaders)(c, preCompressedAsset);
|
|
59
|
+
return c.body(null, 406);
|
|
60
|
+
}
|
|
56
61
|
const filename = preCompressedAsset.selected?.filepath ?? originFilename;
|
|
57
62
|
const data = await fileReader_namespaceObject.fileReader.readFile(filename, 'buffer');
|
|
58
63
|
const mimeType = getStaticMimeType(originFilename);
|
|
@@ -99,6 +104,10 @@ const servePublicDirectoryAsset = async (c, pwd)=>{
|
|
|
99
104
|
const originFilename = await resolvePublicDirectoryAsset(pwd, c.req.path);
|
|
100
105
|
if (null === originFilename) return null;
|
|
101
106
|
const preCompressedAsset = await (0, external_staticPrecompressed_js_namespaceObject.resolvePreCompressedAsset)(c, originFilename);
|
|
107
|
+
if (!preCompressedAsset.acceptable) {
|
|
108
|
+
(0, external_staticPrecompressed_js_namespaceObject.applyPreCompressedAssetHeaders)(c, preCompressedAsset);
|
|
109
|
+
return c.body(null, 406);
|
|
110
|
+
}
|
|
102
111
|
const selectedFilename = preCompressedAsset.selected?.filepath ?? originFilename;
|
|
103
112
|
const publicDirectory = await utils_namespaceObject.fs.realpath(external_path_default().join(pwd, 'public'));
|
|
104
113
|
let realSelectedFilename;
|
|
@@ -119,10 +128,19 @@ const servePublicDirectoryAsset = async (c, pwd)=>{
|
|
|
119
128
|
return c.body(body, 200);
|
|
120
129
|
};
|
|
121
130
|
const createModuleFederationStaticServing = ({ pwd, pathPrefix })=>{
|
|
122
|
-
let
|
|
131
|
+
let moduleFederationAssets = null;
|
|
132
|
+
let moduleFederationAssetsExpiresAt = 0;
|
|
133
|
+
let moduleFederationAssetsRefresh = null;
|
|
123
134
|
const getModuleFederationAssets = async ()=>{
|
|
124
|
-
if (
|
|
125
|
-
|
|
135
|
+
if (moduleFederationAssets && Date.now() < moduleFederationAssetsExpiresAt) return moduleFederationAssets;
|
|
136
|
+
if (!moduleFederationAssetsRefresh) moduleFederationAssetsRefresh = (0, external_staticModuleFederation_js_namespaceObject.getModuleFederationAssetList)(pwd).then((assets)=>{
|
|
137
|
+
moduleFederationAssets = assets;
|
|
138
|
+
moduleFederationAssetsExpiresAt = Date.now() + MODULE_FEDERATION_ASSET_REFRESH_INTERVAL_MS;
|
|
139
|
+
return assets;
|
|
140
|
+
}).finally(()=>{
|
|
141
|
+
moduleFederationAssetsRefresh = null;
|
|
142
|
+
});
|
|
143
|
+
return moduleFederationAssetsRefresh;
|
|
126
144
|
};
|
|
127
145
|
const resolveRequest = async (pathname)=>{
|
|
128
146
|
const requestPath = (0, external_staticModuleFederation_js_namespaceObject.getModuleFederationRequestPath)(pathname, pathPrefix);
|
|
@@ -143,8 +161,13 @@ const createModuleFederationStaticServing = ({ pwd, pathPrefix })=>{
|
|
|
143
161
|
const canUsePreCompressed = !shouldPatchManifest && !shouldPatchRemoteEntry;
|
|
144
162
|
const preCompressedAsset = canUsePreCompressed ? await (0, external_staticPrecompressed_js_namespaceObject.resolvePreCompressedAsset)(c, filepath) : {
|
|
145
163
|
selected: null,
|
|
146
|
-
hasVariant: false
|
|
164
|
+
hasVariant: false,
|
|
165
|
+
acceptable: true
|
|
147
166
|
};
|
|
167
|
+
if (!preCompressedAsset.acceptable) {
|
|
168
|
+
(0, external_staticPrecompressed_js_namespaceObject.applyPreCompressedAssetHeaders)(c, preCompressedAsset);
|
|
169
|
+
return c.body(null, 406);
|
|
170
|
+
}
|
|
148
171
|
const targetFilepath = preCompressedAsset.selected?.filepath ?? filepath;
|
|
149
172
|
const chunk = await fileReader_namespaceObject.fileReader.readFileFromSystem(targetFilepath, 'buffer');
|
|
150
173
|
if (null === chunk) return null;
|
|
@@ -32,6 +32,12 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
32
32
|
});
|
|
33
33
|
const constants_namespaceObject = require("@modern-js/utils/universal/constants");
|
|
34
34
|
const serverActionHandler = async (req, { serverManifest, routeInfo, rscClientManifest })=>{
|
|
35
|
+
if ('POST' !== req.method) return new Response('Method not allowed', {
|
|
36
|
+
status: 405,
|
|
37
|
+
headers: {
|
|
38
|
+
Allow: 'POST'
|
|
39
|
+
}
|
|
40
|
+
});
|
|
35
41
|
const serverBundle = serverManifest?.renderBundles?.[routeInfo.entryName || constants_namespaceObject.MAIN_ENTRY_NAME];
|
|
36
42
|
if (!serverBundle) return new Response('Cannot find server bundle for server action', {
|
|
37
43
|
status: 500
|
package/dist/cjs/utils/error.js
CHANGED
|
@@ -30,69 +30,17 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
30
30
|
__webpack_require__.d(__webpack_exports__, {
|
|
31
31
|
ErrorDigest: ()=>error_ErrorDigest,
|
|
32
32
|
createErrorHtml: ()=>createErrorHtml,
|
|
33
|
-
createSafeFailureHttpResult: ()=>createSafeFailureHttpResult,
|
|
34
|
-
createSafeJsonFailureResponse: ()=>createSafeJsonFailureResponse,
|
|
35
|
-
getSafeFailureStatus: ()=>getSafeFailureStatus,
|
|
33
|
+
createSafeFailureHttpResult: ()=>safe_failure_namespaceObject.createSafeFailureHttpResult,
|
|
34
|
+
createSafeJsonFailureResponse: ()=>safe_failure_namespaceObject.createSafeJsonFailureResponse,
|
|
35
|
+
getSafeFailureStatus: ()=>safe_failure_namespaceObject.getSafeFailureStatus,
|
|
36
36
|
onError: ()=>onError
|
|
37
37
|
});
|
|
38
|
+
const safe_failure_namespaceObject = require("@modern-js/runtime-extensions/safe-failure");
|
|
38
39
|
const external_request_js_namespaceObject = require("./request.js");
|
|
39
40
|
const ERROR_PAGE_TEXT = {
|
|
40
41
|
404: 'This page could not be found.',
|
|
41
42
|
500: 'Internal Server Error.'
|
|
42
43
|
};
|
|
43
|
-
const SAFE_FAILURE_MESSAGES = {
|
|
44
|
-
500: 'Internal Server Error',
|
|
45
|
-
503: 'Service Unavailable'
|
|
46
|
-
};
|
|
47
|
-
const SAFE_FAILURE_CODES = {
|
|
48
|
-
500: 'INTERNAL_SERVER_ERROR',
|
|
49
|
-
503: 'SERVICE_UNAVAILABLE'
|
|
50
|
-
};
|
|
51
|
-
const readErrorProperty = (error, key)=>{
|
|
52
|
-
if ('object' != typeof error || null === error || !(key in error)) return;
|
|
53
|
-
return error[key];
|
|
54
|
-
};
|
|
55
|
-
const normalizeFailureStatus = (value)=>{
|
|
56
|
-
if ('number' != typeof value || !Number.isInteger(value)) return;
|
|
57
|
-
return value >= 400 && value <= 599 ? value : void 0;
|
|
58
|
-
};
|
|
59
|
-
const normalizeRetryAfter = (value)=>{
|
|
60
|
-
if ('number' == typeof value && Number.isFinite(value) && value >= 0) return String(Math.ceil(value));
|
|
61
|
-
if ('string' == typeof value) {
|
|
62
|
-
const trimmed = value.trim();
|
|
63
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
64
|
-
}
|
|
65
|
-
if (value instanceof Date) return value.toUTCString();
|
|
66
|
-
};
|
|
67
|
-
const getSafeFailureStatus = (error)=>normalizeFailureStatus(readErrorProperty(error, 'status')) ?? normalizeFailureStatus(readErrorProperty(error, 'statusCode')) ?? 500;
|
|
68
|
-
const createSafeFailureHttpResult = (error)=>{
|
|
69
|
-
const status = getSafeFailureStatus(error);
|
|
70
|
-
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;
|
|
71
|
-
return {
|
|
72
|
-
status,
|
|
73
|
-
body: {
|
|
74
|
-
success: false,
|
|
75
|
-
error: {
|
|
76
|
-
code: SAFE_FAILURE_CODES[status] ?? 'REQUEST_FAILED',
|
|
77
|
-
message: SAFE_FAILURE_MESSAGES[status] ?? 'Request failed',
|
|
78
|
-
status
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
headers: {
|
|
82
|
-
'content-type': 'application/json; charset=utf-8',
|
|
83
|
-
...retryAfter ? {
|
|
84
|
-
'Retry-After': retryAfter
|
|
85
|
-
} : {}
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
};
|
|
89
|
-
const createSafeJsonFailureResponse = (error)=>{
|
|
90
|
-
const result = createSafeFailureHttpResult(error);
|
|
91
|
-
return new Response(JSON.stringify(result.body), {
|
|
92
|
-
status: result.status,
|
|
93
|
-
headers: result.headers
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
44
|
const createErrorHtml = (status)=>{
|
|
97
45
|
const text = ERROR_PAGE_TEXT[status] || '';
|
|
98
46
|
const title = `${status}: ${text}`;
|
|
@@ -25,7 +25,9 @@ const createWebRequest = (req, res, body)=>{
|
|
|
25
25
|
signal: controller.signal
|
|
26
26
|
};
|
|
27
27
|
res.on('close', ()=>controller.abort('res closed'));
|
|
28
|
-
const
|
|
28
|
+
const protocol = 'encrypted' in req.socket && req.socket.encrypted ? 'https' : 'http';
|
|
29
|
+
const authority = req.headers[':authority'] ?? req.headers.host;
|
|
30
|
+
const url = `${protocol}://${authority}${req.url}`;
|
|
29
31
|
const needsRequestBody = body || !('GET' === method || 'HEAD' === method);
|
|
30
32
|
const cloneableReq = needsRequestBody ? cloneable_readable(req) : null;
|
|
31
33
|
if (needsRequestBody) {
|
|
@@ -11,7 +11,11 @@ const MODULE_FEDERATION_OPTIONAL_FILES = [
|
|
|
11
11
|
'mf-stats.json'
|
|
12
12
|
];
|
|
13
13
|
const trimLeadingSlash = (value)=>value.replace(/^\/+/, '');
|
|
14
|
-
const getModuleFederationRequestPath = (pathname, pathPrefix)=>
|
|
14
|
+
const getModuleFederationRequestPath = (pathname, pathPrefix)=>{
|
|
15
|
+
const normalizedPrefix = `/${trimLeadingSlash(pathPrefix)}`.replace(/\/+$/u, '');
|
|
16
|
+
const requestPath = normalizedPrefix && (pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`)) ? pathname.slice(normalizedPrefix.length) : pathname;
|
|
17
|
+
return trimLeadingSlash(requestPath);
|
|
18
|
+
};
|
|
15
19
|
const isModuleFederationManifestRequest = (requestPath)=>MODULE_FEDERATION_MANIFEST_FILES.includes(requestPath);
|
|
16
20
|
const isBackendModuleFederationManifestRequest = (requestPath)=>requestPath === BACKEND_MODULE_FEDERATION_MANIFEST_FILE;
|
|
17
21
|
const applyModuleFederationAssetHeaders = (c)=>{
|
|
@@ -7,23 +7,32 @@ const PRE_COMPRESSED_SUPPORTED_ENCODINGS = [
|
|
|
7
7
|
'br',
|
|
8
8
|
'gzip'
|
|
9
9
|
];
|
|
10
|
+
const QUALITY_VALUE_PATTERN = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/u;
|
|
10
11
|
const parseAcceptEncoding = (value)=>value.split(',').map((item)=>item.trim()).filter(Boolean).map((item)=>{
|
|
11
12
|
const [rawName, ...params] = item.split(';');
|
|
12
13
|
const name = rawName.trim().toLowerCase();
|
|
13
14
|
let q = 1;
|
|
15
|
+
let qualitySeen = false;
|
|
14
16
|
for (const param of params){
|
|
15
17
|
const [key, rawValue] = param.split('=').map((v)=>v.trim());
|
|
16
|
-
if ('q'
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
if ('q' === key.toLowerCase()) {
|
|
19
|
+
if (qualitySeen || null == rawValue || !QUALITY_VALUE_PATTERN.test(rawValue)) {
|
|
20
|
+
q = 0;
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
qualitySeen = true;
|
|
24
|
+
q = Number(rawValue);
|
|
25
|
+
}
|
|
19
26
|
}
|
|
20
27
|
return {
|
|
21
28
|
name,
|
|
22
29
|
q
|
|
23
30
|
};
|
|
24
31
|
});
|
|
25
|
-
const
|
|
26
|
-
if (!value) return [
|
|
32
|
+
const getAcceptedRepresentations = (value)=>{
|
|
33
|
+
if (!value) return [
|
|
34
|
+
'identity'
|
|
35
|
+
];
|
|
27
36
|
const parsed = parseAcceptEncoding(value);
|
|
28
37
|
const qualityByEncoding = new Map();
|
|
29
38
|
let wildcardQuality;
|
|
@@ -39,10 +48,17 @@ const getAcceptedEncodings = (value)=>{
|
|
|
39
48
|
if (void 0 !== explicit) return explicit;
|
|
40
49
|
return wildcardQuality ?? 0;
|
|
41
50
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
const identityQuality = qualityByEncoding.get('identity') ?? (0 === wildcardQuality ? 0 : 1);
|
|
52
|
+
return [
|
|
53
|
+
...PRE_COMPRESSED_SUPPORTED_ENCODINGS.map((encoding)=>({
|
|
54
|
+
encoding,
|
|
55
|
+
quality: getQuality(encoding)
|
|
56
|
+
})),
|
|
57
|
+
{
|
|
58
|
+
encoding: 'identity',
|
|
59
|
+
quality: identityQuality
|
|
60
|
+
}
|
|
61
|
+
].filter((item)=>item.quality > 0).sort((a, b)=>b.quality - a.quality).map((item)=>item.encoding);
|
|
46
62
|
};
|
|
47
63
|
const appendVaryHeader = (c, value)=>{
|
|
48
64
|
const current = c.res.headers.get('Vary');
|
|
@@ -58,34 +74,38 @@ const resolvePreCompressedAsset = async (c, filepath)=>{
|
|
|
58
74
|
fs.pathExists(gzipPath)
|
|
59
75
|
]);
|
|
60
76
|
const hasVariant = hasBr || hasGzip;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
const acceptedRepresentations = getAcceptedRepresentations(c.req.header('accept-encoding'));
|
|
78
|
+
for (const encoding of acceptedRepresentations){
|
|
79
|
+
if ('identity' === encoding) return {
|
|
80
|
+
selected: null,
|
|
81
|
+
hasVariant,
|
|
82
|
+
acceptable: true
|
|
83
|
+
};
|
|
67
84
|
if ('br' === encoding && hasBr) return {
|
|
68
85
|
selected: {
|
|
69
86
|
filepath: brPath,
|
|
70
87
|
encoding
|
|
71
88
|
},
|
|
72
|
-
hasVariant: true
|
|
89
|
+
hasVariant: true,
|
|
90
|
+
acceptable: true
|
|
73
91
|
};
|
|
74
92
|
if ('gzip' === encoding && hasGzip) return {
|
|
75
93
|
selected: {
|
|
76
94
|
filepath: gzipPath,
|
|
77
95
|
encoding
|
|
78
96
|
},
|
|
79
|
-
hasVariant: true
|
|
97
|
+
hasVariant: true,
|
|
98
|
+
acceptable: true
|
|
80
99
|
};
|
|
81
100
|
}
|
|
82
101
|
return {
|
|
83
102
|
selected: null,
|
|
84
|
-
hasVariant
|
|
103
|
+
hasVariant,
|
|
104
|
+
acceptable: false
|
|
85
105
|
};
|
|
86
106
|
};
|
|
87
107
|
const applyPreCompressedAssetHeaders = (c, preCompressedAsset)=>{
|
|
88
|
-
if (preCompressedAsset.hasVariant) appendVaryHeader(c, 'Accept-Encoding');
|
|
108
|
+
if (preCompressedAsset.hasVariant || !preCompressedAsset.acceptable) appendVaryHeader(c, 'Accept-Encoding');
|
|
89
109
|
if (preCompressedAsset.selected) c.header('Content-Encoding', preCompressedAsset.selected.encoding);
|
|
90
110
|
};
|
|
91
111
|
export { applyPreCompressedAssetHeaders, resolvePreCompressedAsset };
|
|
@@ -4,11 +4,16 @@ import { getMimeType } from "hono/utils/mime";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { applyModuleFederationAssetHeaders, getModuleFederationAssetList, getModuleFederationRequestPath, isBackendModuleFederationManifestRequest, isModuleFederationManifestRequest, patchModuleFederationManifestPublicPath, patchModuleFederationRemoteEntryPublicPath } from "./staticModuleFederation.mjs";
|
|
6
6
|
import { applyPreCompressedAssetHeaders, resolvePreCompressedAsset } from "./staticPrecompressed.mjs";
|
|
7
|
+
const MODULE_FEDERATION_ASSET_REFRESH_INTERVAL_MS = 1000;
|
|
7
8
|
const getStaticMimeType = (filename)=>getMimeType(filename) ?? ('.cjs' === path.extname(filename).toLowerCase() ? "text/javascript; charset=UTF-8" : void 0);
|
|
8
9
|
const servePreCompressedPublicRouteAsset = async (c, pwd, route)=>{
|
|
9
10
|
const { entryPath } = route;
|
|
10
11
|
const originFilename = path.join(pwd, entryPath);
|
|
11
12
|
const preCompressedAsset = await resolvePreCompressedAsset(c, originFilename);
|
|
13
|
+
if (!preCompressedAsset.acceptable) {
|
|
14
|
+
applyPreCompressedAssetHeaders(c, preCompressedAsset);
|
|
15
|
+
return c.body(null, 406);
|
|
16
|
+
}
|
|
12
17
|
const filename = preCompressedAsset.selected?.filepath ?? originFilename;
|
|
13
18
|
const data = await fileReader.readFile(filename, 'buffer');
|
|
14
19
|
const mimeType = getStaticMimeType(originFilename);
|
|
@@ -55,6 +60,10 @@ const servePublicDirectoryAsset = async (c, pwd)=>{
|
|
|
55
60
|
const originFilename = await resolvePublicDirectoryAsset(pwd, c.req.path);
|
|
56
61
|
if (null === originFilename) return null;
|
|
57
62
|
const preCompressedAsset = await resolvePreCompressedAsset(c, originFilename);
|
|
63
|
+
if (!preCompressedAsset.acceptable) {
|
|
64
|
+
applyPreCompressedAssetHeaders(c, preCompressedAsset);
|
|
65
|
+
return c.body(null, 406);
|
|
66
|
+
}
|
|
58
67
|
const selectedFilename = preCompressedAsset.selected?.filepath ?? originFilename;
|
|
59
68
|
const publicDirectory = await fs.realpath(path.join(pwd, 'public'));
|
|
60
69
|
let realSelectedFilename;
|
|
@@ -75,10 +84,19 @@ const servePublicDirectoryAsset = async (c, pwd)=>{
|
|
|
75
84
|
return c.body(body, 200);
|
|
76
85
|
};
|
|
77
86
|
const createModuleFederationStaticServing = ({ pwd, pathPrefix })=>{
|
|
78
|
-
let
|
|
87
|
+
let moduleFederationAssets = null;
|
|
88
|
+
let moduleFederationAssetsExpiresAt = 0;
|
|
89
|
+
let moduleFederationAssetsRefresh = null;
|
|
79
90
|
const getModuleFederationAssets = async ()=>{
|
|
80
|
-
if (
|
|
81
|
-
|
|
91
|
+
if (moduleFederationAssets && Date.now() < moduleFederationAssetsExpiresAt) return moduleFederationAssets;
|
|
92
|
+
if (!moduleFederationAssetsRefresh) moduleFederationAssetsRefresh = getModuleFederationAssetList(pwd).then((assets)=>{
|
|
93
|
+
moduleFederationAssets = assets;
|
|
94
|
+
moduleFederationAssetsExpiresAt = Date.now() + MODULE_FEDERATION_ASSET_REFRESH_INTERVAL_MS;
|
|
95
|
+
return assets;
|
|
96
|
+
}).finally(()=>{
|
|
97
|
+
moduleFederationAssetsRefresh = null;
|
|
98
|
+
});
|
|
99
|
+
return moduleFederationAssetsRefresh;
|
|
82
100
|
};
|
|
83
101
|
const resolveRequest = async (pathname)=>{
|
|
84
102
|
const requestPath = getModuleFederationRequestPath(pathname, pathPrefix);
|
|
@@ -99,8 +117,13 @@ const createModuleFederationStaticServing = ({ pwd, pathPrefix })=>{
|
|
|
99
117
|
const canUsePreCompressed = !shouldPatchManifest && !shouldPatchRemoteEntry;
|
|
100
118
|
const preCompressedAsset = canUsePreCompressed ? await resolvePreCompressedAsset(c, filepath) : {
|
|
101
119
|
selected: null,
|
|
102
|
-
hasVariant: false
|
|
120
|
+
hasVariant: false,
|
|
121
|
+
acceptable: true
|
|
103
122
|
};
|
|
123
|
+
if (!preCompressedAsset.acceptable) {
|
|
124
|
+
applyPreCompressedAssetHeaders(c, preCompressedAsset);
|
|
125
|
+
return c.body(null, 406);
|
|
126
|
+
}
|
|
104
127
|
const targetFilepath = preCompressedAsset.selected?.filepath ?? filepath;
|
|
105
128
|
const chunk = await fileReader.readFileFromSystem(targetFilepath, 'buffer');
|
|
106
129
|
if (null === chunk) return null;
|
|
@@ -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;
|
|
@@ -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 {};
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import type { Monitors } from '@modern-js/types';
|
|
2
|
-
export type SafeFailureEnvelope
|
|
3
|
-
|
|
4
|
-
error: {
|
|
5
|
-
code: string;
|
|
6
|
-
message: string;
|
|
7
|
-
status: number;
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
export type SafeFailureHttpResult = {
|
|
11
|
-
status: number;
|
|
12
|
-
body: SafeFailureEnvelope;
|
|
13
|
-
headers: Record<string, string>;
|
|
14
|
-
};
|
|
15
|
-
export declare const getSafeFailureStatus: (error: unknown) => number;
|
|
16
|
-
export declare const createSafeFailureHttpResult: (error: unknown) => SafeFailureHttpResult;
|
|
17
|
-
export declare const createSafeJsonFailureResponse: (error: unknown) => Response;
|
|
2
|
+
export type { SafeFailureEnvelope, SafeFailureHttpResult, } from '@modern-js/runtime-extensions/safe-failure';
|
|
3
|
+
export { createSafeFailureHttpResult, createSafeJsonFailureResponse, getSafeFailureStatus, } from '@modern-js/runtime-extensions/safe-failure';
|
|
18
4
|
export declare const createErrorHtml: (status: number) => string;
|
|
19
5
|
export declare enum ErrorDigest {
|
|
20
6
|
ENOTF = "Page could not be found",
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"modern",
|
|
18
18
|
"modern.js"
|
|
19
19
|
],
|
|
20
|
-
"version": "3.8.3-ultramodern.
|
|
20
|
+
"version": "3.8.3-ultramodern.3",
|
|
21
21
|
"types": "./dist/types/index.d.ts",
|
|
22
22
|
"main": "./dist/cjs/index.js",
|
|
23
23
|
"exports": {
|
|
@@ -66,9 +66,10 @@
|
|
|
66
66
|
"node": ">=20"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.8.3-ultramodern.
|
|
70
|
-
"@modern-js/runtime-
|
|
71
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.3-ultramodern.
|
|
69
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.8.3-ultramodern.3",
|
|
70
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.8.3-ultramodern.3",
|
|
71
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.8.3-ultramodern.3",
|
|
72
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.3-ultramodern.3",
|
|
72
73
|
"@swc/helpers": "^0.5.23",
|
|
73
74
|
"@web-std/fetch": "^4.2.1",
|
|
74
75
|
"@web-std/file": "^3.0.3",
|
|
@@ -79,7 +80,7 @@
|
|
|
79
80
|
"ts-deepmerge": "8.0.0"
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.8.3-ultramodern.
|
|
83
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.8.3-ultramodern.3",
|
|
83
84
|
"@rslib/core": "0.23.2",
|
|
84
85
|
"@scripts/rstest-config": "2.66.0",
|
|
85
86
|
"@types/cloneable-readable": "^2.0.3",
|
|
@@ -95,7 +96,7 @@
|
|
|
95
96
|
},
|
|
96
97
|
"scripts": {
|
|
97
98
|
"dev": "rslib build --watch",
|
|
98
|
-
"build": "rslib build
|
|
99
|
+
"build": "rslib build",
|
|
99
100
|
"test": "rstest --passWithNoTests"
|
|
100
101
|
}
|
|
101
102
|
}
|