@bleedingdev/modern-js-server-core 3.9.0-ultramodern.1 → 3.9.0-ultramodern.11

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 (51) hide show
  1. package/dist/cjs/adapters/node/index.js +4 -0
  2. package/dist/cjs/adapters/node/plugins/static.js +107 -29
  3. package/dist/cjs/index.js +10 -34
  4. package/dist/cjs/plugins/compat/index.js +1 -0
  5. package/dist/cjs/plugins/render/ssrCache.js +51 -29
  6. package/dist/cjs/serverBase.js +58 -17
  7. package/dist/cjs/utils/error.js +0 -10
  8. package/dist/esm/adapters/node/index.mjs +1 -0
  9. package/dist/esm/adapters/node/plugins/static.mjs +107 -32
  10. package/dist/esm/index.mjs +1 -1
  11. package/dist/esm/plugins/compat/index.mjs +1 -0
  12. package/dist/esm/plugins/render/ssrCache.mjs +51 -29
  13. package/dist/esm/serverBase.mjs +58 -17
  14. package/dist/esm/utils/error.mjs +0 -1
  15. package/dist/esm-node/adapters/node/index.mjs +1 -0
  16. package/dist/esm-node/adapters/node/plugins/static.mjs +107 -32
  17. package/dist/esm-node/index.mjs +1 -1
  18. package/dist/esm-node/plugins/compat/index.mjs +1 -0
  19. package/dist/esm-node/plugins/render/ssrCache.mjs +51 -29
  20. package/dist/esm-node/serverBase.mjs +58 -17
  21. package/dist/esm-node/utils/error.mjs +0 -1
  22. package/dist/types/adapters/node/index.d.ts +2 -0
  23. package/dist/types/adapters/node/plugins/static.d.ts +42 -4
  24. package/dist/types/index.d.ts +1 -2
  25. package/dist/types/serverBase.d.ts +7 -1
  26. package/dist/types/types/config/bff.d.ts +4 -15
  27. package/dist/types/types/config/server.d.ts +1 -3
  28. package/dist/types/types/plugins/plugin.d.ts +15 -1
  29. package/dist/types/types/requestHandler.d.ts +2 -3
  30. package/dist/types/utils/error.d.ts +0 -2
  31. package/package.json +7 -8
  32. package/dist/cjs/adapters/node/plugins/staticModuleFederation.js +0 -173
  33. package/dist/cjs/adapters/node/plugins/staticPrecompressed.js +0 -152
  34. package/dist/cjs/adapters/node/plugins/staticServing.js +0 -206
  35. package/dist/cjs/types/config/bffRuntime.js +0 -18
  36. package/dist/cjs/types/config/serverTelemetry.js +0 -18
  37. package/dist/esm/adapters/node/plugins/staticModuleFederation.mjs +0 -104
  38. package/dist/esm/adapters/node/plugins/staticPrecompressed.mjs +0 -111
  39. package/dist/esm/adapters/node/plugins/staticServing.mjs +0 -152
  40. package/dist/esm/types/config/bffRuntime.mjs +0 -0
  41. package/dist/esm/types/config/serverTelemetry.mjs +0 -0
  42. package/dist/esm-node/adapters/node/plugins/staticModuleFederation.mjs +0 -105
  43. package/dist/esm-node/adapters/node/plugins/staticPrecompressed.mjs +0 -112
  44. package/dist/esm-node/adapters/node/plugins/staticServing.mjs +0 -153
  45. package/dist/esm-node/types/config/bffRuntime.mjs +0 -1
  46. package/dist/esm-node/types/config/serverTelemetry.mjs +0 -1
  47. package/dist/types/adapters/node/plugins/staticModuleFederation.d.ts +0 -13
  48. package/dist/types/adapters/node/plugins/staticPrecompressed.d.ts +0 -13
  49. package/dist/types/adapters/node/plugins/staticServing.d.ts +0 -25
  50. package/dist/types/types/config/bffRuntime.d.ts +0 -116
  51. package/dist/types/types/config/serverTelemetry.d.ts +0 -319
@@ -30,12 +30,8 @@ __webpack_require__.r(__webpack_exports__);
30
30
  __webpack_require__.d(__webpack_exports__, {
31
31
  ErrorDigest: ()=>error_ErrorDigest,
32
32
  createErrorHtml: ()=>createErrorHtml,
33
- createSafeFailureHttpResult: ()=>safe_failure_namespaceObject.createSafeFailureHttpResult,
34
- createSafeJsonFailureResponse: ()=>safe_failure_namespaceObject.createSafeJsonFailureResponse,
35
- getSafeFailureStatus: ()=>safe_failure_namespaceObject.getSafeFailureStatus,
36
33
  onError: ()=>onError
37
34
  });
