@reckona/mreact-router 0.0.138 → 0.0.139
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 +1 -1
- package/dist/actions.d.ts +1 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +32 -12
- package/dist/actions.js.map +1 -1
- package/dist/adapters/static.js +10 -4
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +28 -21
- package/dist/build.js.map +1 -1
- package/dist/cache.d.ts +2 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +23 -1
- package/dist/cache.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +49 -12
- package/dist/client.js.map +1 -1
- package/dist/dev-server.d.ts +1 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +6 -4
- package/dist/dev-server.js.map +1 -1
- package/dist/link.js +17 -3
- package/dist/link.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +10 -7
- package/dist/middleware.js.map +1 -1
- package/dist/multipart.d.ts +5 -0
- package/dist/multipart.d.ts.map +1 -1
- package/dist/multipart.js +24 -1
- package/dist/multipart.js.map +1 -1
- package/dist/native-route-matcher.d.ts +9 -0
- package/dist/native-route-matcher.d.ts.map +1 -1
- package/dist/native-route-matcher.js +23 -11
- package/dist/native-route-matcher.js.map +1 -1
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +8 -1
- package/dist/navigation.js.map +1 -1
- package/dist/render.d.ts +5 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +239 -95
- package/dist/render.js.map +1 -1
- package/dist/route-source.d.ts +3 -1
- package/dist/route-source.d.ts.map +1 -1
- package/dist/route-source.js +13 -11
- package/dist/route-source.js.map +1 -1
- package/dist/route-styles.d.ts +4 -0
- package/dist/route-styles.d.ts.map +1 -1
- package/dist/route-styles.js +15 -3
- package/dist/route-styles.js.map +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +39 -9
- package/dist/serve.js.map +1 -1
- package/dist/server-action-inference.d.ts.map +1 -1
- package/dist/server-action-inference.js +57 -0
- package/dist/server-action-inference.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +27 -11
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +44 -10
- package/src/adapters/static.ts +11 -4
- package/src/build.ts +28 -17
- package/src/cache.ts +33 -1
- package/src/client.ts +49 -12
- package/src/dev-server.ts +11 -4
- package/src/link.ts +24 -3
- package/src/middleware.ts +12 -7
- package/src/multipart.ts +32 -1
- package/src/native-route-matcher.ts +34 -13
- package/src/navigation.ts +11 -2
- package/src/render.ts +321 -113
- package/src/route-source.ts +14 -10
- package/src/route-styles.ts +28 -3
- package/src/serve.ts +68 -15
- package/src/server-action-inference.ts +86 -0
- package/src/vite.ts +39 -12
package/src/route-source.ts
CHANGED
|
@@ -142,7 +142,7 @@ export function mayUseAwaitBoundarySource(code: string): boolean {
|
|
|
142
142
|
|
|
143
143
|
export function routeClosureMayUseAwaitBoundary(options: {
|
|
144
144
|
filename: string;
|
|
145
|
-
files:
|
|
145
|
+
files: RouteSourceLookup;
|
|
146
146
|
projectRoot: string;
|
|
147
147
|
seen?: Set<string> | undefined;
|
|
148
148
|
source: string;
|
|
@@ -184,16 +184,13 @@ export function routeClosureMayUseAwaitBoundary(options: {
|
|
|
184
184
|
continue;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
const importedSource = options.files[resolved];
|
|
188
|
-
|
|
189
187
|
if (
|
|
190
|
-
importedSource !== undefined &&
|
|
191
188
|
routeClosureMayUseAwaitBoundary({
|
|
192
|
-
filename: resolved,
|
|
189
|
+
filename: resolved.filename,
|
|
193
190
|
files: options.files,
|
|
194
191
|
projectRoot: options.projectRoot,
|
|
195
192
|
seen,
|
|
196
|
-
source:
|
|
193
|
+
source: resolved.source,
|
|
197
194
|
})
|
|
198
195
|
) {
|
|
199
196
|
return true;
|
|
@@ -243,10 +240,10 @@ function isRenderedStaticImportReference(
|
|
|
243
240
|
}
|
|
244
241
|
|
|
245
242
|
function resolveLocalSourceImport(
|
|
246
|
-
files:
|
|
243
|
+
files: RouteSourceLookup,
|
|
247
244
|
importer: string,
|
|
248
245
|
specifier: string,
|
|
249
|
-
): string | undefined {
|
|
246
|
+
): { filename: string; source: string } | undefined {
|
|
250
247
|
if (!specifier.startsWith(".")) {
|
|
251
248
|
return undefined;
|
|
252
249
|
}
|
|
@@ -254,14 +251,21 @@ function resolveLocalSourceImport(
|
|
|
254
251
|
const base = join(dirname(importer), specifier);
|
|
255
252
|
|
|
256
253
|
for (const candidate of sourceModuleCandidates(base)) {
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
const source = readRouteSourceLookup(files, candidate);
|
|
255
|
+
if (source !== undefined) {
|
|
256
|
+
return { filename: candidate, source };
|
|
259
257
|
}
|
|
260
258
|
}
|
|
261
259
|
|
|
262
260
|
return undefined;
|
|
263
261
|
}
|
|
264
262
|
|
|
263
|
+
type RouteSourceLookup = Record<string, string> | ((file: string) => string | undefined);
|
|
264
|
+
|
|
265
|
+
function readRouteSourceLookup(files: RouteSourceLookup, file: string): string | undefined {
|
|
266
|
+
return typeof files === "function" ? files(file) : files[file];
|
|
267
|
+
}
|
|
268
|
+
|
|
265
269
|
function sourceFilenameForCompiler(projectRoot: string, filename: string): string {
|
|
266
270
|
return isAbsolute(filename) ? filename : join(projectRoot, filename);
|
|
267
271
|
}
|
package/src/route-styles.ts
CHANGED
|
@@ -7,21 +7,27 @@ export async function collectRouteCssFiles(options: {
|
|
|
7
7
|
appDir: string;
|
|
8
8
|
pageFile: string;
|
|
9
9
|
projectRoot: string;
|
|
10
|
+
readSource?: ((file: string) => Promise<string | undefined> | string | undefined) | undefined;
|
|
10
11
|
}): Promise<string[]> {
|
|
11
12
|
return await collectRouteCssFilesFromSources({
|
|
12
13
|
...options,
|
|
13
|
-
readSource: (file) => readFile(file, "utf8"),
|
|
14
|
+
readSource: options.readSource ?? ((file) => readFile(file, "utf8")),
|
|
14
15
|
});
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export async function collectRouteCssFilesFromSources(options: {
|
|
18
19
|
appDir: string;
|
|
20
|
+
isFile?: ((file: string) => Promise<boolean> | boolean) | undefined;
|
|
19
21
|
pageFile: string;
|
|
20
22
|
projectRoot: string;
|
|
21
23
|
readSource: (file: string) => Promise<string | undefined> | string | undefined;
|
|
22
24
|
}): Promise<string[]> {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
+
const fileExists = options.isFile ?? isFile;
|
|
26
|
+
const shellFiles = (
|
|
27
|
+
await existingRouteShellCandidates(options.appDir, options.pageFile, async (file) =>
|
|
28
|
+
fileExists(file),
|
|
29
|
+
)
|
|
30
|
+
).map((candidate) => candidate.file);
|
|
25
31
|
const files = [...shellFiles, options.pageFile];
|
|
26
32
|
const seen = new Set<string>();
|
|
27
33
|
const cssFiles: string[] = [];
|
|
@@ -57,6 +63,7 @@ export async function collectRouteCssHrefs(options: {
|
|
|
57
63
|
hrefPrefix?: string | undefined;
|
|
58
64
|
pageFile: string;
|
|
59
65
|
projectRoot: string;
|
|
66
|
+
readSource?: ((file: string) => Promise<string | undefined> | string | undefined) | undefined;
|
|
60
67
|
}): Promise<string[]> {
|
|
61
68
|
return (await collectRouteCssFiles(options)).map((file) => {
|
|
62
69
|
const href = `/${relative(options.projectRoot, file).split(sep).join("/")}`;
|
|
@@ -65,6 +72,24 @@ export async function collectRouteCssHrefs(options: {
|
|
|
65
72
|
});
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
export function createCachedRouteSourceReader(
|
|
76
|
+
readSource: (file: string) => Promise<string | undefined> | string | undefined = (file) =>
|
|
77
|
+
readFile(file, "utf8"),
|
|
78
|
+
): (file: string) => Promise<string | undefined> {
|
|
79
|
+
const cache = new Map<string, Promise<string | undefined>>();
|
|
80
|
+
|
|
81
|
+
return (file) => {
|
|
82
|
+
const cached = cache.get(file);
|
|
83
|
+
if (cached !== undefined) {
|
|
84
|
+
return cached;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const loaded = Promise.resolve(readSource(file));
|
|
88
|
+
cache.set(file, loaded);
|
|
89
|
+
return loaded;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
68
93
|
export async function collectSpecialBoundaryFiles(directory: string): Promise<string[]> {
|
|
69
94
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
70
95
|
const files: string[] = [];
|
package/src/serve.ts
CHANGED
|
@@ -9,7 +9,12 @@ import type {
|
|
|
9
9
|
} from "./build.js";
|
|
10
10
|
import type { AppRouterCache } from "./cache.js";
|
|
11
11
|
import type { ClientRouteManifestEntry } from "./client-route-inference.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
createRouteMatcher,
|
|
14
|
+
type AppRoute,
|
|
15
|
+
type MatchedRoute,
|
|
16
|
+
type RouteMatcher,
|
|
17
|
+
} from "./routes.js";
|
|
13
18
|
import type { AppRouterServerActionOptions } from "./actions.js";
|
|
14
19
|
import type { AppRouterImportPolicy } from "./import-policy.js";
|
|
15
20
|
import {
|
|
@@ -76,6 +81,7 @@ interface BuiltRuntime {
|
|
|
76
81
|
>;
|
|
77
82
|
serverActionManifest?: readonly { moduleId: string; exportName: string; inferred?: boolean }[] | undefined;
|
|
78
83
|
serverModuleArtifactLoads: Map<string, Promise<void>>;
|
|
84
|
+
serverModuleClosureFiles: Map<string, readonly string[]>;
|
|
79
85
|
serverModuleFiles: ReadonlyMap<string, string>;
|
|
80
86
|
serverModuleRenderFiles: ReadonlyMap<string, string>;
|
|
81
87
|
serverModuleRequestFiles: ReadonlyMap<string, string>;
|
|
@@ -436,9 +442,11 @@ async function renderBuiltAppRequestWithRuntime(
|
|
|
436
442
|
emitBuiltRenderTiming(options, request, timing, middlewareResult.response.status);
|
|
437
443
|
return middlewareResult.response;
|
|
438
444
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
445
|
+
if (middlewareResult.request !== request) {
|
|
446
|
+
request = middlewareResult.request;
|
|
447
|
+
normalizedPath = normalizeRoutePath(new URL(request.url).pathname);
|
|
448
|
+
matched = options.runtime.routeMatcher.match(normalizedPath);
|
|
449
|
+
}
|
|
442
450
|
|
|
443
451
|
if (request.method === "GET" && options.runtime.prerenderableRoutes.has(normalizedPath)) {
|
|
444
452
|
await loadBuiltServerModuleArtifactsForRequest(options.runtime, matched?.route.file, {
|
|
@@ -450,7 +458,12 @@ async function renderBuiltAppRequestWithRuntime(
|
|
|
450
458
|
await loadBuiltServerModuleArtifactsForRequest(options.runtime, matched?.route.file, {
|
|
451
459
|
includeRender: false,
|
|
452
460
|
});
|
|
453
|
-
const response = await renderBuiltDynamicResponse({
|
|
461
|
+
const response = await renderBuiltDynamicResponse({
|
|
462
|
+
...options,
|
|
463
|
+
matchedRoute: matched,
|
|
464
|
+
request,
|
|
465
|
+
requestUrl: new URL(request.url),
|
|
466
|
+
});
|
|
454
467
|
|
|
455
468
|
await applyBuiltPrerenderInvalidations(
|
|
456
469
|
options.runtime,
|
|
@@ -1002,6 +1015,7 @@ async function materializeBuiltRuntime(options: {
|
|
|
1002
1015
|
? {}
|
|
1003
1016
|
: { serverActionManifest: serverManifest.serverActionManifest }),
|
|
1004
1017
|
serverModuleArtifactLoads: new Map(),
|
|
1018
|
+
serverModuleClosureFiles: new Map(),
|
|
1005
1019
|
serverModuleFiles,
|
|
1006
1020
|
serverModuleRenderFiles,
|
|
1007
1021
|
serverModuleRequestFiles,
|
|
@@ -1273,34 +1287,63 @@ async function loadBuiltServerModuleArtifactClosure(
|
|
|
1273
1287
|
seen: Set<string>,
|
|
1274
1288
|
kind: BuiltServerModuleArtifactKind,
|
|
1275
1289
|
): Promise<void> {
|
|
1290
|
+
for (const closureFile of builtServerModuleClosureFiles(runtime, file)) {
|
|
1291
|
+
if (seen.has(closureFile)) {
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
seen.add(closureFile);
|
|
1295
|
+
await loadBuiltServerModuleArtifact(runtime, closureFile, kind);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function builtServerModuleClosureFiles(
|
|
1300
|
+
runtime: BuiltRuntime,
|
|
1301
|
+
file: string,
|
|
1302
|
+
): readonly string[] {
|
|
1303
|
+
const cached = runtime.serverModuleClosureFiles.get(file);
|
|
1304
|
+
if (cached !== undefined) {
|
|
1305
|
+
return cached;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const closure: string[] = [];
|
|
1309
|
+
collectBuiltServerModuleClosureFiles(runtime, file, new Set(), closure);
|
|
1310
|
+
runtime.serverModuleClosureFiles.set(file, closure);
|
|
1311
|
+
return closure;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function collectBuiltServerModuleClosureFiles(
|
|
1315
|
+
runtime: BuiltRuntime,
|
|
1316
|
+
file: string,
|
|
1317
|
+
seen: Set<string>,
|
|
1318
|
+
closure: string[],
|
|
1319
|
+
): void {
|
|
1276
1320
|
if (seen.has(file)) {
|
|
1277
1321
|
return;
|
|
1278
1322
|
}
|
|
1279
1323
|
seen.add(file);
|
|
1280
|
-
|
|
1281
|
-
await loadBuiltServerModuleArtifact(runtime, file, kind);
|
|
1324
|
+
closure.push(file);
|
|
1282
1325
|
|
|
1283
1326
|
const source = runtime.serverSourceFiles.get(file);
|
|
1284
|
-
|
|
1285
1327
|
if (source === undefined) {
|
|
1286
1328
|
return;
|
|
1287
1329
|
}
|
|
1288
1330
|
|
|
1289
1331
|
for (const specifier of localServerModuleSpecifiers(source)) {
|
|
1290
1332
|
const resolved = resolveBuiltLocalServerSourceImport(runtime, file, specifier);
|
|
1291
|
-
|
|
1292
1333
|
if (resolved !== undefined) {
|
|
1293
|
-
|
|
1334
|
+
collectBuiltServerModuleClosureFiles(runtime, resolved, seen, closure);
|
|
1294
1335
|
}
|
|
1295
1336
|
}
|
|
1296
1337
|
}
|
|
1297
1338
|
|
|
1339
|
+
const localServerModuleImportPattern =
|
|
1340
|
+
/\b(?:import|export)\s+(?:type\s+)?(?:[^"']*?\s+from\s*)?["'](?<source>\.{1,2}\/[^"']+)["']/g;
|
|
1341
|
+
|
|
1298
1342
|
function localServerModuleSpecifiers(code: string): string[] {
|
|
1299
1343
|
const specifiers = new Set<string>();
|
|
1300
|
-
|
|
1301
|
-
/\b(?:import|export)\s+(?:type\s+)?(?:[^"']*?\s+from\s*)?["'](?<source>\.{1,2}\/[^"']+)["']/g;
|
|
1344
|
+
localServerModuleImportPattern.lastIndex = 0;
|
|
1302
1345
|
|
|
1303
|
-
for (const match of code.matchAll(
|
|
1346
|
+
for (const match of code.matchAll(localServerModuleImportPattern)) {
|
|
1304
1347
|
const source = match.groups?.source;
|
|
1305
1348
|
|
|
1306
1349
|
if (source !== undefined) {
|
|
@@ -1380,13 +1423,21 @@ async function readPrerenderedRoute(
|
|
|
1380
1423
|
}
|
|
1381
1424
|
|
|
1382
1425
|
function renderBuiltDynamicResponse(
|
|
1383
|
-
options: RenderBuiltAppRequestOptions & {
|
|
1426
|
+
options: RenderBuiltAppRequestOptions & {
|
|
1427
|
+
matchedRoute?: MatchedRoute | undefined;
|
|
1428
|
+
requestUrl?: URL | undefined;
|
|
1429
|
+
runtime: BuiltRuntime;
|
|
1430
|
+
},
|
|
1384
1431
|
): Promise<Response> {
|
|
1385
1432
|
return renderAppRequest(builtRenderAppRequestOptions(options));
|
|
1386
1433
|
}
|
|
1387
1434
|
|
|
1388
1435
|
function builtRenderAppRequestOptions(
|
|
1389
|
-
options: RenderBuiltAppRequestOptions & {
|
|
1436
|
+
options: RenderBuiltAppRequestOptions & {
|
|
1437
|
+
matchedRoute?: MatchedRoute | undefined;
|
|
1438
|
+
requestUrl?: URL | undefined;
|
|
1439
|
+
runtime: BuiltRuntime;
|
|
1440
|
+
},
|
|
1390
1441
|
): RenderAppRequestOptions {
|
|
1391
1442
|
const renderOptions: RenderAppRequestOptions & {
|
|
1392
1443
|
__mreactLoadServerRenderArtifacts?: ((routeFile: string) => Promise<void>) | undefined;
|
|
@@ -1404,9 +1455,11 @@ function builtRenderAppRequestOptions(
|
|
|
1404
1455
|
request: options.request,
|
|
1405
1456
|
instrumentation: options.instrumentation,
|
|
1406
1457
|
logger: options.logger,
|
|
1458
|
+
matchedRoute: options.matchedRoute,
|
|
1407
1459
|
navigationScripts: options.runtime.navigationScripts,
|
|
1408
1460
|
routeCache: options.routeCache,
|
|
1409
1461
|
routeMatcher: options.runtime.routeMatcher,
|
|
1462
|
+
requestUrl: options.requestUrl,
|
|
1410
1463
|
routes: options.runtime.routes,
|
|
1411
1464
|
__mreactLoadServerRenderArtifacts: async (routeFile: string) => {
|
|
1412
1465
|
await loadBuiltServerModuleArtifactsForRequest(options.runtime, routeFile, {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
2
3
|
import { dirname, relative, sep } from "node:path";
|
|
3
4
|
import {
|
|
4
5
|
analyzeBoundaryGraph,
|
|
@@ -10,6 +11,8 @@ import type * as Ts from "typescript";
|
|
|
10
11
|
|
|
11
12
|
let ts = undefined as unknown as typeof Ts;
|
|
12
13
|
let typescriptLoaded = false;
|
|
14
|
+
const runtimeInferenceCache = new Map<string, RuntimeInferenceCacheEntry>();
|
|
15
|
+
const maxRuntimeInferenceCacheEntries = 512;
|
|
13
16
|
|
|
14
17
|
export interface InferredServerActionReference {
|
|
15
18
|
exportName: string;
|
|
@@ -63,6 +66,16 @@ export async function collectRuntimeInferredServerActions(options: {
|
|
|
63
66
|
return { diagnostics: [], references: new Map() };
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
const sourceHash = formActionSourceHash(options.code);
|
|
70
|
+
const cacheKey = `${options.appDir}\0${options.pageFile}\0${sourceHash}`;
|
|
71
|
+
const cached = runtimeInferenceCache.get(cacheKey);
|
|
72
|
+
if (cached !== undefined && (await runtimeInferenceCacheEntryIsFresh(cached))) {
|
|
73
|
+
return {
|
|
74
|
+
diagnostics: cached.diagnostics.map((diagnostic) => ({ ...diagnostic })),
|
|
75
|
+
references: new Map(cached.references),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
await loadTypeScript();
|
|
67
80
|
const program = ts.createProgram({
|
|
68
81
|
host: createRuntimeProgramHost({
|
|
@@ -121,6 +134,17 @@ export async function collectRuntimeInferredServerActions(options: {
|
|
|
121
134
|
}
|
|
122
135
|
}
|
|
123
136
|
|
|
137
|
+
const dependencies = await runtimeProgramDependencyStats({
|
|
138
|
+
appDir: options.appDir,
|
|
139
|
+
pageFile: options.pageFile,
|
|
140
|
+
program,
|
|
141
|
+
});
|
|
142
|
+
setRuntimeInferenceCacheEntry(cacheKey, {
|
|
143
|
+
dependencies,
|
|
144
|
+
diagnostics: diagnostics.map((diagnostic) => ({ ...diagnostic })),
|
|
145
|
+
references: new Map(references),
|
|
146
|
+
});
|
|
147
|
+
|
|
124
148
|
return { diagnostics, references };
|
|
125
149
|
}
|
|
126
150
|
|
|
@@ -792,6 +816,67 @@ function createRuntimeProgramHost(options: {
|
|
|
792
816
|
};
|
|
793
817
|
}
|
|
794
818
|
|
|
819
|
+
interface RuntimeInferenceCacheEntry {
|
|
820
|
+
dependencies: readonly RuntimeInferenceDependency[];
|
|
821
|
+
diagnostics: readonly ServerActionInferenceDiagnostic[];
|
|
822
|
+
references: ReadonlyMap<string, InferredServerActionReference>;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
interface RuntimeInferenceDependency {
|
|
826
|
+
file: string;
|
|
827
|
+
mtimeMs: number;
|
|
828
|
+
size: number;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async function runtimeProgramDependencyStats(options: {
|
|
832
|
+
appDir: string;
|
|
833
|
+
pageFile: string;
|
|
834
|
+
program: Ts.Program;
|
|
835
|
+
}): Promise<RuntimeInferenceDependency[]> {
|
|
836
|
+
const dependencies = options.program
|
|
837
|
+
.getSourceFiles()
|
|
838
|
+
.map((sourceFile) => sourceFile.fileName)
|
|
839
|
+
.filter((file) => file !== options.pageFile && isInsideAppDir(options.appDir, file));
|
|
840
|
+
const uniqueDependencies = [...new Set(dependencies)];
|
|
841
|
+
|
|
842
|
+
return await Promise.all(
|
|
843
|
+
uniqueDependencies.map(async (file) => {
|
|
844
|
+
const stats = await stat(file);
|
|
845
|
+
return { file, mtimeMs: stats.mtimeMs, size: stats.size };
|
|
846
|
+
}),
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async function runtimeInferenceCacheEntryIsFresh(
|
|
851
|
+
entry: RuntimeInferenceCacheEntry,
|
|
852
|
+
): Promise<boolean> {
|
|
853
|
+
for (const dependency of entry.dependencies) {
|
|
854
|
+
try {
|
|
855
|
+
const stats = await stat(dependency.file);
|
|
856
|
+
if (stats.mtimeMs !== dependency.mtimeMs || stats.size !== dependency.size) {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
} catch {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return true;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function setRuntimeInferenceCacheEntry(
|
|
868
|
+
key: string,
|
|
869
|
+
entry: RuntimeInferenceCacheEntry,
|
|
870
|
+
): void {
|
|
871
|
+
if (runtimeInferenceCache.size >= maxRuntimeInferenceCacheEntries) {
|
|
872
|
+
const oldestKey = runtimeInferenceCache.keys().next().value;
|
|
873
|
+
if (oldestKey !== undefined) {
|
|
874
|
+
runtimeInferenceCache.delete(oldestKey);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
runtimeInferenceCache.set(key, entry);
|
|
878
|
+
}
|
|
879
|
+
|
|
795
880
|
function defaultCompilerOptions(): Ts.CompilerOptions {
|
|
796
881
|
return {
|
|
797
882
|
allowJs: true,
|
|
@@ -819,6 +904,7 @@ export function __readServerActionInferenceTypeScriptLoadedForTests(): boolean {
|
|
|
819
904
|
export function __resetServerActionInferenceTypeScriptForTests(): void {
|
|
820
905
|
ts = undefined as unknown as typeof Ts;
|
|
821
906
|
typescriptLoaded = false;
|
|
907
|
+
runtimeInferenceCache.clear();
|
|
822
908
|
}
|
|
823
909
|
|
|
824
910
|
function formActionOccurrenceKey(reference: {
|
package/src/vite.ts
CHANGED
|
@@ -39,8 +39,12 @@ import {
|
|
|
39
39
|
import { nodeRequestToWebRequest, sendResponse } from "./http.js";
|
|
40
40
|
import { renderAppRequest } from "./render.js";
|
|
41
41
|
import { stripRouteClientOnlyExports } from "./route-source.js";
|
|
42
|
-
import {
|
|
43
|
-
|
|
42
|
+
import {
|
|
43
|
+
collectRouteCssHrefs,
|
|
44
|
+
collectSpecialBoundaryFiles,
|
|
45
|
+
createCachedRouteSourceReader,
|
|
46
|
+
} from "./route-styles.js";
|
|
47
|
+
import { createRouteMatcher, scanAppRoutes, type AppRoute } from "./routes.js";
|
|
44
48
|
import { resolveRequestHost, type RequestHostPolicy } from "./serve.js";
|
|
45
49
|
import { hasJsxSyntax } from "./source-jsx.js";
|
|
46
50
|
import { workspacePackageFile } from "./workspace-packages.js";
|
|
@@ -521,6 +525,20 @@ async function handleAppRouterViteRequest(
|
|
|
521
525
|
|
|
522
526
|
const request = nodeRequestToWebRequest(incoming, origin);
|
|
523
527
|
const routeTransformPlugins = options.navigationScanVitePlugins ?? options.vitePlugins;
|
|
528
|
+
const routes = await scanAppRoutes({ appDir: project.routesDir });
|
|
529
|
+
const routeMatcher = createRouteMatcher(routes);
|
|
530
|
+
const readRouteSource = createCachedRouteSourceReader();
|
|
531
|
+
const [clientStyles, clientStylesByFile, navigationScripts] = await Promise.all([
|
|
532
|
+
devRouteStyles(project, routes, readRouteSource),
|
|
533
|
+
devSpecialRouteStyles(project, readRouteSource),
|
|
534
|
+
devNavigationScripts(
|
|
535
|
+
project.routesDir,
|
|
536
|
+
routes,
|
|
537
|
+
readRouteSource,
|
|
538
|
+
options.clientRouteInferenceCache,
|
|
539
|
+
routeTransformPlugins,
|
|
540
|
+
),
|
|
541
|
+
]);
|
|
524
542
|
|
|
525
543
|
await sendResponse(
|
|
526
544
|
outgoing,
|
|
@@ -532,15 +550,14 @@ async function handleAppRouterViteRequest(
|
|
|
532
550
|
allowedSourceDirs: project.allowedSourceDirs,
|
|
533
551
|
projectRoot: project.projectRoot,
|
|
534
552
|
},
|
|
535
|
-
clientStyles
|
|
536
|
-
clientStylesByFile
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
options.clientRouteInferenceCache,
|
|
540
|
-
routeTransformPlugins,
|
|
541
|
-
),
|
|
553
|
+
clientStyles,
|
|
554
|
+
clientStylesByFile,
|
|
555
|
+
clientRouteInferenceCache: options.clientRouteInferenceCache,
|
|
556
|
+
navigationScripts,
|
|
542
557
|
request,
|
|
543
558
|
routeCache: options.routeCache,
|
|
559
|
+
routeMatcher,
|
|
560
|
+
routes,
|
|
544
561
|
serverActions: options.serverActions,
|
|
545
562
|
vitePlugins: routeTransformPlugins,
|
|
546
563
|
}),
|
|
@@ -796,9 +813,11 @@ function clientAssetBuildErrorResponse(filename: string, error: unknown): Respon
|
|
|
796
813
|
|
|
797
814
|
async function devRouteStyles(
|
|
798
815
|
project: ResolvedAppRouterProject,
|
|
816
|
+
routes: readonly AppRoute[],
|
|
817
|
+
readSource: (file: string) => Promise<string | undefined>,
|
|
799
818
|
): Promise<ReadonlyMap<string, readonly string[]>> {
|
|
800
819
|
const entries = await Promise.all(
|
|
801
|
-
|
|
820
|
+
routes.map(async (route) => {
|
|
802
821
|
if (route.kind !== "page") {
|
|
803
822
|
return undefined;
|
|
804
823
|
}
|
|
@@ -808,6 +827,7 @@ async function devRouteStyles(
|
|
|
808
827
|
hrefPrefix: devCssPrefix,
|
|
809
828
|
pageFile: route.file,
|
|
810
829
|
projectRoot: project.projectRoot,
|
|
830
|
+
readSource,
|
|
811
831
|
});
|
|
812
832
|
|
|
813
833
|
return hrefs.length === 0 ? undefined : ([route.path, hrefs as readonly string[]] as const);
|
|
@@ -822,6 +842,7 @@ async function devRouteStyles(
|
|
|
822
842
|
|
|
823
843
|
async function devSpecialRouteStyles(
|
|
824
844
|
project: ResolvedAppRouterProject,
|
|
845
|
+
readSource: (file: string) => Promise<string | undefined>,
|
|
825
846
|
): Promise<ReadonlyMap<string, readonly string[]>> {
|
|
826
847
|
const entries = await Promise.all(
|
|
827
848
|
(await collectSpecialBoundaryFiles(project.routesDir)).map(async (file) => {
|
|
@@ -830,6 +851,7 @@ async function devSpecialRouteStyles(
|
|
|
830
851
|
hrefPrefix: devCssPrefix,
|
|
831
852
|
pageFile: file,
|
|
832
853
|
projectRoot: project.projectRoot,
|
|
854
|
+
readSource,
|
|
833
855
|
});
|
|
834
856
|
|
|
835
857
|
return hrefs.length === 0 ? undefined : ([file, hrefs as readonly string[]] as const);
|
|
@@ -844,17 +866,22 @@ async function devSpecialRouteStyles(
|
|
|
844
866
|
|
|
845
867
|
async function devNavigationScripts(
|
|
846
868
|
appDir: string,
|
|
869
|
+
routes: readonly AppRoute[],
|
|
870
|
+
readSource: (file: string) => Promise<string | undefined>,
|
|
847
871
|
inferenceCache?: ClientRouteInferenceCache | undefined,
|
|
848
872
|
vitePlugins?: readonly PluginOption[] | undefined,
|
|
849
873
|
): Promise<ReadonlyMap<string, string>> {
|
|
850
874
|
const cache = inferenceCache ?? createClientRouteInferenceCache();
|
|
851
875
|
const entries = await Promise.all(
|
|
852
|
-
|
|
876
|
+
routes.map(async (route) => {
|
|
853
877
|
if (route.kind !== "page") {
|
|
854
878
|
return undefined;
|
|
855
879
|
}
|
|
856
880
|
|
|
857
|
-
const source = await
|
|
881
|
+
const source = await readSource(route.file);
|
|
882
|
+
if (source === undefined) {
|
|
883
|
+
return undefined;
|
|
884
|
+
}
|
|
858
885
|
const navigation = await resolveNavigationRuntime({
|
|
859
886
|
appDir,
|
|
860
887
|
cache,
|