@modern-js/server-core 3.10.0 → 3.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/adapters/node/node.js +63 -4
- package/dist/cjs/adapters/node/plugins/static.js +14 -13
- package/dist/cjs/plugins/monitors.js +4 -7
- package/dist/cjs/plugins/render/index.js +1 -7
- package/dist/cjs/plugins/render/render.js +3 -4
- package/dist/cjs/plugins/render/ssrCache.js +10 -1
- package/dist/cjs/plugins/render/ssrRender.js +0 -4
- package/dist/cjs/serverBase.js +2 -0
- package/dist/esm/adapters/node/node.mjs +64 -5
- package/dist/esm/adapters/node/plugins/static.mjs +14 -13
- package/dist/esm/plugins/monitors.mjs +4 -7
- package/dist/esm/plugins/render/index.mjs +1 -7
- package/dist/esm/plugins/render/render.mjs +3 -4
- package/dist/esm/plugins/render/ssrCache.mjs +10 -1
- package/dist/esm/plugins/render/ssrRender.mjs +1 -5
- package/dist/esm/serverBase.mjs +2 -0
- package/dist/esm-node/adapters/node/node.mjs +64 -5
- package/dist/esm-node/adapters/node/plugins/static.mjs +14 -13
- package/dist/esm-node/plugins/monitors.mjs +4 -7
- package/dist/esm-node/plugins/render/index.mjs +1 -7
- package/dist/esm-node/plugins/render/render.mjs +3 -4
- package/dist/esm-node/plugins/render/ssrCache.mjs +10 -1
- package/dist/esm-node/plugins/render/ssrRender.mjs +1 -5
- package/dist/esm-node/serverBase.mjs +2 -0
- package/dist/types/serverBase.d.ts +1 -0
- package/package.json +7 -7
|
@@ -109,6 +109,67 @@ const createWebRequest = (req, res, body)=>{
|
|
|
109
109
|
}
|
|
110
110
|
return originalRequest;
|
|
111
111
|
};
|
|
112
|
+
const noop = ()=>{};
|
|
113
|
+
const isDestroyed = (res)=>'stream' in res ? res.stream.destroyed : res.destroyed;
|
|
114
|
+
const createAbortError = ()=>Object.assign(new Error('The operation was aborted'), {
|
|
115
|
+
name: 'AbortError',
|
|
116
|
+
code: 'ABORT_ERR'
|
|
117
|
+
});
|
|
118
|
+
const writeBody = async (body, res)=>{
|
|
119
|
+
const reader = body.getReader();
|
|
120
|
+
let abortError;
|
|
121
|
+
let cancelled;
|
|
122
|
+
let resume;
|
|
123
|
+
let waitsForDrain = false;
|
|
124
|
+
const onDrain = ()=>resume?.();
|
|
125
|
+
const abort = (error)=>{
|
|
126
|
+
if (cancelled) return;
|
|
127
|
+
abortError = error;
|
|
128
|
+
cancelled = reader.cancel(error);
|
|
129
|
+
cancelled.catch(noop);
|
|
130
|
+
resume?.();
|
|
131
|
+
};
|
|
132
|
+
const onClose = ()=>abort(createAbortError());
|
|
133
|
+
res.on('close', onClose);
|
|
134
|
+
res.on('error', abort);
|
|
135
|
+
try {
|
|
136
|
+
if (isDestroyed(res)) onClose();
|
|
137
|
+
while(!cancelled){
|
|
138
|
+
let result;
|
|
139
|
+
try {
|
|
140
|
+
result = await reader.read();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
res.destroy(error);
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
if (result.done || cancelled) break;
|
|
146
|
+
try {
|
|
147
|
+
if (!res.write(result.value) && !cancelled) {
|
|
148
|
+
if (!waitsForDrain) {
|
|
149
|
+
waitsForDrain = true;
|
|
150
|
+
res.on('drain', onDrain);
|
|
151
|
+
}
|
|
152
|
+
await new Promise((resolve)=>{
|
|
153
|
+
resume = resolve;
|
|
154
|
+
});
|
|
155
|
+
resume = void 0;
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
abort(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (cancelled) {
|
|
162
|
+
await cancelled;
|
|
163
|
+
throw abortError;
|
|
164
|
+
}
|
|
165
|
+
res.end();
|
|
166
|
+
} finally{
|
|
167
|
+
res.off('close', onClose);
|
|
168
|
+
res.off('error', abort);
|
|
169
|
+
if (waitsForDrain) res.off('drain', onDrain);
|
|
170
|
+
reader.releaseLock();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
112
173
|
const sendResponse = async (response, res)=>{
|
|
113
174
|
res.statusMessage = response.statusText;
|
|
114
175
|
res.statusCode = response.status;
|
|
@@ -117,10 +178,8 @@ const sendResponse = async (response, res)=>{
|
|
|
117
178
|
else res.setHeader(key, value);
|
|
118
179
|
if (cookies.length > 0) res.setHeader('set-cookie', cookies);
|
|
119
180
|
if (response.headers.get('Content-Type')?.match(/text\/event-stream/i) && res instanceof external_node_http_namespaceObject.ServerResponse) res.flushHeaders();
|
|
120
|
-
if (response.body)
|
|
121
|
-
|
|
122
|
-
await response.body.pipeTo(writable);
|
|
123
|
-
} else res.end();
|
|
181
|
+
if (response.body) await writeBody(response.body, res);
|
|
182
|
+
else res.end();
|
|
124
183
|
};
|
|
125
184
|
const handleResponseError = (e, res)=>{
|
|
126
185
|
const err = e instanceof Error ? e : new Error('unknown error', {
|
|
@@ -69,8 +69,9 @@ const serverStaticPlugin = ()=>({
|
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
function createPublicMiddleware({ pwd, routes }) {
|
|
72
|
+
const publicRoutes = routes.filter((route)=>!route.isSSR && route.entryPath?.startsWith('public')).sort(index_js_namespaceObject.sortRoutes);
|
|
72
73
|
return async (c, next)=>{
|
|
73
|
-
const route = matchPublicRoute(c.req,
|
|
74
|
+
const route = matchPublicRoute(c.req, publicRoutes);
|
|
74
75
|
if (route) {
|
|
75
76
|
const { entryPath } = route;
|
|
76
77
|
const filename = external_path_default().join(pwd, entryPath);
|
|
@@ -88,8 +89,8 @@ function createPublicMiddleware({ pwd, routes }) {
|
|
|
88
89
|
return await next();
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
|
-
function matchPublicRoute(req,
|
|
92
|
-
|
|
92
|
+
function matchPublicRoute(req, publicRoutes) {
|
|
93
|
+
return publicRoutes.find((route)=>req.path.startsWith(route.urlPath));
|
|
93
94
|
}
|
|
94
95
|
const isPathInside = (target, root)=>{
|
|
95
96
|
const relative = external_path_default().relative(external_path_default().resolve(root), external_path_default().resolve(target));
|
|
@@ -134,26 +135,26 @@ function createStaticMiddleware(options) {
|
|
|
134
135
|
...staticReg,
|
|
135
136
|
...iconReg
|
|
136
137
|
].join('|')})`);
|
|
138
|
+
const publicMiddleware = createPublicMiddleware({
|
|
139
|
+
pwd,
|
|
140
|
+
routes: routes || []
|
|
141
|
+
});
|
|
137
142
|
return async (c, next)=>{
|
|
138
143
|
const pageRoute = c.get('route');
|
|
139
144
|
const pathname = c.req.path;
|
|
140
145
|
if (pageRoute && '' === external_path_default().extname(pathname)) return next();
|
|
141
146
|
const hit = staticPathRegExp.test(pathname);
|
|
142
|
-
if (!hit) return
|
|
143
|
-
pwd,
|
|
144
|
-
routes: routes || []
|
|
145
|
-
})(c, next);
|
|
147
|
+
if (!hit) return publicMiddleware(c, next);
|
|
146
148
|
else {
|
|
147
149
|
const filepath = external_path_default().join(pwd, pathname.replace(pathPrefix, ()=>''));
|
|
148
150
|
if (!isPathInside(filepath, pwd)) return next();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (mimeType) c.header('Content-Type', mimeType);
|
|
152
|
-
const stat = await utils_namespaceObject.fs.lstat(filepath);
|
|
153
|
-
const { size } = stat;
|
|
151
|
+
const stat = await utils_namespaceObject.fs.stat(filepath).catch(()=>null);
|
|
152
|
+
if (!stat?.isFile()) return next();
|
|
154
153
|
const chunk = await fileReader_namespaceObject.fileReader.readFileFromSystem(filepath, 'buffer');
|
|
155
|
-
c.header('Content-Length', String(size));
|
|
156
154
|
if (null === chunk) return next();
|
|
155
|
+
const mimeType = (0, mime_namespaceObject.getMimeType)(filepath);
|
|
156
|
+
if (mimeType) c.header('Content-Type', mimeType);
|
|
157
|
+
c.header('Content-Length', String(chunk.byteLength));
|
|
157
158
|
const body = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
158
159
|
return c.body(body, 200);
|
|
159
160
|
}
|
|
@@ -158,13 +158,10 @@ const injectServerTiming = ()=>({
|
|
|
158
158
|
const monitors = c.get('monitors');
|
|
159
159
|
monitors?.push(timingMonitor);
|
|
160
160
|
await next();
|
|
161
|
-
serverTimings.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
c.header(external_constants_js_namespaceObject.SERVER_TIMING, value, {
|
|
166
|
-
append: true
|
|
167
|
-
});
|
|
161
|
+
if (0 === serverTimings.length) return;
|
|
162
|
+
const value = serverTimings.map(({ name, desc, dur })=>`bd-${metaName}-${name};${desc ? `decs="${desc}";` : ''} dur=${dur}`).join(', ');
|
|
163
|
+
c.header(external_constants_js_namespaceObject.SERVER_TIMING, value, {
|
|
164
|
+
append: true
|
|
168
165
|
});
|
|
169
166
|
}
|
|
170
167
|
});
|
|
@@ -70,10 +70,6 @@ var __webpack_exports__ = {};
|
|
|
70
70
|
"default"
|
|
71
71
|
].indexOf(__rspack_import_key) < 0) __rspack_reexport[__rspack_import_key] = ()=>_inject__rspack_import_3[__rspack_import_key];
|
|
72
72
|
__webpack_require__.d(__webpack_exports__, __rspack_reexport);
|
|
73
|
-
const DYNAMIC_ROUTE_REG = /\/:./;
|
|
74
|
-
function escapeRegexSpecialChars(path) {
|
|
75
|
-
return path.replace(/[()[\]{}*+?.^$|\\]/g, '\\$&');
|
|
76
|
-
}
|
|
77
73
|
const renderPlugin = ()=>({
|
|
78
74
|
name: '@modern-js/plugin-render',
|
|
79
75
|
setup (api) {
|
|
@@ -87,9 +83,7 @@ var __webpack_exports__ = {};
|
|
|
87
83
|
});
|
|
88
84
|
for (const route of pageRoutes){
|
|
89
85
|
const { urlPath: originUrlPath } = route;
|
|
90
|
-
const
|
|
91
|
-
const escapedPath = isDynamic ? originUrlPath : escapeRegexSpecialChars(originUrlPath);
|
|
92
|
-
const urlPath = escapedPath.endsWith('/') ? `${escapedPath}*` : `${escapedPath}/*`;
|
|
86
|
+
const urlPath = originUrlPath.endsWith('/') ? `${originUrlPath}*` : `${originUrlPath}/*`;
|
|
93
87
|
renderMiddlewares?.forEach((m)=>{
|
|
94
88
|
middlewares.push({
|
|
95
89
|
name: m.name,
|
|
@@ -109,11 +109,11 @@ var __webpack_exports__ = {};
|
|
|
109
109
|
}
|
|
110
110
|
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config }) {
|
|
111
111
|
const router = getRouter(routes);
|
|
112
|
+
const framework = (0, universal_namespaceObject.cutNameByHyphen)(metaName || 'modern-js');
|
|
113
|
+
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
112
114
|
return async (req, { monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext, contextForceCSR, reporter })=>{
|
|
113
115
|
const forMatchpathname = matchPathname ?? (0, index_js_namespaceObject.getPathname)(req);
|
|
114
116
|
const [routeInfo, params] = matchRoute(router, forMatchpathname, matchEntryName);
|
|
115
|
-
const framework = (0, universal_namespaceObject.cutNameByHyphen)(metaName || 'modern-js');
|
|
116
|
-
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
117
117
|
let fallbackReason = null;
|
|
118
118
|
const fallbackWrapper = (reason)=>{
|
|
119
119
|
fallbackReason = reason;
|
|
@@ -139,9 +139,8 @@ var __webpack_exports__ = {};
|
|
|
139
139
|
});
|
|
140
140
|
const finalForceCSR = routeInfo.entryName ? forceCSRMap?.get(routeInfo.entryName) ?? forceCSR : forceCSR;
|
|
141
141
|
const renderMode = await getRenderMode(req, fallbackHeader, routeInfo.isSSR, finalForceCSR, nodeReq, fallbackWrapper, contextForceCSR);
|
|
142
|
-
const headerData = (0, index_js_namespaceObject.parseHeaders)(req);
|
|
143
142
|
const onError = (e, key)=>{
|
|
144
|
-
monitors?.error(`SSR Error - ${key || (e instanceof Error ? e.name : e)}, error = %s, req.url = %s, req.headers = %o`, e instanceof Error ? e.stack || e.message : e, forMatchpathname, getHeadersWithoutCookie(
|
|
143
|
+
monitors?.error(`SSR Error - ${key || (e instanceof Error ? e.name : e)}, error = %s, req.url = %s, req.headers = %o`, e instanceof Error ? e.stack || e.message : e, forMatchpathname, getHeadersWithoutCookie((0, index_js_namespaceObject.parseHeaders)(req)));
|
|
145
144
|
};
|
|
146
145
|
const onTiming = (name, dur)=>{
|
|
147
146
|
monitors?.timing(name, dur, 'SSR');
|
|
@@ -137,6 +137,15 @@ function shouldUseCache(request) {
|
|
|
137
137
|
const hasLoaderQuery = url.searchParams.has('__loader');
|
|
138
138
|
return !(hasRSCAction || hasRSCTree || hasLoaderQuery);
|
|
139
139
|
}
|
|
140
|
+
const cacheKeyRegExps = new Map();
|
|
141
|
+
function getCacheKeyRegExp(key) {
|
|
142
|
+
let regExp = cacheKeyRegExps.get(key);
|
|
143
|
+
if (!regExp) {
|
|
144
|
+
regExp = new RegExp(key);
|
|
145
|
+
cacheKeyRegExps.set(key, regExp);
|
|
146
|
+
}
|
|
147
|
+
return regExp;
|
|
148
|
+
}
|
|
140
149
|
function matchCacheControl(cacheOption, req) {
|
|
141
150
|
if (!cacheOption || !req) return;
|
|
142
151
|
else {
|
|
@@ -144,7 +153,7 @@ function matchCacheControl(cacheOption, req) {
|
|
|
144
153
|
if (isCacheOptionProvider(cacheOption)) return cacheOption(req);
|
|
145
154
|
const url = req.url;
|
|
146
155
|
const options = Object.entries(cacheOption);
|
|
147
|
-
for (const [key, option] of options)if ('*' === key ||
|
|
156
|
+
for (const [key, option] of options)if ('*' === key || getCacheKeyRegExp(key).test(url)) if ('function' == typeof option) return option(req);
|
|
148
157
|
else return option;
|
|
149
158
|
return;
|
|
150
159
|
}
|
|
@@ -40,10 +40,6 @@ async function ssrRender(request, { routeInfo, html, config: userConfig, staticG
|
|
|
40
40
|
const { entryName } = routeInfo;
|
|
41
41
|
const loadableStats = serverManifest.loadableStats || {};
|
|
42
42
|
const routeManifest = serverManifest.routeManifest || {};
|
|
43
|
-
const headers = (0, index_js_namespaceObject.parseHeaders)(request);
|
|
44
|
-
if (nodeReq) {
|
|
45
|
-
for(const key in nodeReq.headers)if (!headers[key]) headers[key] = nodeReq.headers[key];
|
|
46
|
-
}
|
|
47
43
|
const renderBundle = serverManifest.renderBundles?.[entryName || constants_namespaceObject.MAIN_ENTRY_NAME];
|
|
48
44
|
if (!renderBundle) throw new Error(`Can't find renderBundle ${entryName || constants_namespaceObject.MAIN_ENTRY_NAME}`);
|
|
49
45
|
const requestHandler = await renderBundle[SERVER_RUNTIME_ENTRY];
|
package/dist/cjs/serverBase.js
CHANGED
|
@@ -31,6 +31,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
31
31
|
ServerBase: ()=>ServerBase,
|
|
32
32
|
createServerBase: ()=>createServerBase
|
|
33
33
|
});
|
|
34
|
+
const fileReader_namespaceObject = require("@modern-js/runtime-utils/fileReader");
|
|
34
35
|
const server_namespaceObject = require("@modern-js/plugin/server");
|
|
35
36
|
const external_hono_namespaceObject = require("hono");
|
|
36
37
|
const external_context_js_namespaceObject = require("./context.js");
|
|
@@ -111,6 +112,7 @@ class ServerBase {
|
|
|
111
112
|
}
|
|
112
113
|
constructor(options){
|
|
113
114
|
_class_private_method_init(this, _applyMiddlewares);
|
|
115
|
+
this.fileReader = fileReader_namespaceObject.fileReader;
|
|
114
116
|
this.plugins = [];
|
|
115
117
|
this.serverContext = null;
|
|
116
118
|
this.serverOptions = options;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ServerResponse } from "node:http";
|
|
2
|
-
import { Readable
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
3
|
import cloneable_readable from "cloneable-readable";
|
|
4
4
|
import { isResFinalized } from "./helper/index.mjs";
|
|
5
5
|
const createWebRequest = (req, res, body)=>{
|
|
@@ -65,6 +65,67 @@ const createWebRequest = (req, res, body)=>{
|
|
|
65
65
|
}
|
|
66
66
|
return originalRequest;
|
|
67
67
|
};
|
|
68
|
+
const noop = ()=>{};
|
|
69
|
+
const isDestroyed = (res)=>'stream' in res ? res.stream.destroyed : res.destroyed;
|
|
70
|
+
const createAbortError = ()=>Object.assign(new Error('The operation was aborted'), {
|
|
71
|
+
name: 'AbortError',
|
|
72
|
+
code: 'ABORT_ERR'
|
|
73
|
+
});
|
|
74
|
+
const writeBody = async (body, res)=>{
|
|
75
|
+
const reader = body.getReader();
|
|
76
|
+
let abortError;
|
|
77
|
+
let cancelled;
|
|
78
|
+
let resume;
|
|
79
|
+
let waitsForDrain = false;
|
|
80
|
+
const onDrain = ()=>resume?.();
|
|
81
|
+
const abort = (error)=>{
|
|
82
|
+
if (cancelled) return;
|
|
83
|
+
abortError = error;
|
|
84
|
+
cancelled = reader.cancel(error);
|
|
85
|
+
cancelled.catch(noop);
|
|
86
|
+
resume?.();
|
|
87
|
+
};
|
|
88
|
+
const onClose = ()=>abort(createAbortError());
|
|
89
|
+
res.on('close', onClose);
|
|
90
|
+
res.on('error', abort);
|
|
91
|
+
try {
|
|
92
|
+
if (isDestroyed(res)) onClose();
|
|
93
|
+
while(!cancelled){
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result = await reader.read();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
res.destroy(error);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (result.done || cancelled) break;
|
|
102
|
+
try {
|
|
103
|
+
if (!res.write(result.value) && !cancelled) {
|
|
104
|
+
if (!waitsForDrain) {
|
|
105
|
+
waitsForDrain = true;
|
|
106
|
+
res.on('drain', onDrain);
|
|
107
|
+
}
|
|
108
|
+
await new Promise((resolve)=>{
|
|
109
|
+
resume = resolve;
|
|
110
|
+
});
|
|
111
|
+
resume = void 0;
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
abort(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (cancelled) {
|
|
118
|
+
await cancelled;
|
|
119
|
+
throw abortError;
|
|
120
|
+
}
|
|
121
|
+
res.end();
|
|
122
|
+
} finally{
|
|
123
|
+
res.off('close', onClose);
|
|
124
|
+
res.off('error', abort);
|
|
125
|
+
if (waitsForDrain) res.off('drain', onDrain);
|
|
126
|
+
reader.releaseLock();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
68
129
|
const sendResponse = async (response, res)=>{
|
|
69
130
|
res.statusMessage = response.statusText;
|
|
70
131
|
res.statusCode = response.status;
|
|
@@ -73,10 +134,8 @@ const sendResponse = async (response, res)=>{
|
|
|
73
134
|
else res.setHeader(key, value);
|
|
74
135
|
if (cookies.length > 0) res.setHeader('set-cookie', cookies);
|
|
75
136
|
if (response.headers.get('Content-Type')?.match(/text\/event-stream/i) && res instanceof ServerResponse) res.flushHeaders();
|
|
76
|
-
if (response.body)
|
|
77
|
-
|
|
78
|
-
await response.body.pipeTo(writable);
|
|
79
|
-
} else res.end();
|
|
137
|
+
if (response.body) await writeBody(response.body, res);
|
|
138
|
+
else res.end();
|
|
80
139
|
};
|
|
81
140
|
const handleResponseError = (e, res)=>{
|
|
82
141
|
const err = e instanceof Error ? e : new Error('unknown error', {
|
|
@@ -25,8 +25,9 @@ const serverStaticPlugin = ()=>({
|
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
function createPublicMiddleware({ pwd, routes }) {
|
|
28
|
+
const publicRoutes = routes.filter((route)=>!route.isSSR && route.entryPath?.startsWith('public')).sort(sortRoutes);
|
|
28
29
|
return async (c, next)=>{
|
|
29
|
-
const route = matchPublicRoute(c.req,
|
|
30
|
+
const route = matchPublicRoute(c.req, publicRoutes);
|
|
30
31
|
if (route) {
|
|
31
32
|
const { entryPath } = route;
|
|
32
33
|
const filename = path.join(pwd, entryPath);
|
|
@@ -44,8 +45,8 @@ function createPublicMiddleware({ pwd, routes }) {
|
|
|
44
45
|
return await next();
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
|
-
function matchPublicRoute(req,
|
|
48
|
-
|
|
48
|
+
function matchPublicRoute(req, publicRoutes) {
|
|
49
|
+
return publicRoutes.find((route)=>req.path.startsWith(route.urlPath));
|
|
49
50
|
}
|
|
50
51
|
const isPathInside = (target, root)=>{
|
|
51
52
|
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
@@ -90,26 +91,26 @@ function createStaticMiddleware(options) {
|
|
|
90
91
|
...staticReg,
|
|
91
92
|
...iconReg
|
|
92
93
|
].join('|')})`);
|
|
94
|
+
const publicMiddleware = createPublicMiddleware({
|
|
95
|
+
pwd,
|
|
96
|
+
routes: routes || []
|
|
97
|
+
});
|
|
93
98
|
return async (c, next)=>{
|
|
94
99
|
const pageRoute = c.get('route');
|
|
95
100
|
const pathname = c.req.path;
|
|
96
101
|
if (pageRoute && '' === path.extname(pathname)) return next();
|
|
97
102
|
const hit = staticPathRegExp.test(pathname);
|
|
98
|
-
if (!hit) return
|
|
99
|
-
pwd,
|
|
100
|
-
routes: routes || []
|
|
101
|
-
})(c, next);
|
|
103
|
+
if (!hit) return publicMiddleware(c, next);
|
|
102
104
|
else {
|
|
103
105
|
const filepath = path.join(pwd, pathname.replace(pathPrefix, ()=>''));
|
|
104
106
|
if (!isPathInside(filepath, pwd)) return next();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (mimeType) c.header('Content-Type', mimeType);
|
|
108
|
-
const stat = await fs.lstat(filepath);
|
|
109
|
-
const { size } = stat;
|
|
107
|
+
const stat = await fs.stat(filepath).catch(()=>null);
|
|
108
|
+
if (!stat?.isFile()) return next();
|
|
110
109
|
const chunk = await fileReader.readFileFromSystem(filepath, 'buffer');
|
|
111
|
-
c.header('Content-Length', String(size));
|
|
112
110
|
if (null === chunk) return next();
|
|
111
|
+
const mimeType = getMimeType(filepath);
|
|
112
|
+
if (mimeType) c.header('Content-Type', mimeType);
|
|
113
|
+
c.header('Content-Length', String(chunk.byteLength));
|
|
113
114
|
const body = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
114
115
|
return c.body(body, 200);
|
|
115
116
|
}
|
|
@@ -123,13 +123,10 @@ const injectServerTiming = ()=>({
|
|
|
123
123
|
const monitors = c.get('monitors');
|
|
124
124
|
monitors?.push(timingMonitor);
|
|
125
125
|
await next();
|
|
126
|
-
serverTimings.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
c.header(SERVER_TIMING, value, {
|
|
131
|
-
append: true
|
|
132
|
-
});
|
|
126
|
+
if (0 === serverTimings.length) return;
|
|
127
|
+
const value = serverTimings.map(({ name, desc, dur })=>`bd-${metaName}-${name};${desc ? `decs="${desc}";` : ''} dur=${dur}`).join(', ');
|
|
128
|
+
c.header(SERVER_TIMING, value, {
|
|
129
|
+
append: true
|
|
133
130
|
});
|
|
134
131
|
}
|
|
135
132
|
});
|
|
@@ -2,10 +2,6 @@ import { getLoaderCtx } from "../../helper.mjs";
|
|
|
2
2
|
import { sortRoutes } from "../../utils/index.mjs";
|
|
3
3
|
import { requestLatencyMiddleware } from "../monitors.mjs";
|
|
4
4
|
export * from "./inject.mjs";
|
|
5
|
-
const DYNAMIC_ROUTE_REG = /\/:./;
|
|
6
|
-
function escapeRegexSpecialChars(path) {
|
|
7
|
-
return path.replace(/[()[\]{}*+?.^$|\\]/g, '\\$&');
|
|
8
|
-
}
|
|
9
5
|
const renderPlugin = ()=>({
|
|
10
6
|
name: '@modern-js/plugin-render',
|
|
11
7
|
setup (api) {
|
|
@@ -19,9 +15,7 @@ const renderPlugin = ()=>({
|
|
|
19
15
|
});
|
|
20
16
|
for (const route of pageRoutes){
|
|
21
17
|
const { urlPath: originUrlPath } = route;
|
|
22
|
-
const
|
|
23
|
-
const escapedPath = isDynamic ? originUrlPath : escapeRegexSpecialChars(originUrlPath);
|
|
24
|
-
const urlPath = escapedPath.endsWith('/') ? `${escapedPath}*` : `${escapedPath}/*`;
|
|
18
|
+
const urlPath = originUrlPath.endsWith('/') ? `${originUrlPath}*` : `${originUrlPath}/*`;
|
|
25
19
|
renderMiddlewares?.forEach((m)=>{
|
|
26
20
|
middlewares.push({
|
|
27
21
|
name: m.name,
|
|
@@ -61,11 +61,11 @@ function getHeadersWithoutCookie(headers) {
|
|
|
61
61
|
}
|
|
62
62
|
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config }) {
|
|
63
63
|
const router = getRouter(routes);
|
|
64
|
+
const framework = cutNameByHyphen(metaName || 'modern-js');
|
|
65
|
+
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
64
66
|
return async (req, { monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext, contextForceCSR, reporter })=>{
|
|
65
67
|
const forMatchpathname = matchPathname ?? getPathname(req);
|
|
66
68
|
const [routeInfo, params] = matchRoute(router, forMatchpathname, matchEntryName);
|
|
67
|
-
const framework = cutNameByHyphen(metaName || 'modern-js');
|
|
68
|
-
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
69
69
|
let fallbackReason = null;
|
|
70
70
|
const fallbackWrapper = (reason)=>{
|
|
71
71
|
fallbackReason = reason;
|
|
@@ -91,9 +91,8 @@ async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig
|
|
|
91
91
|
});
|
|
92
92
|
const finalForceCSR = routeInfo.entryName ? forceCSRMap?.get(routeInfo.entryName) ?? forceCSR : forceCSR;
|
|
93
93
|
const renderMode = await getRenderMode(req, fallbackHeader, routeInfo.isSSR, finalForceCSR, nodeReq, fallbackWrapper, contextForceCSR);
|
|
94
|
-
const headerData = parseHeaders(req);
|
|
95
94
|
const onError = (e, key)=>{
|
|
96
|
-
monitors?.error(`SSR Error - ${key || (e instanceof Error ? e.name : e)}, error = %s, req.url = %s, req.headers = %o`, e instanceof Error ? e.stack || e.message : e, forMatchpathname, getHeadersWithoutCookie(
|
|
95
|
+
monitors?.error(`SSR Error - ${key || (e instanceof Error ? e.name : e)}, error = %s, req.url = %s, req.headers = %o`, e instanceof Error ? e.stack || e.message : e, forMatchpathname, getHeadersWithoutCookie(parseHeaders(req)));
|
|
97
96
|
};
|
|
98
97
|
const onTiming = (name, dur)=>{
|
|
99
98
|
monitors?.timing(name, dur, 'SSR');
|
|
@@ -103,6 +103,15 @@ function shouldUseCache(request) {
|
|
|
103
103
|
const hasLoaderQuery = url.searchParams.has('__loader');
|
|
104
104
|
return !(hasRSCAction || hasRSCTree || hasLoaderQuery);
|
|
105
105
|
}
|
|
106
|
+
const cacheKeyRegExps = new Map();
|
|
107
|
+
function getCacheKeyRegExp(key) {
|
|
108
|
+
let regExp = cacheKeyRegExps.get(key);
|
|
109
|
+
if (!regExp) {
|
|
110
|
+
regExp = new RegExp(key);
|
|
111
|
+
cacheKeyRegExps.set(key, regExp);
|
|
112
|
+
}
|
|
113
|
+
return regExp;
|
|
114
|
+
}
|
|
106
115
|
function matchCacheControl(cacheOption, req) {
|
|
107
116
|
if (!cacheOption || !req) return;
|
|
108
117
|
else {
|
|
@@ -110,7 +119,7 @@ function matchCacheControl(cacheOption, req) {
|
|
|
110
119
|
if (isCacheOptionProvider(cacheOption)) return cacheOption(req);
|
|
111
120
|
const url = req.url;
|
|
112
121
|
const options = Object.entries(cacheOption);
|
|
113
|
-
for (const [key, option] of options)if ('*' === key ||
|
|
122
|
+
for (const [key, option] of options)if ('*' === key || getCacheKeyRegExp(key).test(url)) if ('function' == typeof option) return option(req);
|
|
114
123
|
else return option;
|
|
115
124
|
return;
|
|
116
125
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MAIN_ENTRY_NAME } from "@modern-js/utils/universal/constants";
|
|
2
2
|
import { X_MODERNJS_RENDER } from "../../constants.mjs";
|
|
3
|
-
import { getPathname
|
|
3
|
+
import { getPathname } from "../../utils/index.mjs";
|
|
4
4
|
import { getCacheResult, matchCacheControl, shouldUseCache } from "./ssrCache.mjs";
|
|
5
5
|
import { createRequestHandlerConfig } from "./utils.mjs";
|
|
6
6
|
const SERVER_RUNTIME_ENTRY = 'requestHandler';
|
|
@@ -8,10 +8,6 @@ async function ssrRender(request, { routeInfo, html, config: userConfig, staticG
|
|
|
8
8
|
const { entryName } = routeInfo;
|
|
9
9
|
const loadableStats = serverManifest.loadableStats || {};
|
|
10
10
|
const routeManifest = serverManifest.routeManifest || {};
|
|
11
|
-
const headers = parseHeaders(request);
|
|
12
|
-
if (nodeReq) {
|
|
13
|
-
for(const key in nodeReq.headers)if (!headers[key]) headers[key] = nodeReq.headers[key];
|
|
14
|
-
}
|
|
15
11
|
const renderBundle = serverManifest.renderBundles?.[entryName || MAIN_ENTRY_NAME];
|
|
16
12
|
if (!renderBundle) throw new Error(`Can't find renderBundle ${entryName || MAIN_ENTRY_NAME}`);
|
|
17
13
|
const requestHandler = await renderBundle[SERVER_RUNTIME_ENTRY];
|
package/dist/esm/serverBase.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fileReader } from "@modern-js/runtime-utils/fileReader";
|
|
1
2
|
import { server as server_server } from "@modern-js/plugin/server";
|
|
2
3
|
import { Hono } from "hono";
|
|
3
4
|
import { run } from "./context.mjs";
|
|
@@ -78,6 +79,7 @@ class ServerBase {
|
|
|
78
79
|
}
|
|
79
80
|
constructor(options){
|
|
80
81
|
_class_private_method_init(this, _applyMiddlewares);
|
|
82
|
+
this.fileReader = fileReader;
|
|
81
83
|
this.plugins = [];
|
|
82
84
|
this.serverContext = null;
|
|
83
85
|
this.serverOptions = options;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { ServerResponse } from "node:http";
|
|
3
|
-
import { Readable
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
4
|
import cloneable_readable from "cloneable-readable";
|
|
5
5
|
import { isResFinalized } from "./helper/index.mjs";
|
|
6
6
|
const createWebRequest = (req, res, body)=>{
|
|
@@ -66,6 +66,67 @@ const createWebRequest = (req, res, body)=>{
|
|
|
66
66
|
}
|
|
67
67
|
return originalRequest;
|
|
68
68
|
};
|
|
69
|
+
const noop = ()=>{};
|
|
70
|
+
const isDestroyed = (res)=>'stream' in res ? res.stream.destroyed : res.destroyed;
|
|
71
|
+
const createAbortError = ()=>Object.assign(new Error('The operation was aborted'), {
|
|
72
|
+
name: 'AbortError',
|
|
73
|
+
code: 'ABORT_ERR'
|
|
74
|
+
});
|
|
75
|
+
const writeBody = async (body, res)=>{
|
|
76
|
+
const reader = body.getReader();
|
|
77
|
+
let abortError;
|
|
78
|
+
let cancelled;
|
|
79
|
+
let resume;
|
|
80
|
+
let waitsForDrain = false;
|
|
81
|
+
const onDrain = ()=>resume?.();
|
|
82
|
+
const abort = (error)=>{
|
|
83
|
+
if (cancelled) return;
|
|
84
|
+
abortError = error;
|
|
85
|
+
cancelled = reader.cancel(error);
|
|
86
|
+
cancelled.catch(noop);
|
|
87
|
+
resume?.();
|
|
88
|
+
};
|
|
89
|
+
const onClose = ()=>abort(createAbortError());
|
|
90
|
+
res.on('close', onClose);
|
|
91
|
+
res.on('error', abort);
|
|
92
|
+
try {
|
|
93
|
+
if (isDestroyed(res)) onClose();
|
|
94
|
+
while(!cancelled){
|
|
95
|
+
let result;
|
|
96
|
+
try {
|
|
97
|
+
result = await reader.read();
|
|
98
|
+
} catch (error) {
|
|
99
|
+
res.destroy(error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
if (result.done || cancelled) break;
|
|
103
|
+
try {
|
|
104
|
+
if (!res.write(result.value) && !cancelled) {
|
|
105
|
+
if (!waitsForDrain) {
|
|
106
|
+
waitsForDrain = true;
|
|
107
|
+
res.on('drain', onDrain);
|
|
108
|
+
}
|
|
109
|
+
await new Promise((resolve)=>{
|
|
110
|
+
resume = resolve;
|
|
111
|
+
});
|
|
112
|
+
resume = void 0;
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
abort(error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (cancelled) {
|
|
119
|
+
await cancelled;
|
|
120
|
+
throw abortError;
|
|
121
|
+
}
|
|
122
|
+
res.end();
|
|
123
|
+
} finally{
|
|
124
|
+
res.off('close', onClose);
|
|
125
|
+
res.off('error', abort);
|
|
126
|
+
if (waitsForDrain) res.off('drain', onDrain);
|
|
127
|
+
reader.releaseLock();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
69
130
|
const sendResponse = async (response, res)=>{
|
|
70
131
|
res.statusMessage = response.statusText;
|
|
71
132
|
res.statusCode = response.status;
|
|
@@ -74,10 +135,8 @@ const sendResponse = async (response, res)=>{
|
|
|
74
135
|
else res.setHeader(key, value);
|
|
75
136
|
if (cookies.length > 0) res.setHeader('set-cookie', cookies);
|
|
76
137
|
if (response.headers.get('Content-Type')?.match(/text\/event-stream/i) && res instanceof ServerResponse) res.flushHeaders();
|
|
77
|
-
if (response.body)
|
|
78
|
-
|
|
79
|
-
await response.body.pipeTo(writable);
|
|
80
|
-
} else res.end();
|
|
138
|
+
if (response.body) await writeBody(response.body, res);
|
|
139
|
+
else res.end();
|
|
81
140
|
};
|
|
82
141
|
const handleResponseError = (e, res)=>{
|
|
83
142
|
const err = e instanceof Error ? e : new Error('unknown error', {
|
|
@@ -26,8 +26,9 @@ const serverStaticPlugin = ()=>({
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
function createPublicMiddleware({ pwd, routes }) {
|
|
29
|
+
const publicRoutes = routes.filter((route)=>!route.isSSR && route.entryPath?.startsWith('public')).sort(sortRoutes);
|
|
29
30
|
return async (c, next)=>{
|
|
30
|
-
const route = matchPublicRoute(c.req,
|
|
31
|
+
const route = matchPublicRoute(c.req, publicRoutes);
|
|
31
32
|
if (route) {
|
|
32
33
|
const { entryPath } = route;
|
|
33
34
|
const filename = path.join(pwd, entryPath);
|
|
@@ -45,8 +46,8 @@ function createPublicMiddleware({ pwd, routes }) {
|
|
|
45
46
|
return await next();
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
|
-
function matchPublicRoute(req,
|
|
49
|
-
|
|
49
|
+
function matchPublicRoute(req, publicRoutes) {
|
|
50
|
+
return publicRoutes.find((route)=>req.path.startsWith(route.urlPath));
|
|
50
51
|
}
|
|
51
52
|
const isPathInside = (target, root)=>{
|
|
52
53
|
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
@@ -91,26 +92,26 @@ function createStaticMiddleware(options) {
|
|
|
91
92
|
...staticReg,
|
|
92
93
|
...iconReg
|
|
93
94
|
].join('|')})`);
|
|
95
|
+
const publicMiddleware = createPublicMiddleware({
|
|
96
|
+
pwd,
|
|
97
|
+
routes: routes || []
|
|
98
|
+
});
|
|
94
99
|
return async (c, next)=>{
|
|
95
100
|
const pageRoute = c.get('route');
|
|
96
101
|
const pathname = c.req.path;
|
|
97
102
|
if (pageRoute && '' === path.extname(pathname)) return next();
|
|
98
103
|
const hit = staticPathRegExp.test(pathname);
|
|
99
|
-
if (!hit) return
|
|
100
|
-
pwd,
|
|
101
|
-
routes: routes || []
|
|
102
|
-
})(c, next);
|
|
104
|
+
if (!hit) return publicMiddleware(c, next);
|
|
103
105
|
else {
|
|
104
106
|
const filepath = path.join(pwd, pathname.replace(pathPrefix, ()=>''));
|
|
105
107
|
if (!isPathInside(filepath, pwd)) return next();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (mimeType) c.header('Content-Type', mimeType);
|
|
109
|
-
const stat = await fs.lstat(filepath);
|
|
110
|
-
const { size } = stat;
|
|
108
|
+
const stat = await fs.stat(filepath).catch(()=>null);
|
|
109
|
+
if (!stat?.isFile()) return next();
|
|
111
110
|
const chunk = await fileReader.readFileFromSystem(filepath, 'buffer');
|
|
112
|
-
c.header('Content-Length', String(size));
|
|
113
111
|
if (null === chunk) return next();
|
|
112
|
+
const mimeType = getMimeType(filepath);
|
|
113
|
+
if (mimeType) c.header('Content-Type', mimeType);
|
|
114
|
+
c.header('Content-Length', String(chunk.byteLength));
|
|
114
115
|
const body = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
115
116
|
return c.body(body, 200);
|
|
116
117
|
}
|
|
@@ -124,13 +124,10 @@ const injectServerTiming = ()=>({
|
|
|
124
124
|
const monitors = c.get('monitors');
|
|
125
125
|
monitors?.push(timingMonitor);
|
|
126
126
|
await next();
|
|
127
|
-
serverTimings.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
c.header(SERVER_TIMING, value, {
|
|
132
|
-
append: true
|
|
133
|
-
});
|
|
127
|
+
if (0 === serverTimings.length) return;
|
|
128
|
+
const value = serverTimings.map(({ name, desc, dur })=>`bd-${metaName}-${name};${desc ? `decs="${desc}";` : ''} dur=${dur}`).join(', ');
|
|
129
|
+
c.header(SERVER_TIMING, value, {
|
|
130
|
+
append: true
|
|
134
131
|
});
|
|
135
132
|
}
|
|
136
133
|
});
|
|
@@ -3,10 +3,6 @@ import { getLoaderCtx } from "../../helper.mjs";
|
|
|
3
3
|
import { sortRoutes } from "../../utils/index.mjs";
|
|
4
4
|
import { requestLatencyMiddleware } from "../monitors.mjs";
|
|
5
5
|
export * from "./inject.mjs";
|
|
6
|
-
const DYNAMIC_ROUTE_REG = /\/:./;
|
|
7
|
-
function escapeRegexSpecialChars(path) {
|
|
8
|
-
return path.replace(/[()[\]{}*+?.^$|\\]/g, '\\$&');
|
|
9
|
-
}
|
|
10
6
|
const renderPlugin = ()=>({
|
|
11
7
|
name: '@modern-js/plugin-render',
|
|
12
8
|
setup (api) {
|
|
@@ -20,9 +16,7 @@ const renderPlugin = ()=>({
|
|
|
20
16
|
});
|
|
21
17
|
for (const route of pageRoutes){
|
|
22
18
|
const { urlPath: originUrlPath } = route;
|
|
23
|
-
const
|
|
24
|
-
const escapedPath = isDynamic ? originUrlPath : escapeRegexSpecialChars(originUrlPath);
|
|
25
|
-
const urlPath = escapedPath.endsWith('/') ? `${escapedPath}*` : `${escapedPath}/*`;
|
|
19
|
+
const urlPath = originUrlPath.endsWith('/') ? `${originUrlPath}*` : `${originUrlPath}/*`;
|
|
26
20
|
renderMiddlewares?.forEach((m)=>{
|
|
27
21
|
middlewares.push({
|
|
28
22
|
name: m.name,
|
|
@@ -64,11 +64,11 @@ function getHeadersWithoutCookie(headers) {
|
|
|
64
64
|
}
|
|
65
65
|
async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig, forceCSR, forceCSRMap, config }) {
|
|
66
66
|
const router = getRouter(routes);
|
|
67
|
+
const framework = cutNameByHyphen(metaName || 'modern-js');
|
|
68
|
+
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
67
69
|
return async (req, { monitors, nodeReq, templates, serverManifest, rscClientManifest, rscSSRManifest, rscServerManifest, locals, matchEntryName, matchPathname, loaderContext, contextForceCSR, reporter })=>{
|
|
68
70
|
const forMatchpathname = matchPathname ?? getPathname(req);
|
|
69
71
|
const [routeInfo, params] = matchRoute(router, forMatchpathname, matchEntryName);
|
|
70
|
-
const framework = cutNameByHyphen(metaName || 'modern-js');
|
|
71
|
-
const fallbackHeader = `x-${framework}-ssr-fallback`;
|
|
72
72
|
let fallbackReason = null;
|
|
73
73
|
const fallbackWrapper = (reason)=>{
|
|
74
74
|
fallbackReason = reason;
|
|
@@ -94,9 +94,8 @@ async function createRender({ routes, pwd, metaName, staticGenerate, cacheConfig
|
|
|
94
94
|
});
|
|
95
95
|
const finalForceCSR = routeInfo.entryName ? forceCSRMap?.get(routeInfo.entryName) ?? forceCSR : forceCSR;
|
|
96
96
|
const renderMode = await getRenderMode(req, fallbackHeader, routeInfo.isSSR, finalForceCSR, nodeReq, fallbackWrapper, contextForceCSR);
|
|
97
|
-
const headerData = parseHeaders(req);
|
|
98
97
|
const onError = (e, key)=>{
|
|
99
|
-
monitors?.error(`SSR Error - ${key || (e instanceof Error ? e.name : e)}, error = %s, req.url = %s, req.headers = %o`, e instanceof Error ? e.stack || e.message : e, forMatchpathname, getHeadersWithoutCookie(
|
|
98
|
+
monitors?.error(`SSR Error - ${key || (e instanceof Error ? e.name : e)}, error = %s, req.url = %s, req.headers = %o`, e instanceof Error ? e.stack || e.message : e, forMatchpathname, getHeadersWithoutCookie(parseHeaders(req)));
|
|
100
99
|
};
|
|
101
100
|
const onTiming = (name, dur)=>{
|
|
102
101
|
monitors?.timing(name, dur, 'SSR');
|
|
@@ -104,6 +104,15 @@ function shouldUseCache(request) {
|
|
|
104
104
|
const hasLoaderQuery = url.searchParams.has('__loader');
|
|
105
105
|
return !(hasRSCAction || hasRSCTree || hasLoaderQuery);
|
|
106
106
|
}
|
|
107
|
+
const cacheKeyRegExps = new Map();
|
|
108
|
+
function getCacheKeyRegExp(key) {
|
|
109
|
+
let regExp = cacheKeyRegExps.get(key);
|
|
110
|
+
if (!regExp) {
|
|
111
|
+
regExp = new RegExp(key);
|
|
112
|
+
cacheKeyRegExps.set(key, regExp);
|
|
113
|
+
}
|
|
114
|
+
return regExp;
|
|
115
|
+
}
|
|
107
116
|
function matchCacheControl(cacheOption, req) {
|
|
108
117
|
if (!cacheOption || !req) return;
|
|
109
118
|
else {
|
|
@@ -111,7 +120,7 @@ function matchCacheControl(cacheOption, req) {
|
|
|
111
120
|
if (isCacheOptionProvider(cacheOption)) return cacheOption(req);
|
|
112
121
|
const url = req.url;
|
|
113
122
|
const options = Object.entries(cacheOption);
|
|
114
|
-
for (const [key, option] of options)if ('*' === key ||
|
|
123
|
+
for (const [key, option] of options)if ('*' === key || getCacheKeyRegExp(key).test(url)) if ('function' == typeof option) return option(req);
|
|
115
124
|
else return option;
|
|
116
125
|
return;
|
|
117
126
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { MAIN_ENTRY_NAME } from "@modern-js/utils/universal/constants";
|
|
3
3
|
import { X_MODERNJS_RENDER } from "../../constants.mjs";
|
|
4
|
-
import { getPathname
|
|
4
|
+
import { getPathname } from "../../utils/index.mjs";
|
|
5
5
|
import { getCacheResult, matchCacheControl, shouldUseCache } from "./ssrCache.mjs";
|
|
6
6
|
import { createRequestHandlerConfig } from "./utils.mjs";
|
|
7
7
|
const SERVER_RUNTIME_ENTRY = 'requestHandler';
|
|
@@ -9,10 +9,6 @@ async function ssrRender(request, { routeInfo, html, config: userConfig, staticG
|
|
|
9
9
|
const { entryName } = routeInfo;
|
|
10
10
|
const loadableStats = serverManifest.loadableStats || {};
|
|
11
11
|
const routeManifest = serverManifest.routeManifest || {};
|
|
12
|
-
const headers = parseHeaders(request);
|
|
13
|
-
if (nodeReq) {
|
|
14
|
-
for(const key in nodeReq.headers)if (!headers[key]) headers[key] = nodeReq.headers[key];
|
|
15
|
-
}
|
|
16
12
|
const renderBundle = serverManifest.renderBundles?.[entryName || MAIN_ENTRY_NAME];
|
|
17
13
|
if (!renderBundle) throw new Error(`Can't find renderBundle ${entryName || MAIN_ENTRY_NAME}`);
|
|
18
14
|
const requestHandler = await renderBundle[SERVER_RUNTIME_ENTRY];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "node:module";
|
|
2
|
+
import { fileReader } from "@modern-js/runtime-utils/fileReader";
|
|
2
3
|
import { server as server_server } from "@modern-js/plugin/server";
|
|
3
4
|
import { Hono } from "hono";
|
|
4
5
|
import { run } from "./context.mjs";
|
|
@@ -79,6 +80,7 @@ class ServerBase {
|
|
|
79
80
|
}
|
|
80
81
|
constructor(options){
|
|
81
82
|
_class_private_method_init(this, _applyMiddlewares);
|
|
83
|
+
this.fileReader = fileReader;
|
|
82
84
|
this.plugins = [];
|
|
83
85
|
this.serverContext = null;
|
|
84
86
|
this.serverOptions = options;
|
|
@@ -9,6 +9,7 @@ export interface ServerBaseOptions extends ServerCreateOptions {
|
|
|
9
9
|
}
|
|
10
10
|
export declare class ServerBase<E extends Env = any> {
|
|
11
11
|
#private;
|
|
12
|
+
readonly fileReader: import("@modern-js/runtime-utils/fileReader").FileReader;
|
|
12
13
|
serverOptions: ServerBaseOptions;
|
|
13
14
|
private app;
|
|
14
15
|
private plugins;
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"modern",
|
|
16
16
|
"modern.js"
|
|
17
17
|
],
|
|
18
|
-
"version": "3.10.
|
|
18
|
+
"version": "3.10.2",
|
|
19
19
|
"types": "./dist/types/index.d.ts",
|
|
20
20
|
"main": "./dist/cjs/index.js",
|
|
21
21
|
"exports": {
|
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
"flatted": "^3.4.4",
|
|
72
72
|
"hono": "^4.11.7",
|
|
73
73
|
"ts-deepmerge": "8.0.0",
|
|
74
|
-
"@modern-js/plugin": "3.10.
|
|
75
|
-
"@modern-js/runtime-utils": "3.10.
|
|
76
|
-
"@modern-js/utils": "3.10.
|
|
74
|
+
"@modern-js/plugin": "3.10.2",
|
|
75
|
+
"@modern-js/runtime-utils": "3.10.2",
|
|
76
|
+
"@modern-js/utils": "3.10.2"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@types/cloneable-readable": "^2.0.3",
|
|
@@ -81,9 +81,9 @@
|
|
|
81
81
|
"@types/node": "^20",
|
|
82
82
|
"http-proxy-middleware": "^2.0.10",
|
|
83
83
|
"typescript": "^5",
|
|
84
|
-
"@modern-js/rslib": "
|
|
85
|
-
"@modern-js/types": "3.10.
|
|
86
|
-
"@scripts/rstest-config": "2.66.
|
|
84
|
+
"@modern-js/rslib": "3.10.2",
|
|
85
|
+
"@modern-js/types": "3.10.2",
|
|
86
|
+
"@scripts/rstest-config": "2.66.1"
|
|
87
87
|
},
|
|
88
88
|
"sideEffects": false,
|
|
89
89
|
"publishConfig": {
|