@reckona/mreact-router 0.0.16 → 0.0.18
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 +76 -8
- 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 +70 -15
- 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 +355 -29
- 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 +11 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +505 -47
- 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 +34 -2
- 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,14 +190,52 @@ 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
|
+
}
|
|
104
239
|
export async function resolveAppRouterMiddleware(options) {
|
|
105
240
|
const middlewareResponse = await runMiddleware(options);
|
|
106
241
|
if (middlewareResponse === undefined) {
|
|
@@ -116,18 +251,43 @@ export async function resolveAppRouterMiddleware(options) {
|
|
|
116
251
|
return { response: middlewareResponse, type: "response" };
|
|
117
252
|
}
|
|
118
253
|
async function renderAppRequestInternal(options) {
|
|
254
|
+
const timing = createRenderTiming(options.logger);
|
|
255
|
+
let phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
119
256
|
const routes = options.routes ?? (await scanAppRoutes({ appDir: options.appDir }));
|
|
257
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeScanMs");
|
|
120
258
|
const url = new URL(options.request.url);
|
|
121
|
-
|
|
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
|
|
122
279
|
? { request: options.request, type: "continue" }
|
|
123
280
|
: await resolveAppRouterMiddleware({
|
|
124
281
|
appDir: options.appDir,
|
|
125
282
|
importPolicy: options.importPolicy,
|
|
283
|
+
instrumentation: options.instrumentation,
|
|
284
|
+
middlewareControl,
|
|
126
285
|
request: options.request,
|
|
127
286
|
serverModules: options.serverModules,
|
|
128
287
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
129
288
|
serverSourceFiles: options.serverSourceFiles,
|
|
130
289
|
});
|
|
290
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "middlewareMs");
|
|
131
291
|
if (middlewareResult.type === "response") {
|
|
132
292
|
return middlewareResult.response;
|
|
133
293
|
}
|
|
@@ -150,7 +310,6 @@ async function renderAppRequestInternal(options) {
|
|
|
150
310
|
serverActions: options.serverActions,
|
|
151
311
|
});
|
|
152
312
|
}
|
|
153
|
-
const matched = options.routeMatcher?.match(url.pathname) ?? matchRoute(routes, url.pathname);
|
|
154
313
|
if (matched === undefined) {
|
|
155
314
|
const notFoundFile = await nearestBoundaryFileForPath({
|
|
156
315
|
appDir: options.appDir,
|
|
@@ -162,6 +321,7 @@ async function renderAppRequestInternal(options) {
|
|
|
162
321
|
assetBaseUrl: options.assetBaseUrl,
|
|
163
322
|
error: undefined,
|
|
164
323
|
request: options.request,
|
|
324
|
+
routePath: url.pathname,
|
|
165
325
|
routeFile: notFoundFile,
|
|
166
326
|
routeScripts: options.clientScripts,
|
|
167
327
|
serverModules: options.serverModules,
|
|
@@ -204,35 +364,44 @@ async function renderAppRequestInternal(options) {
|
|
|
204
364
|
}
|
|
205
365
|
routeCacheContext = beginRouteCacheContext(options.routeCache);
|
|
206
366
|
const clientScript = options.clientScripts?.get(matched.route.path);
|
|
367
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
207
368
|
const originalCode = await readServerSourceFile(matched.route.file, options.serverModuleCacheVersion, options.serverSourceFiles);
|
|
369
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "readSourceMs");
|
|
370
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
208
371
|
const originalAnalysis = await analyzeRouteSource({
|
|
209
372
|
code: originalCode,
|
|
210
373
|
filename: matched.route.file,
|
|
211
374
|
routePath: matched.route.path,
|
|
212
375
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
213
376
|
});
|
|
377
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "sourceAnalysisMs");
|
|
214
378
|
const cachePolicy = originalAnalysis.cachePolicy;
|
|
215
379
|
const navigationScript = options.navigationScripts?.get(matched.route.path);
|
|
216
380
|
const cacheKey = routeCacheKey(options.appDir, matched.route.path, url);
|
|
217
381
|
const mayUseRouteCache = cachePolicy === undefined
|
|
218
382
|
? originalAnalysis.usesRuntimeCacheControl
|
|
219
383
|
: cachePolicy.revalidateSeconds !== 0;
|
|
384
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
220
385
|
const cachedResponse = !mayUseRouteCache
|
|
221
386
|
? undefined
|
|
222
387
|
: await cachedRouteResponse({
|
|
223
388
|
cache: options.routeCache,
|
|
224
389
|
key: cacheKey,
|
|
225
390
|
});
|
|
391
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeCacheMs");
|
|
226
392
|
if (cachedResponse !== undefined) {
|
|
227
393
|
return cachedResponse;
|
|
228
394
|
}
|
|
395
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
229
396
|
const preparedActions = await prepareRouteServerActions({
|
|
230
397
|
appDir: options.appDir,
|
|
231
398
|
code: originalCode,
|
|
232
399
|
pageFile: matched.route.file,
|
|
233
400
|
request: options.request,
|
|
234
401
|
});
|
|
402
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "serverActionsMs");
|
|
235
403
|
const code = preparedActions.code;
|
|
404
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
236
405
|
const routeAnalysis = code === originalCode
|
|
237
406
|
? originalAnalysis
|
|
238
407
|
: await analyzeRouteSource({
|
|
@@ -241,25 +410,32 @@ async function renderAppRequestInternal(options) {
|
|
|
241
410
|
routePath: matched.route.path,
|
|
242
411
|
serverModuleCacheVersion: undefined,
|
|
243
412
|
});
|
|
413
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeCodeAnalysisMs");
|
|
244
414
|
const routeCode = routeAnalysis.routeCode;
|
|
245
415
|
const streamRoute = routeAnalysis.streamRoute;
|
|
246
416
|
const clientInference = routeAnalysis.clientInference;
|
|
247
417
|
const clientRoute = clientInference.client;
|
|
418
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
248
419
|
const dataPromise = routeAnalysis.hasLoader
|
|
249
|
-
?
|
|
420
|
+
? loadRouteDataWithInstrumentation({
|
|
421
|
+
appDir: options.appDir,
|
|
250
422
|
code,
|
|
251
423
|
context: {
|
|
252
424
|
params: matched.params,
|
|
253
425
|
queryClient,
|
|
254
426
|
request: options.request,
|
|
255
427
|
},
|
|
256
|
-
appDir: options.appDir,
|
|
257
428
|
filename: matched.route.file,
|
|
258
429
|
importPolicy: options.importPolicy,
|
|
430
|
+
instrumentation: options.instrumentation,
|
|
431
|
+
request: options.request,
|
|
432
|
+
routeId: routeIdForPath(matched.route.path),
|
|
433
|
+
routePath: matched.route.path,
|
|
259
434
|
serverModules: options.serverModules,
|
|
260
435
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
261
436
|
})
|
|
262
437
|
: undefined;
|
|
438
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loaderStartMs");
|
|
263
439
|
recoveryRoute = {
|
|
264
440
|
clientRoute,
|
|
265
441
|
props: {
|
|
@@ -270,22 +446,28 @@ async function renderAppRequestInternal(options) {
|
|
|
270
446
|
script: clientScript,
|
|
271
447
|
};
|
|
272
448
|
if (streamRoute) {
|
|
449
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
273
450
|
const loadingFile = await nearestExistingBoundaryFileForPage({
|
|
274
451
|
appDir: options.appDir,
|
|
275
452
|
filename: "loading.mreact.tsx",
|
|
276
453
|
pageFile: matched.route.file,
|
|
454
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
277
455
|
});
|
|
456
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loadingBoundaryLookupMs");
|
|
278
457
|
const streamShellResponseHeaders = {
|
|
279
458
|
"content-type": "text/html; charset=utf-8",
|
|
280
459
|
"x-mreact-stream": "1",
|
|
281
460
|
};
|
|
461
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
282
462
|
const mayRenderOutOfOrder = await mayRenderOutOfOrderBoundaryDeep({
|
|
283
463
|
code: routeCode,
|
|
284
464
|
filename: matched.route.file,
|
|
285
465
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
286
466
|
serverSourceFiles: options.serverSourceFiles,
|
|
287
467
|
});
|
|
468
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "outOfOrderAnalysisMs");
|
|
288
469
|
if (loadingFile === undefined && !mayRenderOutOfOrder) {
|
|
470
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
289
471
|
const stringOutput = transformServerModule({
|
|
290
472
|
code: routeCode,
|
|
291
473
|
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
@@ -293,6 +475,7 @@ async function renderAppRequestInternal(options) {
|
|
|
293
475
|
serverModules: options.serverModules,
|
|
294
476
|
serverOutput: "string",
|
|
295
477
|
});
|
|
478
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "stringTransformMs");
|
|
296
479
|
const stringFatalDiagnostics = stringOutput.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
297
480
|
if (stringFatalDiagnostics.length > 0) {
|
|
298
481
|
return new Response(stringFatalDiagnostics.map((diagnostic) => diagnostic.message).join("\n"), {
|
|
@@ -357,15 +540,20 @@ async function renderAppRequestInternal(options) {
|
|
|
357
540
|
html = injectHeadMetadata(html, metadata);
|
|
358
541
|
html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
|
|
359
542
|
html = injectQueryState(html, dehydrate(queryClient));
|
|
360
|
-
const
|
|
361
|
-
headers.set("x-mreact-stream", "1");
|
|
362
|
-
return withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
543
|
+
const response = withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
363
544
|
assetBaseUrl: options.assetBaseUrl,
|
|
364
545
|
currentScript: clientRoute ? clientScript : undefined,
|
|
365
546
|
currentNavigationScript: clientRoute ? undefined : navigationScript,
|
|
366
547
|
routeScripts: options.clientScripts,
|
|
367
|
-
})}${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;
|
|
368
555
|
}
|
|
556
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
369
557
|
const output = transformServerModule({
|
|
370
558
|
code: routeCode,
|
|
371
559
|
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
@@ -374,6 +562,7 @@ async function renderAppRequestInternal(options) {
|
|
|
374
562
|
serverOutput: "stream",
|
|
375
563
|
serverAwaitHydration: clientRoute,
|
|
376
564
|
});
|
|
565
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamTransformMs");
|
|
377
566
|
const fatalDiagnostics = output.diagnostics.filter((diagnostic) => diagnostic.code !== "MR_UNSUPPORTED_SERVER_EVENT_HANDLER");
|
|
378
567
|
if (fatalDiagnostics.length > 0) {
|
|
379
568
|
return new Response(fatalDiagnostics.map((diagnostic) => diagnostic.message).join("\n"), {
|
|
@@ -382,6 +571,7 @@ async function renderAppRequestInternal(options) {
|
|
|
382
571
|
});
|
|
383
572
|
}
|
|
384
573
|
if (loadingFile !== undefined) {
|
|
574
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
385
575
|
const stream = await runServerStreamModuleWithLoading(output.code, {
|
|
386
576
|
appDir: options.appDir,
|
|
387
577
|
assetBaseUrl: options.assetBaseUrl,
|
|
@@ -400,9 +590,12 @@ async function renderAppRequestInternal(options) {
|
|
|
400
590
|
script: clientScript,
|
|
401
591
|
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
402
592
|
});
|
|
403
|
-
|
|
593
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
594
|
+
const response = withOptionalActionCookie(new Response(stream, {
|
|
404
595
|
headers: streamShellResponseHeaders,
|
|
405
596
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
597
|
+
emitRenderTiming(options, timing, response.status);
|
|
598
|
+
return response;
|
|
406
599
|
}
|
|
407
600
|
const data = dataPromise === undefined ? undefined : await dataPromise;
|
|
408
601
|
if (data instanceof Response) {
|
|
@@ -414,6 +607,7 @@ async function renderAppRequestInternal(options) {
|
|
|
414
607
|
queryClient,
|
|
415
608
|
request: options.request,
|
|
416
609
|
};
|
|
610
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
417
611
|
const stream = runServerStreamModule(output.code, {
|
|
418
612
|
appDir: options.appDir,
|
|
419
613
|
assetBaseUrl: options.assetBaseUrl,
|
|
@@ -428,9 +622,12 @@ async function renderAppRequestInternal(options) {
|
|
|
428
622
|
script: clientScript,
|
|
429
623
|
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
430
624
|
});
|
|
431
|
-
|
|
625
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
626
|
+
const response = withOptionalActionCookie(new Response(stream, {
|
|
432
627
|
headers: streamShellResponseHeaders,
|
|
433
628
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
629
|
+
emitRenderTiming(options, timing, response.status);
|
|
630
|
+
return response;
|
|
434
631
|
}
|
|
435
632
|
const output = transformServerModule({
|
|
436
633
|
code: routeCode,
|
|
@@ -545,6 +742,7 @@ async function renderAppRequestInternal(options) {
|
|
|
545
742
|
assetBaseUrl: options.assetBaseUrl,
|
|
546
743
|
error: undefined,
|
|
547
744
|
request: options.request,
|
|
745
|
+
routePath: matched.route.path,
|
|
548
746
|
routeFile: notFoundFile,
|
|
549
747
|
routeScripts: options.clientScripts,
|
|
550
748
|
serverModules: options.serverModules,
|
|
@@ -565,6 +763,7 @@ async function renderAppRequestInternal(options) {
|
|
|
565
763
|
assetBaseUrl: options.assetBaseUrl,
|
|
566
764
|
error,
|
|
567
765
|
request: options.request,
|
|
766
|
+
routePath: matched.route.path,
|
|
568
767
|
routeFile: errorFile,
|
|
569
768
|
routeScripts: options.clientScripts,
|
|
570
769
|
serverModules: options.serverModules,
|
|
@@ -641,6 +840,7 @@ async function nearestExistingBoundaryFileForPage(options) {
|
|
|
641
840
|
appDir: options.appDir,
|
|
642
841
|
filename: options.filename,
|
|
643
842
|
parts,
|
|
843
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
644
844
|
});
|
|
645
845
|
}
|
|
646
846
|
async function nearestBoundaryFileForPath(options) {
|
|
@@ -673,6 +873,12 @@ async function nearestExistingBoundaryFileFromParts(options) {
|
|
|
673
873
|
for (let count = options.parts.length; count >= 0; count -= 1) {
|
|
674
874
|
for (const filename of boundaryFilenameCandidates(options.filename)) {
|
|
675
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
|
+
}
|
|
676
882
|
try {
|
|
677
883
|
await access(candidate);
|
|
678
884
|
return candidate;
|
|
@@ -700,10 +906,14 @@ async function renderSpecialRoute(options) {
|
|
|
700
906
|
}
|
|
701
907
|
const props = {
|
|
702
908
|
data: undefined,
|
|
909
|
+
debug: errorDebugContext(options.error, options.routePath),
|
|
703
910
|
error: normalizeErrorForProps(options.error),
|
|
704
911
|
params: {},
|
|
705
912
|
queryClient: createQueryClient(),
|
|
706
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,
|
|
707
917
|
};
|
|
708
918
|
const pageHtml = await renderServerFileToHtml(options.routeFile, props, options.serverModules, options.serverModuleCacheVersion, options.serverSourceFiles);
|
|
709
919
|
const pageHtmlForLayout = options.navigation?.clientRoute === true
|
|
@@ -754,6 +964,19 @@ function normalizeErrorForProps(error) {
|
|
|
754
964
|
}
|
|
755
965
|
return { message: String(error) };
|
|
756
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
|
+
}
|
|
757
980
|
async function dispatchServerRoute(options) {
|
|
758
981
|
const module = await loadServerRouteModule(options);
|
|
759
982
|
const handler = module[options.request.method] ?? module.ALL ?? module.default;
|
|
@@ -783,7 +1006,7 @@ async function loadServerRouteModule(options) {
|
|
|
783
1006
|
const codeHash = memoizedHashText(code);
|
|
784
1007
|
const moduleCode = artifactCode !== undefined && artifactCode.sourceHash === codeHash ? artifactCode.code : code;
|
|
785
1008
|
const cacheKey = `server-route\0${options.file}\0${options.serverModuleCacheVersion}\0${codeHash}\0${memoizedHashText(moduleCode)}`;
|
|
786
|
-
const cached = serverRouteModuleCache
|
|
1009
|
+
const cached = readRouterRuntimeCacheEntry(serverRouteModuleCache, cacheKey, serverRouteModuleCacheCounters);
|
|
787
1010
|
if (cached !== undefined) {
|
|
788
1011
|
return cached;
|
|
789
1012
|
}
|
|
@@ -797,7 +1020,7 @@ async function loadServerRouteModule(options) {
|
|
|
797
1020
|
serverRouteModuleCache.delete(cacheKey);
|
|
798
1021
|
throw error;
|
|
799
1022
|
});
|
|
800
|
-
setBoundedCacheEntry(serverRouteModuleCache, cacheKey, loaded, maxServerRouteModuleCacheEntries);
|
|
1023
|
+
setBoundedCacheEntry(serverRouteModuleCache, cacheKey, loaded, maxServerRouteModuleCacheEntries, serverRouteModuleCacheCounters);
|
|
801
1024
|
return loaded;
|
|
802
1025
|
}
|
|
803
1026
|
async function runMiddleware(options) {
|
|
@@ -820,6 +1043,9 @@ async function runMiddleware(options) {
|
|
|
820
1043
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
821
1044
|
serverSourceFiles: options.serverSourceFiles,
|
|
822
1045
|
});
|
|
1046
|
+
if (shouldSkipMiddleware(module.config, options.middlewareControl)) {
|
|
1047
|
+
return undefined;
|
|
1048
|
+
}
|
|
823
1049
|
if (!middlewareMatches(module.config, new URL(options.request.url).pathname)) {
|
|
824
1050
|
return undefined;
|
|
825
1051
|
}
|
|
@@ -827,11 +1053,25 @@ async function runMiddleware(options) {
|
|
|
827
1053
|
if (typeof middleware !== "function") {
|
|
828
1054
|
return undefined;
|
|
829
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);
|
|
830
1065
|
try {
|
|
831
1066
|
const response = await middleware(options.request);
|
|
1067
|
+
invokeRouterInstrumentation(options.instrumentation?.onMiddlewareEnd, event);
|
|
832
1068
|
return response instanceof Response ? response : undefined;
|
|
833
1069
|
}
|
|
834
1070
|
catch (error) {
|
|
1071
|
+
invokeRouterInstrumentation(options.instrumentation?.onMiddlewareEnd, {
|
|
1072
|
+
...event,
|
|
1073
|
+
error,
|
|
1074
|
+
});
|
|
835
1075
|
if (isRedirectError(error)) {
|
|
836
1076
|
return new Response(null, {
|
|
837
1077
|
headers: { location: error.location },
|
|
@@ -846,13 +1086,22 @@ async function runMiddleware(options) {
|
|
|
846
1086
|
}
|
|
847
1087
|
return undefined;
|
|
848
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
|
+
}
|
|
849
1098
|
async function loadMiddlewareModule(options) {
|
|
850
1099
|
const code = await readServerSourceFile(options.file, options.serverModuleCacheVersion, options.serverSourceFiles);
|
|
851
1100
|
const cacheKey = options.serverModuleCacheVersion === undefined
|
|
852
1101
|
? undefined
|
|
853
1102
|
: `middleware\0${options.appDir}\0${options.file}\0${options.serverModuleCacheVersion}\0${memoizedHashText(code)}\0${importPolicyCacheKey(options.importPolicy)}`;
|
|
854
1103
|
if (cacheKey !== undefined) {
|
|
855
|
-
const cached = middlewareModuleCache
|
|
1104
|
+
const cached = readRouterRuntimeCacheEntry(middlewareModuleCache, cacheKey, middlewareModuleCacheCounters);
|
|
856
1105
|
if (cached !== undefined) {
|
|
857
1106
|
return cached;
|
|
858
1107
|
}
|
|
@@ -871,7 +1120,7 @@ async function loadMiddlewareModule(options) {
|
|
|
871
1120
|
throw error;
|
|
872
1121
|
});
|
|
873
1122
|
if (cacheKey !== undefined) {
|
|
874
|
-
setBoundedCacheEntry(middlewareModuleCache, cacheKey, loaded, maxMiddlewareModuleCacheEntries);
|
|
1123
|
+
setBoundedCacheEntry(middlewareModuleCache, cacheKey, loaded, maxMiddlewareModuleCacheEntries, middlewareModuleCacheCounters);
|
|
875
1124
|
}
|
|
876
1125
|
return loaded;
|
|
877
1126
|
}
|
|
@@ -975,7 +1224,7 @@ function transformServerModule(options) {
|
|
|
975
1224
|
}
|
|
976
1225
|
const awaitHydrationKey = options.serverAwaitHydration === true ? "1" : "0";
|
|
977
1226
|
const key = `${options.filename}\0${options.serverOutput}\0${sourceHash}\0${awaitHydrationKey}`;
|
|
978
|
-
const cached = serverTransformCache
|
|
1227
|
+
const cached = readRouterRuntimeCacheEntry(serverTransformCache, key, serverTransformCacheCounters);
|
|
979
1228
|
if (cached !== undefined) {
|
|
980
1229
|
return cached;
|
|
981
1230
|
}
|
|
@@ -991,13 +1240,13 @@ function transformServerModule(options) {
|
|
|
991
1240
|
target: "server",
|
|
992
1241
|
...(options.serverAwaitHydration === true ? { serverAwaitHydration: true } : {}),
|
|
993
1242
|
});
|
|
994
|
-
setBoundedCacheEntry(serverTransformCache, key, output, maxServerTransformCacheEntries);
|
|
1243
|
+
setBoundedCacheEntry(serverTransformCache, key, output, maxServerTransformCacheEntries, serverTransformCacheCounters);
|
|
995
1244
|
return output;
|
|
996
1245
|
}
|
|
997
1246
|
async function analyzeRouteSource(options) {
|
|
998
1247
|
const sourceHash = memoizedHashText(options.code);
|
|
999
1248
|
const cacheKey = `${options.serverModuleCacheVersion ?? "dev"}\0${options.filename}\0${sourceHash}`;
|
|
1000
|
-
const cached = routeSourceAnalysisCache
|
|
1249
|
+
const cached = readRouterRuntimeCacheEntry(routeSourceAnalysisCache, cacheKey, routeSourceAnalysisCacheCounters);
|
|
1001
1250
|
if (cached !== undefined) {
|
|
1002
1251
|
return cached;
|
|
1003
1252
|
}
|
|
@@ -1005,7 +1254,7 @@ async function analyzeRouteSource(options) {
|
|
|
1005
1254
|
routeSourceAnalysisCache.delete(cacheKey);
|
|
1006
1255
|
throw error;
|
|
1007
1256
|
});
|
|
1008
|
-
setBoundedCacheEntry(routeSourceAnalysisCache, cacheKey, pending, maxRouteSourceAnalysisCacheEntries);
|
|
1257
|
+
setBoundedCacheEntry(routeSourceAnalysisCache, cacheKey, pending, maxRouteSourceAnalysisCacheEntries, routeSourceAnalysisCacheCounters);
|
|
1009
1258
|
return pending;
|
|
1010
1259
|
}
|
|
1011
1260
|
async function analyzeRouteSourceUncached(options) {
|
|
@@ -1162,7 +1411,7 @@ async function mayRenderOutOfOrderBoundaryDeepInner(options, seen) {
|
|
|
1162
1411
|
seen.add(options.filename);
|
|
1163
1412
|
const sourceHash = memoizedHashText(options.code);
|
|
1164
1413
|
const cacheKey = `${options.serverModuleCacheVersion ?? "dev"}\0${options.filename}\0${sourceHash}`;
|
|
1165
|
-
const cached = routeOutOfOrderBoundaryAnalysisCache
|
|
1414
|
+
const cached = readRouterRuntimeCacheEntry(routeOutOfOrderBoundaryAnalysisCache, cacheKey, routeOutOfOrderBoundaryAnalysisCacheCounters);
|
|
1166
1415
|
if (cached !== undefined) {
|
|
1167
1416
|
return cached;
|
|
1168
1417
|
}
|
|
@@ -1170,7 +1419,7 @@ async function mayRenderOutOfOrderBoundaryDeepInner(options, seen) {
|
|
|
1170
1419
|
routeOutOfOrderBoundaryAnalysisCache.delete(cacheKey);
|
|
1171
1420
|
throw error;
|
|
1172
1421
|
});
|
|
1173
|
-
setBoundedCacheEntry(routeOutOfOrderBoundaryAnalysisCache, cacheKey, pending, maxRouteOutOfOrderBoundaryAnalysisCacheEntries);
|
|
1422
|
+
setBoundedCacheEntry(routeOutOfOrderBoundaryAnalysisCache, cacheKey, pending, maxRouteOutOfOrderBoundaryAnalysisCacheEntries, routeOutOfOrderBoundaryAnalysisCacheCounters);
|
|
1174
1423
|
return pending;
|
|
1175
1424
|
}
|
|
1176
1425
|
async function mayRenderImportedOutOfOrderBoundary(options, seen) {
|
|
@@ -1394,7 +1643,7 @@ async function renderShellPrefixSuffix(appDir, shell, props, slotContext, server
|
|
|
1394
1643
|
? undefined
|
|
1395
1644
|
: `${appDir}\0${shell.file}\0${serverModuleCacheVersion}`;
|
|
1396
1645
|
if (cacheKey !== undefined) {
|
|
1397
|
-
const cached = renderedShellCache
|
|
1646
|
+
const cached = readRouterRuntimeCacheEntry(renderedShellCache, cacheKey, renderedShellCacheCounters);
|
|
1398
1647
|
if (cached !== undefined && cached !== "impure") {
|
|
1399
1648
|
return cached;
|
|
1400
1649
|
}
|
|
@@ -1412,7 +1661,9 @@ async function renderShellPrefixSuffix(appDir, shell, props, slotContext, server
|
|
|
1412
1661
|
}
|
|
1413
1662
|
const component = await loadServerComponent(output.code, shell.file, serverModules, serverModuleCacheVersion);
|
|
1414
1663
|
const rendered = splitLayoutSlot(markShellBoundary(await component(props), shell), slotContext);
|
|
1415
|
-
const cached = cacheKey !== undefined
|
|
1664
|
+
const cached = cacheKey !== undefined
|
|
1665
|
+
? readRouterRuntimeCacheEntry(renderedShellCache, cacheKey, renderedShellCacheCounters)
|
|
1666
|
+
: undefined;
|
|
1416
1667
|
// Detect purity: a zero-arg component cannot depend on props. The
|
|
1417
1668
|
// markShellBoundary + splitLayoutSlot output is then constant for
|
|
1418
1669
|
// the (appDir, shellFile, version) tuple. We only set the cache
|
|
@@ -1424,6 +1675,7 @@ async function renderShellPrefixSuffix(appDir, shell, props, slotContext, server
|
|
|
1424
1675
|
const oldestKey = renderedShellCache.keys().next().value;
|
|
1425
1676
|
if (oldestKey !== undefined) {
|
|
1426
1677
|
renderedShellCache.delete(oldestKey);
|
|
1678
|
+
renderedShellCacheCounters.evictions += 1;
|
|
1427
1679
|
}
|
|
1428
1680
|
}
|
|
1429
1681
|
renderedShellCache.set(cacheKey, rendered);
|
|
@@ -1460,6 +1712,11 @@ function splitLayoutSlot(layoutHtml, slotContext = createSlotRenderContext()) {
|
|
|
1460
1712
|
// next request.
|
|
1461
1713
|
const shellFilesCache = new Map();
|
|
1462
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();
|
|
1463
1720
|
async function shellFilesForPage(appDir, pageFile, serverModuleCacheVersion) {
|
|
1464
1721
|
const cacheKey = serverModuleCacheVersion === undefined
|
|
1465
1722
|
? undefined
|
|
@@ -1518,6 +1775,144 @@ function shellBoundaryId(appDir, directory) {
|
|
|
1518
1775
|
? "root"
|
|
1519
1776
|
: relativeDirectory.replaceAll(sep, "/").replace(/[^A-Za-z0-9_$/-]/g, "_");
|
|
1520
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
|
+
}
|
|
1521
1916
|
function markShellBoundary(html, shell) {
|
|
1522
1917
|
const attributeName = shell.kind === "layout" ? "data-mreact-layout-boundary" : "data-mreact-template-boundary";
|
|
1523
1918
|
if (html.includes(`${attributeName}=`)) {
|
|
@@ -1588,12 +1983,36 @@ async function loadRouteData(options) {
|
|
|
1588
1983
|
const module = await loadRouteLoaderModule(options);
|
|
1589
1984
|
return module.loader === undefined ? undefined : await module.loader(options.context);
|
|
1590
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
|
+
}
|
|
1591
2010
|
async function loadRouteLoaderModule(options) {
|
|
1592
2011
|
const cacheKey = options.serverModuleCacheVersion === undefined
|
|
1593
2012
|
? undefined
|
|
1594
2013
|
: `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}\0${importPolicyCacheKey(options.importPolicy)}`;
|
|
1595
2014
|
if (cacheKey !== undefined) {
|
|
1596
|
-
const cached = routeLoaderModuleCache
|
|
2015
|
+
const cached = readRouterRuntimeCacheEntry(routeLoaderModuleCache, cacheKey, routeLoaderModuleCacheCounters);
|
|
1597
2016
|
if (cached !== undefined) {
|
|
1598
2017
|
return cached;
|
|
1599
2018
|
}
|
|
@@ -1608,7 +2027,7 @@ async function loadRouteLoaderModule(options) {
|
|
|
1608
2027
|
throw error;
|
|
1609
2028
|
});
|
|
1610
2029
|
if (cacheKey !== undefined) {
|
|
1611
|
-
setBoundedCacheEntry(routeLoaderModuleCache, cacheKey, loaded, maxRouteLoaderModuleCacheEntries);
|
|
2030
|
+
setBoundedCacheEntry(routeLoaderModuleCache, cacheKey, loaded, maxRouteLoaderModuleCacheEntries, routeLoaderModuleCacheCounters);
|
|
1612
2031
|
}
|
|
1613
2032
|
return loaded;
|
|
1614
2033
|
}
|
|
@@ -1728,7 +2147,7 @@ async function loadComposedRouteMetadata(options) {
|
|
|
1728
2147
|
? undefined
|
|
1729
2148
|
: `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}`;
|
|
1730
2149
|
if (cacheKey !== undefined) {
|
|
1731
|
-
const cached = composedRouteMetadataCache
|
|
2150
|
+
const cached = readRouterRuntimeCacheEntry(composedRouteMetadataCache, cacheKey, composedRouteMetadataCacheCounters);
|
|
1732
2151
|
if (cached !== undefined) {
|
|
1733
2152
|
return cached;
|
|
1734
2153
|
}
|
|
@@ -1740,7 +2159,7 @@ async function loadComposedRouteMetadata(options) {
|
|
|
1740
2159
|
throw error;
|
|
1741
2160
|
});
|
|
1742
2161
|
if (cacheKey !== undefined) {
|
|
1743
|
-
setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, loaded, maxComposedRouteMetadataCacheEntries);
|
|
2162
|
+
setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, loaded, maxComposedRouteMetadataCacheEntries, composedRouteMetadataCacheCounters);
|
|
1744
2163
|
}
|
|
1745
2164
|
return loaded;
|
|
1746
2165
|
}
|
|
@@ -1825,8 +2244,22 @@ function mergeReadonlyArrays(left, right) {
|
|
|
1825
2244
|
return [...left, ...right];
|
|
1826
2245
|
}
|
|
1827
2246
|
function mergeCspMetadata(left, right) {
|
|
2247
|
+
if (right?.disable === true) {
|
|
2248
|
+
return { disable: true };
|
|
2249
|
+
}
|
|
1828
2250
|
if (left === undefined) {
|
|
1829
|
-
|
|
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;
|
|
1830
2263
|
}
|
|
1831
2264
|
if (right === undefined) {
|
|
1832
2265
|
return left;
|
|
@@ -1835,12 +2268,28 @@ function mergeCspMetadata(left, right) {
|
|
|
1835
2268
|
...left,
|
|
1836
2269
|
...right,
|
|
1837
2270
|
};
|
|
1838
|
-
const directives =
|
|
2271
|
+
const directives = applyCspOverrides(left.directives, right);
|
|
1839
2272
|
if (directives !== undefined) {
|
|
1840
2273
|
merged.directives = directives;
|
|
1841
2274
|
}
|
|
2275
|
+
else {
|
|
2276
|
+
delete merged.directives;
|
|
2277
|
+
}
|
|
1842
2278
|
return merged;
|
|
1843
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
|
+
}
|
|
1844
2293
|
function mergeOpenGraphMetadata(left, right) {
|
|
1845
2294
|
if (left === undefined) {
|
|
1846
2295
|
return right;
|
|
@@ -1913,13 +2362,19 @@ function injectHeadMetadata(html, metadata) {
|
|
|
1913
2362
|
}
|
|
1914
2363
|
return `<head>${tags}</head>${html}`;
|
|
1915
2364
|
}
|
|
1916
|
-
|
|
1917
|
-
|
|
2365
|
+
const DEFAULT_HTML_RESPONSE_HEADERS = Object.freeze({
|
|
2366
|
+
"content-type": "text/html; charset=utf-8",
|
|
2367
|
+
});
|
|
2368
|
+
function responseHeadersForMetadata(metadata, extra) {
|
|
1918
2369
|
const csp = contentSecurityPolicy(metadata?.csp);
|
|
1919
|
-
if (csp
|
|
1920
|
-
|
|
2370
|
+
if (csp === undefined && extra === undefined) {
|
|
2371
|
+
return DEFAULT_HTML_RESPONSE_HEADERS;
|
|
1921
2372
|
}
|
|
1922
|
-
return
|
|
2373
|
+
return {
|
|
2374
|
+
...DEFAULT_HTML_RESPONSE_HEADERS,
|
|
2375
|
+
...(csp === undefined ? undefined : { "content-security-policy": csp }),
|
|
2376
|
+
...extra,
|
|
2377
|
+
};
|
|
1923
2378
|
}
|
|
1924
2379
|
function injectQueryState(html, state) {
|
|
1925
2380
|
if (state.queries.length === 0) {
|
|
@@ -2054,7 +2509,7 @@ function readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles)
|
|
|
2054
2509
|
return readFile(file, "utf8");
|
|
2055
2510
|
}
|
|
2056
2511
|
const key = `${serverModuleCacheVersion}:${file}`;
|
|
2057
|
-
const cached = serverSourceFileCache
|
|
2512
|
+
const cached = readRouterRuntimeCacheEntry(serverSourceFileCache, key, serverSourceFileCacheCounters);
|
|
2058
2513
|
if (cached !== undefined) {
|
|
2059
2514
|
return cached;
|
|
2060
2515
|
}
|
|
@@ -2062,17 +2517,20 @@ function readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles)
|
|
|
2062
2517
|
serverSourceFileCache.delete(key);
|
|
2063
2518
|
throw error;
|
|
2064
2519
|
});
|
|
2065
|
-
setBoundedCacheEntry(serverSourceFileCache, key, loaded, maxServerSourceFileCacheEntries);
|
|
2520
|
+
setBoundedCacheEntry(serverSourceFileCache, key, loaded, maxServerSourceFileCacheEntries, serverSourceFileCacheCounters);
|
|
2066
2521
|
return loaded;
|
|
2067
2522
|
}
|
|
2068
2523
|
function hashText(text) {
|
|
2069
2524
|
return createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
2070
2525
|
}
|
|
2071
|
-
function setBoundedCacheEntry(cache, key, value, maxEntries) {
|
|
2526
|
+
function setBoundedCacheEntry(cache, key, value, maxEntries, counters) {
|
|
2072
2527
|
if (cache.size >= maxEntries) {
|
|
2073
2528
|
const oldestKey = cache.keys().next().value;
|
|
2074
2529
|
if (oldestKey !== undefined) {
|
|
2075
2530
|
cache.delete(oldestKey);
|
|
2531
|
+
if (counters !== undefined) {
|
|
2532
|
+
counters.evictions += 1;
|
|
2533
|
+
}
|
|
2076
2534
|
}
|
|
2077
2535
|
}
|
|
2078
2536
|
cache.set(key, value);
|