@bleedingdev/modern-js-server-core 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20

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 (65) hide show
  1. package/dist/cjs/adapters/node/hono.js +15 -5
  2. package/dist/cjs/adapters/node/index.js +4 -0
  3. package/dist/cjs/adapters/node/plugins/static.js +107 -29
  4. package/dist/cjs/helper.js +1 -1
  5. package/dist/cjs/index.js +10 -34
  6. package/dist/cjs/plugins/compat/index.js +1 -0
  7. package/dist/cjs/plugins/render/render.js +3 -4
  8. package/dist/cjs/plugins/render/ssrCache.js +63 -42
  9. package/dist/cjs/serverBase.js +60 -19
  10. package/dist/cjs/utils/error.js +0 -10
  11. package/dist/cjs/utils/transformStream.js +1 -1
  12. package/dist/esm/adapters/node/hono.mjs +15 -5
  13. package/dist/esm/adapters/node/index.mjs +1 -0
  14. package/dist/esm/adapters/node/plugins/static.mjs +107 -32
  15. package/dist/esm/helper.mjs +1 -1
  16. package/dist/esm/index.mjs +1 -1
  17. package/dist/esm/plugins/compat/index.mjs +1 -0
  18. package/dist/esm/plugins/render/render.mjs +4 -11
  19. package/dist/esm/plugins/render/ssrCache.mjs +63 -42
  20. package/dist/esm/serverBase.mjs +60 -19
  21. package/dist/esm/utils/error.mjs +0 -1
  22. package/dist/esm/utils/transformStream.mjs +1 -1
  23. package/dist/esm-node/adapters/node/hono.mjs +15 -5
  24. package/dist/esm-node/adapters/node/index.mjs +1 -0
  25. package/dist/esm-node/adapters/node/plugins/static.mjs +107 -32
  26. package/dist/esm-node/helper.mjs +1 -1
  27. package/dist/esm-node/index.mjs +1 -1
  28. package/dist/esm-node/plugins/compat/index.mjs +1 -0
  29. package/dist/esm-node/plugins/render/render.mjs +4 -11
  30. package/dist/esm-node/plugins/render/ssrCache.mjs +63 -42
  31. package/dist/esm-node/serverBase.mjs +60 -19
  32. package/dist/esm-node/utils/error.mjs +0 -1
  33. package/dist/esm-node/utils/transformStream.mjs +1 -1
  34. package/dist/types/adapters/node/index.d.ts +2 -0
  35. package/dist/types/adapters/node/plugins/static.d.ts +42 -4
  36. package/dist/types/index.d.ts +1 -2
  37. package/dist/types/serverBase.d.ts +6 -0
  38. package/dist/types/types/config/bff.d.ts +4 -15
  39. package/dist/types/types/config/server.d.ts +1 -3
  40. package/dist/types/types/plugins/plugin.d.ts +15 -1
  41. package/dist/types/types/requestHandler.d.ts +2 -3
  42. package/dist/types/utils/error.d.ts +0 -2
  43. package/package.json +9 -10
  44. package/dist/cjs/adapters/node/plugins/staticModuleFederation.js +0 -173
  45. package/dist/cjs/adapters/node/plugins/staticPrecompressed.js +0 -152
  46. package/dist/cjs/adapters/node/plugins/staticServing.js +0 -206
  47. package/dist/cjs/types/config/bffRuntime.js +0 -18
  48. package/dist/cjs/types/config/serverTelemetry.js +0 -18
  49. package/dist/esm/adapters/node/plugins/staticModuleFederation.mjs +0 -104
  50. package/dist/esm/adapters/node/plugins/staticPrecompressed.mjs +0 -111
  51. package/dist/esm/adapters/node/plugins/staticServing.mjs +0 -152
  52. package/dist/esm/rslib-runtime~1.mjs +0 -18
  53. package/dist/esm/types/config/bffRuntime.mjs +0 -0
  54. package/dist/esm/types/config/serverTelemetry.mjs +0 -0
  55. package/dist/esm-node/adapters/node/plugins/staticModuleFederation.mjs +0 -105
  56. package/dist/esm-node/adapters/node/plugins/staticPrecompressed.mjs +0 -112
  57. package/dist/esm-node/adapters/node/plugins/staticServing.mjs +0 -153
  58. package/dist/esm-node/rslib-runtime~0.mjs +0 -19
  59. package/dist/esm-node/types/config/bffRuntime.mjs +0 -1
  60. package/dist/esm-node/types/config/serverTelemetry.mjs +0 -1
  61. package/dist/types/adapters/node/plugins/staticModuleFederation.d.ts +0 -13
  62. package/dist/types/adapters/node/plugins/staticPrecompressed.d.ts +0 -13
  63. package/dist/types/adapters/node/plugins/staticServing.d.ts +0 -25
  64. package/dist/types/types/config/bffRuntime.d.ts +0 -116
  65. package/dist/types/types/config/serverTelemetry.d.ts +0 -319
