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

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.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -116,6 +116,10 @@ function createMemoryHistory(options) {
116
116
  return typeof to === "string" ? to : createPath(to);
117
117
  },
118
118
 
119
+ encodeLocation(location) {
120
+ return location;
121
+ },
122
+
119
123
  push(to, state) {
120
124
  action = Action.Push;
121
125
  let nextLocation = createMemoryLocation(to, state);
@@ -347,6 +351,14 @@ function parsePath(path) {
347
351
 
348
352
  return parsedPath;
349
353
  }
354
+ function createURL(location) {
355
+ // window.location.origin is "null" (the literal string value) in Firefox
356
+ // under certain conditions, notably when serving from a local HTML file
357
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
358
+ let base = typeof window !== "undefined" && typeof window.location !== "undefined" && window.location.origin !== "null" ? window.location.origin : "unknown://unknown";
359
+ let href = typeof location === "string" ? location : createPath(location);
360
+ return new URL(href, base);
361
+ }
350
362
 
351
363
  function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
352
364
  if (options === void 0) {
@@ -390,7 +402,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
390
402
  if (v5Compat && listener) {
391
403
  listener({
392
404
  action,
393
- location
405
+ location: history.location
394
406
  });
395
407
  }
396
408
  }
@@ -406,7 +418,7 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
406
418
  if (v5Compat && listener) {
407
419
  listener({
408
420
  action,
409
- location: location
421
+ location: history.location
410
422
  });
411
423
  }
412
424
  }
@@ -437,6 +449,16 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
437
449
  return createHref(window, to);
438
450
  },
439
451
 
452
+ encodeLocation(location) {
453
+ // Encode a Location the same way window.location would
454
+ let url = createURL(createPath(location));
455
+ return _extends({}, location, {
456
+ pathname: url.pathname,
457
+ search: url.search,
458
+ hash: url.hash
459
+ });
460
+ },
461
+
440
462
  push,
441
463
  replace,
442
464
 