38
- const safe_failure_namespaceObject = require("@modern-js/runtime-extensions/safe-failure");
39
35
  const external_request_js_namespaceObject = require("./request.js");
40
36
  const ERROR_PAGE_TEXT = {
41
37
  404: 'This page could not be found.',
@@ -90,16 +86,10 @@ function onError(digest, error, monitors, req) {
90
86
  }
91
87
  exports.ErrorDigest = __webpack_exports__.ErrorDigest;
92
88
  exports.createErrorHtml = __webpack_exports__.createErrorHtml;
93
- exports.createSafeFailureHttpResult = __webpack_exports__.createSafeFailureHttpResult;
94
- exports.createSafeJsonFailureResponse = __webpack_exports__.createSafeJsonFailureResponse;
95
- exports.getSafeFailureStatus = __webpack_exports__.getSafeFailureStatus;
96
89
  exports.onError = __webpack_exports__.onError;
97
90
  for(var __rspack_i in __webpack_exports__)if (-1 === [
98
91
  "ErrorDigest",
99
92
  "createErrorHtml",
100
- "createSafeFailureHttpResult",
101
- "createSafeJsonFailureResponse",
102
- "getSafeFailureStatus",
103
93
  "onError"
104
94
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
105
95
  Object.defineProperty(exports, '__esModule', {
@@ -2,3 +2,4 @@ export { loadCacheConfig, loadServerCliConfig, loadServerEnv, loadServerPlugins,
2
2
  export { connectMid2HonoMid, connectMockMid2HonoMid, httpCallBack2HonoMid } from "./hono.mjs";
3
3
  export { createNodeServer, createWebRequest, sendResponse } from "./node.mjs";
4
4
  export { getHtmlTemplates, getServerManifest, injectNodeSeverPlugin, injectResourcePlugin, injectRscManifestPlugin, serverStaticPlugin } from "./plugins/index.mjs";
5
+ export { serveStaticAsset } from "./plugins/static.mjs";
@@ -1,8 +1,55 @@
1
- import path_0 from "path";
1
+ import { fileReader } from "@modern-js/runtime-utils/fileReader";
2
+ import { fs } from "@modern-js/utils";
3
+ import { getMimeType } from "hono/utils/mime";
4
+ import path from "path";
2
5
  import { sortRoutes } from "../../../utils/index.mjs";
3
6
  import { getPublicDirPatterns } from "../../../utils/publicDir.mjs";
4
- import { createModuleFederationStaticServing, servePreCompressedPublicRouteAsset, servePublicDirectoryAsset } from "./staticServing.mjs";
5
- const serverStaticPlugin = ()=>({
7
+ const isPathInside = (target, root)=>{
8
+ const relative = path.relative(path.resolve(root), path.resolve(target));
9
+ return '' === relative || !relative.startsWith(`..${path.sep}`) && '..' !== relative && !path.isAbsolute(relative);
10
+ };
11
+ async function serveStaticAsset(context, asset, respond) {
12
+ let { filename } = asset;
13
+ const { kind } = asset;
14
+ if (asset.root && !isPathInside(filename, asset.root)) return null;
15
+ if (asset.realpath) try {
16
+ const [realFilename, realRoot, stat] = await Promise.all([
17
+ fs.realpath(filename),
18
+ asset.root ? fs.realpath(asset.root) : Promise.resolve(void 0),
19
+ fs.stat(filename)
20
+ ]);
21
+ if (!stat.isFile() || realRoot && !isPathInside(realFilename, realRoot)) return null;
22
+ filename = realFilename;
23
+ asset = {
24
+ ...asset,
25
+ filename,
26
+ root: realRoot
27
+ };
28
+ } catch {
29
+ return null;
30
+ }
31
+ if ('static' === kind && !await fs.pathExists(filename)) return null;
32
+ if (respond) return respond(asset, (representation)=>serveStaticAsset(context, {
33
+ ...asset,
34
+ ...representation,
35
+ filename: representation?.filename ?? asset.filename,
36
+ mimeFilename: asset.mimeFilename ?? filename
37
+ }));
38
+ const mimeType = getMimeType(asset.mimeFilename ?? filename);
39
+ if ('static' === kind && mimeType) context.header('Content-Type', mimeType);
40
+ const size = 'static' === kind ? (await fs.lstat(filename)).size : void 0;
41
+ const data = 'static' === kind ? await fileReader.readFileFromSystem(filename, 'buffer') : await fileReader.readFile(filename, 'buffer');
42
+ if ('static' === kind && (asset.contentLength ?? true)) context.header('Content-Length', String(true === asset.contentLength && data ? data.byteLength : size));
43
+ if (null === data) return null;
44
+ if ('public' === kind && mimeType) context.header('Content-Type', mimeType);
45
+ Object.entries(asset.responseHeaders || {}).forEach(([key, value])=>{
46
+ context.header(key, value);
47
+ });
48
+ if ('public' === kind && asset.contentLength) context.header('Content-Length', String(data.byteLength));
49
+ const body = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
50
+ return context.body(body, 200);
51
+ }
52
+ const serverStaticPlugin = (options = {})=>({
6
53
  name: '@modern-js/plugin-server-static',
7
54
  setup (api) {
8
55
  api.onPrepare(()=>{
@@ -13,7 +60,9 @@ const serverStaticPlugin = ()=>({
13
60
  routes,
14
61
  output: config.output || {},
15
62
  html: config.html || {},
16
- server: config.server || {}
63
+ server: config.server || {},
64
+ ...api.getServerContext().staticAssetResponders,
65
+ ...options
17
66
  });
18
67
  middlewares.push({
19
68
  name: 'server-static',
@@ -22,16 +71,33 @@ const serverStaticPlugin = ()=>({
22
71
  });
23
72
  }
24
73
  });
25
- function createPublicMiddleware({ pwd, routes }) {
74
+ function createPublicMiddleware({ pwd, routes, pathPrefix = '/', respondAsset, respondPublicFallback }) {
26
75
  return async (c, next)=>{
27
- const route = matchPublicRoute(c.req, routes);
28
- if (route) {
29
- const response = await servePreCompressedPublicRouteAsset(c, pwd, route);
30
- if (null !== response) return response;
31
- }
32
- const generatedPublicAsset = await servePublicDirectoryAsset(c, pwd);
33
- if (null !== generatedPublicAsset) return generatedPublicAsset;
34
- return await next();
76
+ const respondPublic = async ()=>{
77
+ const route = matchPublicRoute(c.req, routes);
78
+ if (!route) return null;
79
+ const asset = {
80
+ filename: path.join(pwd, route.entryPath),
81
+ kind: 'public',
82
+ responseHeaders: route.responseHeaders
83
+ };
84
+ const serve = (representation)=>serveStaticAsset(c, {
85
+ ...asset,
86
+ ...representation,
87
+ filename: representation?.filename ?? asset.filename,
88
+ mimeFilename: asset.filename
89
+ });
90
+ const response = respondAsset ? await respondAsset(c, asset, serve, {
91
+ root: pwd,
92
+ pathPrefix
93
+ }) : void 0;
94
+ return void 0 === response ? serve() : response;
95
+ };
96
+ const response = respondPublicFallback ? await respondPublicFallback(c, respondPublic, {
97
+ root: pwd,
98
+ pathPrefix
99
+ }) : await respondPublic();
100
+ return response ?? next();
35
101
  };
36
102
  }
37
103
  function matchPublicRoute(req, routes) {
@@ -76,29 +142,38 @@ function createStaticMiddleware(options) {
76
142
  ...staticReg,
77
143
  ...iconReg
78
144
  ].join('|')})`);
79
- const publicMiddleware = createPublicMiddleware({
80
- pwd,
81
- routes: routes || []
82
- });
83
- const moduleFederationStaticServing = createModuleFederationStaticServing({
84
- pwd,
85
- pathPrefix
86
- });
87
145
  return async (c, next)=>{
88
146
  const pageRoute = c.get('route');
89
147
  const pathname = c.req.path;
90
- if (pageRoute && '' === path_0.extname(pathname)) return next();
148
+ if (pageRoute && '' === path.extname(pathname)) return next();
91
149
  const hit = staticPathRegExp.test(pathname);
92
- const staticServingRequest = await moduleFederationStaticServing.resolveRequest(pathname);
93
- if (null === staticServingRequest) return next();
94
- if (hit) {
95
- const response = await moduleFederationStaticServing.serveStaticHit(c, staticServingRequest);
96
- if (null !== response) return response;
97
- return next();
150
+ if (!hit) return createPublicMiddleware({
151
+ pwd,
152
+ routes: routes || [],
153
+ pathPrefix,
154
+ respondAsset: options.respondAsset,
155
+ respondPublicFallback: options.respondPublicFallback
156
+ })(c, next);
157
+ {
158
+ const filepath = path.join(pwd, pathname.replace(pathPrefix, ()=>''));
159
+ if (!isPathInside(filepath, pwd)) return next();
160
+ if (!await fs.pathExists(filepath)) return next();
161
+ const asset = {
162
+ filename: filepath,
163
+ kind: 'static'
164
+ };
165
+ const serve = (representation)=>serveStaticAsset(c, {
166
+ ...asset,
167
+ ...representation,
168
+ filename: representation?.filename ?? asset.filename,
169
+ mimeFilename: filepath
170
+ });
171
+ const response = options.respondAsset ? await options.respondAsset(c, asset, serve, {
172
+ root: pwd,
173
+ pathPrefix
174
+ }) : void 0;
175
+ return (void 0 === response ? await serve() : response) ?? next();
98
176
  }
99
- const moduleFederationResponse = await moduleFederationStaticServing.serveModuleFederationAsset(c, staticServingRequest);
100
- if (null !== moduleFederationResponse) return moduleFederationResponse;
101
- return publicMiddleware(c, next);
102
177
  };
103
178
  }
104
179
  const prepareFavicons = (favicon)=>{
@@ -106,4 +181,4 @@ const prepareFavicons = (favicon)=>{
106
181
  if (favicon && 'string' == typeof favicon) faviconNames.push(favicon.substring(favicon.lastIndexOf('/') + 1));
107
182
  return faviconNames;
108
183
  };
109
- export { createPublicMiddleware, createStaticMiddleware, serverStaticPlugin };
184
+ export { createPublicMiddleware, createStaticMiddleware, serveStaticAsset, serverStaticPlugin };
@@ -8,5 +8,5 @@ export { AGGRED_DIR } from "./constants.mjs";
8
8
  export { run, useHonoContext } from "./context.mjs";
9
9
  export { getLoaderCtx } from "./helper.mjs";
10
10
  export { createServerBase } from "./serverBase.mjs";
11
- export { ErrorDigest, createErrorHtml, createSafeFailureHttpResult, createSafeJsonFailureResponse, getSafeFailureStatus, onError } from "./utils/index.mjs";
11
+ export { ErrorDigest, createErrorHtml, onError } from "./utils/index.mjs";
12
12
  export { getPublicDirConfig, getPublicDirPatterns, getPublicDirRoutePrefixes, normalizePublicDir, normalizePublicDirPath, resolvePublicDirPaths } from "./utils/publicDir.mjs";
@@ -3,6 +3,7 @@ import { getHookRunners, handleSetupResult } from "./hooks.mjs";
3
3
  const compatPlugin = ()=>({
4
4
  name: '@modern-js/server-compat',
5
5
  registryHooks: {
6
+ handleError: createAsyncPipelineHook(),
6
7
  prepareWebServer: createAsyncPipelineHook(),
7
8
  prepareApiServer: createAsyncPipelineHook(),
8
9
  afterMatch: createAsyncPipelineHook(),
@@ -1,54 +1,67 @@
1
1
  import { createMemoryStorage } from "@modern-js/runtime-utils/storer";
2
2
  import { X_RENDER_CACHE } from "../../constants.mjs";
3
3
  import { createTransformStream, getPathname } from "../../utils/index.mjs";
4
+ const preventsSharedCaching = (headers)=>/(?:^|,)\s*(?:private|no-store|no-cache)\s*(?:[=,]|$)/i.test(headers.get('cache-control') || '');
5
+ const isCacheableResponse = (response)=>200 === response.status && !preventsSharedCaching(response.headers) && !response.headers.has('set-cookie') && !response.headers.get('vary');
4
6
  const removeTailSlash = (s)=>s.replace(/\/+$/, '');
5
7
  const ZERO_RENDER_LEVEL = /"renderLevel":0/;
6
8
  const NO_SSR_CACHE = /<meta\s+[^>]*name=["']no-ssr-cache["'][^>]*>/i;
7
9
  async function processCache({ request, key, requestHandler, requestHandlerOptions, ttl, container, cacheStatus }) {
8
10
  const response = await requestHandler(request, requestHandlerOptions);
9
11
  const { onError } = requestHandlerOptions;
10
- const nonCacheableStatusCodes = [
11
- 204,
12
- 305,
13
- 404,
14
- 405,
15
- 500,
16
- 501,
17
- 502,
18
- 503,
19
- 504
20
- ];
21
- if (nonCacheableStatusCodes.includes(response.status)) return response;
12
+ const deleteCache = async ()=>{
13
+ try {
14
+ await container.delete(key);
15
+ } catch {
16
+ (onError || console.error)('[render-cache] delete cache failed');
17
+ }
18
+ };
19
+ if (!isCacheableResponse(response) || !response.body) {
20
+ await deleteCache();
21
+ return response;
22
+ }
23
+ const headers = Object.fromEntries(response.headers);
22
24
  const decoder = new TextDecoder();
23
25
  if (response.body) {
24
26
  const stream = createTransformStream();
25
27
  const reader = response.body.getReader();
26
28
  const writer = stream.writable.getWriter();
27
29
  let html = '';
28
- const push = ()=>reader.read().then(({ done, value })=>{
30
+ const push = ()=>reader.read().then(async ({ done, value })=>{
29
31
  if (done) {
32
+ html += decoder.decode();
30
33
  const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html);
31
- if (match) return void writer.close();
34
+ if (match) {
35
+ await deleteCache();
36
+ return writer.close();
37
+ }
32
38
  const current = Date.now();
33
39
  const cache = {
34
40
  val: html,
35
- cursor: current
41
+ cursor: current,
42
+ headers
36
43
  };
37
44
  container.set(key, JSON.stringify(cache), {
38
45
  ttl
39
46
  }).catch(()=>{
40
- if (onError) onError(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
41
- else console.error(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
47
+ (onError || console.error)('[render-cache] set cache failed');
42
48
  });
43
- writer.close();
44
- return;
49
+ return writer.close();
45
50
  }
46
- const content = decoder.decode(value);
51
+ const content = decoder.decode(value, {
52
+ stream: true
53
+ });
47
54
  html += content;
48
- writer.write(value);
49
- push();
55
+ await writer.write(value);
56
+ return push();
50
57
  });
51
- push();
58
+ push().catch(async (error)=>{
59
+ await Promise.allSettled([
60
+ writer.abort(error),
61
+ reader.cancel(error)
62
+ ]);
63
+ (onError || console.error)('[render-cache] response stream failed');
64
+ });
52
65
  cacheStatus && response.headers.set(X_RENDER_CACHE, cacheStatus);
53
66
  return new Response(stream.readable, {
54
67
  status: response.status,
@@ -63,9 +76,12 @@ function computedKey(req, cacheControl) {
63
76
  const pathname = getPathname(req);
64
77
  const { customKey } = cacheControl;
65
78
  const defaultKey = '/' === pathname ? pathname : removeTailSlash(pathname);
66
- if (!customKey) return defaultKey;
67
- if ('string' == typeof customKey) return customKey;
68
- return customKey(defaultKey);
79
+ if (customKey) if ('string' == typeof customKey) return customKey;
80
+ else return customKey(defaultKey);
81
+ {
82
+ const url = new URL(req.url);
83
+ return `${url.origin}${defaultKey}${url.search}`;
84
+ }
69
85
  }
70
86
  function shouldUseCache(request) {
71
87
  const url = new URL(request.url);
@@ -95,13 +111,15 @@ function matchCacheControl(cacheOption, req) {
95
111
  async function getCacheResult(request, options) {
96
112
  const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options;
97
113
  const { onError } = requestHandlerOptions;
98
- const key = computedKey(request, cacheControl);
114
+ const hasCredentials = request.headers.has('cookie') || request.headers.has('authorization');
115
+ if ('GET' !== request.method || !shouldUseCache(request) || preventsSharedCaching(request.headers) || hasCredentials && !cacheControl.customKey) return requestHandler(request, requestHandlerOptions);
116
+ const key = `${CACHE_NAMESPACE}:v2:${computedKey(request, cacheControl)}`;
99
117
  let value;
100
118
  try {
101
119
  value = await container.get(key);
102
120
  } catch (_) {
103
- if (onError) onError(`[render-cache] get cache failed, key: ${key}`);
104
- else console.error(`[render-cache] get cache failed, key: ${key}`);
121
+ if (onError) onError('[render-cache] get cache failed');
122
+ else console.error('[render-cache] get cache failed');
105
123
  value = void 0;
106
124
  }
107
125
  const { maxAge, staleWhileRevalidate } = cacheControl;
@@ -122,6 +140,7 @@ async function getCacheResult(request, options) {
122
140
  const cacheStatus = 'hit';
123
141
  return new Response(cache.val, {
124
142
  headers: {
143
+ ...cache.headers,
125
144
  [X_RENDER_CACHE]: cacheStatus
126
145
  }
127
146
  });
@@ -145,10 +164,13 @@ async function getCacheResult(request, options) {
145
164
  container
146
165
  }).then(async (response)=>{
147
166
  await response.text();
167
+ }).catch(()=>{
168
+ (onError || console.error)('[render-cache] revalidation failed');
148
169
  });
149
170
  const cacheStatus = 'stale';
150
171
  return new Response(cache.val, {
151
172
  headers: {
173
+ ...cache.headers,
152
174
  [X_RENDER_CACHE]: cacheStatus
153
175
  }
154
176
  });
@@ -1,4 +1,5 @@
1
1
  import { server as server_server } from "@modern-js/plugin/server";
2
+ import { logger } from "@modern-js/utils";
2
3
  import { Hono } from "hono";
3
4
  import { run } from "./context.mjs";
4
5
  import { handleSetupResult } from "./plugins/compat/hooks.mjs";
@@ -17,22 +18,54 @@ function _class_private_method_init(obj, privateSet) {
17
18
  var _applyMiddlewares = /*#__PURE__*/ new WeakSet();
18
19
  class ServerBase {
19
20
  async init() {
20
- const { serverConfig, config: cliConfig } = this.serverOptions;
21
- const mergedConfig = loadConfig({
22
- cliConfig,
23
- serverConfig: serverConfig || {}
24
- });
25
- const { serverContext } = await server_server.run({
26
- plugins: this.plugins,
27
- options: this.serverOptions,
28
- config: mergedConfig,
29
- handleSetupResult: handleSetupResult
30
- });
31
- serverContext.serverBase = this;
32
- this.serverContext = serverContext;
33
- await serverContext.hooks.onPrepare.call();
34
- _class_private_method_get(this, _applyMiddlewares, applyMiddlewares).call(this);
35
- return this;
21
+ try {
22
+ const { serverConfig, config: cliConfig } = this.serverOptions;
23
+ const mergedConfig = loadConfig({
24
+ cliConfig,
25
+ serverConfig: serverConfig || {}
26
+ });
27
+ const { serverContext } = await server_server.run({
28
+ plugins: this.plugins,
29
+ options: this.serverOptions,
30
+ config: mergedConfig,
31
+ handleSetupResult: handleSetupResult
32
+ });
33
+ serverContext.serverBase = this;
34
+ this.serverContext = serverContext;
35
+ await serverContext.hooks.onPrepare.call();
36
+ _class_private_method_get(this, _applyMiddlewares, applyMiddlewares).call(this);
37
+ return this;
38
+ } catch (error) {
39
+ await this.dispose().catch((disposeError)=>{
40
+ logger.error(disposeError);
41
+ });
42
+ throw error;
43
+ }
44
+ }
45
+ onDispose(disposer) {
46
+ if (this.disposePromise) throw new Error('Cannot register a disposer on a retired server.');
47
+ this.disposers.add(disposer);
48
+ return ()=>{
49
+ this.disposers.delete(disposer);
50
+ };
51
+ }
52
+ dispose() {
53
+ if (!this.disposePromise) {
54
+ const disposers = [
55
+ ...this.disposers
56
+ ].reverse();
57
+ this.disposers.clear();
58
+ this.disposePromise = Promise.resolve().then(async ()=>{
59
+ const errors = [];
60
+ for (const disposer of disposers)try {
61
+ await disposer();
62
+ } catch (error) {
63
+ errors.push(error);
64
+ }
65
+ if (errors.length > 0) throw new AggregateError(errors, 'Failed to dispose server.');
66
+ });
67
+ }
68
+ return this.disposePromise;
36
69
  }
37
70
  addPlugins(plugins) {
38
71
  this.plugins.push(...plugins);
@@ -78,7 +111,15 @@ class ServerBase {
78
111
  }
79
112
  constructor(options){
80
113
  _class_private_method_init(this, _applyMiddlewares);
81
- this.plugins = [];
114
+ this.plugins = [
115
+ {
116
+ name: '@modern-js/server-lifecycle',
117
+ _registryApi: ()=>({
118
+ onDispose: (disposer)=>this.onDispose(disposer)
119
+ })
120
+ }
121
+ ];
122
+ this.disposers = new Set();
82
123
  this.serverContext = null;
83
124
  this.serverOptions = options;
84
125
  this.app = new Hono();
@@ -50,5 +50,4 @@ function onError(digest, error, monitors, req) {
50
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)}`);
51
51
  else console.error(`Server Error - ${digest}, error = ${error instanceof Error ? error.stack || error.message : error} `);
52
52
  }
53
- export { createSafeFailureHttpResult, createSafeJsonFailureResponse, getSafeFailureStatus } from "@modern-js/runtime-extensions/safe-failure";
54
53
  export { createErrorHtml, error_ErrorDigest as ErrorDigest, onError };
@@ -3,3 +3,4 @@ export { loadCacheConfig, loadServerCliConfig, loadServerEnv, loadServerPlugins,
3
3
  export { connectMid2HonoMid, connectMockMid2HonoMid, httpCallBack2HonoMid } from "./hono.mjs";
4
4
  export { createNodeServer, createWebRequest, sendResponse } from "./node.mjs";
5
5
  export { getHtmlTemplates, getServerManifest, injectNodeSeverPlugin, injectResourcePlugin, injectRscManifestPlugin, serverStaticPlugin } from "./plugins/index.mjs";
6
+ export { serveStaticAsset } from "./plugins/static.mjs";
@@ -1,9 +1,56 @@
1
1
  import "node:module";
2
- import path_0 from "path";
2
+ import { fileReader } from "@modern-js/runtime-utils/fileReader";
3
+ import { fs } from "@modern-js/utils";
4
+ import { getMimeType } from "hono/utils/mime";
5
+ import path from "path";
3
6
  import { sortRoutes } from "../../../utils/index.mjs";
4
7
  import { getPublicDirPatterns } from "../../../utils/publicDir.mjs";
5
- import { createModuleFederationStaticServing, servePreCompressedPublicRouteAsset, servePublicDirectoryAsset } from "./staticServing.mjs";
6
- const serverStaticPlugin = ()=>({
8
+ const isPathInside = (target, root)=>{
9
+ const relative = path.relative(path.resolve(root), path.resolve(target));
10
+ return '' === relative || !relative.startsWith(`..${path.sep}`) && '..' !== relative && !path.isAbsolute(relative);
11
+ };
12
+ async function serveStaticAsset(context, asset, respond) {
13
+ let { filename } = asset;
14
+ const { kind } = asset;
15
+ if (asset.root && !isPathInside(filename, asset.root)) return null;
16
+ if (asset.realpath) try {
17
+ const [realFilename, realRoot, stat] = await Promise.all([
18
+ fs.realpath(filename),
19
+ asset.root ? fs.realpath(asset.root) : Promise.resolve(void 0),
20
+ fs.stat(filename)
21
+ ]);
22
+ if (!stat.isFile() || realRoot && !isPathInside(realFilename, realRoot)) return null;
23
+ filename = realFilename;
24
+ asset = {
25
+ ...asset,
26
+ filename,
27
+ root: realRoot
28
+ };
29
+ } catch {
30
+ return null;
31
+ }
32
+ if ('static' === kind && !await fs.pathExists(filename)) return null;
33
+ if (respond) return respond(asset, (representation)=>serveStaticAsset(context, {
34
+ ...asset,
35
+ ...representation,
36
+ filename: representation?.filename ?? asset.filename,
37
+ mimeFilename: asset.mimeFilename ?? filename
38
+ }));
39
+ const mimeType = getMimeType(asset.mimeFilename ?? filename);
40
+ if ('static' === kind && mimeType) context.header('Content-Type', mimeType);
41
+ const size = 'static' === kind ? (await fs.lstat(filename)).size : void 0;
42
+ const data = 'static' === kind ? await fileReader.readFileFromSystem(filename, 'buffer') : await fileReader.readFile(filename, 'buffer');
43
+ if ('static' === kind && (asset.contentLength ?? true)) context.header('Content-Length', String(true === asset.contentLength && data ? data.byteLength : size));
44
+ if (null === data) return null;
45
+ if ('public' === kind && mimeType) context.header('Content-Type', mimeType);
46
+ Object.entries(asset.responseHeaders || {}).forEach(([key, value])=>{
47
+ context.header(key, value);
48
+ });
49
+ if ('public' === kind && asset.contentLength) context.header('Content-Length', String(data.byteLength));
50
+ const body = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
51
+ return context.body(body, 200);
52
+ }
53
+ const serverStaticPlugin = (options = {})=>({
7
54
  name: '@modern-js/plugin-server-static',
8
55
  setup (api) {
9
56
  api.onPrepare(()=>{
@@ -14,7 +61,9 @@ const serverStaticPlugin = ()=>({
14
61
  routes,
15
62
  output: config.output || {},
16
63
  html: config.html || {},
17
- server: config.server || {}
64
+ server: config.server || {},
65
+ ...api.getServerContext().staticAssetResponders,
66
+ ...options
18
67
  });
19
68
  middlewares.push({
20
69
  name: 'server-static',
@@ -23,16 +72,33 @@ const serverStaticPlugin = ()=>({
23
72
  });
24
73
  }
25
74
  });
26
- function createPublicMiddleware({ pwd, routes }) {
75
+ function createPublicMiddleware({ pwd, routes, pathPrefix = '/', respondAsset, respondPublicFallback }) {
27
76
  return async (c, next)=>{
28
- const route = matchPublicRoute(c.req, routes);
29
- if (route) {
30
- const response = await servePreCompressedPublicRouteAsset(c, pwd, route);
31
- if (null !== response) return response;
32
- }
33
- const generatedPublicAsset = await servePublicDirectoryAsset(c, pwd);
34
- if (null !== generatedPublicAsset) return generatedPublicAsset;
35
- return await next();
77
+ const respondPublic = async ()=>{
78
+ const route = matchPublicRoute(c.req, routes);
79
+ if (!route) return null;
80
+ const asset = {
81
+ filename: path.join(pwd, route.entryPath),
82
+ kind: 'public',
83
+ responseHeaders: route.responseHeaders
84
+ };
85
+ const serve = (representation)=>serveStaticAsset(c, {
86
+ ...asset,
87
+ ...representation,
88
+ filename: representation?.filename ?? asset.filename,
89
+ mimeFilename: asset.filename
90
+ });
91
+ const response = respondAsset ? await respondAsset(c, asset, serve, {
92
+ root: pwd,
93
+ pathPrefix
94
+ }) : void 0;
95
+ return void 0 === response ? serve() : response;
96
+ };
97
+ const response = respondPublicFallback ? await respondPublicFallback(c, respondPublic, {
98
+ root: pwd,
99
+ pathPrefix
100
+ }) : await respondPublic();
101
+ return response ?? next();
36
102
  };
37
103
  }
38
104
  function matchPublicRoute(req, routes) {
@@ -77,29 +143,38 @@ function createStaticMiddleware(options) {
77
143
  ...staticReg,
78
144
  ...iconReg
79
145
  ].join('|')})`);
80
- const publicMiddleware = createPublicMiddleware({
81
- pwd,
82
- routes: routes || []
83
- });
84
- const moduleFederationStaticServing = createModuleFederationStaticServing({
85
- pwd,
86
- pathPrefix
87
- });
88
146
  return async (c, next)=>{
89
147
  const pageRoute = c.get('route');
90
148
  const pathname = c.req.path;
91
- if (pageRoute && '' === path_0.extname(pathname)) return next();
149
+ if (pageRoute && '' === path.extname(pathname)) return next();
92
150
  const hit = staticPathRegExp.test(pathname);
93
- const staticServingRequest = await moduleFederationStaticServing.resolveRequest(pathname);
94
- if (null === staticServingRequest) return next();
95
- if (hit) {
96
- const response = await moduleFederationStaticServing.serveStaticHit(c, staticServingRequest);
97
- if (null !== response) return response;
98
- return next();
151
+ if (!hit) return createPublicMiddleware({
152
+ pwd,
153
+ routes: routes || [],
154
+ pathPrefix,
155
+ respondAsset: options.respondAsset,
156
+ respondPublicFallback: options.respondPublicFallback
157
+ })(c, next);
158
+ {
159
+ const filepath = path.join(pwd, pathname.replace(pathPrefix, ()=>''));
160
+ if (!isPathInside(filepath, pwd)) return next();
161
+ if (!await fs.pathExists(filepath)) return next();
162
+ const asset = {
163
+ filename: filepath,
164
+ kind: 'static'
165
+ };
166
+ const serve = (representation)=>serveStaticAsset(c, {
167
+ ...asset,
168
+ ...representation,
169
+ filename: representation?.filename ?? asset.filename,
170
+ mimeFilename: filepath
171
+ });
172
+ const response = options.respondAsset ? await options.respondAsset(c, asset, serve, {
173
+ root: pwd,
174
+ pathPrefix
175
+ }) : void 0;
176
+ return (void 0 === response ? await serve() : response) ?? next();
99
177
  }
100
- const moduleFederationResponse = await moduleFederationStaticServing.serveModuleFederationAsset(c, staticServingRequest);
101
- if (null !== moduleFederationResponse) return moduleFederationResponse;
102
- return publicMiddleware(c, next);
103
178
  };
104
179
  }
105
180
  const prepareFavicons = (favicon)=>{
@@ -107,4 +182,4 @@ const prepareFavicons = (favicon)=>{
107
182
  if (favicon && 'string' == typeof favicon) faviconNames.push(favicon.substring(favicon.lastIndexOf('/') + 1));
108
183
  return faviconNames;
109
184
  };
110
- export { createPublicMiddleware, createStaticMiddleware, serverStaticPlugin };
185
+ export { createPublicMiddleware, createStaticMiddleware, serveStaticAsset, serverStaticPlugin };