@reckona/mreact-router 0.0.179 → 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/dist/actions.d.ts +1 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +48 -7
- package/dist/actions.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +59 -18
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +65 -12
- package/dist/build.js.map +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +55 -1
- package/dist/cache.js.map +1 -1
- package/dist/cookies.d.ts.map +1 -1
- package/dist/cookies.js +14 -0
- package/dist/cookies.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +60 -10
- package/src/adapters/cloudflare.ts +105 -41
- package/src/build.ts +93 -30
- package/src/cache.ts +62 -1
- package/src/cookies.ts +14 -0
|
@@ -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?:
|
|
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
|
-
| (() =>
|
|
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
|
-
| (() =>
|
|
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, () =>
|
|
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,
|
|
@@ -574,11 +620,17 @@ async function runWithCloudflareQueryClient<T>(
|
|
|
574
620
|
queryClient: QueryClient,
|
|
575
621
|
fn: () => T,
|
|
576
622
|
): Promise<Awaited<T>> {
|
|
623
|
+
if (cloudflareQueryClientFallbackInstalled) {
|
|
624
|
+
installQueryAsyncStorage(cloudflareQueryClientStorage);
|
|
625
|
+
return await runWithSerializedCloudflareQueryClient(queryClient, fn);
|
|
626
|
+
}
|
|
627
|
+
|
|
577
628
|
try {
|
|
578
629
|
return await runWithQueryClient(queryClient, fn);
|
|
579
630
|
} catch (error) {
|
|
580
631
|
if (isQueryClientScopeUnavailableError(error)) {
|
|
581
632
|
installQueryAsyncStorage(cloudflareQueryClientStorage);
|
|
633
|
+
cloudflareQueryClientFallbackInstalled = true;
|
|
582
634
|
return await runWithSerializedCloudflareQueryClient(queryClient, fn);
|
|
583
635
|
}
|
|
584
636
|
|
|
@@ -588,6 +640,7 @@ async function runWithCloudflareQueryClient<T>(
|
|
|
588
640
|
|
|
589
641
|
const cloudflareQueryClientStorage = createCloudflareQueryClientStorage();
|
|
590
642
|
let cloudflareQueryClientFallbackQueue: Promise<void> = Promise.resolve();
|
|
643
|
+
let cloudflareQueryClientFallbackInstalled = false;
|
|
591
644
|
|
|
592
645
|
async function runWithSerializedCloudflareQueryClient<T>(
|
|
593
646
|
queryClient: QueryClient,
|
|
@@ -598,7 +651,10 @@ async function runWithSerializedCloudflareQueryClient<T>(
|
|
|
598
651
|
const current = new Promise<void>((resolve) => {
|
|
599
652
|
releaseCurrent = resolve;
|
|
600
653
|
});
|
|
601
|
-
cloudflareQueryClientFallbackQueue = previous.then(
|
|
654
|
+
cloudflareQueryClientFallbackQueue = previous.then(
|
|
655
|
+
() => current,
|
|
656
|
+
() => current,
|
|
657
|
+
);
|
|
602
658
|
|
|
603
659
|
await previous;
|
|
604
660
|
|
|
@@ -662,7 +718,9 @@ async function dispatchCloudflareServerRoute<Env>(
|
|
|
662
718
|
context: CloudflareServerRouteContext<Env>,
|
|
663
719
|
): Promise<Response> {
|
|
664
720
|
const handler =
|
|
665
|
-
module[request.method as keyof CloudflareServerRouteModule<Env>] ??
|
|
721
|
+
module[request.method as keyof CloudflareServerRouteModule<Env>] ??
|
|
722
|
+
module.ALL ??
|
|
723
|
+
module.default;
|
|
666
724
|
|
|
667
725
|
if (typeof handler !== "function") {
|
|
668
726
|
return new Response("Method Not Allowed", { status: 405 });
|
|
@@ -721,17 +779,17 @@ function selectCloudflarePageComponent<Data, Env>(
|
|
|
721
779
|
return appExport as CloudflareRouteModuleComponent<Data, Env>;
|
|
722
780
|
}
|
|
723
781
|
|
|
724
|
-
const cloudflareRouteComponentExport = readCloudflareModuleExport(
|
|
782
|
+
const cloudflareRouteComponentExport = readCloudflareModuleExport(
|
|
783
|
+
module,
|
|
784
|
+
"CloudflareRouteComponent",
|
|
785
|
+
);
|
|
725
786
|
if (typeof cloudflareRouteComponentExport === "function") {
|
|
726
787
|
return cloudflareRouteComponentExport as CloudflareRouteModuleComponent<Data, Env>;
|
|
727
788
|
}
|
|
728
789
|
|
|
729
790
|
for (const name of Object.keys(module)) {
|
|
730
791
|
const value = readCloudflareModuleExport(module, name);
|
|
731
|
-
if (
|
|
732
|
-
!cloudflareReservedPageModuleExportNames.has(name) &&
|
|
733
|
-
typeof value === "function"
|
|
734
|
-
) {
|
|
792
|
+
if (!cloudflareReservedPageModuleExportNames.has(name) && typeof value === "function") {
|
|
735
793
|
return value as CloudflareRouteModuleComponent<Data, Env>;
|
|
736
794
|
}
|
|
737
795
|
}
|
|
@@ -752,7 +810,12 @@ function readCloudflareModuleExport(module: object, name: string): unknown {
|
|
|
752
810
|
return descriptor.get?.call(module);
|
|
753
811
|
}
|
|
754
812
|
|
|
755
|
-
const cloudflareDiagnosticPageModuleExportNames = [
|
|
813
|
+
const cloudflareDiagnosticPageModuleExportNames = [
|
|
814
|
+
"default",
|
|
815
|
+
"App",
|
|
816
|
+
"CloudflareRouteComponent",
|
|
817
|
+
"slots",
|
|
818
|
+
] as const;
|
|
756
819
|
|
|
757
820
|
// Fixed names + typeof only (never values) so the 500 response is useful without
|
|
758
821
|
// listing app-specific export names. Accessors are not invoked.
|
|
@@ -841,9 +904,10 @@ async function dispatchCloudflareMetadataRoute(
|
|
|
841
904
|
return new Response(body, {
|
|
842
905
|
headers: {
|
|
843
906
|
"cache-control": "public, max-age=3600",
|
|
844
|
-
"content-type":
|
|
845
|
-
|
|
846
|
-
|
|
907
|
+
"content-type":
|
|
908
|
+
typeof value === "string" && value.trimStart().startsWith("<svg")
|
|
909
|
+
? "image/svg+xml"
|
|
910
|
+
: "application/octet-stream",
|
|
847
911
|
},
|
|
848
912
|
});
|
|
849
913
|
}
|
|
@@ -1135,7 +1199,10 @@ async function handleCloudflareRequest<Env>(
|
|
|
1135
1199
|
return response;
|
|
1136
1200
|
}
|
|
1137
1201
|
|
|
1138
|
-
function isCloudflarePublicAssetPath(
|
|
1202
|
+
function isCloudflarePublicAssetPath(
|
|
1203
|
+
manifest: CloudflareClientManifest,
|
|
1204
|
+
pathname: string,
|
|
1205
|
+
): boolean {
|
|
1139
1206
|
return (manifest.publicAssets ?? []).includes(pathname);
|
|
1140
1207
|
}
|
|
1141
1208
|
|
|
@@ -1190,9 +1257,7 @@ function cacheControlHasDirective(cacheControl: string | null, directive: string
|
|
|
1190
1257
|
}
|
|
1191
1258
|
|
|
1192
1259
|
const normalizedDirective = directive.toLowerCase();
|
|
1193
|
-
return cacheControl
|
|
1194
|
-
.split(",")
|
|
1195
|
-
.some((part) => part.trim().toLowerCase() === normalizedDirective);
|
|
1260
|
+
return cacheControl.split(",").some((part) => part.trim().toLowerCase() === normalizedDirective);
|
|
1196
1261
|
}
|
|
1197
1262
|
|
|
1198
1263
|
function prerenderedResponse(
|
|
@@ -1221,10 +1286,7 @@ function prerenderedResponse(
|
|
|
1221
1286
|
});
|
|
1222
1287
|
}
|
|
1223
1288
|
|
|
1224
|
-
function cloudflareRouteRequiresModule(
|
|
1225
|
-
route: AppRoute,
|
|
1226
|
-
manifest: BuiltServerManifest,
|
|
1227
|
-
): boolean {
|
|
1289
|
+
function cloudflareRouteRequiresModule(route: AppRoute, manifest: BuiltServerManifest): boolean {
|
|
1228
1290
|
return (
|
|
1229
1291
|
route.kind === "metadata" ||
|
|
1230
1292
|
route.kind === "server" ||
|
|
@@ -1238,17 +1300,11 @@ function cloudflareRouteGlobKeyMatchesRoute(key: string, routeFile: string): boo
|
|
|
1238
1300
|
const normalizedKey = normalizeCloudflareRouteModulePath(key);
|
|
1239
1301
|
const normalizedRoute = normalizeCloudflareRouteModulePath(routeFile);
|
|
1240
1302
|
|
|
1241
|
-
return (
|
|
1242
|
-
normalizedKey === normalizedRoute ||
|
|
1243
|
-
normalizedKey.endsWith(`/${normalizedRoute}`)
|
|
1244
|
-
);
|
|
1303
|
+
return normalizedKey === normalizedRoute || normalizedKey.endsWith(`/${normalizedRoute}`);
|
|
1245
1304
|
}
|
|
1246
1305
|
|
|
1247
1306
|
function normalizeCloudflareRouteModulePath(path: string): string {
|
|
1248
|
-
const withoutPrefix = path
|
|
1249
|
-
.replace(/\\/g, "/")
|
|
1250
|
-
.replace(/^\.\//, "")
|
|
1251
|
-
.replace(/^\/+/, "");
|
|
1307
|
+
const withoutPrefix = path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
1252
1308
|
|
|
1253
1309
|
return withoutPrefix.replace(/\.(?:mjs|js|ts|tsx)$/, "");
|
|
1254
1310
|
}
|
|
@@ -1270,8 +1326,7 @@ function matchCloudflareRoute(
|
|
|
1270
1326
|
|
|
1271
1327
|
if (
|
|
1272
1328
|
catchAllIndex !== -1 &&
|
|
1273
|
-
pathSegments.length <
|
|
1274
|
-
catchAllIndex + 1 + route.segments.length - catchAllIndex - 1
|
|
1329
|
+
pathSegments.length < catchAllIndex + 1 + route.segments.length - catchAllIndex - 1
|
|
1275
1330
|
) {
|
|
1276
1331
|
continue;
|
|
1277
1332
|
}
|
|
@@ -1368,7 +1423,9 @@ function matchCloudflareRoute(
|
|
|
1368
1423
|
function compareCloudflareRoutes(a: AppRoute, b: AppRoute): number {
|
|
1369
1424
|
const scoreDelta = routeSpecificityScore(b) - routeSpecificityScore(a);
|
|
1370
1425
|
|
|
1371
|
-
return scoreDelta === 0
|
|
1426
|
+
return scoreDelta === 0
|
|
1427
|
+
? a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind)
|
|
1428
|
+
: scoreDelta;
|
|
1372
1429
|
}
|
|
1373
1430
|
|
|
1374
1431
|
function routeSpecificityScore(route: AppRoute): number {
|
|
@@ -1530,7 +1587,9 @@ function isCloudflareRouteMetadata(value: RouteMetadata | undefined): value is R
|
|
|
1530
1587
|
return value !== undefined;
|
|
1531
1588
|
}
|
|
1532
1589
|
|
|
1533
|
-
function mergeCloudflareRouteMetadata(
|
|
1590
|
+
function mergeCloudflareRouteMetadata(
|
|
1591
|
+
metadata: readonly RouteMetadata[],
|
|
1592
|
+
): RouteMetadata | undefined {
|
|
1534
1593
|
if (metadata.length === 0) {
|
|
1535
1594
|
return undefined;
|
|
1536
1595
|
}
|
|
@@ -1555,7 +1614,12 @@ function mergeCloudflareRouteMetadata(metadata: readonly RouteMetadata[]): Route
|
|
|
1555
1614
|
...(icons === undefined ? {} : { icons }),
|
|
1556
1615
|
...(openGraph === undefined && openGraphImages === undefined
|
|
1557
1616
|
? {}
|
|
1558
|
-
: {
|
|
1617
|
+
: {
|
|
1618
|
+
openGraph: {
|
|
1619
|
+
...openGraph,
|
|
1620
|
+
...(openGraphImages === undefined ? {} : { images: openGraphImages }),
|
|
1621
|
+
},
|
|
1622
|
+
}),
|
|
1559
1623
|
};
|
|
1560
1624
|
|
|
1561
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 } =
|
|
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<
|
|
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 {
|
package/src/cache.ts
CHANGED
|
@@ -348,9 +348,70 @@ function requestCarriesCredentials(request: Request | undefined): boolean {
|
|
|
348
348
|
return false;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
|
|
351
|
+
if (request.headers.has("authorization") || request.headers.has("cookie")) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for (const name of request.headers.keys()) {
|
|
356
|
+
const lower = name.toLowerCase();
|
|
357
|
+
if (PUBLIC_ROUTE_CACHE_REQUEST_HEADERS.has(lower)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (
|
|
362
|
+
lower === "x-api-key" ||
|
|
363
|
+
lower === "cf-access-jwt-assertion" ||
|
|
364
|
+
lower === "proxy-authorization" ||
|
|
365
|
+
lower === "x-auth-token" ||
|
|
366
|
+
lower === "x-authenticated-user" ||
|
|
367
|
+
lower === "x-forwarded-user" ||
|
|
368
|
+
lower === "x-session-id" ||
|
|
369
|
+
lower === "x-user-email" ||
|
|
370
|
+
lower === "x-user-id" ||
|
|
371
|
+
lower.endsWith("-api-key") ||
|
|
372
|
+
lower.endsWith("-auth-token") ||
|
|
373
|
+
lower.endsWith("-session-id") ||
|
|
374
|
+
lower.endsWith("-user") ||
|
|
375
|
+
lower.endsWith("-user-email") ||
|
|
376
|
+
lower.endsWith("-user-id") ||
|
|
377
|
+
lower.includes("-jwt-") ||
|
|
378
|
+
lower.endsWith("-jwt")
|
|
379
|
+
) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return false;
|
|
352
387
|
}
|
|
353
388
|
|
|
389
|
+
const PUBLIC_ROUTE_CACHE_REQUEST_HEADERS = new Set([
|
|
390
|
+
"accept",
|
|
391
|
+
"accept-encoding",
|
|
392
|
+
"accept-language",
|
|
393
|
+
"cache-control",
|
|
394
|
+
"connection",
|
|
395
|
+
"dnt",
|
|
396
|
+
"host",
|
|
397
|
+
"pragma",
|
|
398
|
+
"purpose",
|
|
399
|
+
"referer",
|
|
400
|
+
"sec-ch-prefers-color-scheme",
|
|
401
|
+
"sec-ch-prefers-reduced-motion",
|
|
402
|
+
"sec-ch-ua",
|
|
403
|
+
"sec-ch-ua-mobile",
|
|
404
|
+
"sec-ch-ua-platform",
|
|
405
|
+
"sec-fetch-dest",
|
|
406
|
+
"sec-fetch-mode",
|
|
407
|
+
"sec-fetch-site",
|
|
408
|
+
"sec-fetch-user",
|
|
409
|
+
"upgrade-insecure-requests",
|
|
410
|
+
"user-agent",
|
|
411
|
+
"x-mreact-navigation",
|
|
412
|
+
"x-mreact-navigation-cache",
|
|
413
|
+
]);
|
|
414
|
+
|
|
354
415
|
/**
|
|
355
416
|
* Invalidates cached route responses for a normalized app-router path.
|
|
356
417
|
*
|
package/src/cookies.ts
CHANGED
|
@@ -73,6 +73,20 @@ export function serializeCookie(
|
|
|
73
73
|
if (options.sameSite === "None" && options.secure !== true) {
|
|
74
74
|
throw new TypeError("SameSite=None requires Secure");
|
|
75
75
|
}
|
|
76
|
+
if (name.startsWith("__Secure-") && options.secure !== true) {
|
|
77
|
+
throw new TypeError("__Secure- cookies require Secure");
|
|
78
|
+
}
|
|
79
|
+
if (name.startsWith("__Host-")) {
|
|
80
|
+
if (options.secure !== true) {
|
|
81
|
+
throw new TypeError("__Host- cookies require Secure");
|
|
82
|
+
}
|
|
83
|
+
if (options.path !== "/") {
|
|
84
|
+
throw new TypeError("__Host- cookies require Path=/");
|
|
85
|
+
}
|
|
86
|
+
if (options.domain !== undefined) {
|
|
87
|
+
throw new TypeError("__Host- cookies must not set Domain");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
76
90
|
|
|
77
91
|
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
78
92
|
|