@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.
Files changed (39) hide show
  1. package/dist/cjs/adapters/node/node.js +3 -1
  2. package/dist/cjs/adapters/node/plugins/staticModuleFederation.js +5 -1
  3. package/dist/cjs/adapters/node/plugins/staticPrecompressed.js +39 -19
  4. package/dist/cjs/adapters/node/plugins/staticServing.js +27 -4
  5. package/dist/cjs/plugins/compat/hooks.js +0 -1
  6. package/dist/cjs/plugins/compat/index.js +0 -1
  7. package/dist/cjs/plugins/render/inject.js +3 -13
  8. package/dist/cjs/plugins/render/render.js +9 -8
  9. package/dist/cjs/plugins/render/serverActionHandler.js +6 -0
  10. package/dist/cjs/utils/error.js +4 -56
  11. package/dist/esm/adapters/node/node.mjs +3 -1
  12. package/dist/esm/adapters/node/plugins/staticModuleFederation.mjs +5 -1
  13. package/dist/esm/adapters/node/plugins/staticPrecompressed.mjs +39 -19
  14. package/dist/esm/adapters/node/plugins/staticServing.mjs +27 -4
  15. package/dist/esm/plugins/compat/hooks.mjs +0 -1
  16. package/dist/esm/plugins/compat/index.mjs +1 -2
  17. package/dist/esm/plugins/render/inject.mjs +3 -13
  18. package/dist/esm/plugins/render/render.mjs +9 -8
  19. package/dist/esm/plugins/render/serverActionHandler.mjs +6 -0
  20. package/dist/esm/utils/error.mjs +2 -54
  21. package/dist/esm-node/adapters/node/node.mjs +3 -1
  22. package/dist/esm-node/adapters/node/plugins/staticModuleFederation.mjs +5 -1
  23. package/dist/esm-node/adapters/node/plugins/staticPrecompressed.mjs +39 -19
  24. package/dist/esm-node/adapters/node/plugins/staticServing.mjs +27 -4
  25. package/dist/esm-node/plugins/compat/hooks.mjs +0 -1
  26. package/dist/esm-node/plugins/compat/index.mjs +1 -2
  27. package/dist/esm-node/plugins/render/inject.mjs +3 -13
  28. package/dist/esm-node/plugins/render/render.mjs +9 -8
  29. package/dist/esm-node/plugins/render/serverActionHandler.mjs +6 -0
  30. package/dist/esm-node/utils/error.mjs +2 -54
  31. package/dist/types/adapters/node/plugins/staticPrecompressed.d.ts +1 -0
  32. package/dist/types/adapters/node/plugins/staticServing.d.ts +4 -4
  33. package/dist/types/plugins/render/inject.d.ts +1 -1
  34. package/dist/types/plugins/render/render.d.ts +2 -3
  35. package/dist/types/types/plugins/base.d.ts +1 -13
  36. package/dist/types/types/plugins/index.d.ts +1 -1
  37. package/dist/types/types/plugins/plugin.d.ts +2 -4
  38. package/dist/types/utils/error.d.ts +2 -16
  39. 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 url = `http://${req.headers.host}${req.url}`;
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)=>trimLeadingSlash(pathname.replace(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' !== key.toLowerCase() || null == rawValue) continue;
50
- const parsedQ = Number(rawValue);
51
- if (!Number.isNaN(parsedQ)) q = Math.max(0, Math.min(parsedQ, 1));
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 getAcceptedEncodings = (value)=>{
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
- return PRE_COMPRESSED_SUPPORTED_ENCODINGS.map((encoding)=>({
76
- encoding,
77
- quality: getQuality(encoding)
78
- })).filter((item)=>item.quality > 0).sort((a, b)=>b.quality - a.quality).map((item)=>item.encoding);
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
- if (!hasVariant) return {
95
- selected: null,
96
- hasVariant: false
97
- };
98
- const acceptedEncodings = getAcceptedEncodings(c.req.header('accept-encoding'));
99
- for (const encoding of acceptedEncodings){
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: true
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 moduleFederationAssetsPromise = null;
131
+ let moduleFederationAssets = null;
132
+ let moduleFederationAssetsExpiresAt = 0;
133
+ let moduleFederationAssetsRefresh = null;
123
134
  const getModuleFederationAssets = async ()=>{
124
- if (!moduleFederationAssetsPromise) moduleFederationAssetsPromise = (0, external_staticModuleFederation_js_namespaceObject.getModuleFederationAssetList)(pwd);
125
- return moduleFederationAssetsPromise;
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;
@@ -33,7 +33,6 @@ function getHookRunners(context) {
33
33
  config: (params)=>hooks.modifyConfig.call(params),
34
34
  prepare: ()=>hooks.onPrepare.call(),
35
35
  reset: (params)=>hooks.onReset.call(params),
36
- fallback: (input)=>hooks.fallback.call(input),
37
36
  prepareWebServer: (input)=>hooks.prepareWebServer.call(input),
38
37
  prepareApiServer: (input)=>hooks.prepareApiServer.call(input),
39
38
  afterMatch: (ctx)=>hooks.afterMatch.call(ctx),
@@ -36,7 +36,6 @@ const external_hooks_js_namespaceObject = require("./hooks.js");
36
36
  const compatPlugin = ()=>({
37
37
  name: '@modern-js/server-compat',
38
38
  registryHooks: {
39
- fallback: (0, plugin_namespaceObject.createAsyncHook)(),
40
39
  prepareWebServer: (0, plugin_namespaceObject.createAsyncPipelineHook)(),
41
40
  prepareApiServer: (0, plugin_namespaceObject.createAsyncPipelineHook)(),
42
41
  afterMatch: (0, plugin_namespaceObject.createAsyncPipelineHook)(),
@@ -38,23 +38,14 @@ const injectRenderHandlerPlugin = ({ staticGenerate, cacheConfig })=>({
38
38
  api.onPrepare(async ()=>{
39
39
  const { distDirectory: pwd, routes, metaName } = api.getServerContext();
40
40
  const config = api.getServerConfig();
41
- const hooks = api.getHooks();
42
41
  if (!routes) return;
43
- const onFallback = async (reason, error, context)=>{
44
- await hooks.fallback.call({
45
- reason,
46
- error,
47
- context
48
- });
49
- };
50
42
  const getRenderHandlerOptions = {
51
43
  pwd: pwd,
52
44
  routes,
53
45
  config,
54
46
  metaName,
55
47
  cacheConfig: cacheConfig,
56
- staticGenerate,
57
- onFallback
48
+ staticGenerate
58
49
  };
59
50
  const render = await getRenderHandler(getRenderHandlerOptions);
60
51
  api.updateServerContext({
@@ -64,7 +55,7 @@ const injectRenderHandlerPlugin = ({ staticGenerate, cacheConfig })=>({
64
55
  });
65
56
  }
66
57
  });
67
- async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate, onFallback }) {
58
+ async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, staticGenerate }) {
68
59
  const ssrConfig = config.server?.ssr;
69
60
  const ssrByEntries = config.server?.ssrByEntries;
70
61
  const forceCSR = 'object' == typeof ssrConfig ? ssrConfig.forceCSR : false;
@@ -81,8 +72,7 @@ async function getRenderHandler({ pwd, routes, config, cacheConfig, metaName, st
81
72
  forceCSR,
82
73
  forceCSRMap,
83
74
  nonce: config.security?.nonce,
84
- metaName: metaName || 'modern-js',
85
- onFallback
75
+ metaName: metaName || 'modern-js'
86
76
  });
87
77
  return render;
88
78
  }
@@ -108,7 +108,7 @@ var __webpack_exports__ = {};
108
108
  delete _headers.cookie;
109
109
  return _headers;
110
110
  }
111
- async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config, onFallback }) {
111
+ async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config }) {
112
112
  const router = getRouter(routes);
113
113
  return async (req, { monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext, serverContext, contextForceCSR, reporter })=>{
114
114
  const forMatchpathname = matchPathname ?? (0, index_js_namespaceObject.getPathname)(req);
@@ -116,13 +116,14 @@ var __webpack_exports__ = {};
116
116
  const framework = (0, universal_namespaceObject.cutNameByHyphen)(metaName || 'modern-js');
117
117
  const fallbackHeader = `x-${framework}-ssr-fallback`;
118
118
  let fallbackReason = null;
119
- const fallbackContext = {
120
- request: req,
121
- monitors
122
- };
123
- const fallbackWrapper = async (reason, error)=>{
119
+ const fallbackWrapper = (reason)=>{
124
120
  fallbackReason = reason;
125
- return onFallback?.(reason, error, fallbackContext);
121
+ monitors?.warn('fallback to CSR reason: %o', {
122
+ type: reason
123
+ });
124
+ monitors?.counter('ssr-fallback', {
125
+ type: reason
126
+ });
126
127
  };
127
128
  if (!routeInfo) return new Response((0, index_js_namespaceObject.createErrorHtml)(404), {
128
129
  status: 404,
@@ -218,7 +219,7 @@ var __webpack_exports__ = {};
218
219
  response = await (0, external_ssrRender_js_namespaceObject.ssrRender)(request, options);
219
220
  } catch (e) {
220
221
  options.onError(e, index_js_namespaceObject.ErrorDigest.ERENDER);
221
- await fallbackWrapper('error', e);
222
+ fallbackWrapper('error');
222
223
  response = await csrRender(request, {
223
224
  ...options,
224
225
  html: injectFallbackReasonToHtml({
@@ -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
@@ -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 url = `http://${req.headers.host}${req.url}`;
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)=>trimLeadingSlash(pathname.replace(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' !== key.toLowerCase() || null == rawValue) continue;
17
- const parsedQ = Number(rawValue);
18
- if (!Number.isNaN(parsedQ)) q = Math.max(0, Math.min(parsedQ, 1));
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 getAcceptedEncodings = (value)=>{
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
- return PRE_COMPRESSED_SUPPORTED_ENCODINGS.map((encoding)=>({
43
- encoding,
44
- quality: getQuality(encoding)
45
- })).filter((item)=>item.quality > 0).sort((a, b)=>b.quality - a.quality).map((item)=>item.encoding);
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
- if (!hasVariant) return {
62
- selected: null,
63
- hasVariant: false
64
- };
65
- const acceptedEncodings = getAcceptedEncodings(c.req.header('accept-encoding'));
66
- for (const encoding of acceptedEncodings){
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: true
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 moduleFederationAssetsPromise = null;
87
+ let moduleFederationAssets = null;
88
+ let moduleFederationAssetsExpiresAt = 0;
89
+ let moduleFederationAssetsRefresh = null;
79
90
  const getModuleFederationAssets = async ()=>{
80
- if (!moduleFederationAssetsPromise) moduleFederationAssetsPromise = getModuleFederationAssetList(pwd);
81
- return moduleFederationAssetsPromise;
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;
@@ -4,7 +4,6 @@ function getHookRunners(context) {
4
4
  config: (params)=>hooks.modifyConfig.call(params),
5
5
  prepare: ()=>hooks.onPrepare.call(),
6
6
  reset: (params)=>hooks.onReset.call(params),
7
- fallback: (input)=>hooks.fallback.call(input),
8
7
  prepareWebServer: (input)=>hooks.prepareWebServer.call(input),
9
8
  prepareApiServer: (input)=>hooks.prepareApiServer.call(input),
10
9
  afterMatch: (ctx)=>hooks.afterMatch.call(ctx),
@@ -1,9 +1,8 @@
1
- import { createAsyncHook, createAsyncPipelineHook } from "@modern-js/plugin";
1
+ import { createAsyncPipelineHook } from "@modern-js/plugin";
2
2
  import { getHookRunners, handleSetupResult } from "./hooks.mjs";
3
3
  const compatPlugin = ()=>({
4
4
  name: '@modern-js/server-compat',
5
5
  registryHooks: {
6
- fallback: createAsyncHook(),
7
6
  prepareWebServer: createAsyncPipelineHook(),
8
7
  prepareApiServer: createAsyncPipelineHook(),
9
8
  afterMatch: createAsyncPipelineHook(),