@reckona/mreact-router 0.0.180 → 0.0.181

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reckona/mreact-router",
3
- "version": "0.0.180",
3
+ "version": "0.0.181",
4
4
  "description": "File-system app router, SSR, actions, and deployment adapters for mreact.",
5
5
  "keywords": [
6
6
  "jsx",
@@ -105,20 +105,20 @@
105
105
  },
106
106
  "dependencies": {
107
107
  "typescript": "^6.0.3",
108
- "@reckona/mreact": "0.0.180",
109
- "@reckona/mreact-compat": "0.0.180",
110
- "@reckona/mreact-devtools": "0.0.180",
111
- "@reckona/mreact-compiler": "0.0.180",
112
- "@reckona/mreact-query": "0.0.180",
113
- "@reckona/mreact-reactive-core": "0.0.180",
114
- "@reckona/mreact-reactive-dom": "0.0.180",
115
- "@reckona/mreact-server": "0.0.180",
116
- "@reckona/mreact-shared": "0.0.180"
108
+ "@reckona/mreact": "0.0.181",
109
+ "@reckona/mreact-compat": "0.0.181",
110
+ "@reckona/mreact-compiler": "0.0.181",
111
+ "@reckona/mreact-query": "0.0.181",
112
+ "@reckona/mreact-devtools": "0.0.181",
113
+ "@reckona/mreact-reactive-core": "0.0.181",
114
+ "@reckona/mreact-reactive-dom": "0.0.181",
115
+ "@reckona/mreact-server": "0.0.181",
116
+ "@reckona/mreact-shared": "0.0.181"
117
117
  },
118
118
  "peerDependencies": {
119
119
  "vite": ">=8 <9"
120
120
  },
121
121
  "optionalDependencies": {
122
- "@reckona/mreact-router-native": "0.0.180"
122
+ "@reckona/mreact-router-native": "0.0.181"
123
123
  }
124
124
  }
@@ -28,10 +28,11 @@ import {
28
28
  requestLogFields,
29
29
  type AppRouterLogger,
30
30
  } from "../logger.js";
31
+ import { middlewareMatches, type MiddlewareModule } from "../middleware.js";
31
32
  import { normalizeRoutePath } from "../route-path.js";
32
33
  import type { AppRoute } from "../routes.js";
33
34
  import { contentSecurityPolicy } from "../csp.js";
34
- import { isNotFoundError } from "../navigation.js";
35
+ import { isNotFoundError, rewriteLocation } from "../navigation.js";
35
36
  import { validateRouteMetadata } from "../metadata.js";
36
37
  import { routeSecurityHeaders } from "../security-headers.js";
37
38
  import type { AppRouterPrerenderStore } from "../serve.js";
