@remix-run/router 1.0.2 → 1.0.3-pre.0

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/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.0.2
2
+ * @remix-run/router v1.0.3-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -390,7 +390,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
390
390
  if (v5Compat && listener) {
391
391
  listener({
392
392
  action,
393
- location
393
+ location: history.location
394
394
  });
395
395
  }
396
396
  }
@@ -406,7 +406,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
406
406
  if (v5Compat && listener) {
407
407
  listener({
408
408
  action,
409
- location: location
409
+ location: history.location
410
410
  });
411
411
  }
412
412
  }
@@ -518,7 +518,10 @@ function matchRoutes(routes, locationArg, basename) {
518
518
  let matches = null;
519
519
 
520
520
  for (let i = 0; matches == null && i < branches.length; ++i) {
521
- matches = matchRouteBranch(branches[i], pathname);
521
+ matches = matchRouteBranch(branches[i], // incoming pathnames are always encoded from either window.location or
522
+ // from route.navigate, but we want to match against the unencoded paths
523
+ // in the route definitions
524
+ safelyDecodeURI(pathname));
522
525
  }
523
526
 
524
527
  return matches;
@@ -762,6 +765,15 @@ function compilePath(path, caseSensitive, end) {
762
765
  return [matcher, paramNames];
763
766
  }
764
767
 
768
+ function safelyDecodeURI(value) {
769
+ try {
770
+ return decodeURI(value);
771
+ } catch (error) {
772
+ warning(false, "The URL path \"" + value + "\" could not be decoded because it is is a " + "malformed URL segment. This is probably due to a bad percent " + ("encoding (" + error + ")."));
773
+ return value;
774
+ }
775
+ }
776
+
765
777
  function safelyDecodeURIComponent(value, paramName) {
766
778
  try {
767
779
  return decodeURIComponent(value);
@@ -861,9 +873,36 @@ function getInvalidPathError(char, field, dest, path) {
861
873
  }
862
874
  /**
863
875
  * @private
876
+ *
877
+ * When processing relative navigation we want to ignore ancestor routes that
878
+ * do not contribute to the path, such that index/pathless layout routes don't
879
+ * interfere.
880
+ *
881
+ * For example, when moving a route element into an index route and/or a
882
+ * pathless layout route, relative link behavior contained within should stay
883
+ * the same. Both of the following examples should link back to the root:
884
+ *
885
+ * <Route path="/">
886
+ * <Route path="accounts" element={<Link to=".."}>
887
+ * </Route>
888
+ *
889
+ * <Route path="/">
890
+ * <Route path="accounts">
891
+ * <Route element={<AccountsLayout />}> // <-- Does not contribute
892
+ * <Route index element={<Link to=".."} /> // <-- Does not contribute
893
+ * </Route
894
+ * </Route>
895
+ * </Route>
864
896
  */
865
897
 
866
898
 
899
+ function getPathContributingMatches(matches) {
900
+ return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
901
+ }
902
+ /**
903
+ * @private
904
+ */
905
+
867
906
  function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
868
907
  if (isPathRelative === void 0) {
869
908
  isPathRelative = false;
@@ -1184,7 +1223,9 @@ const IDLE_FETCHER = {
1184
1223
  formAction: undefined,
1185
1224
  formEncType: undefined,
1186
1225
  formData: undefined
1187
- }; //#endregion
1226
+ };
1227
+ const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
1228
+ const isServer = !isBrowser; //#endregion
1188
1229
  ////////////////////////////////////////////////////////////////////////////////
1189
1230
  //#region createRouter
1190
1231
  ////////////////////////////////////////////////////////////////////////////////
@@ -1391,7 +1432,18 @@ function createRouter(init) {
1391
1432
  submission,
1392
1433
  error
1393
1434
  } = normalizeNavigateOptions(to, opts);
1394
- let location = createLocation(state.location, path, opts && opts.state);
1435
+ let location = createLocation(state.location, path, opts && opts.state); // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
1436
+ // URL from window.location, so we need to encode it here so the behavior
1437
+ // remains the same as POP and non-data-router usages. new URL() does all
1438
+ // the same encoding we'd get from a history.pushState/window.location read
1439
+ // without having to touch history
1440
+
1441
+ let url = createURL(createPath(location));
1442
+ location = _extends({}, location, {
1443
+ pathname: url.pathname,
1444
+ search: url.search,
1445
+ hash: url.hash
1446
+ });
1395
1447
  let historyAction = (opts && opts.replace) === true || submission != null ? Action.Replace : Action.Push;
1396
1448
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1397
1449
  return await startNavigation(historyAction, location, {
@@ -1557,7 +1609,7 @@ function createRouter(init) {
1557
1609
  if (!actionMatch.route.action) {
1558
1610
  result = getMethodNotAllowedResult(location);
1559
1611
  } else {
1560
- result = await callLoaderOrAction("action", request, actionMatch);
1612
+ result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
1561
1613
 
1562
1614
  if (request.signal.aborted) {
1563
1615
  return {
@@ -1652,7 +1704,7 @@ function createRouter(init) {
1652
1704
  if (!isUninterruptedRevalidation) {
1653
1705
  revalidatingFetchers.forEach(_ref2 => {
1654
1706
  let [key] = _ref2;
1655
- const fetcher = state.fetchers.get(key);
1707
+ let fetcher = state.fetchers.get(key);
1656
1708
  let revalidatingFetcher = {
1657
1709
  state: "loading",
1658
1710
  data: fetcher && fetcher.data,
@@ -1680,7 +1732,7 @@ function createRouter(init) {
1680
1732
  results,
1681
1733
  loaderResults,
1682
1734
  fetcherResults
1683
- } = await callLoadersAndMaybeResolveData(state.matches, matchesToLoad, revalidatingFetchers, request);
1735
+ } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
1684
1736
 
1685
1737
  if (request.signal.aborted) {
1686
1738
  return {
@@ -1738,7 +1790,7 @@ function createRouter(init) {
1738
1790
 
1739
1791
 
1740
1792
  function fetch(key, routeId, href, opts) {
1741
- if (typeof AbortController === "undefined") {
1793
+ if (isServer) {
1742
1794
  throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
1743
1795
  }
1744
1796
 
@@ -1757,19 +1809,19 @@ function createRouter(init) {
1757
1809
  let match = getTargetMatch(matches, path);
1758
1810
 
1759
1811
  if (submission) {
1760
- handleFetcherAction(key, routeId, path, match, submission);
1812
+ handleFetcherAction(key, routeId, path, match, matches, submission);
1761
1813
  return;
1762
1814
  } // Store off the match so we can call it's shouldRevalidate on subsequent
1763
1815
  // revalidations
1764
1816
 
1765
1817
 
1766
- fetchLoadMatches.set(key, [path, match]);
1767
- handleFetcherLoader(key, routeId, path, match);
1818
+ fetchLoadMatches.set(key, [path, match, matches]);
1819
+ handleFetcherLoader(key, routeId, path, match, matches);
1768
1820
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
1769
1821
  // errors, and revalidation
1770
1822
 
1771
1823
 
1772
- async function handleFetcherAction(key, routeId, path, match, submission) {
1824
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
1773
1825
  interruptActiveLoads();
1774
1826
  fetchLoadMatches.delete(key);
1775
1827
 
@@ -1798,7 +1850,7 @@ function createRouter(init) {
1798
1850
  let abortController = new AbortController();
1799
1851
  let fetchRequest = createRequest(path, abortController.signal, submission);
1800
1852
  fetchControllers.set(key, abortController);
1801
- let actionResult = await callLoaderOrAction("action", fetchRequest, match);
1853
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
1802
1854
 
1803
1855
  if (fetchRequest.signal.aborted) {
1804
1856
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -1890,7 +1942,7 @@ function createRouter(init) {
1890
1942
  results,
1891
1943
  loaderResults,
1892
1944
  fetcherResults
1893
- } = await callLoadersAndMaybeResolveData(state.matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
1945
+ } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
1894
1946
 
1895
1947
  if (abortController.signal.aborted) {
1896
1948
  return;
@@ -1952,7 +2004,7 @@ function createRouter(init) {
1952
2004
  } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
1953
2005
 
1954
2006
 
1955
- async function handleFetcherLoader(key, routeId, path, match) {
2007
+ async function handleFetcherLoader(key, routeId, path, match, matches) {
1956
2008
  let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
1957
2009
 
1958
2010
  let loadingFetcher = {
@@ -1971,7 +2023,7 @@ function createRouter(init) {
1971
2023
  let abortController = new AbortController();
1972
2024
  let fetchRequest = createRequest(path, abortController.signal);
1973
2025
  fetchControllers.set(key, abortController);
1974
- let result = await callLoaderOrAction("loader", fetchRequest, match); // Deferred isn't supported or fetcher loads, await everything and treat it
2026
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported or fetcher loads, await everything and treat it
1975
2027
  // as a normal load. resolveDeferredData will return undefined if this
1976
2028
  // fetcher gets aborted, so we just leave result untouched and short circuit
1977
2029
  // below if that happens
@@ -2064,13 +2116,13 @@ function createRouter(init) {
2064
2116
  });
2065
2117
  }
2066
2118
 
2067
- async function callLoadersAndMaybeResolveData(currentMatches, matchesToLoad, fetchersToLoad, request) {
2119
+ async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2068
2120
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2069
2121
  // then slice off the results into separate arrays so we can handle them
2070
2122
  // accordingly
2071
- let results = await Promise.all([...matchesToLoad.map(m => callLoaderOrAction("loader", request, m)), ...fetchersToLoad.map(_ref8 => {
2072
- let [, href, match] = _ref8;
2073
- return callLoaderOrAction("loader", createRequest(href, request.signal), match);
2123
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(_ref8 => {
2124
+ let [, href, match, fetchMatches] = _ref8;
2125
+ return callLoaderOrAction("loader", createRequest(href, request.signal), match, fetchMatches, router.basename);
2074
2126
  })]);
2075
2127
  let loaderResults = results.slice(0, matchesToLoad.length);
2076
2128
  let fetcherResults = results.slice(matchesToLoad.length);
@@ -2262,7 +2314,9 @@ function createRouter(init) {
2262
2314
  navigate,
2263
2315
  fetch,
2264
2316
  revalidate,
2265
- createHref,
2317
+ // Passthrough to history-aware createHref used by useHref so we get proper
2318
+ // hash-aware URLs in DOM paths
2319
+ createHref: to => init.history.createHref(to),
2266
2320
  getFetcher,
2267
2321
  deleteFetcher,
2268
2322
  dispose,
@@ -2275,15 +2329,75 @@ function createRouter(init) {
2275
2329
  //#region createStaticHandler
2276
2330
  ////////////////////////////////////////////////////////////////////////////////
2277
2331
 
2332
+ const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
2333
+ const validRequestMethods = new Set(["GET", "HEAD", ...validActionMethods]);
2278
2334
  function unstable_createStaticHandler(routes) {
2279
2335
  invariant(routes.length > 0, "You must provide a non-empty routes array to unstable_createStaticHandler");
2280
2336
  let dataRoutes = convertRoutesToDataRoutes(routes);
2337
+ /**
2338
+ * The query() method is intended for document requests, in which we want to
2339
+ * call an optional action and potentially multiple loaders for all nested
2340
+ * routes. It returns a StaticHandlerContext object, which is very similar
2341
+ * to the router state (location, loaderData, actionData, errors, etc.) and
2342
+ * also adds SSR-specific information such as the statusCode and headers
2343
+ * from action/loaders Responses.
2344
+ *
2345
+ * It _should_ never throw and should report all errors through the
2346
+ * returned context.errors object, properly associating errors to their error
2347
+ * boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be
2348
+ * used to emulate React error boundaries during SSr by performing a second
2349
+ * pass only down to the boundaryId.
2350
+ *
2351
+ * The one exception where we do not return a StaticHandlerContext is when a
2352
+ * redirect response is returned or thrown from any action/loader. We
2353
+ * propagate that out and return the raw Response so the HTTP server can
2354
+ * return it directly.
2355
+ */
2281
2356
 
2282
2357
  async function query(request) {
2283
- let {
2284
- location,
2285
- result
2286
- } = await queryImpl(request);
2358
+ let url = new URL(request.url);
2359
+ let location = createLocation("", createPath(url), null, "default");
2360
+ let matches = matchRoutes(dataRoutes, location);
2361
+
2362
+ if (!validRequestMethods.has(request.method)) {
2363
+ let {
2364
+ matches: methodNotAllowedMatches,
2365
+ route,
2366
+ error
2367
+ } = getMethodNotAllowedMatches(dataRoutes);
2368
+ return {
2369
+ location,
2370
+ matches: methodNotAllowedMatches,
2371
+ loaderData: {},
2372
+ actionData: null,
2373
+ errors: {
2374
+ [route.id]: error
2375
+ },
2376
+ statusCode: error.status,
2377
+ loaderHeaders: {},
2378
+ actionHeaders: {}
2379
+ };
2380
+ } else if (!matches) {
2381
+ let {
2382
+ matches: notFoundMatches,
2383
+ route,
2384
+ error
2385
+ } = getNotFoundMatches(dataRoutes);
2386
+ return {
2387
+ location,
2388
+ matches: notFoundMatches,
2389
+ loaderData: {},
2390
+ actionData: null,
2391
+ errors: {
2392
+ [route.id]: error
2393
+ },
2394
+ statusCode: error.status,
2395
+ loaderHeaders: {},
2396
+ actionHeaders: {}
2397
+ };
2398
+ }
2399
+
2400
+ let result = await queryImpl(request, location, matches);
2287
2401
 
2288
2402
  if (result instanceof Response) {
2289
2403
  return result;
@@ -2296,11 +2410,52 @@ function unstable_createStaticHandler(routes) {
2296
2410
  location
2297
2411
  }, result);
2298
2412
  }
2413
+ /**
2414
+ * The queryRoute() method is intended for targeted route requests, either
2415
+ * for fetch ?_data requests or resource route requests. In this case, we
2416
+ * are only ever calling a single action or loader, and we are returning the
2417
+ * returned value directly. In most cases, this will be a Response returned
2418
+ * from the action/loader, but it may be a primitive or other value as well -
2419
+ * and in such cases the calling context should handle that accordingly.
2420
+ *
2421
+ * We do respect the throw/return differentiation, so if an action/loader
2422
+ * throws, then this method will throw the value. This is important so we
2423
+ * can do proper boundary identification in Remix where a thrown Response
2424
+ * must go to the Catch Boundary but a returned Response is happy-path.
2425
+ *
2426
+ * One thing to note is that any Router-initiated thrown Response (such as a
2427
+ * 404 or 405) will have a custom X-Remix-Router-Error: "yes" header on it
2428
+ * in order to differentiate from responses thrown from user actions/loaders.
2429
+ */
2430
+
2299
2431
 
2300
2432
  async function queryRoute(request, routeId) {
2301
- let {
2302
- result
2303
- } = await queryImpl(request, routeId);
2433
+ let url = new URL(request.url);
2434
+ let location = createLocation("", createPath(url), null, "default");
2435
+ let matches = matchRoutes(dataRoutes, location);
2436
+
2437
+ if (!validRequestMethods.has(request.method)) {
2438
+ throw createRouterErrorResponse(null, {
2439
+ status: 405,
2440
+ statusText: "Method Not Allowed"
2441
+ });
2442
+ } else if (!matches) {
2443
+ throw createRouterErrorResponse(null, {
2444
+ status: 404,
2445
+ statusText: "Not Found"
2446
+ });
2447
+ }
2448
+
2449
+ let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
2450
+
2451
+ if (!match) {
2452
+ throw createRouterErrorResponse(null, {
2453
+ status: 404,
2454
+ statusText: "Not Found"
2455
+ });
2456
+ }
2457
+
2458
+ let result = await queryImpl(request, location, matches, match);
2304
2459
 
2305
2460
  if (result instanceof Response) {
2306
2461
  return result;
@@ -2309,77 +2464,48 @@ function unstable_createStaticHandler(routes) {
2309
2464
  let error = result.errors ? Object.values(result.errors)[0] : undefined;
2310
2465
 
2311
2466
  if (error !== undefined) {
2312
- // While we always re-throw Responses returned from loaders/actions
2313
- // directly for route requests and prevent the unwrapping into an
2314
- // ErrorResponse, we still need this for error cases _prior_ the
2315
- // execution of the loader/action, such as a 404/405 error.
2316
- if (isRouteErrorResponse(error)) {
2317
- return new Response(error.data, {
2318
- status: error.status,
2319
- statusText: error.statusText
2320
- });
2321
- } // If we got back result.errors, that means the loader/action threw
2467
+ // If we got back result.errors, that means the loader/action threw
2322
2468
  // _something_ that wasn't a Response, but it's not guaranteed/required
2323
2469
  // to be an `instanceof Error` either, so we have to use throw here to
2324
2470
  // preserve the "error" state outside of queryImpl.
2325
-
2326
-
2327
2471
  throw error;
2328
2472
  } // Pick off the right state value to return
2329
2473
 
2330
2474
 
2331
2475
  let routeData = [result.actionData, result.loaderData].find(v => v);
2332
- let value = Object.values(routeData || {})[0];
2333
-
2334
- if (isRouteErrorResponse(value)) {
2335
- return new Response(value.data, {
2336
- status: value.status,
2337
- statusText: value.statusText
2338
- });
2339
- }
2340
-
2341
- return value;
2476
+ return Object.values(routeData || {})[0];
2342
2477
  }
2343
2478
 
2344
- async function queryImpl(request, routeId) {
2345
- invariant(request.method !== "HEAD", "query()/queryRoute() do not support HEAD requests");
2479
+ async function queryImpl(request, location, matches, routeMatch) {
2346
2480
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2347
- let {
2348
- location,
2349
- matches,
2350
- shortCircuitState
2351
- } = matchRequest(request, routeId);
2352
2481
 
2353
2482
  try {
2354
- if (shortCircuitState) {
2355
- return {
2356
- location,
2357
- result: shortCircuitState
2358
- };
2483
+ if (validActionMethods.has(request.method)) {
2484
+ let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), routeMatch != null);
2485
+ return result;
2359
2486
  }
2360
2487
 
2361
- if (request.method !== "GET") {
2362
- let result = await submit(request, matches, getTargetMatch(matches, location), routeId != null);
2363
- return {
2364
- location,
2365
- result
2366
- };
2367
- }
2368
-
2369
- let result = await loadRouteData(request, matches, routeId != null);
2370
- return {
2371
- location,
2372
- result: _extends({}, result, {
2373
- actionData: null,
2374
- actionHeaders: {}
2375
- })
2376
- };
2488
+ let result = await loadRouteData(request, matches, routeMatch);
2489
+ return result instanceof Response ? result : _extends({}, result, {
2490
+ actionData: null,
2491
+ actionHeaders: {}
2492
+ });
2377
2493
  } catch (e) {
2378
- if (e instanceof Response) {
2379
- return {
2380
- location,
2381
- result: e
2382
- };
2494
+ // If the user threw/returned a Response in callLoaderOrAction, we throw
2495
+ // it to bail out and then return or throw here based on whether the user
2496
+ // returned or threw
2497
+ if (isQueryRouteResponse(e)) {
2498
+ if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
2499
+ throw e.response;
2500
+ }
2501
+
2502
+ return e.response;
2503
+ } // Redirects are always returned since they don't propagate to catch
2504
+ // boundaries
2505
+
2506
+
2507
+ if (isRedirectResponse(e)) {
2508
+ return e;
2383
2509
  }
2384
2510
 
2385
2511
  throw e;
@@ -2390,10 +2516,19 @@ function unstable_createStaticHandler(routes) {
2390
2516
  let result;
2391
2517
 
2392
2518
  if (!actionMatch.route.action) {
2393
- let href = createHref(new URL(request.url));
2519
+ let href = createServerHref(new URL(request.url));
2520
+
2521
+ if (isRouteRequest) {
2522
+ throw createRouterErrorResponse(null, {
2523
+ status: 405,
2524
+ statusText: "Method Not Allowed"
2525
+ });
2526
+ }
2527
+
2394
2528
  result = getMethodNotAllowedResult(href);
2395
2529
  } else {
2396
- result = await callLoaderOrAction("action", request, actionMatch, true, isRouteRequest);
2530
+ result = await callLoaderOrAction("action", request, actionMatch, matches, undefined, // Basename not currently supported in static handlers
2531
+ true, isRouteRequest);
2397
2532
 
2398
2533
  if (request.signal.aborted) {
2399
2534
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2403,7 +2538,7 @@ function unstable_createStaticHandler(routes) {
2403
2538
 
2404
2539
  if (isRedirectResult(result)) {
2405
2540
  // Uhhhh - this should never happen, we should always throw these from
2406
- // calLoaderOrAction, but the type narrowing here keeps TS happy and we
2541
+ // callLoaderOrAction, but the type narrowing here keeps TS happy and we
2407
2542
  // can get back on the "throw all redirect responses" train here should
2408
2543
  // this ever happen :/
2409
2544
  throw new Response(null, {
@@ -2419,6 +2554,8 @@ function unstable_createStaticHandler(routes) {
2419
2554
  }
2420
2555
 
2421
2556
  if (isRouteRequest) {
2557
+ // Note: This should only be non-Response values if we get here, since
2558
+ // isRouteRequest should throw any Response received in callLoaderOrAction
2422
2559
  if (isErrorResult(result)) {
2423
2560
  let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2424
2561
  return {
@@ -2455,7 +2592,7 @@ function unstable_createStaticHandler(routes) {
2455
2592
  // Store off the pending error - we use it to determine which loaders
2456
2593
  // to call and will commit it when we complete the navigation
2457
2594
  let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2458
- let context = await loadRouteData(request, matches, isRouteRequest, {
2595
+ let context = await loadRouteData(request, matches, undefined, {
2459
2596
  [boundaryMatch.route.id]: result.error
2460
2597
  }); // action status codes take precedence over loader status codes
2461
2598
 
@@ -2468,7 +2605,7 @@ function unstable_createStaticHandler(routes) {
2468
2605
  });
2469
2606
  }
2470
2607
 
2471
- let context = await loadRouteData(request, matches, isRouteRequest);
2608
+ let context = await loadRouteData(request, matches);
2472
2609
  return _extends({}, context, result.statusCode ? {
2473
2610
  statusCode: result.statusCode
2474
2611
  } : {}, {
@@ -2481,8 +2618,10 @@ function unstable_createStaticHandler(routes) {
2481
2618
  });
2482
2619
  }
2483
2620
 
2484
- async function loadRouteData(request, matches, isRouteRequest, pendingActionError) {
2485
- let matchesToLoad = getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]).filter(m => m.route.loader); // Short circuit if we have no loaders to run
2621
+ async function loadRouteData(request, matches, routeMatch, pendingActionError) {
2622
+ let isRouteRequest = routeMatch != null;
2623
+ let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
2624
+ let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run
2486
2625
 
2487
2626
  if (matchesToLoad.length === 0) {
2488
2627
  return {
@@ -2494,7 +2633,8 @@ function unstable_createStaticHandler(routes) {
2494
2633
  };
2495
2634
  }
2496
2635
 
2497
- let results = await Promise.all([...matchesToLoad.map(m => callLoaderOrAction("loader", request, m, true, isRouteRequest))]);
2636
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, undefined, // Basename not currently supported in static handlers
2637
+ true, isRouteRequest))]);
2498
2638
 
2499
2639
  if (request.signal.aborted) {
2500
2640
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2515,43 +2655,12 @@ function unstable_createStaticHandler(routes) {
2515
2655
  });
2516
2656
  }
2517
2657
 
2518
- function matchRequest(req, routeId) {
2519
- let url = new URL(req.url);
2520
- let location = createLocation("", createPath(url), null, "default");
2521
- let matches = matchRoutes(dataRoutes, location);
2522
-
2523
- if (matches && routeId) {
2524
- matches = matches.filter(m => m.route.id === routeId);
2525
- } // Short circuit with a 404 if we match nothing
2526
-
2527
-
2528
- if (!matches) {
2529
- let {
2530
- matches: notFoundMatches,
2531
- route,
2532
- error
2533
- } = getNotFoundMatches(dataRoutes);
2534
- return {
2535
- location,
2536
- matches: notFoundMatches,
2537
- shortCircuitState: {
2538
- matches: notFoundMatches,
2539
- loaderData: {},
2540
- actionData: null,
2541
- errors: {
2542
- [route.id]: error
2543
- },
2544
- statusCode: 404,
2545
- loaderHeaders: {},
2546
- actionHeaders: {}
2547
- }
2548
- };
2549
- }
2550
-
2551
- return {
2552
- location,
2553
- matches
2554
- };
2658
+ function createRouterErrorResponse(body, init) {
2659
+ return new Response(body, _extends({}, init, {
2660
+ headers: _extends({}, init.headers, {
2661
+ "X-Remix-Router-Error": "yes"
2662
+ })
2663
+ }));
2555
2664
  }
2556
2665
 
2557
2666
  return {
@@ -2600,7 +2709,7 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2600
2709
  path,
2601
2710
  submission: {
2602
2711
  formMethod: opts.formMethod,
2603
- formAction: createHref(parsePath(path)),
2712
+ formAction: createServerHref(parsePath(path)),
2604
2713
  formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2605
2714
  formData: opts.formData
2606
2715
  }
@@ -2683,16 +2792,16 @@ function getMatchesToLoad(state, matches, submission, location, isRevalidationRe
2683
2792
 
2684
2793
  let revalidatingFetchers = [];
2685
2794
  fetchLoadMatches && fetchLoadMatches.forEach((_ref10, key) => {
2686
- let [href, match] = _ref10;
2795
+ let [href, match, fetchMatches] = _ref10;
2687
2796
 
2688
2797
  // This fetcher was cancelled from a prior action submission - force reload
2689
2798
  if (cancelledFetcherLoads.includes(key)) {
2690
- revalidatingFetchers.push([key, href, match]);
2799
+ revalidatingFetchers.push([key, href, match, fetchMatches]);
2691
2800
  } else if (isRevalidationRequired) {
2692
2801
  let shouldRevalidate = shouldRevalidateLoader(href, match, submission, href, match, isRevalidationRequired, actionResult);
2693
2802
 
2694
2803
  if (shouldRevalidate) {
2695
- revalidatingFetchers.push([key, href, match]);
2804
+ revalidatingFetchers.push([key, href, match, fetchMatches]);
2696
2805
  }
2697
2806
  }
2698
2807
  });
@@ -2754,9 +2863,9 @@ function shouldRevalidateLoader(currentLocation, currentMatch, submission, locat
2754
2863
  return defaultShouldRevalidate;
2755
2864
  }
2756
2865
 
2757
- async function callLoaderOrAction(type, request, match, skipRedirects, isRouteRequest) {
2758
- if (skipRedirects === void 0) {
2759
- skipRedirects = false;
2866
+ async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest) {
2867
+ if (isStaticRequest === void 0) {
2868
+ isStaticRequest = false;
2760
2869
  }
2761
2870
 
2762
2871
  if (isRouteRequest === void 0) {
@@ -2788,20 +2897,30 @@ async function callLoaderOrAction(type, request, match, skipRedirects, isRouteRe
2788
2897
  }
2789
2898
 
2790
2899
  if (result instanceof Response) {
2791
- // Process redirects
2792
- let status = result.status;
2793
- let location = result.headers.get("Location"); // For SSR single-route requests, we want to hand Responses back directly
2794
- // without unwrapping
2900
+ let status = result.status; // Process redirects
2795
2901
 
2796
- if (isRouteRequest) {
2797
- throw result;
2798
- }
2902
+ if (status >= 300 && status <= 399) {
2903
+ let location = result.headers.get("Location");
2904
+ invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header"); // Support relative routing in redirects
2799
2905
 
2800
- if (status >= 300 && status <= 399 && location != null) {
2801
- // Don't process redirects in the router during SSR document requests.
2906
+ let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
2907
+ let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
2908
+ let requestPath = createURL(request.url).pathname;
2909
+ let resolvedLocation = resolveTo(location, routePathnames, requestPath);
2910
+ invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + result.headers.get("Location")); // Prepend the basename to the redirect location if we have one
2911
+
2912
+ if (basename) {
2913
+ let path = resolvedLocation.pathname;
2914
+ resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
2915
+ }
2916
+
2917
+ location = createPath(resolvedLocation); // Don't process redirects in the router during static requests requests.
2802
2918
  // Instead, throw the Response and let the server handle it with an HTTP
2803
- // redirect
2804
- if (skipRedirects) {
2919
+ // redirect. We also update the Location header in place in this flow so
2920
+ // basename and relative routing is taken into account
2921
+
2922
+ if (isStaticRequest) {
2923
+ result.headers.set("Location", location);
2805
2924
  throw result;
2806
2925
  }
2807
2926
 
@@ -2811,6 +2930,17 @@ async function callLoaderOrAction(type, request, match, skipRedirects, isRouteRe
2811
2930
  location,
2812
2931
  revalidate: result.headers.get("X-Remix-Revalidate") !== null
2813
2932
  };
2933
+ } // For SSR single-route requests, we want to hand Responses back directly
2934
+ // without unwrapping. We do this with the QueryRouteResponse wrapper
2935
+ // interface so we can know whether it was returned or thrown
2936
+
2937
+
2938
+ if (isRouteRequest) {
2939
+ // eslint-disable-next-line no-throw-literal
2940
+ throw {
2941
+ type: resultType || ResultType.data,
2942
+ response: result
2943
+ };
2814
2944
  }
2815
2945
 
2816
2946
  let data;
@@ -3026,10 +3156,10 @@ function findNearestBoundary(matches, routeId) {
3026
3156
  return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
3027
3157
  }
3028
3158
 
3029
- function getNotFoundMatches(routes) {
3159
+ function getShortCircuitMatches(routes, status, statusText) {
3030
3160
  // Prefer a root layout route if present, otherwise shim in a route object
3031
- let route = routes.find(r => r.index || r.path === "" || r.path === "/") || {
3032
- id: "__shim-404-route__"
3161
+ let route = routes.find(r => r.index || !r.path || r.path === "/") || {
3162
+ id: "__shim-" + status + "-route__"
3033
3163
  };
3034
3164
  return {
3035
3165
  matches: [{
@@ -3039,16 +3169,24 @@ function getNotFoundMatches(routes) {
3039
3169
  route
3040
3170
  }],
3041
3171
  route,
3042
- error: new ErrorResponse(404, "Not Found", null)
3172
+ error: new ErrorResponse(status, statusText, null)
3043
3173
  };
3044
3174
  }
3045
3175
 
3176
+ function getNotFoundMatches(routes) {
3177
+ return getShortCircuitMatches(routes, 404, "Not Found");
3178
+ }
3179
+
3180
+ function getMethodNotAllowedMatches(routes) {
3181
+ return getShortCircuitMatches(routes, 405, "Method Not Allowed");
3182
+ }
3183
+
3046
3184
  function getMethodNotAllowedResult(path) {
3047
- let href = typeof path === "string" ? path : createHref(path);
3185
+ let href = typeof path === "string" ? path : createServerHref(path);
3048
3186
  console.warn("You're trying to submit to a route that does not have an action. To " + "fix this, please add an `action` function to the route for " + ("[" + href + "]"));
3049
3187
  return {
3050
3188
  type: ResultType.error,
3051
- error: new ErrorResponse(405, "Method Not Allowed", "No action found for [" + href + "]")
3189
+ error: new ErrorResponse(405, "Method Not Allowed", "")
3052
3190
  };
3053
3191
  } // Find any returned redirect errors, starting from the lowest match
3054
3192
 
@@ -3064,7 +3202,7 @@ function findRedirect(results) {
3064
3202
  } // Create an href to represent a "server" URL without the hash
3065
3203
 
3066
3204
 
3067
- function createHref(location) {
3205
+ function createServerHref(location) {
3068
3206
  return (location.pathname || "") + (location.search || "");
3069
3207
  }
3070
3208
 
@@ -3084,6 +3222,20 @@ function isRedirectResult(result) {
3084
3222
  return (result && result.type) === ResultType.redirect;
3085
3223
  }
3086
3224
 
3225
+ function isRedirectResponse(result) {
3226
+ if (!(result instanceof Response)) {
3227
+ return false;
3228
+ }
3229
+
3230
+ let status = result.status;
3231
+ let location = result.headers.get("Location");
3232
+ return status >= 300 && status <= 399 && location != null;
3233
+ }
3234
+
3235
+ function isQueryRouteResponse(obj) {
3236
+ return obj && obj.response instanceof Response && (obj.type === ResultType.data || ResultType.error);
3237
+ }
3238
+
3087
3239
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
3088
3240
  for (let index = 0; index < results.length; index++) {
3089
3241
  let result = results[index];
@@ -3160,18 +3312,22 @@ function createUseMatchesMatch(match, loaderData) {
3160
3312
  function getTargetMatch(matches, location) {
3161
3313
  let search = typeof location === "string" ? parsePath(location).search : location.search;
3162
3314
 
3163
- if (matches[matches.length - 1].route.index && !hasNakedIndexQuery(search || "")) {
3164
- return matches.slice(-2)[0];
3165
- }
3315
+ if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
3316
+ // Return the leaf index route when index is present
3317
+ return matches[matches.length - 1];
3318
+ } // Otherwise grab the deepest "path contributing" match (ignoring index and
3319
+ // pathless layout routes)
3320
+
3166
3321
 
3167
- return matches.slice(-1)[0];
3322
+ let pathMatches = getPathContributingMatches(matches);
3323
+ return pathMatches[pathMatches.length - 1];
3168
3324
  }
3169
3325
 
3170
3326
  function createURL(location) {
3171
3327
  let base = typeof window !== "undefined" && typeof window.location !== "undefined" ? window.location.origin : "unknown://unknown";
3172
- let href = typeof location === "string" ? location : createHref(location);
3328
+ let href = typeof location === "string" ? location : createServerHref(location);
3173
3329
  return new URL(href, base);
3174
3330
  } //#endregion
3175
3331
 
3176
- export { AbortedDeferredError, Action, ErrorResponse, IDLE_FETCHER, IDLE_NAVIGATION, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, defer, generatePath, getStaticContextFromError, getToPathname, invariant, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, resolvePath, resolveTo, stripBasename, unstable_createStaticHandler, warning };
3332
+ export { AbortedDeferredError, Action, ErrorResponse, IDLE_FETCHER, IDLE_NAVIGATION, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, defer, generatePath, getStaticContextFromError, getToPathname, invariant, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, resolvePath, resolveTo, stripBasename, unstable_createStaticHandler, warning };
3177
3333
  //# sourceMappingURL=router.js.map