@reckona/mreact-router 0.0.137 → 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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/dist/actions.d.ts +1 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +32 -12
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/static.js +10 -4
  7. package/dist/adapters/static.js.map +1 -1
  8. package/dist/build.d.ts.map +1 -1
  9. package/dist/build.js +28 -21
  10. package/dist/build.js.map +1 -1
  11. package/dist/cache.d.ts +2 -0
  12. package/dist/cache.d.ts.map +1 -1
  13. package/dist/cache.js +23 -1
  14. package/dist/cache.js.map +1 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +49 -12
  17. package/dist/client.js.map +1 -1
  18. package/dist/dev-server.d.ts +1 -0
  19. package/dist/dev-server.d.ts.map +1 -1
  20. package/dist/dev-server.js +6 -4
  21. package/dist/dev-server.js.map +1 -1
  22. package/dist/link.js +17 -3
  23. package/dist/link.js.map +1 -1
  24. package/dist/middleware.d.ts.map +1 -1
  25. package/dist/middleware.js +10 -7
  26. package/dist/middleware.js.map +1 -1
  27. package/dist/multipart.d.ts +5 -0
  28. package/dist/multipart.d.ts.map +1 -1
  29. package/dist/multipart.js +24 -1
  30. package/dist/multipart.js.map +1 -1
  31. package/dist/native-route-matcher.d.ts +9 -0
  32. package/dist/native-route-matcher.d.ts.map +1 -1
  33. package/dist/native-route-matcher.js +23 -11
  34. package/dist/native-route-matcher.js.map +1 -1
  35. package/dist/navigation.d.ts.map +1 -1
  36. package/dist/navigation.js +8 -1
  37. package/dist/navigation.js.map +1 -1
  38. package/dist/render.d.ts +5 -1
  39. package/dist/render.d.ts.map +1 -1
  40. package/dist/render.js +239 -95
  41. package/dist/render.js.map +1 -1
  42. package/dist/route-source.d.ts +3 -1
  43. package/dist/route-source.d.ts.map +1 -1
  44. package/dist/route-source.js +13 -11
  45. package/dist/route-source.js.map +1 -1
  46. package/dist/route-styles.d.ts +4 -0
  47. package/dist/route-styles.d.ts.map +1 -1
  48. package/dist/route-styles.js +15 -3
  49. package/dist/route-styles.js.map +1 -1
  50. package/dist/serve.d.ts.map +1 -1
  51. package/dist/serve.js +39 -9
  52. package/dist/serve.js.map +1 -1
  53. package/dist/server-action-inference.d.ts.map +1 -1
  54. package/dist/server-action-inference.js +57 -0
  55. package/dist/server-action-inference.js.map +1 -1
  56. package/dist/vite.d.ts.map +1 -1
  57. package/dist/vite.js +47 -11
  58. package/dist/vite.js.map +1 -1
  59. package/package.json +11 -11
  60. package/src/actions.ts +44 -10
  61. package/src/adapters/static.ts +11 -4
  62. package/src/build.ts +28 -17
  63. package/src/cache.ts +33 -1
  64. package/src/client.ts +49 -12
  65. package/src/dev-server.ts +11 -4
  66. package/src/link.ts +24 -3
  67. package/src/middleware.ts +12 -7
  68. package/src/multipart.ts +32 -1
  69. package/src/native-route-matcher.ts +34 -13
  70. package/src/navigation.ts +11 -2
  71. package/src/render.ts +321 -113
  72. package/src/route-source.ts +14 -10
  73. package/src/route-styles.ts +28 -3
  74. package/src/serve.ts +68 -15
  75. package/src/server-action-inference.ts +86 -0
  76. package/src/vite.ts +59 -12