@@ -202,7 +203,9 @@ export interface CloudflareMetadataRouteContext {
202
203
  * Defines the default export accepted from a Cloudflare metadata route module.
203
204
  */
204
205
  export interface CloudflareMetadataRouteModule {
205
- default?: ((context: CloudflareMetadataRouteContext) => unknown | PromiseLike<unknown>) | undefined;
206
+ default?:
207
+ | ((context: CloudflareMetadataRouteContext) => unknown | PromiseLike<unknown>)
208
+ | undefined;
206
209
  }
207
210
 
208
211
  /**
@@ -219,7 +222,9 @@ export type CloudflareRouteModuleRegistryEntry<Env = unknown> =
219
222
  export type CloudflareRouteModuleRegistry<Env = unknown> = Record<
220
223
  string,
221
224
  | CloudflareRouteModuleRegistryEntry<Env>
222
- | (() => CloudflareRouteModuleRegistryEntry<Env> | PromiseLike<CloudflareRouteModuleRegistryEntry<Env>>)
225
+ | (() =>
226
+ | CloudflareRouteModuleRegistryEntry<Env>
227
+ | PromiseLike<CloudflareRouteModuleRegistryEntry<Env>>)
223
228
  >;
224
229
 
225
230
  /**
@@ -243,7 +248,9 @@ export interface CloudflareRouteModuleRendererOptions<Env = unknown> {
243
248
  export type CloudflareRouteModuleGlob<Env = unknown> = Record<
244
249
  string,
245
250
  | CloudflareRouteModuleRegistryEntry<Env>
246
- | (() => CloudflareRouteModuleRegistryEntry<Env> | PromiseLike<CloudflareRouteModuleRegistryEntry<Env>>)
251
+ | (() =>
252
+ | CloudflareRouteModuleRegistryEntry<Env>
253
+ | PromiseLike<CloudflareRouteModuleRegistryEntry<Env>>)
247
254
  >;
248
255
 
249
256
  /**
@@ -317,6 +324,7 @@ export interface CloudflarePrerenderStoreOptions {
317
324
  }
318
325
 
319
326
  const clientPrefix = "/_mreact/client/";
327
+ const cloudflareMiddlewareRouteModuleKey = "__middleware__";
320
328
  const defaultPrerenderCacheOrigin = "https://mreact.local";
321
329
  const defaultPrerenderCachePrefix = "/_mreact/prerender";
322
330
 
@@ -375,10 +383,7 @@ export function createCloudflareBuiltRequestHandler<Env = unknown>(
375
383
  return createCloudflareRequestHandler({
376
384
  ...options,
377
385
  render(request, context) {
378
- const matched = matchCloudflareRoute(
379
- sortedRoutes,
380
- new URL(request.url).pathname,
381
- );
386
+ const matched = matchCloudflareRoute(sortedRoutes, new URL(request.url).pathname);
382
387
 
383
388
  if (matched === undefined || options.renderRoute === undefined) {
384
389
  return new Response("Not Found", { status: 404 });
@@ -402,6 +407,12 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
402
407
  options: CloudflareRouteModuleRendererOptions<Env>,
403
408
  ): NonNullable<CloudflareBuiltRequestHandlerOptions<Env>["renderRoute"]> {
404
409
  return async (request, context) => {
410
+ const middlewareResult = await resolveCloudflareRouteModuleMiddleware(options.modules, request);
411
+ if (middlewareResult.type === "response") {
412
+ return middlewareResult.response;
413
+ }
414
+ request = middlewareResult.request;
415
+
405
416
  if (context.route.kind !== "server" && isCloudflareNavigationRequest(request)) {
406
417
  return cloudflareDocumentReloadNavigationResponse();
407
418
  }
@@ -460,7 +471,9 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
460
471
  data =
461
472
  pageModule.loader === undefined
462
473
  ? undefined
463
- : await runWithCloudflareQueryClient(queryClient, () => pageModule.loader!(loaderContext));
474
+ : await runWithCloudflareQueryClient(queryClient, () =>
475
+ pageModule.loader!(loaderContext),
476
+ );
464
477
  } catch (error) {
465
478
  if (error instanceof Response) {
466
479
  return error;
@@ -534,6 +547,39 @@ export function createCloudflareRouteModuleRenderer<Env = unknown>(
534
547
  };
535
548
  }
536
549
 
550
+ async function resolveCloudflareRouteModuleMiddleware<Env>(
551
+ modules: CloudflareRouteModuleRegistry<Env>,
552
+ request: Request,
553
+ ): Promise<{ request: Request; type: "continue" } | { response: Response; type: "response" }> {
554
+ const module = (await loadCloudflareRouteModule(modules, cloudflareMiddlewareRouteModuleKey)) as
555
+ | MiddlewareModule
556
+ | undefined;
557
+
558
+ if (module === undefined || !middlewareMatches(module.config, new URL(request.url).pathname)) {
559
+ return { request, type: "continue" };
560
+ }
561
+
562
+ const middleware = module.middleware ?? module.default;
563
+ if (typeof middleware !== "function") {
564
+ return { request, type: "continue" };
565
+ }
566
+
567
+ const response = await middleware(request);
568
+ if (!(response instanceof Response)) {
569
+ return { request, type: "continue" };
570
+ }
571
+
572
+ const location = rewriteLocation(response);
573
+ if (location !== undefined) {
574
+ return {
575
+ request: new Request(new URL(location, request.url), request),
576
+ type: "continue",
577
+ };
578
+ }
579
+
580
+ return { response, type: "response" };
581
+ }
582
+
537
583
  async function injectCloudflareQueryState<T extends Response | string>(
538
584
  document: T,
539
585
  state: DehydratedQueryClient,
@@ -605,7 +651,10 @@ async function runWithSerializedCloudflareQueryClient<T>(
605
651
  const current = new Promise<void>((resolve) => {
606
652
  releaseCurrent = resolve;
607
653
  });
608
- cloudflareQueryClientFallbackQueue = previous.then(() => current, () => current);
654
+ cloudflareQueryClientFallbackQueue = previous.then(
655
+ () => current,
656
+ () => current,
657
+ );
609
658
 
610
659
  await previous;
611
660
 
@@ -669,7 +718,9 @@ async function dispatchCloudflareServerRoute<Env>(
669
718
  context: CloudflareServerRouteContext<Env>,
670
719
  ): Promise<Response> {
671
720
  const handler =
672
- module[request.method as keyof CloudflareServerRouteModule<Env>] ?? module.ALL ?? module.default;
721
+ module[request.method as keyof CloudflareServerRouteModule<Env>] ??
722
+ module.ALL ??
723
+ module.default;
673
724
 
674
725
  if (typeof handler !== "function") {
675
726
  return new Response("Method Not Allowed", { status: 405 });
@@ -728,17 +779,17 @@ function selectCloudflarePageComponent<Data, Env>(
728
779
  return appExport as CloudflareRouteModuleComponent<Data, Env>;
729
780
  }
730
781
 
731
- const cloudflareRouteComponentExport = readCloudflareModuleExport(module, "CloudflareRouteComponent");
782
+ const cloudflareRouteComponentExport = readCloudflareModuleExport(
783
+ module,
784
+ "CloudflareRouteComponent",
785
+ );
732
786
  if (typeof cloudflareRouteComponentExport === "function") {
733
787
  return cloudflareRouteComponentExport as CloudflareRouteModuleComponent<Data, Env>;
734
788
  }
735
789
 
736
790
  for (const name of Object.keys(module)) {
737
791
  const value = readCloudflareModuleExport(module, name);
738
- if (
739
- !cloudflareReservedPageModuleExportNames.has(name) &&
740
- typeof value === "function"
741
- ) {
792
+ if (!cloudflareReservedPageModuleExportNames.has(name) && typeof value === "function") {
742
793
  return value as CloudflareRouteModuleComponent<Data, Env>;
743
794
  }
744
795
  }
@@ -759,7 +810,12 @@ function readCloudflareModuleExport(module: object, name: string): unknown {
759
810
  return descriptor.get?.call(module);
760
811
  }
761
812
 
762
- const cloudflareDiagnosticPageModuleExportNames = ["default", "App", "CloudflareRouteComponent", "slots"] as const;
813
+ const cloudflareDiagnosticPageModuleExportNames = [
814
+ "default",
815
+ "App",
816
+ "CloudflareRouteComponent",
817
+ "slots",
818
+ ] as const;
763
819
 
764
820
  // Fixed names + typeof only (never values) so the 500 response is useful without
765
821
  // listing app-specific export names. Accessors are not invoked.
@@ -848,9 +904,10 @@ async function dispatchCloudflareMetadataRoute(
848
904
  return new Response(body, {
849
905
  headers: {
850
906
  "cache-control": "public, max-age=3600",
851
- "content-type": typeof value === "string" && value.trimStart().startsWith("<svg")
852
- ? "image/svg+xml"
853
- : "application/octet-stream",
907
+ "content-type":
908
+ typeof value === "string" && value.trimStart().startsWith("<svg")
909
+ ? "image/svg+xml"
910
+ : "application/octet-stream",
854
911
  },
855
912
  });
856
913
  }
@@ -1142,7 +1199,10 @@ async function handleCloudflareRequest<Env>(
1142
1199
  return response;
1143
1200
  }
1144
1201
 
1145
- function isCloudflarePublicAssetPath(manifest: CloudflareClientManifest, pathname: string): boolean {
1202
+ function isCloudflarePublicAssetPath(
1203
+ manifest: CloudflareClientManifest,
1204
+ pathname: string,
1205
+ ): boolean {
1146
1206
  return (manifest.publicAssets ?? []).includes(pathname);
1147
1207
  }
1148
1208
 
@@ -1197,9 +1257,7 @@ function cacheControlHasDirective(cacheControl: string | null, directive: string
1197
1257
  }
1198
1258
 
1199
1259
  const normalizedDirective = directive.toLowerCase();
1200
- return cacheControl
1201
- .split(",")
1202
- .some((part) => part.trim().toLowerCase() === normalizedDirective);
1260
+ return cacheControl.split(",").some((part) => part.trim().toLowerCase() === normalizedDirective);
1203
1261
  }
1204
1262
 
1205
1263
  function prerenderedResponse(
@@ -1228,10 +1286,7 @@ function prerenderedResponse(
1228
1286
  });
1229
1287
  }
1230
1288
 
1231
- function cloudflareRouteRequiresModule(
1232
- route: AppRoute,
1233
- manifest: BuiltServerManifest,
1234
- ): boolean {
1289
+ function cloudflareRouteRequiresModule(route: AppRoute, manifest: BuiltServerManifest): boolean {
1235
1290
  return (
1236
1291
  route.kind === "metadata" ||
1237
1292
  route.kind === "server" ||
@@ -1245,17 +1300,11 @@ function cloudflareRouteGlobKeyMatchesRoute(key: string, routeFile: string): boo
1245
1300
  const normalizedKey = normalizeCloudflareRouteModulePath(key);
1246
1301
  const normalizedRoute = normalizeCloudflareRouteModulePath(routeFile);
1247
1302
 
1248
- return (
1249
- normalizedKey === normalizedRoute ||
1250
- normalizedKey.endsWith(`/${normalizedRoute}`)
1251
- );
1303
+ return normalizedKey === normalizedRoute || normalizedKey.endsWith(`/${normalizedRoute}`);
1252
1304
  }
1253
1305
 
1254
1306
  function normalizeCloudflareRouteModulePath(path: string): string {
1255
- const withoutPrefix = path
1256
- .replace(/\\/g, "/")
1257
- .replace(/^\.\//, "")
1258
- .replace(/^\/+/, "");
1307
+ const withoutPrefix = path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
1259
1308
 
1260
1309
  return withoutPrefix.replace(/\.(?:mjs|js|ts|tsx)$/, "");
1261
1310
  }
@@ -1277,8 +1326,7 @@ function matchCloudflareRoute(
1277
1326
 
1278
1327
  if (
1279
1328
  catchAllIndex !== -1 &&
1280
- pathSegments.length <
1281
- catchAllIndex + 1 + route.segments.length - catchAllIndex - 1
1329
+ pathSegments.length < catchAllIndex + 1 + route.segments.length - catchAllIndex - 1
1282
1330
  ) {
1283
1331
  continue;
1284
1332
  }
@@ -1375,7 +1423,9 @@ function matchCloudflareRoute(
1375
1423
  function compareCloudflareRoutes(a: AppRoute, b: AppRoute): number {
1376
1424
  const scoreDelta = routeSpecificityScore(b) - routeSpecificityScore(a);
1377
1425
 
1378
- return scoreDelta === 0 ? a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind) : scoreDelta;
1426
+ return scoreDelta === 0
1427
+ ? a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind)
1428
+ : scoreDelta;
1379
1429
  }
1380
1430
 
1381
1431
  function routeSpecificityScore(route: AppRoute): number {
@@ -1537,7 +1587,9 @@ function isCloudflareRouteMetadata(value: RouteMetadata | undefined): value is R
1537
1587
  return value !== undefined;
1538
1588
  }
1539
1589
 
1540
- function mergeCloudflareRouteMetadata(metadata: readonly RouteMetadata[]): RouteMetadata | undefined {
1590
+ function mergeCloudflareRouteMetadata(
1591
+ metadata: readonly RouteMetadata[],
1592
+ ): RouteMetadata | undefined {
1541
1593
  if (metadata.length === 0) {
1542
1594
  return undefined;
1543
1595
  }
@@ -1562,7 +1614,12 @@ function mergeCloudflareRouteMetadata(metadata: readonly RouteMetadata[]): Route
1562
1614
  ...(icons === undefined ? {} : { icons }),
1563
1615
  ...(openGraph === undefined && openGraphImages === undefined
1564
1616
  ? {}
1565
- : { openGraph: { ...openGraph, ...(openGraphImages === undefined ? {} : { images: openGraphImages }) } }),
1617
+ : {
1618
+ openGraph: {
1619
+ ...openGraph,
1620
+ ...(openGraphImages === undefined ? {} : { images: openGraphImages }),
1621
+ },
1622
+ }),
1566
1623
  };
1567
1624
 
1568
1625
  return mergedMetadata;
package/src/build.ts CHANGED
@@ -514,8 +514,8 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
514
514
  }),
515
515
  ),
516
516
  ]);
517
- const { artifacts: serverModules, sharedChunks: serverModuleSharedChunks } = await (
518
- shouldTrackBuildPhases === false
517
+ const { artifacts: serverModules, sharedChunks: serverModuleSharedChunks } =
518
+ await (shouldTrackBuildPhases === false
519
519
  ? buildServerModuleArtifacts({
520
520
  bundleRequestRuntimePackages: shouldBuildAwsLambda,
521
521
  bundleCache: new Map(),
@@ -547,8 +547,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
547
547
  serverTransformCache,
548
548
  vitePlugins,
549
549
  }),
550
- )
551
- );
550
+ ));
552
551
  const serverRoutes = routes.map((route) => ({
553
552
  ...route,
554
553
  file: relative(project.projectRoot, route.file),
@@ -626,10 +625,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
626
625
  ...(navigationRuntimeScript === undefined ? [] : [navigationRuntimeScript]),
627
626
  ]),
628
627
  ).sort();
629
- const serverModuleClosureFiles = buildServerModuleClosureManifest(
630
- serverModules,
631
- sourceAnalysis,
632
- );
628
+ const serverModuleClosureFiles = buildServerModuleClosureManifest(serverModules, sourceAnalysis);
633
629
  const prerenderedRoutes =
634
630
  shouldTrackBuildPhases === false
635
631
  ? await prerenderStaticRoutes({
@@ -713,9 +709,7 @@ export async function buildApp(options: BuildAppOptions): Promise<BuildAppResult
713
709
  ...(serverActionManifest.allowedActions.length === 0
714
710
  ? {}
715
711
  : { serverActionManifest: serverActionManifest.allowedActions }),
716
- ...(Object.keys(serverModuleClosureFiles).length === 0
717
- ? {}
718
- : { serverModuleClosureFiles }),
712
+ ...(Object.keys(serverModuleClosureFiles).length === 0 ? {} : { serverModuleClosureFiles }),
719
713
  ...(Object.keys(serverModuleArtifacts.files).length === 0
720
714
  ? {}
721
715
  : { serverModuleFiles: serverModuleArtifacts.files }),
@@ -1051,13 +1045,7 @@ function collectManifestServerModuleClosureFiles(
1051
1045
  for (const specifier of localManifestServerModuleSpecifiers(source)) {
1052
1046
  const resolved = resolveManifestLocalServerSourceImport(file, specifier, sourceFiles);
1053
1047
  if (resolved !== undefined) {
1054
- collectManifestServerModuleClosureFiles(
1055
- resolved,
1056
- sourceAnalysis,
1057
- sourceFiles,
1058
- seen,
1059
- closure,
1060
- );
1048
+ collectManifestServerModuleClosureFiles(resolved, sourceAnalysis, sourceFiles, seen, closure);
1061
1049
  }
1062
1050
  }
1063
1051
  }
@@ -2238,7 +2226,10 @@ async function buildServerModuleArtifacts(options: {
2238
2226
  project: ResolvedAppRouterProject;
2239
2227
  projectRoot: string;
2240
2228
  routes: readonly AppRoute[];
2241
- serverActionReferencesByFile: ReadonlyMap<string, readonly BuiltServerActionExpressionReference[]>;
2229
+ serverActionReferencesByFile: ReadonlyMap<
2230
+ string,
2231
+ readonly BuiltServerActionExpressionReference[]
2232
+ >;
2242
2233
  sourceAnalysis: BuildSourceAnalysisScope;
2243
2234
  serverTransformCache: ServerTransformCache;
2244
2235
  vitePlugins?: readonly PluginOption[] | undefined;
@@ -2323,11 +2314,7 @@ async function buildServerModuleArtifacts(options: {
2323
2314
  continue;
2324
2315
  }
2325
2316
 
2326
- if (
2327
- requestArtifactFiles.has(file) &&
2328
- route?.kind !== "server" &&
2329
- route?.kind !== "metadata"
2330
- ) {
2317
+ if (requestArtifactFiles.has(file) && route?.kind !== "server" && route?.kind !== "metadata") {
2331
2318
  requestBatchEntries.push({
2332
2319
  code: stripRouteRequestOnlyExports(source, absoluteFile),
2333
2320
  filename: absoluteFile,
@@ -3138,6 +3125,8 @@ interface RouteRequestModuleBatchOutput {
3138
3125
  sharedChunks: readonly SharedServerModuleChunk[];
3139
3126
  }
3140
3127
 
3128
+ const cloudflareMiddlewareRouteModuleKey = "__middleware__";
3129
+
3141
3130
  async function writeCloudflareRouteModules(options: {
3142
3131
  cacheDir?: string | undefined;
3143
3132
  cloudflareDir: string;
@@ -3195,6 +3184,26 @@ async function writeCloudflareRouteModules(options: {
3195
3184
  root: options.projectRoot,
3196
3185
  vitePlugins: options.vitePlugins,
3197
3186
  });
3187
+ const cloudflareMiddlewareFile = findCloudflareMiddlewareFile({
3188
+ files: options.files,
3189
+ projectRoot: options.projectRoot,
3190
+ routesDir: options.routesDir,
3191
+ });
3192
+ const middlewareRouteModules = await buildCloudflareMiddlewareModuleBatch({
3193
+ cacheDir: options.cacheDir,
3194
+ define: options.define,
3195
+ root: options.projectRoot,
3196
+ routes:
3197
+ cloudflareMiddlewareFile === undefined
3198
+ ? []
3199
+ : [
3200
+ {
3201
+ filename: cloudflareMiddlewareFile,
3202
+ routeId: cloudflareMiddlewareRouteModuleKey,
3203
+ },
3204
+ ],
3205
+ vitePlugins: options.vitePlugins,
3206
+ });
3198
3207
  const directComponentRoutes = await collectCloudflareDirectComponentRoutes({
3199
3208
  requiredRoutes,
3200
3209
  routesDir: options.routesDir,
@@ -3235,6 +3244,7 @@ async function writeCloudflareRouteModules(options: {
3235
3244
  writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, loaderRouteModules),
3236
3245
  writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, directComponentModules),
3237
3246
  writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, stringShellComponentModules),
3247
+ writeCloudflareBatchedRouteModuleChunks(options.cloudflareDir, middlewareRouteModules),
3238
3248
  ]);
3239
3249
 
3240
3250
  const registryEntries = await mapWithBuildConcurrency(
@@ -3354,6 +3364,7 @@ async function writeCloudflareRouteModules(options: {
3354
3364
 
3355
3365
  const registrySource = [
3356
3366
  `export const routeModules = {`,
3367
+ ...cloudflareMiddlewareRegistryEntries(middlewareRouteModules),
3357
3368
  ...registryEntries.map((entry) => ` ${entry},`),
3358
3369
  `};`,
3359
3370
  `export default routeModules;`,
@@ -3365,6 +3376,31 @@ async function writeCloudflareRouteModules(options: {
3365
3376
  return { registryFile: "route-modules.mjs" };
3366
3377
  }
3367
3378
 
3379
+ function findCloudflareMiddlewareFile(options: {
3380
+ files: Record<string, string>;
3381
+ projectRoot: string;
3382
+ routesDir: string;
3383
+ }): string | undefined {
3384
+ const relativeRoutesDir = relative(options.projectRoot, options.routesDir).replaceAll(sep, "/");
3385
+
3386
+ const relativeFile = ["middleware.ts", "middleware.mreact.ts"]
3387
+ .map((file) => (relativeRoutesDir === "" ? file : `${relativeRoutesDir}/${file}`))
3388
+ .find((file) => options.files[file] !== undefined);
3389
+
3390
+ return relativeFile === undefined ? undefined : join(options.projectRoot, relativeFile);
3391
+ }
3392
+
3393
+ function cloudflareMiddlewareRegistryEntries(modules: CloudflareBatchedRouteModules): string[] {
3394
+ const middleware = modules.entries.get(cloudflareMiddlewareRouteModuleKey);
3395
+ if (middleware === undefined) {
3396
+ return [];
3397
+ }
3398
+
3399
+ return [
3400
+ ` ${JSON.stringify(cloudflareMiddlewareRouteModuleKey)}: () => import(${JSON.stringify(`./${middleware.fileName}`)}),`,
3401
+ ];
3402
+ }
3403
+
3368
3404
  function cloudflarePageRouteFacadeModuleSource(componentImport: string): string {
3369
3405
  return `import * as componentModule from ${JSON.stringify(componentImport)};
3370
3406
 
@@ -4904,6 +4940,27 @@ async function buildCloudflareRouteLoaderModuleBatch(options: {
4904
4940
  });
4905
4941
  }
4906
4942
 
4943
+ async function buildCloudflareMiddlewareModuleBatch(options: {
4944
+ cacheDir?: string | undefined;
4945
+ define?: UserConfig["define"] | undefined;
4946
+ routes: readonly { filename: string; routeId: string }[];
4947
+ root: string;
4948
+ vitePlugins?: readonly PluginOption[] | undefined;
4949
+ }): Promise<CloudflareBatchedRouteModules> {
4950
+ return await bundleCloudflareModuleBatch({
4951
+ cacheDir: options.cacheDir,
4952
+ define: options.define,
4953
+ entries: options.routes.map((route) => ({
4954
+ code: cloudflareMiddlewareModuleEntry(route.filename),
4955
+ filename: `${route.filename}.mreact-cloudflare-middleware.js`,
4956
+ name: `${route.routeId}.middleware`,
4957
+ routeId: route.routeId,
4958
+ })),
4959
+ root: options.root,
4960
+ vitePlugins: options.vitePlugins,
4961
+ });
4962
+ }
4963
+
4907
4964
  export async function __buildCloudflareRouteLoaderModuleBatchForTests(options: {
4908
4965
  cacheDir?: string | undefined;
4909
4966
  define?: UserConfig["define"] | undefined;
@@ -5001,6 +5058,15 @@ function cloudflareRouteLoaderModuleEntry(filename: string): string {
5001
5058
  return `export { loader } from ${JSON.stringify(filename)};`;
5002
5059
  }
5003
5060
 
5061
+ function cloudflareMiddlewareModuleEntry(filename: string): string {
5062
+ return `import * as middlewareModule from ${JSON.stringify(filename)};
5063
+
5064
+ export const config = middlewareModule.config;
5065
+ export const middleware = middlewareModule.middleware;
5066
+ const defaultMiddleware = middlewareModule.default;
5067
+ export default defaultMiddleware;`;
5068
+ }
5069
+
5004
5070
  async function buildCloudflareRouteMetadataExportModule(options: {
5005
5071
  cacheDir?: string | undefined;
5006
5072
  define?: UserConfig["define"] | undefined;
@@ -5678,10 +5744,7 @@ async function writeClientRouteBundles(options: {
5678
5744
  await writeFile(join(options.clientDir, asset.fileName), source);
5679
5745
  }
5680
5746
 
5681
- const generatedAssets = new Set<string>([
5682
- ...routeCssAssets.assets,
5683
- ...specialCssAssets.assets,
5684
- ]);
5747
+ const generatedAssets = new Set<string>([...routeCssAssets.assets, ...specialCssAssets.assets]);
5685
5748
 
5686
5749
  for (const chunk of output.chunks) {
5687
5750
  generatedAssets.add(chunk.fileName);
@@ -5777,13 +5840,13 @@ async function writeSpecialRouteCssAssetBatches(options: {
5777
5840
 
5778
5841
  return batch.css.length === 0
5779
5842
  ? undefined
5780
- : ({
5843
+ : {
5781
5844
  assets: batch.assets,
5782
5845
  style: {
5783
5846
  css: batch.css,
5784
5847
  file: relative(options.appDir, file).split(sep).join("/"),
5785
5848
  } satisfies ClientStyleManifestEntry,
5786
- });
5849
+ };
5787
5850
  });
5788
5851
 
5789
5852
  return {