@@ -518,7 +540,13 @@ function matchRoutes(routes, locationArg, basename) {
518
540
  let matches = null;
519
541
 
520
542
  for (let i = 0; matches == null && i < branches.length; ++i) {
521
- matches = matchRouteBranch(branches[i], pathname);
543
+ matches = matchRouteBranch(branches[i], // Incoming pathnames are generally encoded from either window.location
544
+ // or from router.navigate, but we want to match against the unencoded
545
+ // paths in the route definitions. Memory router locations won't be
546
+ // encoded here but there also shouldn't be anything to decode so this
547
+ // should be a safe operation. This avoids needing matchRoutes to be
548
+ // history-aware.
549
+ safelyDecodeURI(pathname));
522
550
  }
523
551
 
524
552
  return matches;
@@ -762,6 +790,15 @@ function compilePath(path, caseSensitive, end) {
762
790
  return [matcher, paramNames];
763
791
  }
764
792
 
793
+ function safelyDecodeURI(value) {
794
+ try {
795
+ return decodeURI(value);
796
+ } catch (error) {
797
+ 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 + ")."));
798
+ return value;
799
+ }
800
+ }
801
+
765
802
  function safelyDecodeURIComponent(value, paramName) {
766
803
  try {
767
804
  return decodeURIComponent(value);
@@ -861,9 +898,36 @@ function getInvalidPathError(char, field, dest, path) {
861
898
  }
862
899
  /**
863
900
  * @private
901
+ *
902
+ * When processing relative navigation we want to ignore ancestor routes that
903
+ * do not contribute to the path, such that index/pathless layout routes don't
904
+ * interfere.
905
+ *
906
+ * For example, when moving a route element into an index route and/or a
907
+ * pathless layout route, relative link behavior contained within should stay
908
+ * the same. Both of the following examples should link back to the root:
909
+ *
910
+ * <Route path="/">
911
+ * <Route path="accounts" element={<Link to=".."}>
912
+ * </Route>
913
+ *
914
+ * <Route path="/">
915
+ * <Route path="accounts">
916
+ * <Route element={<AccountsLayout />}> // <-- Does not contribute
917
+ * <Route index element={<Link to=".."} /> // <-- Does not contribute
918
+ * </Route
919
+ * </Route>
920
+ * </Route>
864
921
  */
865
922
 
866
923
 
924
+ function getPathContributingMatches(matches) {
925
+ return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
926
+ }
927
+ /**
928
+ * @private
929
+ */
930
+
867
931
  function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
868
932
  if (isPathRelative === void 0) {
869
933
  isPathRelative = false;
@@ -1184,7 +1248,9 @@ const IDLE_FETCHER = {
1184
1248
  formAction: undefined,
1185
1249
  formEncType: undefined,
1186
1250
  formData: undefined
1187
- }; //#endregion
1251
+ };
1252
+ const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
1253
+ const isServer = !isBrowser; //#endregion
1188
1254
  ////////////////////////////////////////////////////////////////////////////////
1189
1255
  //#region createRouter
1190
1256
  ////////////////////////////////////////////////////////////////////////////////
@@ -1391,7 +1457,13 @@ function createRouter(init) {
1391
1457
  submission,
1392
1458
  error
1393
1459
  } = normalizeNavigateOptions(to, opts);
1394
- let location = createLocation(state.location, path, opts && opts.state);
1460
+ let location = createLocation(state.location, path, opts && opts.state); // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
1461
+ // URL from window.location, so we need to encode it here so the behavior
1462
+ // remains the same as POP and non-data-router usages. new URL() does all
1463
+ // the same encoding we'd get from a history.pushState/window.location read
1464
+ // without having to touch history
1465
+
1466
+ location = init.history.encodeLocation(location);
1395
1467
  let historyAction = (opts && opts.replace) === true || submission != null ? Action.Replace : Action.Push;
1396
1468
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1397
1469
  return await startNavigation(historyAction, location, {
@@ -1557,7 +1629,7 @@ function createRouter(init) {
1557
1629
  if (!actionMatch.route.action) {
1558
1630
  result = getMethodNotAllowedResult(location);
1559
1631
  } else {
1560
- result = await callLoaderOrAction("action", request, actionMatch);
1632
+ result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
1561
1633
 
1562
1634
  if (request.signal.aborted) {
1563
1635
  return {
@@ -1652,7 +1724,7 @@ function createRouter(init) {
1652
1724
  if (!isUninterruptedRevalidation) {
1653
1725
  revalidatingFetchers.forEach(_ref2 => {
1654
1726
  let [key] = _ref2;
1655
- const fetcher = state.fetchers.get(key);
1727
+ let fetcher = state.fetchers.get(key);
1656
1728
  let revalidatingFetcher = {
1657
1729
  state: "loading",
1658
1730
  data: fetcher && fetcher.data,
@@ -1680,7 +1752,7 @@ function createRouter(init) {
1680
1752
  results,
1681
1753
  loaderResults,
1682
1754
  fetcherResults
1683
- } = await callLoadersAndMaybeResolveData(state.matches, matchesToLoad, revalidatingFetchers, request);
1755
+ } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
1684
1756
 
1685
1757
  if (request.signal.aborted) {
1686
1758
  return {
@@ -1738,7 +1810,7 @@ function createRouter(init) {
1738
1810
 
1739
1811
 
1740
1812
  function fetch(key, routeId, href, opts) {
1741
- if (typeof AbortController === "undefined") {
1813
+ if (isServer) {
1742
1814
  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
1815
  }
1744
1816
 
@@ -1757,19 +1829,19 @@ function createRouter(init) {
1757
1829
  let match = getTargetMatch(matches, path);
1758
1830
 
1759
1831
  if (submission) {
1760
- handleFetcherAction(key, routeId, path, match, submission);
1832
+ handleFetcherAction(key, routeId, path, match, matches, submission);
1761
1833
  return;
1762
1834
  } // Store off the match so we can call it's shouldRevalidate on subsequent
1763
1835
  // revalidations
1764
1836
 
1765
1837
 
1766
- fetchLoadMatches.set(key, [path, match]);
1767
- handleFetcherLoader(key, routeId, path, match);
1838
+ fetchLoadMatches.set(key, [path, match, matches]);
1839
+ handleFetcherLoader(key, routeId, path, match, matches);
1768
1840
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
1769
1841
  // errors, and revalidation
1770
1842
 
1771
1843
 
1772
- async function handleFetcherAction(key, routeId, path, match, submission) {
1844
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
1773
1845
  interruptActiveLoads();
1774
1846
  fetchLoadMatches.delete(key);
1775
1847
 
@@ -1798,7 +1870,7 @@ function createRouter(init) {
1798
1870
  let abortController = new AbortController();
1799
1871
  let fetchRequest = createRequest(path, abortController.signal, submission);
1800
1872
  fetchControllers.set(key, abortController);
1801
- let actionResult = await callLoaderOrAction("action", fetchRequest, match);
1873
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
1802
1874
 
1803
1875
  if (fetchRequest.signal.aborted) {
1804
1876
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -1890,7 +1962,7 @@ function createRouter(init) {
1890
1962
  results,
1891
1963
  loaderResults,
1892
1964
  fetcherResults
1893
- } = await callLoadersAndMaybeResolveData(state.matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
1965
+ } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
1894
1966
 
1895
1967
  if (abortController.signal.aborted) {
1896
1968
  return;
@@ -1952,7 +2024,7 @@ function createRouter(init) {
1952
2024
  } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
1953
2025
 
1954
2026
 
1955
- async function handleFetcherLoader(key, routeId, path, match) {
2027
+ async function handleFetcherLoader(key, routeId, path, match, matches) {
1956
2028
  let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
1957
2029
 
1958
2030
  let loadingFetcher = {
@@ -1971,7 +2043,7 @@ function createRouter(init) {
1971
2043
  let abortController = new AbortController();
1972
2044
  let fetchRequest = createRequest(path, abortController.signal);
1973
2045
  fetchControllers.set(key, abortController);
1974
- let result = await callLoaderOrAction("loader", fetchRequest, match); // Deferred isn't supported or fetcher loads, await everything and treat it
2046
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported or fetcher loads, await everything and treat it
1975
2047
  // as a normal load. resolveDeferredData will return undefined if this
1976
2048
  // fetcher gets aborted, so we just leave result untouched and short circuit
1977
2049
  // below if that happens
@@ -2064,13 +2136,13 @@ function createRouter(init) {
2064
2136
  });
2065
2137
  }
2066
2138
 
2067
- async function callLoadersAndMaybeResolveData(currentMatches, matchesToLoad, fetchersToLoad, request) {
2139
+ async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2068
2140
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2069
2141
  // then slice off the results into separate arrays so we can handle them
2070
2142
  // 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);
2143
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(_ref8 => {
2144
+ let [, href, match, fetchMatches] = _ref8;
2145
+ return callLoaderOrAction("loader", createRequest(href, request.signal), match, fetchMatches, router.basename);
2074
2146
  })]);
2075
2147
  let loaderResults = results.slice(0, matchesToLoad.length);
2076
2148
  let fetcherResults = results.slice(matchesToLoad.length);
@@ -2262,7 +2334,9 @@ function createRouter(init) {
2262
2334
  navigate,
2263
2335
  fetch,
2264
2336
  revalidate,
2265
- createHref,
2337
+ // Passthrough to history-aware createHref used by useHref so we get proper
2338
+ // hash-aware URLs in DOM paths
2339
+ createHref: to => init.history.createHref(to),
2266
2340
  getFetcher,
2267
2341
  deleteFetcher,
2268
2342
  dispose,
@@ -2275,15 +2349,75 @@ function createRouter(init) {
2275
2349
  //#region createStaticHandler
2276
2350
  ////////////////////////////////////////////////////////////////////////////////
2277
2351
 
2352
+ const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
2353
+ const validRequestMethods = new Set(["GET", "HEAD", ...validActionMethods]);
2278
2354
  function unstable_createStaticHandler(routes) {
2279
2355
  invariant(routes.length > 0, "You must provide a non-empty routes array to unstable_createStaticHandler");
2280
2356
  let dataRoutes = convertRoutesToDataRoutes(routes);
2357
+ /**
2358
+ * The query() method is intended for document requests, in which we want to
2359
+ * call an optional action and potentially multiple loaders for all nested
2360
+ * routes. It returns a StaticHandlerContext object, which is very similar
2361
+ * to the router state (location, loaderData, actionData, errors, etc.) and
2362
+ * also adds SSR-specific information such as the statusCode and headers
2363
+ * from action/loaders Responses.
2364
+ *
2365
+ * It _should_ never throw and should report all errors through the
2366
+ * returned context.errors object, properly associating errors to their error
2367
+ * boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be
2368
+ * used to emulate React error boundaries during SSr by performing a second
2369
+ * pass only down to the boundaryId.
2370
+ *
2371
+ * The one exception where we do not return a StaticHandlerContext is when a
2372
+ * redirect response is returned or thrown from any action/loader. We
2373
+ * propagate that out and return the raw Response so the HTTP server can
2374
+ * return it directly.
2375
+ */
2281
2376
 
2282
2377
  async function query(request) {
2283
- let {
2284
- location,
2285
- result
2286
- } = await queryImpl(request);
2378
+ let url = new URL(request.url);
2379
+ let location = createLocation("", createPath(url), null, "default");
2380
+ let matches = matchRoutes(dataRoutes, location);
2381
+
2382
+ if (!validRequestMethods.has(request.method)) {
2383
+ let {
2384
+ matches: methodNotAllowedMatches,
2385
+ route,
2386
+ error
2387
+ } = getMethodNotAllowedMatches(dataRoutes);
2388
+ return {
2389
+ location,
2390
+ matches: methodNotAllowedMatches,
2391
+ loaderData: {},
2392
+ actionData: null,
2393
+ errors: {
2394
+ [route.id]: error
2395
+ },
2396
+ statusCode: error.status,
2397
+ loaderHeaders: {},
2398
+ actionHeaders: {}
2399
+ };
2400
+ } else if (!matches) {
2401
+ let {
2402
+ matches: notFoundMatches,
2403
+ route,
2404
+ error
2405
+ } = getNotFoundMatches(dataRoutes);
2406
+ return {
2407
+ location,
2408
+ matches: notFoundMatches,
2409
+ loaderData: {},
2410
+ actionData: null,
2411
+ errors: {
2412
+ [route.id]: error
2413
+ },
2414
+ statusCode: error.status,
2415
+ loaderHeaders: {},
2416
+ actionHeaders: {}
2417
+ };
2418
+ }
2419
+
2420
+ let result = await queryImpl(request, location, matches);
2287
2421
 
2288
2422
  if (result instanceof Response) {
2289
2423
  return result;
@@ -2296,11 +2430,52 @@ function unstable_createStaticHandler(routes) {
2296
2430
  location
2297
2431
  }, result);
2298
2432
  }
2433
+ /**
2434
+ * The queryRoute() method is intended for targeted route requests, either
2435
+ * for fetch ?_data requests or resource route requests. In this case, we
2436
+ * are only ever calling a single action or loader, and we are returning the
2437
+ * returned value directly. In most cases, this will be a Response returned
2438
+ * from the action/loader, but it may be a primitive or other value as well -
2439
+ * and in such cases the calling context should handle that accordingly.
2440
+ *
2441
+ * We do respect the throw/return differentiation, so if an action/loader
2442
+ * throws, then this method will throw the value. This is important so we
2443
+ * can do proper boundary identification in Remix where a thrown Response
2444
+ * must go to the Catch Boundary but a returned Response is happy-path.
2445
+ *
2446
+ * One thing to note is that any Router-initiated thrown Response (such as a
2447
+ * 404 or 405) will have a custom X-Remix-Router-Error: "yes" header on it
2448
+ * in order to differentiate from responses thrown from user actions/loaders.
2449
+ */
2450
+
2299
2451
 
2300
2452
  async function queryRoute(request, routeId) {
2301
- let {
2302
- result
2303
- } = await queryImpl(request, routeId);
2453
+ let url = new URL(request.url);
2454
+ let location = createLocation("", createPath(url), null, "default");
2455
+ let matches = matchRoutes(dataRoutes, location);
2456
+
2457
+ if (!validRequestMethods.has(request.method)) {
2458
+ throw createRouterErrorResponse(null, {
2459
+ status: 405,
2460
+ statusText: "Method Not Allowed"
2461
+ });
2462
+ } else if (!matches) {
2463
+ throw createRouterErrorResponse(null, {
2464
+ status: 404,
2465
+ statusText: "Not Found"
2466
+ });
2467
+ }
2468
+
2469
+ let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
2470
+
2471
+ if (!match) {
2472
+ throw createRouterErrorResponse(null, {
2473
+ status: 404,
2474
+ statusText: "Not Found"
2475
+ });
2476
+ }
2477
+
2478
+ let result = await queryImpl(request, location, matches, match);
2304
2479
 
2305
2480
  if (result instanceof Response) {
2306
2481
  return result;
@@ -2309,77 +2484,48 @@ function unstable_createStaticHandler(routes) {
2309
2484
  let error = result.errors ? Object.values(result.errors)[0] : undefined;
2310
2485
 
2311
2486
  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
2487
+ // If we got back result.errors, that means the loader/action threw
2322
2488
  // _something_ that wasn't a Response, but it's not guaranteed/required
2323
2489
  // to be an `instanceof Error` either, so we have to use throw here to
2324
2490
  // preserve the "error" state outside of queryImpl.
2325
-
2326
-
2327
2491
  throw error;
2328
2492
  } // Pick off the right state value to return
2329
2493
 
2330
2494
 
2331
2495
  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;
2496
+ return Object.values(routeData || {})[0];
2342
2497
  }
2343
2498
 
2344
- async function queryImpl(request, routeId) {
2345
- invariant(request.method !== "HEAD", "query()/queryRoute() do not support HEAD requests");
2499
+ async function queryImpl(request, location, matches, routeMatch) {
2346
2500
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2347
- let {
2348
- location,
2349
- matches,
2350
- shortCircuitState
2351
- } = matchRequest(request, routeId);
2352
2501
 
2353
2502
  try {
2354
- if (shortCircuitState) {
2355
- return {
2356
- location,
2357
- result: shortCircuitState
2358
- };
2503
+ if (validActionMethods.has(request.method)) {
2504
+ let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), routeMatch != null);
2505
+ return result;
2359
2506
  }
2360
2507
 
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
- };
2508
+ let result = await loadRouteData(request, matches, routeMatch);
2509
+ return result instanceof Response ? result : _extends({}, result, {
2510
+ actionData: null,
2511
+ actionHeaders: {}
2512
+ });
2377
2513
  } catch (e) {
2378
- if (e instanceof Response) {
2379
- return {
2380
- location,
2381
- result: e
2382
- };
2514
+ // If the user threw/returned a Response in callLoaderOrAction, we throw
2515
+ // it to bail out and then return or throw here based on whether the user
2516
+ // returned or threw
2517
+ if (isQueryRouteResponse(e)) {
2518
+ if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
2519
+ throw e.response;
2520
+ }
2521
+
2522
+ return e.response;
2523
+ } // Redirects are always returned since they don't propagate to catch
2524
+ // boundaries
2525
+
2526
+
2527
+ if (isRedirectResponse(e)) {
2528
+ return e;
2383
2529
  }
2384
2530
 
2385
2531
  throw e;
@@ -2390,10 +2536,17 @@ function unstable_createStaticHandler(routes) {
2390
2536
  let result;
2391
2537
 
2392
2538
  if (!actionMatch.route.action) {
2393
- let href = createHref(new URL(request.url));
2394
- result = getMethodNotAllowedResult(href);
2539
+ if (isRouteRequest) {
2540
+ throw createRouterErrorResponse(null, {
2541
+ status: 405,
2542
+ statusText: "Method Not Allowed"
2543
+ });
2544
+ }
2545
+
2546
+ result = getMethodNotAllowedResult(request.url);
2395
2547
  } else {
2396
- result = await callLoaderOrAction("action", request, actionMatch, true, isRouteRequest);
2548
+ result = await callLoaderOrAction("action", request, actionMatch, matches, undefined, // Basename not currently supported in static handlers
2549
+ true, isRouteRequest);
2397
2550
 
2398
2551
  if (request.signal.aborted) {
2399
2552
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2403,7 +2556,7 @@ function unstable_createStaticHandler(routes) {
2403
2556
 
2404
2557
  if (isRedirectResult(result)) {
2405
2558
  // Uhhhh - this should never happen, we should always throw these from
2406
- // calLoaderOrAction, but the type narrowing here keeps TS happy and we
2559
+ // callLoaderOrAction, but the type narrowing here keeps TS happy and we
2407
2560
  // can get back on the "throw all redirect responses" train here should
2408
2561
  // this ever happen :/
2409
2562
  throw new Response(null, {
@@ -2419,6 +2572,8 @@ function unstable_createStaticHandler(routes) {
2419
2572
  }
2420
2573
 
2421
2574
  if (isRouteRequest) {
2575
+ // Note: This should only be non-Response values if we get here, since
2576
+ // isRouteRequest should throw any Response received in callLoaderOrAction
2422
2577
  if (isErrorResult(result)) {
2423
2578
  let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2424
2579
  return {
@@ -2455,7 +2610,7 @@ function unstable_createStaticHandler(routes) {
2455
2610
  // Store off the pending error - we use it to determine which loaders
2456
2611
  // to call and will commit it when we complete the navigation
2457
2612
  let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2458
- let context = await loadRouteData(request, matches, isRouteRequest, {
2613
+ let context = await loadRouteData(request, matches, undefined, {
2459
2614
  [boundaryMatch.route.id]: result.error
2460
2615
  }); // action status codes take precedence over loader status codes
2461
2616
 
@@ -2468,7 +2623,7 @@ function unstable_createStaticHandler(routes) {
2468
2623
  });
2469
2624
  }
2470
2625
 
2471
- let context = await loadRouteData(request, matches, isRouteRequest);
2626
+ let context = await loadRouteData(request, matches);
2472
2627
  return _extends({}, context, result.statusCode ? {
2473
2628
  statusCode: result.statusCode
2474
2629
  } : {}, {
@@ -2481,8 +2636,10 @@ function unstable_createStaticHandler(routes) {
2481
2636
  });
2482
2637
  }
2483
2638
 
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
2639
+ async function loadRouteData(request, matches, routeMatch, pendingActionError) {
2640
+ let isRouteRequest = routeMatch != null;
2641
+ let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
2642
+ let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run
2486
2643
 
2487
2644
  if (matchesToLoad.length === 0) {
2488
2645
  return {
@@ -2494,7 +2651,8 @@ function unstable_createStaticHandler(routes) {
2494
2651
  };
2495
2652
  }
2496
2653
 
2497
- let results = await Promise.all([...matchesToLoad.map(m => callLoaderOrAction("loader", request, m, true, isRouteRequest))]);
2654
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, undefined, // Basename not currently supported in static handlers
2655
+ true, isRouteRequest))]);
2498
2656
 
2499
2657
  if (request.signal.aborted) {
2500
2658
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2515,43 +2673,12 @@ function unstable_createStaticHandler(routes) {
2515
2673
  });
2516
2674
  }
2517
2675
 
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
- };
2676
+ function createRouterErrorResponse(body, init) {
2677
+ return new Response(body, _extends({}, init, {
2678
+ headers: _extends({}, init.headers, {
2679
+ "X-Remix-Router-Error": "yes"
2680
+ })
2681
+ }));
2555
2682
  }
2556
2683
 
2557
2684
  return {
@@ -2600,7 +2727,7 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2600
2727
  path,
2601
2728
  submission: {
2602
2729
  formMethod: opts.formMethod,
2603
- formAction: createHref(parsePath(path)),
2730
+ formAction: stripHashFromPath(path),
2604
2731
  formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2605
2732
  formData: opts.formData
2606
2733
  }
@@ -2683,16 +2810,16 @@ function getMatchesToLoad(state, matches, submission, location, isRevalidationRe
2683
2810
 
2684
2811
  let revalidatingFetchers = [];
2685
2812
  fetchLoadMatches && fetchLoadMatches.forEach((_ref10, key) => {
2686
- let [href, match] = _ref10;
2813
+ let [href, match, fetchMatches] = _ref10;
2687
2814
 
2688
2815
  // This fetcher was cancelled from a prior action submission - force reload
2689
2816
  if (cancelledFetcherLoads.includes(key)) {
2690
- revalidatingFetchers.push([key, href, match]);
2817
+ revalidatingFetchers.push([key, href, match, fetchMatches]);
2691
2818
  } else if (isRevalidationRequired) {
2692
2819
  let shouldRevalidate = shouldRevalidateLoader(href, match, submission, href, match, isRevalidationRequired, actionResult);
2693
2820
 
2694
2821
  if (shouldRevalidate) {
2695
- revalidatingFetchers.push([key, href, match]);
2822
+ revalidatingFetchers.push([key, href, match, fetchMatches]);
2696
2823
  }
2697
2824
  }
2698
2825
  });
@@ -2754,9 +2881,9 @@ function shouldRevalidateLoader(currentLocation, currentMatch, submission, locat
2754
2881
  return defaultShouldRevalidate;
2755
2882
  }
2756
2883
 
2757
- async function callLoaderOrAction(type, request, match, skipRedirects, isRouteRequest) {
2758
- if (skipRedirects === void 0) {
2759
- skipRedirects = false;
2884
+ async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest) {
2885
+ if (isStaticRequest === void 0) {
2886
+ isStaticRequest = false;
2760
2887
  }
2761
2888
 
2762
2889
  if (isRouteRequest === void 0) {
@@ -2788,20 +2915,30 @@ async function callLoaderOrAction(type, request, match, skipRedirects, isRouteRe
2788
2915
  }
2789
2916
 
2790
2917
  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
2918
+ let status = result.status; // Process redirects
2795
2919
 
2796
- if (isRouteRequest) {
2797
- throw result;
2798
- }
2920
+ if (status >= 300 && status <= 399) {
2921
+ let location = result.headers.get("Location");
2922
+ invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header"); // Support relative routing in redirects
2923
+
2924
+ let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
2925
+ let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
2926
+ let requestPath = createURL(request.url).pathname;
2927
+ let resolvedLocation = resolveTo(location, routePathnames, requestPath);
2928
+ invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + result.headers.get("Location")); // Prepend the basename to the redirect location if we have one
2799
2929
 
2800
- if (status >= 300 && status <= 399 && location != null) {
2801
- // Don't process redirects in the router during SSR document requests.
2930
+ if (basename) {
2931
+ let path = resolvedLocation.pathname;
2932
+ resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
2933
+ }
2934
+
2935
+ location = createPath(resolvedLocation); // Don't process redirects in the router during static requests requests.
2802
2936
  // Instead, throw the Response and let the server handle it with an HTTP
2803
- // redirect
2804
- if (skipRedirects) {
2937
+ // redirect. We also update the Location header in place in this flow so
2938
+ // basename and relative routing is taken into account
2939
+
2940
+ if (isStaticRequest) {
2941
+ result.headers.set("Location", location);
2805
2942
  throw result;
2806
2943
  }
2807
2944
 
@@ -2811,6 +2948,17 @@ async function callLoaderOrAction(type, request, match, skipRedirects, isRouteRe
2811
2948
  location,
2812
2949
  revalidate: result.headers.get("X-Remix-Revalidate") !== null
2813
2950
  };
2951
+ } // For SSR single-route requests, we want to hand Responses back directly
2952
+ // without unwrapping. We do this with the QueryRouteResponse wrapper
2953
+ // interface so we can know whether it was returned or thrown
2954
+
2955
+
2956
+ if (isRouteRequest) {
2957
+ // eslint-disable-next-line no-throw-literal
2958
+ throw {
2959
+ type: resultType || ResultType.data,
2960
+ response: result
2961
+ };
2814
2962
  }
2815
2963
 
2816
2964
  let data;
@@ -2859,7 +3007,7 @@ async function callLoaderOrAction(type, request, match, skipRedirects, isRouteRe
2859
3007
  }
2860
3008
 
2861
3009
  function createRequest(location, signal, submission) {
2862
- let url = createURL(location).toString();
3010
+ let url = createURL(stripHashFromPath(location)).toString();
2863
3011
  let init = {
2864
3012
  signal
2865
3013
  };
@@ -3026,10 +3174,10 @@ function findNearestBoundary(matches, routeId) {
3026
3174
  return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
3027
3175
  }
3028
3176
 
3029
- function getNotFoundMatches(routes) {
3177
+ function getShortCircuitMatches(routes, status, statusText) {
3030
3178
  // 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__"
3179
+ let route = routes.find(r => r.index || !r.path || r.path === "/") || {
3180
+ id: "__shim-" + status + "-route__"
3033
3181
  };
3034
3182
  return {
3035
3183
  matches: [{
@@ -3039,16 +3187,24 @@ function getNotFoundMatches(routes) {
3039
3187
  route
3040
3188
  }],
3041
3189
  route,
3042
- error: new ErrorResponse(404, "Not Found", null)
3190
+ error: new ErrorResponse(status, statusText, null)
3043
3191
  };
3044
3192
  }
3045
3193
 
3194
+ function getNotFoundMatches(routes) {
3195
+ return getShortCircuitMatches(routes, 404, "Not Found");
3196
+ }
3197
+
3198
+ function getMethodNotAllowedMatches(routes) {
3199
+ return getShortCircuitMatches(routes, 405, "Method Not Allowed");
3200
+ }
3201
+
3046
3202
  function getMethodNotAllowedResult(path) {
3047
- let href = typeof path === "string" ? path : createHref(path);
3203
+ let href = typeof path === "string" ? path : createPath(path);
3048
3204
  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
3205
  return {
3050
3206
  type: ResultType.error,
3051
- error: new ErrorResponse(405, "Method Not Allowed", "No action found for [" + href + "]")
3207
+ error: new ErrorResponse(405, "Method Not Allowed", "")
3052
3208
  };
3053
3209
  } // Find any returned redirect errors, starting from the lowest match
3054
3210
 
@@ -3061,11 +3217,13 @@ function findRedirect(results) {
3061
3217
  return result;
3062
3218
  }
3063
3219
  }
3064
- } // Create an href to represent a "server" URL without the hash
3065
-
3220
+ }
3066
3221
 
3067
- function createHref(location) {
3068
- return (location.pathname || "") + (location.search || "");
3222
+ function stripHashFromPath(path) {
3223
+ let parsedPath = typeof path === "string" ? parsePath(path) : path;
3224
+ return createPath(_extends({}, parsedPath, {
3225
+ hash: ""
3226
+ }));
3069
3227
  }
3070
3228
 
3071
3229
  function isHashChangeOnly(a, b) {
@@ -3084,6 +3242,20 @@ function isRedirectResult(result) {
3084
3242
  return (result && result.type) === ResultType.redirect;
3085
3243
  }
3086
3244
 
3245
+ function isRedirectResponse(result) {
3246
+ if (!(result instanceof Response)) {
3247
+ return false;
3248
+ }
3249
+
3250
+ let status = result.status;
3251
+ let location = result.headers.get("Location");
3252
+ return status >= 300 && status <= 399 && location != null;
3253
+ }
3254
+
3255
+ function isQueryRouteResponse(obj) {
3256
+ return obj && obj.response instanceof Response && (obj.type === ResultType.data || ResultType.error);
3257
+ }
3258
+
3087
3259
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
3088
3260
  for (let index = 0; index < results.length; index++) {
3089
3261
  let result = results[index];
@@ -3160,18 +3332,16 @@ function createUseMatchesMatch(match, loaderData) {
3160
3332
  function getTargetMatch(matches, location) {
3161
3333
  let search = typeof location === "string" ? parsePath(location).search : location.search;
3162
3334
 
3163
- if (matches[matches.length - 1].route.index && !hasNakedIndexQuery(search || "")) {
3164
- return matches.slice(-2)[0];
3165
- }
3335
+ if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
3336
+ // Return the leaf index route when index is present
3337
+ return matches[matches.length - 1];
3338
+ } // Otherwise grab the deepest "path contributing" match (ignoring index and
3339
+ // pathless layout routes)
3166
3340
 
3167
- return matches.slice(-1)[0];
3168
- }
3169
3341
 
3170
- function createURL(location) {
3171
- let base = typeof window !== "undefined" && typeof window.location !== "undefined" ? window.location.origin : "unknown://unknown";
3172
- let href = typeof location === "string" ? location : createHref(location);
3173
- return new URL(href, base);
3342
+ let pathMatches = getPathContributingMatches(matches);
3343
+ return pathMatches[pathMatches.length - 1];
3174
3344
  } //#endregion
3175
3345
 
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 };
3346
+ 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
3347
  //# sourceMappingURL=router.js.map