@@ -142,7 +142,7 @@ export function mayUseAwaitBoundarySource(code: string): boolean {
142
142
 
143
143
  export function routeClosureMayUseAwaitBoundary(options: {
144
144
  filename: string;
145
- files: Record<string, string>;
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: importedSource,
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: Record<string, string>,
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
- if (files[candidate] !== undefined) {
258
- return candidate;
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
  }
@@ -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 shellFiles = (await existingRouteShellCandidates(options.appDir, options.pageFile, isFile))
24
- .map((candidate) => candidate.file);
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 { createRouteMatcher, type AppRoute, type RouteMatcher } from "./routes.js";
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
- request = middlewareResult.request;
440
- normalizedPath = normalizeRoutePath(new URL(request.url).pathname);
441
- matched = options.runtime.routeMatcher.match(normalizedPath);
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({ ...options, request });
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
- await loadBuiltServerModuleArtifactClosure(runtime, resolved, seen, kind);
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
- const importPattern =
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(importPattern)) {
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 & { runtime: BuiltRuntime },
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 & { runtime: BuiltRuntime },
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 { collectRouteCssHrefs, collectSpecialBoundaryFiles } from "./route-styles.js";
43
- import { scanAppRoutes } from "./routes.js";
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";
@@ -187,6 +191,11 @@ export function createAppRouterVitePlugin(options: AppRouterVitePluginOptions):
187
191
 
188
192
  return {
189
193
  optimizeDeps: {
194
+ // Every client-importable mreact package must resolve through the
195
+ // same dev module graph. Prebundling any of them inlines a second
196
+ // reactive-core (and devtools) copy into the deps chunk, which
197
+ // silently breaks cross-package cell tracking, and also bypasses the
198
+ // react/react-dom alias resolution this plugin provides.
190
199
  exclude: [
191
200
  "react",
192
201
  "react-dom",
@@ -195,6 +204,21 @@ export function createAppRouterVitePlugin(options: AppRouterVitePluginOptions):
195
204
  "react/jsx-dev-runtime",
196
205
  "react/jsx-runtime",
197
206
  "@reckona/mreact",
207
+ "@reckona/mreact-auth",
208
+ "@reckona/mreact-compat",
209
+ "@reckona/mreact-devtools",
210
+ "@reckona/mreact-dom",
211
+ "@reckona/mreact-forms",
212
+ "@reckona/mreact-next",
213
+ "@reckona/mreact-query",
214
+ "@reckona/mreact-reactive-core",
215
+ "@reckona/mreact-reactive-dom",
216
+ "@reckona/mreact-router",
217
+ "@reckona/mreact-scheduler",
218
+ "@reckona/mreact-shared",
219
+ "@reckona/mreact-store",
220
+ "@reckona/mreact-test-utils",
221
+ "@reckona/mreact-virtual",
198
222
  ],
199
223
  },
200
224
  };
@@ -501,6 +525,20 @@ async function handleAppRouterViteRequest(
501
525
 
502
526
  const request = nodeRequestToWebRequest(incoming, origin);
503
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
+ ]);
504
542
 
505
543
  await sendResponse(
506
544
  outgoing,
@@ -512,15 +550,14 @@ async function handleAppRouterViteRequest(
512
550
  allowedSourceDirs: project.allowedSourceDirs,
513
551
  projectRoot: project.projectRoot,
514
552
  },
515
- clientStyles: await devRouteStyles(project),
516
- clientStylesByFile: await devSpecialRouteStyles(project),
517
- navigationScripts: await devNavigationScripts(
518
- project.routesDir,
519
- options.clientRouteInferenceCache,
520
- routeTransformPlugins,
521
- ),
553
+ clientStyles,
554
+ clientStylesByFile,
555
+ clientRouteInferenceCache: options.clientRouteInferenceCache,
556
+ navigationScripts,
522
557
  request,
523
558
  routeCache: options.routeCache,
559
+ routeMatcher,
560
+ routes,
524
561
  serverActions: options.serverActions,
525
562
  vitePlugins: routeTransformPlugins,
526
563
  }),
@@ -776,9 +813,11 @@ function clientAssetBuildErrorResponse(filename: string, error: unknown): Respon
776
813
 
777
814
  async function devRouteStyles(
778
815
  project: ResolvedAppRouterProject,
816
+ routes: readonly AppRoute[],
817
+ readSource: (file: string) => Promise<string | undefined>,
779
818
  ): Promise<ReadonlyMap<string, readonly string[]>> {
780
819
  const entries = await Promise.all(
781
- (await scanAppRoutes({ appDir: project.routesDir })).map(async (route) => {
820
+ routes.map(async (route) => {
782
821
  if (route.kind !== "page") {
783
822
  return undefined;
784
823
  }
@@ -788,6 +827,7 @@ async function devRouteStyles(
788
827
  hrefPrefix: devCssPrefix,
789
828
  pageFile: route.file,
790
829
  projectRoot: project.projectRoot,
830
+ readSource,
791
831
  });
792
832
 
793
833
  return hrefs.length === 0 ? undefined : ([route.path, hrefs as readonly string[]] as const);
@@ -802,6 +842,7 @@ async function devRouteStyles(
802
842
 
803
843
  async function devSpecialRouteStyles(
804
844
  project: ResolvedAppRouterProject,
845
+ readSource: (file: string) => Promise<string | undefined>,
805
846
  ): Promise<ReadonlyMap<string, readonly string[]>> {
806
847
  const entries = await Promise.all(
807
848
  (await collectSpecialBoundaryFiles(project.routesDir)).map(async (file) => {
@@ -810,6 +851,7 @@ async function devSpecialRouteStyles(
810
851
  hrefPrefix: devCssPrefix,
811
852
  pageFile: file,
812
853
  projectRoot: project.projectRoot,
854
+ readSource,
813
855
  });
814
856
 
815
857
  return hrefs.length === 0 ? undefined : ([file, hrefs as readonly string[]] as const);
@@ -824,17 +866,22 @@ async function devSpecialRouteStyles(
824
866
 
825
867
  async function devNavigationScripts(
826
868
  appDir: string,
869
+ routes: readonly AppRoute[],
870
+ readSource: (file: string) => Promise<string | undefined>,
827
871
  inferenceCache?: ClientRouteInferenceCache | undefined,
828
872
  vitePlugins?: readonly PluginOption[] | undefined,
829
873
  ): Promise<ReadonlyMap<string, string>> {
830
874
  const cache = inferenceCache ?? createClientRouteInferenceCache();
831
875
  const entries = await Promise.all(
832
- (await scanAppRoutes({ appDir })).map(async (route) => {
876
+ routes.map(async (route) => {
833
877
  if (route.kind !== "page") {
834
878
  return undefined;
835
879
  }
836
880
 
837
- const source = await readFile(route.file, "utf8");
881
+ const source = await readSource(route.file);
882
+ if (source === undefined) {
883
+ return undefined;
884
+ }
838
885
  const navigation = await resolveNavigationRuntime({
839
886
  appDir,
840
887
  cache,