@reckona/mreact-router 0.0.180 → 0.0.182
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.map +1 -1
- package/dist/actions.js +20 -8
- package/dist/actions.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +53 -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/built-assets.d.ts.map +1 -1
- package/dist/built-assets.js +2 -2
- package/dist/built-assets.js.map +1 -1
- package/dist/cache-config.d.ts +1 -1
- package/dist/cache-config.d.ts.map +1 -1
- package/dist/cache-config.js.map +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +18 -1
- package/dist/cache.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -6
- package/dist/client.js.map +1 -1
- package/dist/csrf.js +10 -6
- package/dist/csrf.js.map +1 -1
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +14 -1
- package/dist/navigation.js.map +1 -1
- package/dist/render.d.ts +2 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +81 -38
- package/dist/render.js.map +1 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +5 -3
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +26 -9
- package/src/adapters/cloudflare.ts +98 -41
- package/src/build.ts +93 -30
- package/src/built-assets.ts +2 -3
- package/src/cache-config.ts +1 -0
- package/src/cache.ts +20 -1
- package/src/client.ts +16 -6
- package/src/csrf.ts +13 -6
- package/src/navigation.ts +16 -1
- package/src/render.ts +168 -70
- package/src/vite.ts +6 -3
|
@@ -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,
|
|
@@ -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(
|
|
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>] ??
|
|
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(
|
|
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 = [
|
|
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":
|
|
852
|
-
|
|
853
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
: {
|
|
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 } =
|
|
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/built-assets.ts
CHANGED
|
@@ -87,9 +87,8 @@ export async function readBuiltPublicAsset(
|
|
|
87
87
|
return undefined;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const normalized =
|
|
91
|
-
|
|
92
|
-
if (isAbsolute(normalized) || normalized === ".." || normalized.startsWith("../")) {
|
|
90
|
+
const normalized = safeBuiltClientAssetPath(relativePath);
|
|
91
|
+
if (normalized === undefined) {
|
|
93
92
|
return undefined;
|
|
94
93
|
}
|
|
95
94
|
|
package/src/cache-config.ts
CHANGED
package/src/cache.ts
CHANGED
|
@@ -203,6 +203,11 @@ export function routeCachePolicyFromSource(code: string): RouteCachePolicy | und
|
|
|
203
203
|
const seconds = match?.groups?.seconds === undefined ? undefined : Number(match.groups.seconds);
|
|
204
204
|
|
|
205
205
|
if (seconds === undefined || !Number.isFinite(seconds)) {
|
|
206
|
+
if (/^\s*export\s+const\s+revalidate\s*=/m.test(code)) {
|
|
207
|
+
console.warn(
|
|
208
|
+
"export const revalidate must be a plain integer literal, e.g. export const revalidate = 3600",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
206
211
|
return undefined;
|
|
207
212
|
}
|
|
208
213
|
|
|
@@ -362,8 +367,10 @@ function requestCarriesCredentials(request: Request | undefined): boolean {
|
|
|
362
367
|
lower === "x-api-key" ||
|
|
363
368
|
lower === "cf-access-jwt-assertion" ||
|
|
364
369
|
lower === "proxy-authorization" ||
|
|
370
|
+
lower === "x-access-token" ||
|
|
365
371
|
lower === "x-auth-token" ||
|
|
366
372
|
lower === "x-authenticated-user" ||
|
|
373
|
+
lower === "x-id-token" ||
|
|
367
374
|
lower === "x-forwarded-user" ||
|
|
368
375
|
lower === "x-session-id" ||
|
|
369
376
|
lower === "x-user-email" ||
|
|
@@ -380,7 +387,7 @@ function requestCarriesCredentials(request: Request | undefined): boolean {
|
|
|
380
387
|
return true;
|
|
381
388
|
}
|
|
382
389
|
|
|
383
|
-
|
|
390
|
+
continue;
|
|
384
391
|
}
|
|
385
392
|
|
|
386
393
|
return false;
|
|
@@ -391,12 +398,18 @@ const PUBLIC_ROUTE_CACHE_REQUEST_HEADERS = new Set([
|
|
|
391
398
|
"accept-encoding",
|
|
392
399
|
"accept-language",
|
|
393
400
|
"cache-control",
|
|
401
|
+
"cf-connecting-ip",
|
|
402
|
+
"cf-ipcountry",
|
|
403
|
+
"cf-ray",
|
|
394
404
|
"connection",
|
|
395
405
|
"dnt",
|
|
396
406
|
"host",
|
|
407
|
+
"if-none-match",
|
|
397
408
|
"pragma",
|
|
409
|
+
"priority",
|
|
398
410
|
"purpose",
|
|
399
411
|
"referer",
|
|
412
|
+
"save-data",
|
|
400
413
|
"sec-ch-prefers-color-scheme",
|
|
401
414
|
"sec-ch-prefers-reduced-motion",
|
|
402
415
|
"sec-ch-ua",
|
|
@@ -408,8 +421,14 @@ const PUBLIC_ROUTE_CACHE_REQUEST_HEADERS = new Set([
|
|
|
408
421
|
"sec-fetch-user",
|
|
409
422
|
"upgrade-insecure-requests",
|
|
410
423
|
"user-agent",
|
|
424
|
+
"via",
|
|
411
425
|
"x-mreact-navigation",
|
|
412
426
|
"x-mreact-navigation-cache",
|
|
427
|
+
"x-real-ip",
|
|
428
|
+
"x-request-id",
|
|
429
|
+
"x-forwarded-for",
|
|
430
|
+
"x-forwarded-host",
|
|
431
|
+
"x-forwarded-proto",
|
|
413
432
|
]);
|
|
414
433
|
|
|
415
434
|
/**
|
package/src/client.ts
CHANGED
|
@@ -3849,6 +3849,20 @@ function __mreactObserveViewportPrefetchAnchors(root) {
|
|
|
3849
3849
|
|
|
3850
3850
|
function __mreactApplyOutOfOrderFragments(root) {
|
|
3851
3851
|
const fragments = Array.from(root.querySelectorAll("template[data-mreact-oob-fragment]"));
|
|
3852
|
+
const completionMarkers = new Map();
|
|
3853
|
+
for (const marker of root.querySelectorAll("[data-mreact-oob-complete]")) {
|
|
3854
|
+
const id = marker.getAttribute("data-mreact-oob-complete");
|
|
3855
|
+
if (!completionMarkers.has(id)) {
|
|
3856
|
+
completionMarkers.set(id, marker);
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
const placeholders = new Map();
|
|
3860
|
+
for (const placeholder of root.querySelectorAll("[data-mreact-oob-placeholder]")) {
|
|
3861
|
+
const id = placeholder.getAttribute("data-mreact-oob-placeholder");
|
|
3862
|
+
if (!placeholders.has(id)) {
|
|
3863
|
+
placeholders.set(id, placeholder);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3852
3866
|
|
|
3853
3867
|
for (const fragment of fragments) {
|
|
3854
3868
|
const id = fragment.getAttribute("data-mreact-oob-fragment");
|
|
@@ -3857,16 +3871,12 @@ function __mreactApplyOutOfOrderFragments(root) {
|
|
|
3857
3871
|
continue;
|
|
3858
3872
|
}
|
|
3859
3873
|
|
|
3860
|
-
const completionMarker =
|
|
3861
|
-
.find((candidate) => candidate.getAttribute("data-mreact-oob-complete") === id);
|
|
3862
|
-
|
|
3874
|
+
const completionMarker = completionMarkers.get(id);
|
|
3863
3875
|
if (completionMarker === undefined) {
|
|
3864
3876
|
continue;
|
|
3865
3877
|
}
|
|
3866
3878
|
|
|
3867
|
-
const placeholder =
|
|
3868
|
-
.find((candidate) => candidate.getAttribute("data-mreact-oob-placeholder") === id);
|
|
3869
|
-
|
|
3879
|
+
const placeholder = placeholders.get(id);
|
|
3870
3880
|
if (placeholder === undefined) {
|
|
3871
3881
|
continue;
|
|
3872
3882
|
}
|