@reckona/mreact-router 0.0.15 → 0.0.17
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/README.md +72 -5
- package/dist/adapters/aws-lambda.d.ts +10 -0
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +50 -9
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/node.d.ts +3 -0
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +1 -0
- package/dist/adapters/node.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +35 -12
- package/dist/build.js.map +1 -1
- package/dist/cache-config.d.ts +3 -0
- package/dist/cache-config.d.ts.map +1 -0
- package/dist/cache-config.js +15 -0
- package/dist/cache-config.js.map +1 -0
- package/dist/cache-stats.d.ts +17 -0
- package/dist/cache-stats.d.ts.map +1 -0
- package/dist/cache-stats.js +28 -0
- package/dist/cache-stats.js.map +1 -0
- package/dist/cli-options.d.ts +2 -1
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +21 -0
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +151 -2
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/csp.d.ts +3 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +1 -1
- package/dist/csp.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +8 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/module-runner.d.ts +2 -0
- package/dist/module-runner.d.ts.map +1 -1
- package/dist/module-runner.js +18 -7
- package/dist/module-runner.js.map +1 -1
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +14 -4
- package/dist/navigation.js.map +1 -1
- package/dist/render.d.ts +26 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +530 -60
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +1 -0
- package/dist/route-source.js.map +1 -1
- package/dist/runtime-cache.d.ts +4 -0
- package/dist/runtime-cache.d.ts.map +1 -0
- package/dist/runtime-cache.js +6 -0
- package/dist/runtime-cache.js.map +1 -0
- package/dist/serve.d.ts +9 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +72 -12
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +7 -2
- package/dist/session.js.map +1 -1
- package/dist/trace.d.ts +41 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +46 -0
- package/dist/trace.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +8 -8
package/dist/render.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
import { access, readFile, stat } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, relative, sep } from "node:path";
|
|
5
5
|
import { transform, } from "@reckona/mreact-compiler";
|
|
6
6
|
import { createQueryClient, dehydrate, __MREACT_QUERY_STATE_SCRIPT_ID, runWithQueryClient, } from "@reckona/mreact-query";
|
|
7
7
|
import { build as bundle } from "esbuild";
|
|
8
8
|
import { createStringSink, renderAsyncBoundary, renderOutOfOrderReorderScript, renderToReadableStream, } from "@reckona/mreact-server";
|
|
9
|
-
import { hydrationMarkerParts, inferClientRouteModule, withHydrationMarkers, withRouteMarkers, } from "./client.js";
|
|
9
|
+
import { hydrationMarkerParts, inferClientRouteModule, routeIdForPath, withHydrationMarkers, withRouteMarkers, } from "./client.js";
|
|
10
10
|
import { assetPath } from "./assets.js";
|
|
11
11
|
import { escapeHtmlAttribute, escapeHtmlText as escapeHtml, } from "@reckona/mreact-shared/html-escape";
|
|
12
12
|
import { matchRoute, scanAppRoutes } from "./routes.js";
|
|
13
13
|
import { dispatchServerActionRequest, prepareRouteServerActions, serverActionCookie, } from "./actions.js";
|
|
14
14
|
import { beginRouteCacheContext, cachedRouteResponse, cacheRouteResponse, routeCacheKey, routeCachePolicyFromSource, } from "./cache.js";
|
|
15
|
+
import { resolveRouterCacheLimit } from "./cache-config.js";
|
|
15
16
|
import { importAppRouterFileModule, importAppRouterSourceModule } from "./module-runner.js";
|
|
16
17
|
import { contentSecurityPolicy } from "./csp.js";
|
|
17
18
|
import { htmlResponse } from "./http.js";
|
|
18
19
|
import { isNotFoundError, isRedirectError, rewriteLocation } from "./navigation.js";
|
|
19
20
|
import { createAppRouterImportPolicyPlugin } from "./import-policy.js";
|
|
20
21
|
import { hasLoaderExport, isStreamRouteSource, stripRouteModuleExports } from "./route-source.js";
|
|
22
|
+
import { emitRouterLog, logDurationMs, logNow } from "./logger.js";
|
|
23
|
+
import { createRouterRuntimeCacheCounters, readRouterRuntimeCacheEntry, routerRuntimeCacheStat, } from "./cache-stats.js";
|
|
24
|
+
import { invokeRouterInstrumentation, traceContextFromRequest, } from "./trace.js";
|
|
21
25
|
const nativeEscapeTransform = {
|
|
22
26
|
batchImportName: "escapeHtmlBatch",
|
|
23
27
|
batchImportSource: "@reckona/mreact-router/native-escape",
|
|
@@ -52,6 +56,18 @@ export async function preloadBuiltRequestModules(options) {
|
|
|
52
56
|
});
|
|
53
57
|
continue;
|
|
54
58
|
}
|
|
59
|
+
const analysis = await analyzeRouteSource({
|
|
60
|
+
code,
|
|
61
|
+
filename: route.file,
|
|
62
|
+
routePath: route.path,
|
|
63
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
64
|
+
});
|
|
65
|
+
await preloadBuiltPageRouteModules({
|
|
66
|
+
...options,
|
|
67
|
+
analysis,
|
|
68
|
+
code,
|
|
69
|
+
file: route.file,
|
|
70
|
+
});
|
|
55
71
|
if (hasLoaderExport(code)) {
|
|
56
72
|
await loadRouteLoaderModule({
|
|
57
73
|
appDir: options.appDir,
|
|
@@ -64,22 +80,103 @@ export async function preloadBuiltRequestModules(options) {
|
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
}
|
|
83
|
+
async function preloadBuiltPageRouteModules(options) {
|
|
84
|
+
const routeCode = options.analysis.routeCode;
|
|
85
|
+
const stringOutput = transformServerModule({
|
|
86
|
+
code: routeCode,
|
|
87
|
+
clientBoundaryImports: options.analysis.clientInference.clientBoundaryImports,
|
|
88
|
+
filename: options.file,
|
|
89
|
+
serverModules: options.serverModules,
|
|
90
|
+
serverOutput: "string",
|
|
91
|
+
});
|
|
92
|
+
assertNoFatalServerDiagnostics(stringOutput.diagnostics);
|
|
93
|
+
await loadServerModule(stringOutput.code, options.file, options.serverModules, options.serverModuleCacheVersion);
|
|
94
|
+
if (options.analysis.streamRoute) {
|
|
95
|
+
const streamOutput = transformServerModule({
|
|
96
|
+
code: routeCode,
|
|
97
|
+
clientBoundaryImports: options.analysis.clientInference.clientBoundaryImports,
|
|
98
|
+
filename: options.file,
|
|
99
|
+
serverModules: options.serverModules,
|
|
100
|
+
serverOutput: "stream",
|
|
101
|
+
serverAwaitHydration: options.analysis.clientInference.client,
|
|
102
|
+
});
|
|
103
|
+
assertNoFatalServerDiagnostics(streamOutput.diagnostics);
|
|
104
|
+
await loadServerStreamModule(streamOutput.code, options.file, options.serverModules, options.serverModuleCacheVersion);
|
|
105
|
+
}
|
|
106
|
+
await preloadShellModulesForPage({
|
|
107
|
+
appDir: options.appDir,
|
|
108
|
+
pageFile: options.file,
|
|
109
|
+
serverModules: options.serverModules,
|
|
110
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
111
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
112
|
+
});
|
|
113
|
+
await loadComposedRouteMetadata({
|
|
114
|
+
appDir: options.appDir,
|
|
115
|
+
code: options.code,
|
|
116
|
+
filename: options.file,
|
|
117
|
+
importPolicy: options.importPolicy,
|
|
118
|
+
serverModules: options.serverModules,
|
|
119
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
120
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async function preloadShellModulesForPage(options) {
|
|
124
|
+
const shellFiles = await shellFilesForPage(options.appDir, options.pageFile, options.serverModuleCacheVersion);
|
|
125
|
+
for (const shell of shellFiles) {
|
|
126
|
+
const code = await readServerSourceFile(shell.file, options.serverModuleCacheVersion, options.serverSourceFiles);
|
|
127
|
+
const output = transformServerModule({
|
|
128
|
+
code,
|
|
129
|
+
filename: shell.file,
|
|
130
|
+
serverModules: options.serverModules,
|
|
131
|
+
serverOutput: "string",
|
|
132
|
+
});
|
|
133
|
+
assertNoFatalServerDiagnostics(output.diagnostics);
|
|
134
|
+
await loadServerModule(output.code, shell.file, options.serverModules, options.serverModuleCacheVersion);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function assertNoFatalServerDiagnostics(diagnostics) {
|
|
138
|
+
const fatalDiagnostics = diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
139
|
+
if (fatalDiagnostics.length > 0) {
|
|
140
|
+
throw new Error(fatalDiagnostics.map((diagnostic) => diagnostic.message).join("\n"));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
67
143
|
const serverTransformCache = new Map();
|
|
144
|
+
const serverTransformCacheCounters = createRouterRuntimeCacheCounters();
|
|
68
145
|
const serverSourceFileCache = new Map();
|
|
146
|
+
const serverSourceFileCacheCounters = createRouterRuntimeCacheCounters();
|
|
69
147
|
const routeSourceAnalysisCache = new Map();
|
|
148
|
+
const routeSourceAnalysisCacheCounters = createRouterRuntimeCacheCounters();
|
|
70
149
|
const routeOutOfOrderBoundaryAnalysisCache = new Map();
|
|
150
|
+
const routeOutOfOrderBoundaryAnalysisCacheCounters = createRouterRuntimeCacheCounters();
|
|
71
151
|
const routeLoaderModuleCache = new Map();
|
|
152
|
+
const routeLoaderModuleCacheCounters = createRouterRuntimeCacheCounters();
|
|
72
153
|
const middlewareModuleCache = new Map();
|
|
154
|
+
const middlewareModuleCacheCounters = createRouterRuntimeCacheCounters();
|
|
73
155
|
const serverRouteModuleCache = new Map();
|
|
156
|
+
const serverRouteModuleCacheCounters = createRouterRuntimeCacheCounters();
|
|
74
157
|
const composedRouteMetadataCache = new Map();
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
158
|
+
const composedRouteMetadataCacheCounters = createRouterRuntimeCacheCounters();
|
|
159
|
+
const maxServerTransformCacheEntries = resolveRouterCacheLimit("SERVER_TRANSFORM", 512);
|
|
160
|
+
const maxServerSourceFileCacheEntries = resolveRouterCacheLimit("SERVER_SOURCE_FILE", 512);
|
|
161
|
+
const maxRouteSourceAnalysisCacheEntries = resolveRouterCacheLimit("ROUTE_SOURCE_ANALYSIS", 512);
|
|
162
|
+
const maxRouteOutOfOrderBoundaryAnalysisCacheEntries = resolveRouterCacheLimit("ROUTE_OUT_OF_ORDER_BOUNDARY_ANALYSIS", 512);
|
|
163
|
+
const maxRouteLoaderModuleCacheEntries = resolveRouterCacheLimit("ROUTE_LOADER_MODULE", 512);
|
|
164
|
+
const maxMiddlewareModuleCacheEntries = resolveRouterCacheLimit("MIDDLEWARE_MODULE", 64);
|
|
165
|
+
const maxServerRouteModuleCacheEntries = resolveRouterCacheLimit("SERVER_ROUTE_MODULE", 512);
|
|
166
|
+
const maxComposedRouteMetadataCacheEntries = resolveRouterCacheLimit("COMPOSED_ROUTE_METADATA", 512);
|
|
167
|
+
export function routerRenderRuntimeCacheStats() {
|
|
168
|
+
return [
|
|
169
|
+
routerRuntimeCacheStat("server-transform", serverTransformCache, maxServerTransformCacheEntries, serverTransformCacheCounters),
|
|
170
|
+
routerRuntimeCacheStat("server-source-file", serverSourceFileCache, maxServerSourceFileCacheEntries, serverSourceFileCacheCounters),
|
|
171
|
+
routerRuntimeCacheStat("route-source-analysis", routeSourceAnalysisCache, maxRouteSourceAnalysisCacheEntries, routeSourceAnalysisCacheCounters),
|
|
172
|
+
routerRuntimeCacheStat("route-out-of-order-boundary-analysis", routeOutOfOrderBoundaryAnalysisCache, maxRouteOutOfOrderBoundaryAnalysisCacheEntries, routeOutOfOrderBoundaryAnalysisCacheCounters),
|
|
173
|
+
routerRuntimeCacheStat("route-loader-module", routeLoaderModuleCache, maxRouteLoaderModuleCacheEntries, routeLoaderModuleCacheCounters),
|
|
174
|
+
routerRuntimeCacheStat("middleware-module", middlewareModuleCache, maxMiddlewareModuleCacheEntries, middlewareModuleCacheCounters),
|
|
175
|
+
routerRuntimeCacheStat("server-route-module", serverRouteModuleCache, maxServerRouteModuleCacheEntries, serverRouteModuleCacheCounters),
|
|
176
|
+
routerRuntimeCacheStat("composed-route-metadata", composedRouteMetadataCache, maxComposedRouteMetadataCacheEntries, composedRouteMetadataCacheCounters),
|
|
177
|
+
routerRuntimeCacheStat("rendered-shell", renderedShellCache, MAX_RENDERED_SHELL_CACHE_ENTRIES, renderedShellCacheCounters),
|
|
178
|
+
];
|
|
179
|
+
}
|
|
83
180
|
// Issue 086: per-shell prefix/suffix cache. Pure layouts (whose
|
|
84
181
|
// exported component takes zero arguments and therefore cannot
|
|
85
182
|
// depend on the request props) produce the same HTML for every
|
|
@@ -93,38 +190,113 @@ const maxComposedRouteMetadataCacheEntries = 512;
|
|
|
93
190
|
// edits without server restart.
|
|
94
191
|
const renderedShellCache = new Map();
|
|
95
192
|
const MAX_RENDERED_SHELL_CACHE_ENTRIES = 1024;
|
|
193
|
+
const renderedShellCacheCounters = createRouterRuntimeCacheCounters();
|
|
96
194
|
export async function renderAppRequest(options) {
|
|
97
195
|
const authStorage = authRequestStorage();
|
|
98
196
|
if (authStorage.getStore() === undefined) {
|
|
99
197
|
return authStorage.run({}, () => renderAppRequest(options));
|
|
100
198
|
}
|
|
199
|
+
const trace = traceContextFromRequest(options.request);
|
|
200
|
+
const url = new URL(options.request.url);
|
|
201
|
+
const requestEvent = {
|
|
202
|
+
method: options.request.method,
|
|
203
|
+
path: url.pathname,
|
|
204
|
+
request: options.request,
|
|
205
|
+
...(trace === undefined ? {} : { trace }),
|
|
206
|
+
};
|
|
207
|
+
invokeRouterInstrumentation(options.instrumentation?.onRequestStart, requestEvent);
|
|
101
208
|
const response = await renderAppRequestInternal(options);
|
|
209
|
+
invokeRouterInstrumentation(options.instrumentation?.onRequestEnd, {
|
|
210
|
+
...requestEvent,
|
|
211
|
+
status: response.status,
|
|
212
|
+
});
|
|
102
213
|
return applyAppRouterResponseHook(response, options);
|
|
103
214
|
}
|
|
215
|
+
function createRenderTiming(logger) {
|
|
216
|
+
return logger?.debug === undefined ? undefined : { phases: {} };
|
|
217
|
+
}
|
|
218
|
+
function renderTimingPhaseStartedAt(timing) {
|
|
219
|
+
return timing === undefined ? undefined : logNow();
|
|
220
|
+
}
|
|
221
|
+
function finishRenderTimingPhase(timing, startedAt, phaseName) {
|
|
222
|
+
if (timing === undefined || startedAt === undefined) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
timing.phases[phaseName] = logDurationMs(startedAt);
|
|
226
|
+
}
|
|
227
|
+
function emitRenderTiming(options, timing, status) {
|
|
228
|
+
if (timing === undefined) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
emitRouterLog(options.logger, "debug", {
|
|
232
|
+
method: options.request.method,
|
|
233
|
+
path: new URL(options.request.url).pathname,
|
|
234
|
+
phases: timing.phases,
|
|
235
|
+
status,
|
|
236
|
+
type: "router:render:timing",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
export async function resolveAppRouterMiddleware(options) {
|
|
240
|
+
const middlewareResponse = await runMiddleware(options);
|
|
241
|
+
if (middlewareResponse === undefined) {
|
|
242
|
+
return { request: options.request, type: "continue" };
|
|
243
|
+
}
|
|
244
|
+
const location = rewriteLocation(middlewareResponse);
|
|
245
|
+
if (location !== undefined) {
|
|
246
|
+
return {
|
|
247
|
+
request: new Request(new URL(location, options.request.url), options.request),
|
|
248
|
+
type: "continue",
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return { response: middlewareResponse, type: "response" };
|
|
252
|
+
}
|
|
104
253
|
async function renderAppRequestInternal(options) {
|
|
254
|
+
const timing = createRenderTiming(options.logger);
|
|
255
|
+
let phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
105
256
|
const routes = options.routes ?? (await scanAppRoutes({ appDir: options.appDir }));
|
|
257
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeScanMs");
|
|
106
258
|
const url = new URL(options.request.url);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
259
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
260
|
+
const matched = options.routeMatcher?.match(url.pathname) ?? matchRoute(routes, url.pathname);
|
|
261
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeMatchMs");
|
|
262
|
+
const hasMiddleware = options.skipMiddleware === true
|
|
263
|
+
? false
|
|
264
|
+
: await hasAppMiddleware({
|
|
265
|
+
appDir: options.appDir,
|
|
266
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
267
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
268
|
+
});
|
|
269
|
+
const middlewareControl = hasMiddleware && matched?.route.kind === "page"
|
|
270
|
+
? await loadRouteMiddlewareControl({
|
|
271
|
+
appDir: options.appDir,
|
|
272
|
+
pageFile: matched.route.file,
|
|
273
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
274
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
275
|
+
})
|
|
276
|
+
: undefined;
|
|
277
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
278
|
+
const middlewareResult = options.skipMiddleware === true || !hasMiddleware
|
|
279
|
+
? { request: options.request, type: "continue" }
|
|
280
|
+
: await resolveAppRouterMiddleware({
|
|
110
281
|
appDir: options.appDir,
|
|
111
282
|
importPolicy: options.importPolicy,
|
|
283
|
+
instrumentation: options.instrumentation,
|
|
284
|
+
middlewareControl,
|
|
112
285
|
request: options.request,
|
|
113
286
|
serverModules: options.serverModules,
|
|
114
287
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
115
288
|
serverSourceFiles: options.serverSourceFiles,
|
|
116
289
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
return middlewareResponse;
|
|
290
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "middlewareMs");
|
|
291
|
+
if (middlewareResult.type === "response") {
|
|
292
|
+
return middlewareResult.response;
|
|
293
|
+
}
|
|
294
|
+
if (middlewareResult.request !== options.request) {
|
|
295
|
+
return renderAppRequestInternal({
|
|
296
|
+
...options,
|
|
297
|
+
request: middlewareResult.request,
|
|
298
|
+
skipMiddleware: true,
|
|
299
|
+
});
|
|
128
300
|
}
|
|
129
301
|
if (url.pathname === "/_mreact/actions") {
|
|
130
302
|
return dispatchServerActionRequest({
|
|
@@ -138,7 +310,6 @@ async function renderAppRequestInternal(options) {
|
|
|
138
310
|
serverActions: options.serverActions,
|
|
139
311
|
});
|
|
140
312
|
}
|
|
141
|
-
const matched = options.routeMatcher?.match(url.pathname) ?? matchRoute(routes, url.pathname);
|
|
142
313
|
if (matched === undefined) {
|
|
143
314
|
const notFoundFile = await nearestBoundaryFileForPath({
|
|
144
315
|
appDir: options.appDir,
|
|
@@ -150,6 +321,7 @@ async function renderAppRequestInternal(options) {
|
|
|
150
321
|
assetBaseUrl: options.assetBaseUrl,
|
|
151
322
|
error: undefined,
|
|
152
323
|
request: options.request,
|
|
324
|
+
routePath: url.pathname,
|
|
153
325
|
routeFile: notFoundFile,
|
|
154
326
|
routeScripts: options.clientScripts,
|
|
155
327
|
serverModules: options.serverModules,
|
|
@@ -192,35 +364,44 @@ async function renderAppRequestInternal(options) {
|
|
|
192
364
|
}
|
|
193
365
|
routeCacheContext = beginRouteCacheContext(options.routeCache);
|
|
194
366
|
const clientScript = options.clientScripts?.get(matched.route.path);
|
|
367
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
195
368
|
const originalCode = await readServerSourceFile(matched.route.file, options.serverModuleCacheVersion, options.serverSourceFiles);
|
|
369
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "readSourceMs");
|
|
370
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
196
371
|
const originalAnalysis = await analyzeRouteSource({
|
|
197
372
|
code: originalCode,
|
|
198
373
|
filename: matched.route.file,
|
|
199
374
|
routePath: matched.route.path,
|
|
200
375
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
201
376
|
});
|
|
377
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "sourceAnalysisMs");
|
|
202
378
|
const cachePolicy = originalAnalysis.cachePolicy;
|
|
203
379
|
const navigationScript = options.navigationScripts?.get(matched.route.path);
|
|
204
380
|
const cacheKey = routeCacheKey(options.appDir, matched.route.path, url);
|
|
205
381
|
const mayUseRouteCache = cachePolicy === undefined
|
|
206
382
|
? originalAnalysis.usesRuntimeCacheControl
|
|
207
383
|
: cachePolicy.revalidateSeconds !== 0;
|
|
384
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
208
385
|
const cachedResponse = !mayUseRouteCache
|
|
209
386
|
? undefined
|
|
210
387
|
: await cachedRouteResponse({
|
|
211
388
|
cache: options.routeCache,
|
|
212
389
|
key: cacheKey,
|
|
213
390
|
});
|
|
391
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeCacheMs");
|
|
214
392
|
if (cachedResponse !== undefined) {
|
|
215
393
|
return cachedResponse;
|
|
216
394
|
}
|
|
395
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
217
396
|
const preparedActions = await prepareRouteServerActions({
|
|
218
397
|
appDir: options.appDir,
|
|
219
398
|
code: originalCode,
|
|
220
399
|
pageFile: matched.route.file,
|
|
221
400
|
request: options.request,
|
|
222
401
|
});
|
|
402
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "serverActionsMs");
|
|
223
403
|
const code = preparedActions.code;
|
|
404
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
224
405
|
const routeAnalysis = code === originalCode
|
|
225
406
|
? originalAnalysis
|
|
226
407
|
: await analyzeRouteSource({
|
|
@@ -229,25 +410,32 @@ async function renderAppRequestInternal(options) {
|
|
|
229
410
|
routePath: matched.route.path,
|
|
230
411
|
serverModuleCacheVersion: undefined,
|
|
231
412
|
});
|
|
413
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeCodeAnalysisMs");
|
|
232
414
|
const routeCode = routeAnalysis.routeCode;
|
|
233
415
|
const streamRoute = routeAnalysis.streamRoute;
|
|
234
416
|
const clientInference = routeAnalysis.clientInference;
|
|
235
417
|
const clientRoute = clientInference.client;
|
|
418
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
236
419
|
const dataPromise = routeAnalysis.hasLoader
|
|
237
|
-
?
|
|
420
|
+
? loadRouteDataWithInstrumentation({
|
|
421
|
+
appDir: options.appDir,
|
|
238
422
|
code,
|
|
239
423
|
context: {
|
|
240
424
|
params: matched.params,
|
|
241
425
|
queryClient,
|
|
242
426
|
request: options.request,
|
|
243
427
|
},
|
|
244
|
-
appDir: options.appDir,
|
|
245
428
|
filename: matched.route.file,
|
|
246
429
|
importPolicy: options.importPolicy,
|
|
430
|
+
instrumentation: options.instrumentation,
|
|
431
|
+
request: options.request,
|
|
432
|
+
routeId: routeIdForPath(matched.route.path),
|
|
433
|
+
routePath: matched.route.path,
|
|
247
434
|
serverModules: options.serverModules,
|
|
248
435
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
249
436
|
})
|
|
250
437
|
: undefined;
|
|
438
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loaderStartMs");
|
|
251
439
|
recoveryRoute = {
|
|
252
440
|
clientRoute,
|
|
253
441
|
props: {
|
|
@@ -258,22 +446,28 @@ async function renderAppRequestInternal(options) {
|
|
|
258
446
|
script: clientScript,
|
|
259
447
|
};
|
|
260
448
|
if (streamRoute) {
|
|
449
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
261
450
|
const loadingFile = await nearestExistingBoundaryFileForPage({
|
|
262
451
|
appDir: options.appDir,
|
|
263
452
|
filename: "loading.mreact.tsx",
|
|
264
453
|
pageFile: matched.route.file,
|
|
454
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
265
455
|
});
|
|
456
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loadingBoundaryLookupMs");
|
|
266
457
|
const streamShellResponseHeaders = {
|
|
267
458
|
"content-type": "text/html; charset=utf-8",
|
|
268
459
|
"x-mreact-stream": "1",
|
|
269
460
|
};
|
|
461
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
270
462
|
const mayRenderOutOfOrder = await mayRenderOutOfOrderBoundaryDeep({
|
|
271
463
|
code: routeCode,
|
|
272
464
|
filename: matched.route.file,
|
|
273
465
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
274
466
|
serverSourceFiles: options.serverSourceFiles,
|
|
275
467
|
});
|
|
468
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "outOfOrderAnalysisMs");
|
|
276
469
|
if (loadingFile === undefined && !mayRenderOutOfOrder) {
|
|
470
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
277
471
|
const stringOutput = transformServerModule({
|
|
278
472
|
code: routeCode,
|
|
279
473
|
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
@@ -281,6 +475,7 @@ async function renderAppRequestInternal(options) {
|
|
|
281
475
|
serverModules: options.serverModules,
|
|
282
476
|
serverOutput: "string",
|
|
283
477
|
});
|
|
478
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "stringTransformMs");
|
|
284
479
|
const stringFatalDiagnostics = stringOutput.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
285
480
|
if (stringFatalDiagnostics.length > 0) {
|
|
286
481
|
return new Response(stringFatalDiagnostics.map((diagnostic) => diagnostic.message).join("\n"), {
|
|
@@ -345,15 +540,20 @@ async function renderAppRequestInternal(options) {
|
|
|
345
540
|
html = injectHeadMetadata(html, metadata);
|
|
346
541
|
html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
|
|
347
542
|
html = injectQueryState(html, dehydrate(queryClient));
|
|
348
|
-
const
|
|
349
|
-
headers.set("x-mreact-stream", "1");
|
|
350
|
-
return withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
543
|
+
const response = withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
351
544
|
assetBaseUrl: options.assetBaseUrl,
|
|
352
545
|
currentScript: clientRoute ? clientScript : undefined,
|
|
353
546
|
currentNavigationScript: clientRoute ? undefined : navigationScript,
|
|
354
547
|
routeScripts: options.clientScripts,
|
|
355
|
-
})}${html}`, {
|
|
548
|
+
})}${html}`, {
|
|
549
|
+
headers: responseHeadersForMetadata(metadata, {
|
|
550
|
+
"x-mreact-stream": "1",
|
|
551
|
+
}),
|
|
552
|
+
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
553
|
+
emitRenderTiming(options, timing, response.status);
|
|
554
|
+
return response;
|
|
356
555
|
}
|
|
556
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
357
557
|
const output = transformServerModule({
|
|
358
558
|
code: routeCode,
|
|
359
559
|
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
@@ -362,6 +562,7 @@ async function renderAppRequestInternal(options) {
|
|
|
362
562
|
serverOutput: "stream",
|
|
363
563
|
serverAwaitHydration: clientRoute,
|
|
364
564
|
});
|
|
565
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamTransformMs");
|
|
365
566
|
const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
366
567
|
if (fatalDiagnostics.length > 0) {
|
|
367
568
|
return new Response(fatalDiagnostics.map((diagnostic) => diagnostic.message).join("\n"), {
|
|
@@ -370,6 +571,7 @@ async function renderAppRequestInternal(options) {
|
|
|
370
571
|
});
|
|
371
572
|
}
|
|
372
573
|
if (loadingFile !== undefined) {
|
|
574
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
373
575
|
const stream = await runServerStreamModuleWithLoading(output.code, {
|
|
374
576
|
appDir: options.appDir,
|
|
375
577
|
assetBaseUrl: options.assetBaseUrl,
|
|
@@ -388,9 +590,12 @@ async function renderAppRequestInternal(options) {
|
|
|
388
590
|
script: clientScript,
|
|
389
591
|
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
390
592
|
});
|
|
391
|
-
|
|
593
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
594
|
+
const response = withOptionalActionCookie(new Response(stream, {
|
|
392
595
|
headers: streamShellResponseHeaders,
|
|
393
596
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
597
|
+
emitRenderTiming(options, timing, response.status);
|
|
598
|
+
return response;
|
|
394
599
|
}
|
|
395
600
|
const data = dataPromise === undefined ? undefined : await dataPromise;
|
|
396
601
|
if (data instanceof Response) {
|
|
@@ -402,6 +607,7 @@ async function renderAppRequestInternal(options) {
|
|
|
402
607
|
queryClient,
|
|
403
608
|
request: options.request,
|
|
404
609
|
};
|
|
610
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
405
611
|
const stream = runServerStreamModule(output.code, {
|
|
406
612
|
appDir: options.appDir,
|
|
407
613
|
assetBaseUrl: options.assetBaseUrl,
|
|
@@ -416,9 +622,12 @@ async function renderAppRequestInternal(options) {
|
|
|
416
622
|
script: clientScript,
|
|
417
623
|
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
418
624
|
});
|
|
419
|
-
|
|
625
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
626
|
+
const response = withOptionalActionCookie(new Response(stream, {
|
|
420
627
|
headers: streamShellResponseHeaders,
|
|
421
628
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
629
|
+
emitRenderTiming(options, timing, response.status);
|
|
630
|
+
return response;
|
|
422
631
|
}
|
|
423
632
|
const output = transformServerModule({
|
|
424
633
|
code: routeCode,
|
|
@@ -533,6 +742,7 @@ async function renderAppRequestInternal(options) {
|
|
|
533
742
|
assetBaseUrl: options.assetBaseUrl,
|
|
534
743
|
error: undefined,
|
|
535
744
|
request: options.request,
|
|
745
|
+
routePath: matched.route.path,
|
|
536
746
|
routeFile: notFoundFile,
|
|
537
747
|
routeScripts: options.clientScripts,
|
|
538
748
|
serverModules: options.serverModules,
|
|
@@ -553,6 +763,7 @@ async function renderAppRequestInternal(options) {
|
|
|
553
763
|
assetBaseUrl: options.assetBaseUrl,
|
|
554
764
|
error,
|
|
555
765
|
request: options.request,
|
|
766
|
+
routePath: matched.route.path,
|
|
556
767
|
routeFile: errorFile,
|
|
557
768
|
routeScripts: options.clientScripts,
|
|
558
769
|
serverModules: options.serverModules,
|
|
@@ -629,6 +840,7 @@ async function nearestExistingBoundaryFileForPage(options) {
|
|
|
629
840
|
appDir: options.appDir,
|
|
630
841
|
filename: options.filename,
|
|
631
842
|
parts,
|
|
843
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
632
844
|
});
|
|
633
845
|
}
|
|
634
846
|
async function nearestBoundaryFileForPath(options) {
|
|
@@ -661,6 +873,12 @@ async function nearestExistingBoundaryFileFromParts(options) {
|
|
|
661
873
|
for (let count = options.parts.length; count >= 0; count -= 1) {
|
|
662
874
|
for (const filename of boundaryFilenameCandidates(options.filename)) {
|
|
663
875
|
const candidate = join(options.appDir, ...options.parts.slice(0, count), filename);
|
|
876
|
+
if (options.serverSourceFiles !== undefined) {
|
|
877
|
+
if (options.serverSourceFiles.has(candidate)) {
|
|
878
|
+
return candidate;
|
|
879
|
+
}
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
664
882
|
try {
|
|
665
883
|
await access(candidate);
|
|
666
884
|
return candidate;
|
|
@@ -688,10 +906,14 @@ async function renderSpecialRoute(options) {
|
|
|
688
906
|
}
|
|
689
907
|
const props = {
|
|
690
908
|
data: undefined,
|
|
909
|
+
debug: errorDebugContext(options.error, options.routePath),
|
|
691
910
|
error: normalizeErrorForProps(options.error),
|
|
692
911
|
params: {},
|
|
693
912
|
queryClient: createQueryClient(),
|
|
694
913
|
request: options.request,
|
|
914
|
+
requestId: requestIdForErrorContext(options.request),
|
|
915
|
+
routeId: routeIdForPath(options.routePath ?? new URL(options.request.url).pathname),
|
|
916
|
+
traceId: traceContextFromRequest(options.request)?.traceId,
|
|
695
917
|
};
|
|
696
918
|
const pageHtml = await renderServerFileToHtml(options.routeFile, props, options.serverModules, options.serverModuleCacheVersion, options.serverSourceFiles);
|
|
697
919
|
const pageHtmlForLayout = options.navigation?.clientRoute === true
|
|
@@ -742,6 +964,19 @@ function normalizeErrorForProps(error) {
|
|
|
742
964
|
}
|
|
743
965
|
return { message: String(error) };
|
|
744
966
|
}
|
|
967
|
+
function requestIdForErrorContext(request) {
|
|
968
|
+
return request.headers.get("x-request-id") ?? randomUUID();
|
|
969
|
+
}
|
|
970
|
+
function errorDebugContext(error, routePath) {
|
|
971
|
+
if (process.env.NODE_ENV === "production" || !(error instanceof Error)) {
|
|
972
|
+
return undefined;
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
...(error.cause === undefined ? {} : { cause: error.cause }),
|
|
976
|
+
...(routePath === undefined ? {} : { route: { matched: routePath } }),
|
|
977
|
+
...(error.stack === undefined ? {} : { stack: error.stack }),
|
|
978
|
+
};
|
|
979
|
+
}
|
|
745
980
|
async function dispatchServerRoute(options) {
|
|
746
981
|
const module = await loadServerRouteModule(options);
|
|
747
982
|
const handler = module[options.request.method] ?? module.ALL ?? module.default;
|
|
@@ -771,7 +1006,7 @@ async function loadServerRouteModule(options) {
|
|
|
771
1006
|
const codeHash = memoizedHashText(code);
|
|
772
1007
|
const moduleCode = artifactCode !== undefined && artifactCode.sourceHash === codeHash ? artifactCode.code : code;
|
|
773
1008
|
const cacheKey = `server-route\0${options.file}\0${options.serverModuleCacheVersion}\0${codeHash}\0${memoizedHashText(moduleCode)}`;
|
|
774
|
-
const cached = serverRouteModuleCache
|
|
1009
|
+
const cached = readRouterRuntimeCacheEntry(serverRouteModuleCache, cacheKey, serverRouteModuleCacheCounters);
|
|
775
1010
|
if (cached !== undefined) {
|
|
776
1011
|
return cached;
|
|
777
1012
|
}
|
|
@@ -785,7 +1020,7 @@ async function loadServerRouteModule(options) {
|
|
|
785
1020
|
serverRouteModuleCache.delete(cacheKey);
|
|
786
1021
|
throw error;
|
|
787
1022
|
});
|
|
788
|
-
setBoundedCacheEntry(serverRouteModuleCache, cacheKey, loaded, maxServerRouteModuleCacheEntries);
|
|
1023
|
+
setBoundedCacheEntry(serverRouteModuleCache, cacheKey, loaded, maxServerRouteModuleCacheEntries, serverRouteModuleCacheCounters);
|
|
789
1024
|
return loaded;
|
|
790
1025
|
}
|
|
791
1026
|
async function runMiddleware(options) {
|
|
@@ -808,6 +1043,9 @@ async function runMiddleware(options) {
|
|
|
808
1043
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
809
1044
|
serverSourceFiles: options.serverSourceFiles,
|
|
810
1045
|
});
|
|
1046
|
+
if (shouldSkipMiddleware(module.config, options.middlewareControl)) {
|
|
1047
|
+
return undefined;
|
|
1048
|
+
}
|
|
811
1049
|
if (!middlewareMatches(module.config, new URL(options.request.url).pathname)) {
|
|
812
1050
|
return undefined;
|
|
813
1051
|
}
|
|
@@ -815,11 +1053,25 @@ async function runMiddleware(options) {
|
|
|
815
1053
|
if (typeof middleware !== "function") {
|
|
816
1054
|
return undefined;
|
|
817
1055
|
}
|
|
1056
|
+
const trace = traceContextFromRequest(options.request);
|
|
1057
|
+
const event = {
|
|
1058
|
+
method: options.request.method,
|
|
1059
|
+
name: "middleware",
|
|
1060
|
+
path: new URL(options.request.url).pathname,
|
|
1061
|
+
request: options.request,
|
|
1062
|
+
...(trace === undefined ? {} : { trace }),
|
|
1063
|
+
};
|
|
1064
|
+
invokeRouterInstrumentation(options.instrumentation?.onMiddlewareStart, event);
|
|
818
1065
|
try {
|
|
819
1066
|
const response = await middleware(options.request);
|
|
1067
|
+
invokeRouterInstrumentation(options.instrumentation?.onMiddlewareEnd, event);
|
|
820
1068
|
return response instanceof Response ? response : undefined;
|
|
821
1069
|
}
|
|
822
1070
|
catch (error) {
|
|
1071
|
+
invokeRouterInstrumentation(options.instrumentation?.onMiddlewareEnd, {
|
|
1072
|
+
...event,
|
|
1073
|
+
error,
|
|
1074
|
+
});
|
|
823
1075
|
if (isRedirectError(error)) {
|
|
824
1076
|
return new Response(null, {
|
|
825
1077
|
headers: { location: error.location },
|
|
@@ -834,13 +1086,22 @@ async function runMiddleware(options) {
|
|
|
834
1086
|
}
|
|
835
1087
|
return undefined;
|
|
836
1088
|
}
|
|
1089
|
+
function shouldSkipMiddleware(config, control) {
|
|
1090
|
+
if (control?.skip === true) {
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
if (!Array.isArray(control?.skip)) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
return typeof config?.id === "string" && control.skip.includes(config.id);
|
|
1097
|
+
}
|
|
837
1098
|
async function loadMiddlewareModule(options) {
|
|
838
1099
|
const code = await readServerSourceFile(options.file, options.serverModuleCacheVersion, options.serverSourceFiles);
|
|
839
1100
|
const cacheKey = options.serverModuleCacheVersion === undefined
|
|
840
1101
|
? undefined
|
|
841
1102
|
: `middleware\0${options.appDir}\0${options.file}\0${options.serverModuleCacheVersion}\0${memoizedHashText(code)}\0${importPolicyCacheKey(options.importPolicy)}`;
|
|
842
1103
|
if (cacheKey !== undefined) {
|
|
843
|
-
const cached = middlewareModuleCache
|
|
1104
|
+
const cached = readRouterRuntimeCacheEntry(middlewareModuleCache, cacheKey, middlewareModuleCacheCounters);
|
|
844
1105
|
if (cached !== undefined) {
|
|
845
1106
|
return cached;
|
|
846
1107
|
}
|
|
@@ -859,7 +1120,7 @@ async function loadMiddlewareModule(options) {
|
|
|
859
1120
|
throw error;
|
|
860
1121
|
});
|
|
861
1122
|
if (cacheKey !== undefined) {
|
|
862
|
-
setBoundedCacheEntry(middlewareModuleCache, cacheKey, loaded, maxMiddlewareModuleCacheEntries);
|
|
1123
|
+
setBoundedCacheEntry(middlewareModuleCache, cacheKey, loaded, maxMiddlewareModuleCacheEntries, middlewareModuleCacheCounters);
|
|
863
1124
|
}
|
|
864
1125
|
return loaded;
|
|
865
1126
|
}
|
|
@@ -963,7 +1224,7 @@ function transformServerModule(options) {
|
|
|
963
1224
|
}
|
|
964
1225
|
const awaitHydrationKey = options.serverAwaitHydration === true ? "1" : "0";
|
|
965
1226
|
const key = `${options.filename}\0${options.serverOutput}\0${sourceHash}\0${awaitHydrationKey}`;
|
|
966
|
-
const cached = serverTransformCache
|
|
1227
|
+
const cached = readRouterRuntimeCacheEntry(serverTransformCache, key, serverTransformCacheCounters);
|
|
967
1228
|
if (cached !== undefined) {
|
|
968
1229
|
return cached;
|
|
969
1230
|
}
|
|
@@ -979,13 +1240,13 @@ function transformServerModule(options) {
|
|
|
979
1240
|
target: "server",
|
|
980
1241
|
...(options.serverAwaitHydration === true ? { serverAwaitHydration: true } : {}),
|
|
981
1242
|
});
|
|
982
|
-
setBoundedCacheEntry(serverTransformCache, key, output, maxServerTransformCacheEntries);
|
|
1243
|
+
setBoundedCacheEntry(serverTransformCache, key, output, maxServerTransformCacheEntries, serverTransformCacheCounters);
|
|
983
1244
|
return output;
|
|
984
1245
|
}
|
|
985
1246
|
async function analyzeRouteSource(options) {
|
|
986
1247
|
const sourceHash = memoizedHashText(options.code);
|
|
987
1248
|
const cacheKey = `${options.serverModuleCacheVersion ?? "dev"}\0${options.filename}\0${sourceHash}`;
|
|
988
|
-
const cached = routeSourceAnalysisCache
|
|
1249
|
+
const cached = readRouterRuntimeCacheEntry(routeSourceAnalysisCache, cacheKey, routeSourceAnalysisCacheCounters);
|
|
989
1250
|
if (cached !== undefined) {
|
|
990
1251
|
return cached;
|
|
991
1252
|
}
|
|
@@ -993,7 +1254,7 @@ async function analyzeRouteSource(options) {
|
|
|
993
1254
|
routeSourceAnalysisCache.delete(cacheKey);
|
|
994
1255
|
throw error;
|
|
995
1256
|
});
|
|
996
|
-
setBoundedCacheEntry(routeSourceAnalysisCache, cacheKey, pending, maxRouteSourceAnalysisCacheEntries);
|
|
1257
|
+
setBoundedCacheEntry(routeSourceAnalysisCache, cacheKey, pending, maxRouteSourceAnalysisCacheEntries, routeSourceAnalysisCacheCounters);
|
|
997
1258
|
return pending;
|
|
998
1259
|
}
|
|
999
1260
|
async function analyzeRouteSourceUncached(options) {
|
|
@@ -1150,7 +1411,7 @@ async function mayRenderOutOfOrderBoundaryDeepInner(options, seen) {
|
|
|
1150
1411
|
seen.add(options.filename);
|
|
1151
1412
|
const sourceHash = memoizedHashText(options.code);
|
|
1152
1413
|
const cacheKey = `${options.serverModuleCacheVersion ?? "dev"}\0${options.filename}\0${sourceHash}`;
|
|
1153
|
-
const cached = routeOutOfOrderBoundaryAnalysisCache
|
|
1414
|
+
const cached = readRouterRuntimeCacheEntry(routeOutOfOrderBoundaryAnalysisCache, cacheKey, routeOutOfOrderBoundaryAnalysisCacheCounters);
|
|
1154
1415
|
if (cached !== undefined) {
|
|
1155
1416
|
return cached;
|
|
1156
1417
|
}
|
|
@@ -1158,7 +1419,7 @@ async function mayRenderOutOfOrderBoundaryDeepInner(options, seen) {
|
|
|
1158
1419
|
routeOutOfOrderBoundaryAnalysisCache.delete(cacheKey);
|
|
1159
1420
|
throw error;
|
|
1160
1421
|
});
|
|
1161
|
-
setBoundedCacheEntry(routeOutOfOrderBoundaryAnalysisCache, cacheKey, pending, maxRouteOutOfOrderBoundaryAnalysisCacheEntries);
|
|
1422
|
+
setBoundedCacheEntry(routeOutOfOrderBoundaryAnalysisCache, cacheKey, pending, maxRouteOutOfOrderBoundaryAnalysisCacheEntries, routeOutOfOrderBoundaryAnalysisCacheCounters);
|
|
1162
1423
|
return pending;
|
|
1163
1424
|
}
|
|
1164
1425
|
async function mayRenderImportedOutOfOrderBoundary(options, seen) {
|
|
@@ -1382,7 +1643,7 @@ async function renderShellPrefixSuffix(appDir, shell, props, slotContext, server
|
|
|
1382
1643
|
? undefined
|
|
1383
1644
|
: `${appDir}\0${shell.file}\0${serverModuleCacheVersion}`;
|
|
1384
1645
|
if (cacheKey !== undefined) {
|
|
1385
|
-
const cached = renderedShellCache
|
|
1646
|
+
const cached = readRouterRuntimeCacheEntry(renderedShellCache, cacheKey, renderedShellCacheCounters);
|
|
1386
1647
|
if (cached !== undefined && cached !== "impure") {
|
|
1387
1648
|
return cached;
|
|
1388
1649
|
}
|
|
@@ -1400,7 +1661,9 @@ async function renderShellPrefixSuffix(appDir, shell, props, slotContext, server
|
|
|
1400
1661
|
}
|
|
1401
1662
|
const component = await loadServerComponent(output.code, shell.file, serverModules, serverModuleCacheVersion);
|
|
1402
1663
|
const rendered = splitLayoutSlot(markShellBoundary(await component(props), shell), slotContext);
|
|
1403
|
-
const cached = cacheKey !== undefined
|
|
1664
|
+
const cached = cacheKey !== undefined
|
|
1665
|
+
? readRouterRuntimeCacheEntry(renderedShellCache, cacheKey, renderedShellCacheCounters)
|
|
1666
|
+
: undefined;
|
|
1404
1667
|
// Detect purity: a zero-arg component cannot depend on props. The
|
|
1405
1668
|
// markShellBoundary + splitLayoutSlot output is then constant for
|
|
1406
1669
|
// the (appDir, shellFile, version) tuple. We only set the cache
|
|
@@ -1412,6 +1675,7 @@ async function renderShellPrefixSuffix(appDir, shell, props, slotContext, server
|
|
|
1412
1675
|
const oldestKey = renderedShellCache.keys().next().value;
|
|
1413
1676
|
if (oldestKey !== undefined) {
|
|
1414
1677
|
renderedShellCache.delete(oldestKey);
|
|
1678
|
+
renderedShellCacheCounters.evictions += 1;
|
|
1415
1679
|
}
|
|
1416
1680
|
}
|
|
1417
1681
|
renderedShellCache.set(cacheKey, rendered);
|
|
@@ -1448,6 +1712,11 @@ function splitLayoutSlot(layoutHtml, slotContext = createSlotRenderContext()) {
|
|
|
1448
1712
|
// next request.
|
|
1449
1713
|
const shellFilesCache = new Map();
|
|
1450
1714
|
const MAX_SHELL_FILES_CACHE_ENTRIES = 1024;
|
|
1715
|
+
const routeMiddlewareControlCache = new Map();
|
|
1716
|
+
const MAX_ROUTE_MIDDLEWARE_CONTROL_CACHE_ENTRIES = 1024;
|
|
1717
|
+
const appMiddlewareFileCache = new Map();
|
|
1718
|
+
const MAX_APP_MIDDLEWARE_FILE_CACHE_ENTRIES = 1024;
|
|
1719
|
+
const routeMiddlewareControlSourceCache = new Map();
|
|
1451
1720
|
async function shellFilesForPage(appDir, pageFile, serverModuleCacheVersion) {
|
|
1452
1721
|
const cacheKey = serverModuleCacheVersion === undefined
|
|
1453
1722
|
? undefined
|
|
@@ -1506,6 +1775,144 @@ function shellBoundaryId(appDir, directory) {
|
|
|
1506
1775
|
? "root"
|
|
1507
1776
|
: relativeDirectory.replaceAll(sep, "/").replace(/[^A-Za-z0-9_$/-]/g, "_");
|
|
1508
1777
|
}
|
|
1778
|
+
async function hasAppMiddleware(options) {
|
|
1779
|
+
const middlewareFiles = [
|
|
1780
|
+
join(options.appDir, "middleware.ts"),
|
|
1781
|
+
join(options.appDir, "middleware.mreact.ts"),
|
|
1782
|
+
];
|
|
1783
|
+
if (options.serverSourceFiles !== undefined) {
|
|
1784
|
+
return middlewareFiles.some((file) => options.serverSourceFiles?.has(file) === true);
|
|
1785
|
+
}
|
|
1786
|
+
const cacheKey = options.serverModuleCacheVersion === undefined
|
|
1787
|
+
? undefined
|
|
1788
|
+
: `${options.appDir}\0${options.serverModuleCacheVersion}`;
|
|
1789
|
+
if (cacheKey !== undefined) {
|
|
1790
|
+
const cached = appMiddlewareFileCache.get(cacheKey);
|
|
1791
|
+
if (cached !== undefined) {
|
|
1792
|
+
return cached;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
const loaded = hasAppMiddlewareUncached(middlewareFiles).catch((error) => {
|
|
1796
|
+
if (cacheKey !== undefined) {
|
|
1797
|
+
appMiddlewareFileCache.delete(cacheKey);
|
|
1798
|
+
}
|
|
1799
|
+
throw error;
|
|
1800
|
+
});
|
|
1801
|
+
if (cacheKey !== undefined) {
|
|
1802
|
+
if (appMiddlewareFileCache.size >= MAX_APP_MIDDLEWARE_FILE_CACHE_ENTRIES) {
|
|
1803
|
+
const oldestKey = appMiddlewareFileCache.keys().next().value;
|
|
1804
|
+
if (oldestKey !== undefined) {
|
|
1805
|
+
appMiddlewareFileCache.delete(oldestKey);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
appMiddlewareFileCache.set(cacheKey, loaded);
|
|
1809
|
+
}
|
|
1810
|
+
return loaded;
|
|
1811
|
+
}
|
|
1812
|
+
async function hasAppMiddlewareUncached(files) {
|
|
1813
|
+
for (const file of files) {
|
|
1814
|
+
try {
|
|
1815
|
+
await access(file);
|
|
1816
|
+
return true;
|
|
1817
|
+
}
|
|
1818
|
+
catch {
|
|
1819
|
+
// Missing middleware files are allowed.
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
return false;
|
|
1823
|
+
}
|
|
1824
|
+
async function loadRouteMiddlewareControl(options) {
|
|
1825
|
+
const cacheKey = options.serverModuleCacheVersion === undefined
|
|
1826
|
+
? undefined
|
|
1827
|
+
: `${options.appDir}\0${options.pageFile}\0${options.serverModuleCacheVersion}`;
|
|
1828
|
+
if (cacheKey !== undefined) {
|
|
1829
|
+
const cached = routeMiddlewareControlCache.get(cacheKey);
|
|
1830
|
+
if (cached !== undefined) {
|
|
1831
|
+
return cached;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
const loaded = loadRouteMiddlewareControlUncached(options).catch((error) => {
|
|
1835
|
+
if (cacheKey !== undefined) {
|
|
1836
|
+
routeMiddlewareControlCache.delete(cacheKey);
|
|
1837
|
+
}
|
|
1838
|
+
throw error;
|
|
1839
|
+
});
|
|
1840
|
+
if (cacheKey !== undefined) {
|
|
1841
|
+
if (routeMiddlewareControlCache.size >= MAX_ROUTE_MIDDLEWARE_CONTROL_CACHE_ENTRIES) {
|
|
1842
|
+
const oldestKey = routeMiddlewareControlCache.keys().next().value;
|
|
1843
|
+
if (oldestKey !== undefined) {
|
|
1844
|
+
routeMiddlewareControlCache.delete(oldestKey);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
routeMiddlewareControlCache.set(cacheKey, loaded);
|
|
1848
|
+
}
|
|
1849
|
+
return loaded;
|
|
1850
|
+
}
|
|
1851
|
+
async function loadRouteMiddlewareControlUncached(options) {
|
|
1852
|
+
const controls = [];
|
|
1853
|
+
const shellFiles = await shellFilesForPage(options.appDir, options.pageFile, options.serverModuleCacheVersion);
|
|
1854
|
+
for (const shell of shellFiles) {
|
|
1855
|
+
if (shell.kind !== "layout") {
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
controls.push(await loadRouteMiddlewareControlFile({
|
|
1859
|
+
file: shell.file,
|
|
1860
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1861
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1862
|
+
}));
|
|
1863
|
+
}
|
|
1864
|
+
controls.push(await loadRouteMiddlewareControlFile({
|
|
1865
|
+
file: options.pageFile,
|
|
1866
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1867
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1868
|
+
}));
|
|
1869
|
+
return mergeRouteMiddlewareControls(controls);
|
|
1870
|
+
}
|
|
1871
|
+
async function loadRouteMiddlewareControlFile(options) {
|
|
1872
|
+
if (options.serverSourceFiles !== undefined || options.serverModuleCacheVersion !== undefined) {
|
|
1873
|
+
return parseRouteMiddlewareControl(await readServerSourceFile(options.file, options.serverModuleCacheVersion, options.serverSourceFiles));
|
|
1874
|
+
}
|
|
1875
|
+
const fileStat = await stat(options.file);
|
|
1876
|
+
const cached = routeMiddlewareControlSourceCache.get(options.file);
|
|
1877
|
+
if (cached !== undefined && cached.mtimeMs === fileStat.mtimeMs) {
|
|
1878
|
+
return cached.control;
|
|
1879
|
+
}
|
|
1880
|
+
const control = parseRouteMiddlewareControl(await readFile(options.file, "utf8"));
|
|
1881
|
+
routeMiddlewareControlSourceCache.set(options.file, {
|
|
1882
|
+
control,
|
|
1883
|
+
mtimeMs: fileStat.mtimeMs,
|
|
1884
|
+
});
|
|
1885
|
+
return control;
|
|
1886
|
+
}
|
|
1887
|
+
function parseRouteMiddlewareControl(code) {
|
|
1888
|
+
if (!/\bexport\s+const\s+middleware\s*=/.test(code)) {
|
|
1889
|
+
return undefined;
|
|
1890
|
+
}
|
|
1891
|
+
if (/\bmiddleware\s*=\s*\{[\s\S]*?\bskip\s*:\s*true\b/.test(code)) {
|
|
1892
|
+
return { skip: true };
|
|
1893
|
+
}
|
|
1894
|
+
const skipArray = /\bmiddleware\s*=\s*\{[\s\S]*?\bskip\s*:\s*\[([\s\S]*?)\]/.exec(code);
|
|
1895
|
+
if (skipArray === null) {
|
|
1896
|
+
return undefined;
|
|
1897
|
+
}
|
|
1898
|
+
const ids = Array.from(skipArray[1]?.matchAll(/["']([^"']+)["']/g) ?? [], (match) => match[1])
|
|
1899
|
+
.filter((id) => id !== undefined);
|
|
1900
|
+
return ids.length === 0 ? undefined : { skip: ids };
|
|
1901
|
+
}
|
|
1902
|
+
function mergeRouteMiddlewareControls(controls) {
|
|
1903
|
+
const skippedIds = new Set();
|
|
1904
|
+
for (const control of controls) {
|
|
1905
|
+
if (control?.skip === true) {
|
|
1906
|
+
return { skip: true };
|
|
1907
|
+
}
|
|
1908
|
+
if (Array.isArray(control?.skip)) {
|
|
1909
|
+
for (const id of control.skip) {
|
|
1910
|
+
skippedIds.add(id);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
return skippedIds.size === 0 ? undefined : { skip: [...skippedIds] };
|
|
1915
|
+
}
|
|
1509
1916
|
function markShellBoundary(html, shell) {
|
|
1510
1917
|
const attributeName = shell.kind === "layout" ? "data-mreact-layout-boundary" : "data-mreact-template-boundary";
|
|
1511
1918
|
if (html.includes(`${attributeName}=`)) {
|
|
@@ -1576,12 +1983,36 @@ async function loadRouteData(options) {
|
|
|
1576
1983
|
const module = await loadRouteLoaderModule(options);
|
|
1577
1984
|
return module.loader === undefined ? undefined : await module.loader(options.context);
|
|
1578
1985
|
}
|
|
1986
|
+
async function loadRouteDataWithInstrumentation(options) {
|
|
1987
|
+
const trace = traceContextFromRequest(options.request);
|
|
1988
|
+
const event = {
|
|
1989
|
+
method: options.request.method,
|
|
1990
|
+
path: new URL(options.request.url).pathname,
|
|
1991
|
+
request: options.request,
|
|
1992
|
+
routeId: options.routeId,
|
|
1993
|
+
routePath: options.routePath,
|
|
1994
|
+
...(trace === undefined ? {} : { trace }),
|
|
1995
|
+
};
|
|
1996
|
+
invokeRouterInstrumentation(options.instrumentation?.onLoaderStart, event);
|
|
1997
|
+
try {
|
|
1998
|
+
const data = await loadRouteData(options);
|
|
1999
|
+
invokeRouterInstrumentation(options.instrumentation?.onLoaderEnd, event);
|
|
2000
|
+
return data;
|
|
2001
|
+
}
|
|
2002
|
+
catch (error) {
|
|
2003
|
+
invokeRouterInstrumentation(options.instrumentation?.onLoaderEnd, {
|
|
2004
|
+
...event,
|
|
2005
|
+
error,
|
|
2006
|
+
});
|
|
2007
|
+
throw error;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1579
2010
|
async function loadRouteLoaderModule(options) {
|
|
1580
2011
|
const cacheKey = options.serverModuleCacheVersion === undefined
|
|
1581
2012
|
? undefined
|
|
1582
2013
|
: `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}\0${importPolicyCacheKey(options.importPolicy)}`;
|
|
1583
2014
|
if (cacheKey !== undefined) {
|
|
1584
|
-
const cached = routeLoaderModuleCache
|
|
2015
|
+
const cached = readRouterRuntimeCacheEntry(routeLoaderModuleCache, cacheKey, routeLoaderModuleCacheCounters);
|
|
1585
2016
|
if (cached !== undefined) {
|
|
1586
2017
|
return cached;
|
|
1587
2018
|
}
|
|
@@ -1596,7 +2027,7 @@ async function loadRouteLoaderModule(options) {
|
|
|
1596
2027
|
throw error;
|
|
1597
2028
|
});
|
|
1598
2029
|
if (cacheKey !== undefined) {
|
|
1599
|
-
setBoundedCacheEntry(routeLoaderModuleCache, cacheKey, loaded, maxRouteLoaderModuleCacheEntries);
|
|
2030
|
+
setBoundedCacheEntry(routeLoaderModuleCache, cacheKey, loaded, maxRouteLoaderModuleCacheEntries, routeLoaderModuleCacheCounters);
|
|
1600
2031
|
}
|
|
1601
2032
|
return loaded;
|
|
1602
2033
|
}
|
|
@@ -1716,7 +2147,7 @@ async function loadComposedRouteMetadata(options) {
|
|
|
1716
2147
|
? undefined
|
|
1717
2148
|
: `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}`;
|
|
1718
2149
|
if (cacheKey !== undefined) {
|
|
1719
|
-
const cached = composedRouteMetadataCache
|
|
2150
|
+
const cached = readRouterRuntimeCacheEntry(composedRouteMetadataCache, cacheKey, composedRouteMetadataCacheCounters);
|
|
1720
2151
|
if (cached !== undefined) {
|
|
1721
2152
|
return cached;
|
|
1722
2153
|
}
|
|
@@ -1728,7 +2159,7 @@ async function loadComposedRouteMetadata(options) {
|
|
|
1728
2159
|
throw error;
|
|
1729
2160
|
});
|
|
1730
2161
|
if (cacheKey !== undefined) {
|
|
1731
|
-
setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, loaded, maxComposedRouteMetadataCacheEntries);
|
|
2162
|
+
setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, loaded, maxComposedRouteMetadataCacheEntries, composedRouteMetadataCacheCounters);
|
|
1732
2163
|
}
|
|
1733
2164
|
return loaded;
|
|
1734
2165
|
}
|
|
@@ -1813,8 +2244,22 @@ function mergeReadonlyArrays(left, right) {
|
|
|
1813
2244
|
return [...left, ...right];
|
|
1814
2245
|
}
|
|
1815
2246
|
function mergeCspMetadata(left, right) {
|
|
2247
|
+
if (right?.disable === true) {
|
|
2248
|
+
return { disable: true };
|
|
2249
|
+
}
|
|
1816
2250
|
if (left === undefined) {
|
|
1817
|
-
|
|
2251
|
+
if (right === undefined) {
|
|
2252
|
+
return undefined;
|
|
2253
|
+
}
|
|
2254
|
+
const merged = { ...right };
|
|
2255
|
+
const directives = applyCspOverrides(undefined, right);
|
|
2256
|
+
if (directives !== undefined) {
|
|
2257
|
+
merged.directives = directives;
|
|
2258
|
+
}
|
|
2259
|
+
else {
|
|
2260
|
+
delete merged.directives;
|
|
2261
|
+
}
|
|
2262
|
+
return merged;
|
|
1818
2263
|
}
|
|
1819
2264
|
if (right === undefined) {
|
|
1820
2265
|
return left;
|
|
@@ -1823,12 +2268,28 @@ function mergeCspMetadata(left, right) {
|
|
|
1823
2268
|
...left,
|
|
1824
2269
|
...right,
|
|
1825
2270
|
};
|
|
1826
|
-
const directives =
|
|
2271
|
+
const directives = applyCspOverrides(left.directives, right);
|
|
1827
2272
|
if (directives !== undefined) {
|
|
1828
2273
|
merged.directives = directives;
|
|
1829
2274
|
}
|
|
2275
|
+
else {
|
|
2276
|
+
delete merged.directives;
|
|
2277
|
+
}
|
|
1830
2278
|
return merged;
|
|
1831
2279
|
}
|
|
2280
|
+
function applyCspOverrides(left, right) {
|
|
2281
|
+
if (right === undefined) {
|
|
2282
|
+
return left;
|
|
2283
|
+
}
|
|
2284
|
+
const merged = { ...left, ...right.directives };
|
|
2285
|
+
for (const [name, value] of Object.entries(right.replace ?? {})) {
|
|
2286
|
+
merged[name] = value;
|
|
2287
|
+
}
|
|
2288
|
+
for (const name of right.remove ?? []) {
|
|
2289
|
+
delete merged[name];
|
|
2290
|
+
}
|
|
2291
|
+
return Object.keys(merged).length === 0 ? undefined : merged;
|
|
2292
|
+
}
|
|
1832
2293
|
function mergeOpenGraphMetadata(left, right) {
|
|
1833
2294
|
if (left === undefined) {
|
|
1834
2295
|
return right;
|
|
@@ -1901,13 +2362,19 @@ function injectHeadMetadata(html, metadata) {
|
|
|
1901
2362
|
}
|
|
1902
2363
|
return `<head>${tags}</head>${html}`;
|
|
1903
2364
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
2365
|
+
const DEFAULT_HTML_RESPONSE_HEADERS = Object.freeze({
|
|
2366
|
+
"content-type": "text/html; charset=utf-8",
|
|
2367
|
+
});
|
|
2368
|
+
function responseHeadersForMetadata(metadata, extra) {
|
|
1906
2369
|
const csp = contentSecurityPolicy(metadata?.csp);
|
|
1907
|
-
if (csp
|
|
1908
|
-
|
|
2370
|
+
if (csp === undefined && extra === undefined) {
|
|
2371
|
+
return DEFAULT_HTML_RESPONSE_HEADERS;
|
|
1909
2372
|
}
|
|
1910
|
-
return
|
|
2373
|
+
return {
|
|
2374
|
+
...DEFAULT_HTML_RESPONSE_HEADERS,
|
|
2375
|
+
...(csp === undefined ? undefined : { "content-security-policy": csp }),
|
|
2376
|
+
...extra,
|
|
2377
|
+
};
|
|
1911
2378
|
}
|
|
1912
2379
|
function injectQueryState(html, state) {
|
|
1913
2380
|
if (state.queries.length === 0) {
|
|
@@ -2042,7 +2509,7 @@ function readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles)
|
|
|
2042
2509
|
return readFile(file, "utf8");
|
|
2043
2510
|
}
|
|
2044
2511
|
const key = `${serverModuleCacheVersion}:${file}`;
|
|
2045
|
-
const cached = serverSourceFileCache
|
|
2512
|
+
const cached = readRouterRuntimeCacheEntry(serverSourceFileCache, key, serverSourceFileCacheCounters);
|
|
2046
2513
|
if (cached !== undefined) {
|
|
2047
2514
|
return cached;
|
|
2048
2515
|
}
|
|
@@ -2050,17 +2517,20 @@ function readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles)
|
|
|
2050
2517
|
serverSourceFileCache.delete(key);
|
|
2051
2518
|
throw error;
|
|
2052
2519
|
});
|
|
2053
|
-
setBoundedCacheEntry(serverSourceFileCache, key, loaded, maxServerSourceFileCacheEntries);
|
|
2520
|
+
setBoundedCacheEntry(serverSourceFileCache, key, loaded, maxServerSourceFileCacheEntries, serverSourceFileCacheCounters);
|
|
2054
2521
|
return loaded;
|
|
2055
2522
|
}
|
|
2056
2523
|
function hashText(text) {
|
|
2057
2524
|
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
2058
2525
|
}
|
|
2059
|
-
function setBoundedCacheEntry(cache, key, value, maxEntries) {
|
|
2526
|
+
function setBoundedCacheEntry(cache, key, value, maxEntries, counters) {
|
|
2060
2527
|
if (cache.size >= maxEntries) {
|
|
2061
2528
|
const oldestKey = cache.keys().next().value;
|
|
2062
2529
|
if (oldestKey !== undefined) {
|
|
2063
2530
|
cache.delete(oldestKey);
|
|
2531
|
+
if (counters !== undefined) {
|
|
2532
|
+
counters.evictions += 1;
|
|
2533
|
+
}
|
|
2064
2534
|
}
|
|
2065
2535
|
}
|
|
2066
2536
|
cache.set(key, value);
|