Skip to content

Commit 8f1bac3

Browse files
committed
Revert "Remove middleware depth restrictions (#13172)"
This reverts commit fb014ce.
1 parent c24bc42 commit 8f1bac3

File tree

5 files changed

+155
-61
lines changed

5 files changed

+155
-61
lines changed

.changeset/popular-hats-love.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

integration/middleware-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2016,7 +2016,7 @@ test.describe("Middleware", () => {
20162016
appFixture.close();
20172017
});
20182018

2019-
test("still calls middleware for all matches on granular data requests", async ({
2019+
test("only calls middleware as deep as needed for granular data requests", async ({
20202020
page,
20212021
}) => {
20222022
let fixture = await createFixture({
@@ -2101,7 +2101,7 @@ test.describe("Middleware", () => {
21012101

21022102
(await page.$('a[href="/a/b"]'))?.click();
21032103
await page.waitForSelector("[data-b]");
2104-
expect(await page.locator("[data-a]").textContent()).toBe("A: a,b");
2104+
expect(await page.locator("[data-a]").textContent()).toBe("A: a");
21052105
expect(await page.locator("[data-b]").textContent()).toBe("B: a,b");
21062106

21072107
appFixture.close();

packages/react-router/__tests__/router/context-middleware-test.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -762,10 +762,6 @@ describe("context/middleware", () => {
762762
"parent action start",
763763
"child 1 start - throwing",
764764
"parent loader start",
765-
"child 1 loader start",
766-
"child 2 loader start",
767-
"child 2 loader end",
768-
"child 1 loader end",
769765
"parent loader end",
770766
]);
771767
expect(router.state.loaderData).toMatchInlineSnapshot(`
@@ -901,10 +897,6 @@ describe("context/middleware", () => {
901897
"child 2 start",
902898
"child 2 end - throwing",
903899
"parent loader start",
904-
"child 1 loader start",
905-
"child 2 loader start",
906-
"child 2 loader end",
907-
"child 1 loader end",
908900
"parent loader end",
909901
]);
910902
expect(router.state.loaderData).toEqual({

packages/react-router/lib/dom/ssr/single-fetch.tsx

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ export function StreamTransfer({
136136
}
137137
}
138138

139+
function middlewareErrorHandler(
140+
e: MiddlewareError,
141+
keyedResults: Record<string, DataStrategyResult>
142+
) {
143+
// we caught an error running the middleware, copy that overtop any
144+
// non-error result for the route
145+
Object.assign(keyedResults, {
146+
[e.routeId]: { type: "error", result: e.error },
147+
});
148+
}
149+
139150
export function getSingleFetchDataStrategy(
140151
manifest: AssetsManifest,
141152
routeModules: RouteModules,
@@ -150,9 +161,17 @@ export function getSingleFetchDataStrategy(
150161
if (request.method !== "GET") {
151162
return runMiddlewarePipeline(
152163
args,
164+
matches.findIndex((m) => m.shouldLoad),
153165
false,
154-
() => singleFetchActionStrategy(request, matches, basename),
155-
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
166+
async (keyedResults) => {
167+
let results = await singleFetchActionStrategy(
168+
request,
169+
matches,
170+
basename
171+
);
172+
Object.assign(keyedResults, results);
173+
},
174+
middlewareErrorHandler
156175
) as Promise<Record<string, DataStrategyResult>>;
157176
}
158177

@@ -197,11 +216,24 @@ export function getSingleFetchDataStrategy(
197216
!manifest.routes[m.route.id]?.hasClientLoader
198217
);
199218
if (!foundRevalidatingServerLoader) {
219+
// Skip single fetch and just call the loaders in parallel when this is
220+
// a SPA mode navigation
221+
let tailIdx = [...matches].reverse().findIndex((m) => m.shouldLoad);
222+
let lowestLoadingIndex = tailIdx < 0 ? 0 : matches.length - 1 - tailIdx;
200223
return runMiddlewarePipeline(
201224
args,
225+
lowestLoadingIndex,
202226
false,
203-
() => nonSsrStrategy(manifest, request, matches, basename),
204-
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
227+
async (keyedResults) => {
228+
let results = await nonSsrStrategy(
229+
manifest,
230+
request,
231+
matches,
232+
basename
233+
);
234+
Object.assign(keyedResults, results);
235+
},
236+
middlewareErrorHandler
205237
) as Promise<Record<string, DataStrategyResult>>;
206238
}
207239
}
@@ -210,27 +242,47 @@ export function getSingleFetchDataStrategy(
210242
if (fetcherKey) {
211243
return runMiddlewarePipeline(
212244
args,
245+
matches.findIndex((m) => m.shouldLoad),
213246
false,
214-
() => singleFetchLoaderFetcherStrategy(request, matches, basename),
215-
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
247+
async (keyedResults) => {
248+
let results = await singleFetchLoaderFetcherStrategy(
249+
request,
250+
matches,
251+
basename
252+
);
253+
Object.assign(keyedResults, results);
254+
},
255+
middlewareErrorHandler
216256
) as Promise<Record<string, DataStrategyResult>>;
217257
}
218258

219259
// Navigational loads are more complex...
260+
261+
// Determine how deep to run middleware
262+
let lowestLoadingIndex = getLowestLoadingIndex(
263+
manifest,
264+
routeModules,
265+
getRouter(),
266+
matches
267+
);
268+
220269
return runMiddlewarePipeline(
221270
args,
271+
lowestLoadingIndex,
222272
false,
223-
() =>
224-
singleFetchLoaderNavigationStrategy(
273+
async (keyedResults) => {
274+
let results = await singleFetchLoaderNavigationStrategy(
225275
manifest,
226276
routeModules,
227277
ssr,
228278
getRouter(),
229279
request,
230280
matches,
231281
basename
232-
),
233-
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
282+
);
283+
Object.assign(keyedResults, results);
284+
},
285+
middlewareErrorHandler
234286
) as Promise<Record<string, DataStrategyResult>>;
235287
};
236288
}
@@ -319,6 +371,27 @@ function isOptedOut(
319371
);
320372
}
321373

374+
function getLowestLoadingIndex(
375+
manifest: AssetsManifest,
376+
routeModules: RouteModules,
377+
router: DataRouter,
378+
matches: DataStrategyFunctionArgs["matches"]
379+
) {
380+
let tailIdx = [...matches]
381+
.reverse()
382+
.findIndex(
383+
(m) =>
384+
m.shouldLoad ||
385+
!isOptedOut(
386+
manifest.routes[m.route.id],
387+
routeModules[m.route.id],
388+
m,
389+
router
390+
)
391+
);
392+
return tailIdx < 0 ? 0 : matches.length - 1 - tailIdx;
393+
}
394+
322395
// Loaders are trickier since we only want to hit the server once, so we
323396
// create a singular promise for all server-loader routes to latch onto.
324397
async function singleFetchLoaderNavigationStrategy(

packages/react-router/lib/router/router.ts

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3490,6 +3490,12 @@ export function createStaticHandler(
34903490
"`requestContext` must bean instance of `unstable_RouterContextProvider`"
34913491
);
34923492
try {
3493+
// Run middleware as far deep as the deepest loader to be executed
3494+
let tailIdx = [...matches]
3495+
.reverse()
3496+
.findIndex((m) => !filterMatchesToLoad || filterMatchesToLoad(m));
3497+
let lowestLoadingIdx = tailIdx < 0 ? 0 : matches.length - 1 - tailIdx;
3498+
34933499
let renderedStaticContext: StaticHandlerContext | undefined;
34943500
let response = await runMiddlewarePipeline(
34953501
{
@@ -3500,6 +3506,7 @@ export function createStaticHandler(
35003506
// this to the proper type knowing it's not an `AppLoadContext`
35013507
context: requestContext as unstable_RouterContextProvider,
35023508
},
3509+
lowestLoadingIdx,
35033510
true,
35043511
async () => {
35053512
let result = await queryImpl(
@@ -3693,6 +3700,7 @@ export function createStaticHandler(
36933700
// this to the proper type knowing it's not an `AppLoadContext`
36943701
context: requestContext as unstable_RouterContextProvider,
36953702
},
3703+
matches.length - 1,
36963704
true,
36973705
async () => {
36983706
let result = await queryImpl(
@@ -4932,65 +4940,91 @@ async function defaultDataStrategyWithMiddleware(
49324940
return defaultDataStrategy(args);
49334941
}
49344942

4935-
return runMiddlewarePipeline(
4943+
// Determine how far down we'll be loading so we only run middleware to that
4944+
// point. This prevents us from calling middleware below an action error
4945+
// boundary below which we don't run loaders
4946+
let lastIndex = args.matches.length - 1;
4947+
for (let i = lastIndex; i >= 0; i--) {
4948+
if (args.matches[i].shouldLoad) {
4949+
lastIndex = i;
4950+
break;
4951+
}
4952+
}
4953+
4954+
let results = await runMiddlewarePipeline(
49364955
args,
4956+
lastIndex,
49374957
false,
4938-
() => defaultDataStrategy(args),
4939-
(e) => ({ [e.routeId]: { type: "error", result: e.error } })
4940-
) as Promise<Record<string, DataStrategyResult>>;
4958+
async (keyedResults: Record<string, DataStrategyResult>) => {
4959+
Object.assign(keyedResults, await defaultDataStrategy(args));
4960+
},
4961+
(e, keyedResults) => {
4962+
// we caught an error running the middleware, copy that overtop any
4963+
// non-error result for the route
4964+
Object.assign(keyedResults, {
4965+
[e.routeId]: { type: "error", result: e.error },
4966+
});
4967+
}
4968+
);
4969+
return results as Record<string, DataStrategyResult>;
49414970
}
49424971

49434972
type MutableMiddlewareState = {
4944-
handlerResult: unknown;
4973+
keyedResults: Record<string, DataStrategyResult>;
49454974
propagateResult: boolean;
49464975
};
49474976

4948-
export async function runMiddlewarePipeline<T extends boolean>(
4949-
args: (
4977+
export async function runMiddlewarePipeline(
4978+
{
4979+
request,
4980+
params,
4981+
context,
4982+
matches,
4983+
}: (
49504984
| LoaderFunctionArgs<unstable_RouterContextProvider>
49514985
| ActionFunctionArgs<unstable_RouterContextProvider>
49524986
) & {
4953-
// Don't use `DataStrategyFunctionArgs` directly so we can we reduce these
4954-
// back from `DataStrategyMatch` to regular matches for use in the staticHandler
49554987
matches: AgnosticDataRouteMatch[];
49564988
},
4957-
propagateResult: T,
4958-
handler: () => T extends true
4959-
? MaybePromise<Response>
4960-
: MaybePromise<Record<string, DataStrategyResult>>,
4961-
errorHandler: (error: MiddlewareError) => unknown
4989+
lastIndex: number,
4990+
propagateResult: boolean,
4991+
handler: (results: Record<string, DataStrategyResult>) => unknown,
4992+
errorHandler: (
4993+
error: MiddlewareError,
4994+
results: Record<string, DataStrategyResult>
4995+
) => unknown
49624996
): Promise<unknown> {
4963-
let { matches, request, params, context } = args;
49644997
let middlewareState: MutableMiddlewareState = {
4965-
handlerResult: undefined,
4998+
keyedResults: {},
49664999
propagateResult,
49675000
};
49685001
try {
4969-
let tuples = matches.flatMap((m) =>
4970-
m.route.unstable_middleware
4971-
? m.route.unstable_middleware.map((fn) => [m.route.id, fn])
4972-
: []
4973-
) as [string, unstable_MiddlewareFunction][];
49745002
let result = await callRouteMiddleware(
5003+
matches
5004+
.slice(0, lastIndex + 1)
5005+
.flatMap((m) =>
5006+
m.route.unstable_middleware
5007+
? m.route.unstable_middleware.map((fn) => [m.route.id, fn])
5008+
: []
5009+
) as [string, unstable_MiddlewareFunction][],
5010+
0,
49755011
{ request, params, context },
4976-
tuples,
49775012
middlewareState,
49785013
handler
49795014
);
49805015
return middlewareState.propagateResult
49815016
? result
4982-
: middlewareState.handlerResult;
5017+
: middlewareState.keyedResults;
49835018
} catch (e) {
49845019
if (!(e instanceof MiddlewareError)) {
49855020
// This shouldn't happen? This would have to come from a bug in our
49865021
// library code...
49875022
throw e;
49885023
}
4989-
let result = await errorHandler(e);
4990-
if (propagateResult || !middlewareState.handlerResult) {
4991-
return result;
4992-
}
4993-
return Object.assign(middlewareState.handlerResult, result);
5024+
let result = await errorHandler(e, middlewareState.keyedResults);
5025+
return middlewareState.propagateResult
5026+
? result
5027+
: middlewareState.keyedResults;
49945028
}
49955029
}
49965030

@@ -5004,13 +5038,13 @@ export class MiddlewareError {
50045038
}
50055039

50065040
async function callRouteMiddleware(
5041+
middlewares: [string, unstable_MiddlewareFunction][],
5042+
idx: number,
50075043
args:
50085044
| LoaderFunctionArgs<unstable_RouterContextProvider>
50095045
| ActionFunctionArgs<unstable_RouterContextProvider>,
5010-
middlewares: [string, unstable_MiddlewareFunction][],
50115046
middlewareState: MutableMiddlewareState,
5012-
handler: () => void,
5013-
idx = 0
5047+
handler: (r: Record<string, DataStrategyResult>) => void
50145048
): Promise<unknown> {
50155049
let { request } = args;
50165050
if (request.signal.aborted) {
@@ -5025,8 +5059,8 @@ async function callRouteMiddleware(
50255059
let tuple = middlewares[idx];
50265060
if (!tuple) {
50275061
// We reached the end of our middlewares, call the handler
5028-
middlewareState.handlerResult = await handler();
5029-
return middlewareState.handlerResult;
5062+
let result = await handler(middlewareState.keyedResults);
5063+
return result;
50305064
}
50315065

50325066
let [routeId, middleware] = tuple;
@@ -5038,11 +5072,11 @@ async function callRouteMiddleware(
50385072
}
50395073
nextCalled = true;
50405074
let result = await callRouteMiddleware(
5041-
args,
50425075
middlewares,
5076+
idx + 1,
5077+
args,
50435078
middlewareState,
5044-
handler,
5045-
idx + 1
5079+
handler
50465080
);
50475081
if (middlewareState.propagateResult) {
50485082
nextResult = result;

0 commit comments

Comments
 (0)