@@ -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();
@@ -105,7 +146,7 @@ function applyMiddlewares() {
105
146
  if (middleware.before) {
106
147
  const targetIndex = finalMiddlewares.findIndex((item)=>{
107
148
  if (middleware.before?.includes(item.name)) return true;
108
- return false;
149
+ else return false;
109
150
  });
110
151
  if (-1 !== targetIndex) finalMiddlewares.splice(targetIndex, 0, middleware);
111
152
  else finalMiddlewares.push(middleware);
@@ -126,7 +167,7 @@ function applyMiddlewares() {
126
167
  }
127
168
  function handler2Handlers(handler) {
128
169
  if (Array.isArray(handler)) return handler;
129
- return [
170
+ else return [
130
171
  handler
131
172
  ];
132
173
  }
@@ -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 };
@@ -14,7 +14,7 @@ function transformResponse(response, transformCb) {
14
14
  if (response.body) {
15
15
  const stream = createTransformStream(async (before)=>{
16
16
  if (Array.isArray(transformCb)) return transformCb.reduce(async (before, cb)=>cb(await before), Promise.resolve(before));
17
- return transformCb(before);
17
+ else return transformCb(before);
18
18
  });
19
19
  response.body.pipeThrough(stream);
20
20
  readable = stream.readable;
@@ -31,12 +31,22 @@ const httpCallBack2HonoMid = (handler)=>async (context, next)=>{
31
31
  };
32
32
  const noop = ()=>{};
33
33
  const connectMid2HonoMid = (handler)=>async (context, next)=>new Promise((resolve, reject)=>{
34
- const { req, res } = context.env.node;
34
+ const { req, res: rawRes } = context.env.node;
35
+ const res = rawRes;
35
36
  if (handler.length < 3) resolve(handler(req, res, noop));
36
- else handler(req, res, (err)=>{
37
- if (err) reject(err);
38
- else resolve(next());
39
- });
37
+ else {
38
+ const onFinish = ()=>{
39
+ context.res;
40
+ context.finalized = true;
41
+ resolve();
42
+ };
43
+ res.once?.('finish', onFinish);
44
+ handler(req, res, (err)=>{
45
+ res.removeListener?.('finish', onFinish);
46
+ if (err) reject(err);
47
+ else resolve(next());
48
+ });
49
+ }
40
50
  });
41
51
  const connectMockMid2HonoMid = (handler)=>async (context, next)=>new Promise((resolve, reject)=>{
42
52
  const { req, res: rawRes } = context.env.node;
@@ -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
+ else {
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 };
@@ -3,7 +3,7 @@ const LOADER_CONTEXT = 'loaderContext';
3
3
  function getLoaderCtx(c) {
4
4
  const loaderContext = c.get(LOADER_CONTEXT);
5
5
  if (loaderContext) return loaderContext;
6
- {
6
+ else {
7
7
  const loaderContext = new Map();
8
8
  c.set(LOADER_CONTEXT, loaderContext);
9
9
  return loaderContext;
@@ -9,5 +9,5 @@ export { AGGRED_DIR } from "./constants.mjs";
9
9
  export { run, useHonoContext } from "./context.mjs";
10
10
  export { getLoaderCtx } from "./helper.mjs";
11
11
  export { createServerBase } from "./serverBase.mjs";
12
- export { ErrorDigest, createErrorHtml, createSafeFailureHttpResult, createSafeJsonFailureResponse, getSafeFailureStatus, onError } from "./utils/index.mjs";
12
+ export { ErrorDigest, createErrorHtml, onError } from "./utils/index.mjs";
13
13
  export { getPublicDirConfig, getPublicDirPatterns, getPublicDirRoutePrefixes, normalizePublicDir, normalizePublicDirPath, resolvePublicDirPaths } from "./utils/publicDir.mjs";
@@ -4,6 +4,7 @@ import { getHookRunners, handleSetupResult } from "./hooks.mjs";
4
4
  const compatPlugin = ()=>({
5
5
  name: '@modern-js/server-compat',
6
6
  registryHooks: {
7
+ handleError: createAsyncPipelineHook(),
7
8
  prepareWebServer: createAsyncPipelineHook(),
8
9
  prepareApiServer: createAsyncPipelineHook(),
9
10
  afterMatch: createAsyncPipelineHook(),
@@ -8,14 +8,8 @@ import { dataHandler } from "./dataHandler.mjs";
8
8
  import { renderRscHandler } from "./renderRscHandler.mjs";
9
9
  import { serverActionHandler } from "./serverActionHandler.mjs";
10
10
  import { ssrRender } from "./ssrRender.mjs";
11
- import { __webpack_require__ } from "../../rslib-runtime~0.mjs";
12
11
  import { createRequire as __rspack_createRequire } from "node:module";
13
12
  const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);
14
- __webpack_require__.add({
15
- "@modern-js/runtime-utils/router" (module) {
16
- module.exports = __rspack_createRequire_require("@modern-js/runtime-utils/router");
17
- }
18
- });
19
13
  const DYNAMIC_ROUTE_REG = /\/:./;
20
14
  function getRouter(routes) {
21
15
  const dynamicRoutes = [];
@@ -48,8 +42,7 @@ function matchRoute(router, pathname, entryName) {
48
42
  void 0,
49
43
  {}
50
44
  ];
51
- }
52
- {
45
+ } else {
53
46
  const result = matchedRoutes[0];
54
47
  if (!result) return [
55
48
  void 0,
@@ -166,7 +159,7 @@ async function renderHandler(request, options, mode, fallbackWrapper, framework)
166
159
  const { nestedRoutesJson } = serverManifest;
167
160
  const routes = nestedRoutesJson?.[options.routeInfo.entryName];
168
161
  if (routes) {
169
- const { matchRoutes } = __webpack_require__("@modern-js/runtime-utils/router");
162
+ const { matchRoutes } = __rspack_createRequire_require("@modern-js/runtime-utils/router");
170
163
  const url = new URL(request.url);
171
164
  const matchedRoutes = matchRoutes(routes, url.pathname, options.routeInfo.urlPath);
172
165
  if (matchedRoutes) {
@@ -204,7 +197,7 @@ async function getRenderMode(req, fallbackHeader, isSSR, forceCSR, nodeReq, onFa
204
197
  if (req.headers.get('x-rsc-action')) return 'rsc-action';
205
198
  if (req.headers.get('x-rsc-tree')) return 'rsc-tree';
206
199
  if (!isSSR) return 'csr';
207
- {
200
+ else {
208
201
  if (query.__loader) return 'data';
209
202
  const fallbackHeaderValue = req.headers.get(fallbackHeader) || nodeReq?.headers[fallbackHeader];
210
203
  if (forceCSR && (query.csr || fallbackHeaderValue || contextForceCSR)) {
@@ -233,6 +226,6 @@ async function csrRender(request, options) {
233
226
  [X_MODERNJS_RENDER]: 'client'
234
227
  })
235
228
  });
236
- return csrRscRender(request, options);
229
+ else return csrRscRender(request, options);
237
230
  }
238
231
  export { createRender, matchRoute };
@@ -2,54 +2,67 @@ import "node:module";
2
2
  import { createMemoryStorage } from "@modern-js/runtime-utils/storer";
3
3
  import { X_RENDER_CACHE } from "../../constants.mjs";
4
4
  import { createTransformStream, getPathname } from "../../utils/index.mjs";
5
+ const preventsSharedCaching = (headers)=>/(?:^|,)\s*(?:private|no-store|no-cache)\s*(?:[=,]|$)/i.test(headers.get('cache-control') || '');
6
+ const isCacheableResponse = (response)=>200 === response.status && !preventsSharedCaching(response.headers) && !response.headers.has('set-cookie') && !response.headers.get('vary');
5
7
  const removeTailSlash = (s)=>s.replace(/\/+$/, '');
6
8
  const ZERO_RENDER_LEVEL = /"renderLevel":0/;
7
9
  const NO_SSR_CACHE = /<meta\s+[^>]*name=["']no-ssr-cache["'][^>]*>/i;
8
10
  async function processCache({ request, key, requestHandler, requestHandlerOptions, ttl, container, cacheStatus }) {
9
11
  const response = await requestHandler(request, requestHandlerOptions);
10
12
  const { onError } = requestHandlerOptions;
11
- const nonCacheableStatusCodes = [
12
- 204,
13
- 305,
14
- 404,
15
- 405,
16
- 500,
17
- 501,
18
- 502,
19
- 503,
20
- 504
21
- ];
22
- if (nonCacheableStatusCodes.includes(response.status)) return response;
13
+ const deleteCache = async ()=>{
14
+ try {
15
+ await container.delete(key);
16
+ } catch {
17
+ (onError || console.error)('[render-cache] delete cache failed');
18
+ }
19
+ };
20
+ if (!isCacheableResponse(response) || !response.body) {
21
+ await deleteCache();
22
+ return response;
23
+ }
24
+ const headers = Object.fromEntries(response.headers);
23
25
  const decoder = new TextDecoder();
24
26
  if (response.body) {
25
27
  const stream = createTransformStream();
26
28
  const reader = response.body.getReader();
27
29
  const writer = stream.writable.getWriter();
28
30
  let html = '';
29
- const push = ()=>reader.read().then(({ done, value })=>{
31
+ const push = ()=>reader.read().then(async ({ done, value })=>{
30
32
  if (done) {
33
+ html += decoder.decode();
31
34
  const match = ZERO_RENDER_LEVEL.test(html) || NO_SSR_CACHE.test(html);
32
- if (match) return void writer.close();
35
+ if (match) {
36
+ await deleteCache();
37
+ return writer.close();
38
+ }
33
39
  const current = Date.now();
34
40
  const cache = {
35
41
  val: html,
36
- cursor: current
42
+ cursor: current,
43
+ headers
37
44
  };
38
45
  container.set(key, JSON.stringify(cache), {
39
46
  ttl
40
47
  }).catch(()=>{
41
- if (onError) onError(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
42
- else console.error(`[render-cache] set cache failed, key: ${key}, value: ${JSON.stringify(cache)}`);
48
+ (onError || console.error)('[render-cache] set cache failed');
43
49
  });
44
- writer.close();
45
- return;
50
+ return writer.close();
46
51
  }
47
- const content = decoder.decode(value);
52
+ const content = decoder.decode(value, {
53
+ stream: true
54
+ });
48
55
  html += content;
49
- writer.write(value);
50
- push();
56
+ await writer.write(value);
57
+ return push();
51
58
  });
52
- push();
59
+ push().catch(async (error)=>{
60
+ await Promise.allSettled([
61
+ writer.abort(error),
62
+ reader.cancel(error)
63
+ ]);
64
+ (onError || console.error)('[render-cache] response stream failed');
65
+ });
53
66
  cacheStatus && response.headers.set(X_RENDER_CACHE, cacheStatus);
54
67
  return new Response(stream.readable, {
55
68
  status: response.status,
@@ -64,9 +77,12 @@ function computedKey(req, cacheControl) {
64
77
  const pathname = getPathname(req);
65
78
  const { customKey } = cacheControl;
66
79
  const defaultKey = '/' === pathname ? pathname : removeTailSlash(pathname);
67
- if (!customKey) return defaultKey;
68
- if ('string' == typeof customKey) return customKey;
69
- return customKey(defaultKey);
80
+ if (customKey) if ('string' == typeof customKey) return customKey;
81
+ else return customKey(defaultKey);
82
+ else {
83
+ const url = new URL(req.url);
84
+ return `${url.origin}${defaultKey}${url.search}`;
85
+ }
70
86
  }
71
87
  function shouldUseCache(request) {
72
88
  const url = new URL(request.url);
@@ -77,7 +93,7 @@ function shouldUseCache(request) {
77
93
  }
78
94
  function matchCacheControl(cacheOption, req) {
79
95
  if (!cacheOption || !req) return;
80
- {
96
+ else {
81
97
  if (isCacheControl(cacheOption)) return cacheOption;
82
98
  if (isCacheOptionProvider(cacheOption)) return cacheOption(req);
83
99
  const url = req.url;
@@ -96,13 +112,15 @@ function matchCacheControl(cacheOption, req) {
96
112
  async function getCacheResult(request, options) {
97
113
  const { cacheControl, container = storage, requestHandler, requestHandlerOptions } = options;
98
114
  const { onError } = requestHandlerOptions;
99
- const key = computedKey(request, cacheControl);
115
+ const hasCredentials = request.headers.has('cookie') || request.headers.has('authorization');
116
+ if ('GET' !== request.method || !shouldUseCache(request) || preventsSharedCaching(request.headers) || hasCredentials && !cacheControl.customKey) return requestHandler(request, requestHandlerOptions);
117
+ const key = `${CACHE_NAMESPACE}:v2:${computedKey(request, cacheControl)}`;
100
118
  let value;
101
119
  try {
102
120
  value = await container.get(key);
103
121
  } catch (_) {
104
- if (onError) onError(`[render-cache] get cache failed, key: ${key}`);
105
- else console.error(`[render-cache] get cache failed, key: ${key}`);
122
+ if (onError) onError('[render-cache] get cache failed');
123
+ else console.error('[render-cache] get cache failed');
106
124
  value = void 0;
107
125
  }
108
126
  const { maxAge, staleWhileRevalidate } = cacheControl;
@@ -116,27 +134,27 @@ async function getCacheResult(request, options) {
116
134
  container,
117
135
  cacheStatus: 'miss'
118
136
  });
119
- {
137
+ else {
120
138
  const cache = JSON.parse(value);
121
139
  const interval = Date.now() - cache.cursor;
122
140
  if (interval <= maxAge) {
123
141
  const cacheStatus = 'hit';
124
142
  return new Response(cache.val, {
125
143
  headers: {
144
+ ...cache.headers,
126
145
  [X_RENDER_CACHE]: cacheStatus
127
146
  }
128
147
  });
129
- }
130
- if (!(interval <= staleWhileRevalidate + maxAge)) return processCache({
131
- key,
132
- request,
133
- requestHandler,
134
- requestHandlerOptions,
135
- ttl,
136
- container,
137
- cacheStatus: 'expired'
138
- });
139
- {
148
+ } else {
149
+ if (!(interval <= staleWhileRevalidate + maxAge)) return processCache({
150
+ key,
151
+ request,
152
+ requestHandler,
153
+ requestHandlerOptions,
154
+ ttl,
155
+ container,
156
+ cacheStatus: 'expired'
157
+ });
140
158
  processCache({
141
159
  key,
142
160
  request,
@@ -146,10 +164,13 @@ async function getCacheResult(request, options) {
146
164
  container
147
165
  }).then(async (response)=>{
148
166
  await response.text();
167
+ }).catch(()=>{
168
+ (onError || console.error)('[render-cache] revalidation failed');
149
169
  });
150
170
  const cacheStatus = 'stale';
151
171
  return new Response(cache.val, {
152
172
  headers: {
173
+ ...cache.headers,
153
174
  [X_RENDER_CACHE]: cacheStatus
154
175
  }
155
176
  });