@remix-run/router 1.0.3 → 1.0.4-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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.0.3
2
+ * @remix-run/router v1.0.4-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -115,8 +115,13 @@
115
115
  return typeof to === "string" ? to : createPath(to);
116
116
  },
117
117
 
118
- encodeLocation(location) {
119
- return location;
118
+ encodeLocation(to) {
119
+ let path = typeof to === "string" ? parsePath(to) : to;
120
+ return {
121
+ pathname: path.pathname || "",
122
+ search: path.search || "",
123
+ hash: path.hash || ""
124
+ };
120
125
  },
121
126
 
122
127
  push(to, state) {
@@ -474,14 +479,14 @@
474
479
  return createHref(window, to);
475
480
  },
476
481
 
477
- encodeLocation(location) {
482
+ encodeLocation(to) {
478
483
  // Encode a Location the same way window.location would
479
- let url = createURL(createPath(location));
480
- return _extends({}, location, {
484
+ let url = createURL(typeof to === "string" ? to : createPath(to));
485
+ return {
481
486
  pathname: url.pathname,
482
487
  search: url.search,
483
488
  hash: url.hash
484
- });
489
+ };
485
490
  },
486
491
 
487
492
  push,
@@ -1256,10 +1261,21 @@
1256
1261
  */
1257
1262
 
1258
1263
  class ErrorResponse {
1259
- constructor(status, statusText, data) {
1264
+ constructor(status, statusText, data, internal) {
1265
+ if (internal === void 0) {
1266
+ internal = false;
1267
+ }
1268
+
1260
1269
  this.status = status;
1261
1270
  this.statusText = statusText || "";
1262
- this.data = data;
1271
+ this.internal = internal;
1272
+
1273
+ if (data instanceof Error) {
1274
+ this.data = data.toString();
1275
+ this.error = data;
1276
+ } else {
1277
+ this.data = data;
1278
+ }
1263
1279
  }
1264
1280
 
1265
1281
  }
@@ -1279,6 +1295,12 @@
1279
1295
  * A Router instance manages all navigation and data loading/mutations
1280
1296
  */
1281
1297
 
1298
+ const validActionMethodsArr = ["post", "put", "patch", "delete"];
1299
+ const validActionMethods = new Set(validActionMethodsArr);
1300
+ const validRequestMethodsArr = ["get", ...validActionMethodsArr];
1301
+ const validRequestMethods = new Set(validRequestMethodsArr);
1302
+ const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
1303
+ const redirectPreserveMethodStatusCodes = new Set([307, 308]);
1282
1304
  const IDLE_NAVIGATION = {
1283
1305
  state: "idle",
1284
1306
  location: undefined,
@@ -1329,11 +1351,13 @@
1329
1351
  if (initialMatches == null) {
1330
1352
  // If we do not match a user-provided-route, fall back to the root
1331
1353
  // to allow the error boundary to take over
1354
+ let error = getInternalRouterError(404, {
1355
+ pathname: init.history.location.pathname
1356
+ });
1332
1357
  let {
1333
1358
  matches,
1334
- route,
1335
- error
1336
- } = getNotFoundMatches(dataRoutes);
1359
+ route
1360
+ } = getShortCircuitMatches(dataRoutes);
1337
1361
  initialMatches = matches;
1338
1362
  initialErrors = {
1339
1363
  [route.id]: error
@@ -1509,7 +1533,7 @@
1509
1533
  // the same encoding we'd get from a history.pushState/window.location read
1510
1534
  // without having to touch history
1511
1535
 
1512
- location = init.history.encodeLocation(location);
1536
+ location = _extends({}, location, init.history.encodeLocation(location));
1513
1537
  let historyAction = (opts && opts.replace) === true || submission != null ? exports.Action.Replace : exports.Action.Push;
1514
1538
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1515
1539
  return await startNavigation(historyAction, location, {
@@ -1573,11 +1597,13 @@
1573
1597
  let matches = matchRoutes(dataRoutes, location, init.basename); // Short circuit with a 404 on the root error boundary if we match nothing
1574
1598
 
1575
1599
  if (!matches) {
1600
+ let error = getInternalRouterError(404, {
1601
+ pathname: location.pathname
1602
+ });
1576
1603
  let {
1577
1604
  matches: notFoundMatches,
1578
- route,
1579
- error
1580
- } = getNotFoundMatches(dataRoutes); // Cancel all pending deferred on 404s since we don't keep any routes
1605
+ route
1606
+ } = getShortCircuitMatches(dataRoutes); // Cancel all pending deferred on 404s since we don't keep any routes
1581
1607
 
1582
1608
  cancelActiveDeferreds();
1583
1609
  completeNavigation(location, {
@@ -1673,7 +1699,14 @@
1673
1699
  let actionMatch = getTargetMatch(matches, location);
1674
1700
 
1675
1701
  if (!actionMatch.route.action) {
1676
- result = getMethodNotAllowedResult(location);
1702
+ result = {
1703
+ type: ResultType.error,
1704
+ error: getInternalRouterError(405, {
1705
+ method: request.method,
1706
+ pathname: location.pathname,
1707
+ routeId: actionMatch.route.id
1708
+ })
1709
+ };
1677
1710
  } else {
1678
1711
  result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
1679
1712
 
@@ -1685,12 +1718,7 @@
1685
1718
  }
1686
1719
 
1687
1720
  if (isRedirectResult(result)) {
1688
- let redirectNavigation = _extends({
1689
- state: "loading",
1690
- location: createLocation(state.location, result.location)
1691
- }, submission);
1692
-
1693
- await startRedirectNavigation(result, redirectNavigation, opts && opts.replace);
1721
+ await startRedirectNavigation(state, result, opts && opts.replace === true);
1694
1722
  return {
1695
1723
  shortCircuited: true
1696
1724
  };
@@ -1817,8 +1845,7 @@
1817
1845
  let redirect = findRedirect(results);
1818
1846
 
1819
1847
  if (redirect) {
1820
- let redirectNavigation = getLoaderRedirect(state, redirect);
1821
- await startRedirectNavigation(redirect, redirectNavigation, replace);
1848
+ await startRedirectNavigation(state, redirect, replace);
1822
1849
  return {
1823
1850
  shortCircuited: true
1824
1851
  };
@@ -1864,7 +1891,9 @@
1864
1891
  let matches = matchRoutes(dataRoutes, href, init.basename);
1865
1892
 
1866
1893
  if (!matches) {
1867
- setFetcherError(key, routeId, new ErrorResponse(404, "Not Found", null));
1894
+ setFetcherError(key, routeId, getInternalRouterError(404, {
1895
+ pathname: href
1896
+ }));
1868
1897
  return;
1869
1898
  }
1870
1899
 
@@ -1892,9 +1921,11 @@
1892
1921
  fetchLoadMatches.delete(key);
1893
1922
 
1894
1923
  if (!match.route.action) {
1895
- let {
1896
- error
1897
- } = getMethodNotAllowedResult(path);
1924
+ let error = getInternalRouterError(405, {
1925
+ method: submission.formMethod,
1926
+ pathname: path,
1927
+ routeId: routeId
1928
+ });
1898
1929
  setFetcherError(key, routeId, error);
1899
1930
  return;
1900
1931
  } // Put this fetcher into it's submitting state
@@ -1942,14 +1973,7 @@
1942
1973
  updateState({
1943
1974
  fetchers: new Map(state.fetchers)
1944
1975
  });
1945
-
1946
- let redirectNavigation = _extends({
1947
- state: "loading",
1948
- location: createLocation(state.location, actionResult.location)
1949
- }, submission);
1950
-
1951
- await startRedirectNavigation(actionResult, redirectNavigation);
1952
- return;
1976
+ return startRedirectNavigation(state, actionResult);
1953
1977
  } // Process any non-redirect errors thrown
1954
1978
 
1955
1979
 
@@ -2023,9 +2047,7 @@
2023
2047
  let redirect = findRedirect(results);
2024
2048
 
2025
2049
  if (redirect) {
2026
- let redirectNavigation = getLoaderRedirect(state, redirect);
2027
- await startRedirectNavigation(redirect, redirectNavigation);
2028
- return;
2050
+ return startRedirectNavigation(state, redirect);
2029
2051
  } // Process and commit output from loaders
2030
2052
 
2031
2053
 
@@ -2110,8 +2132,7 @@
2110
2132
 
2111
2133
 
2112
2134
  if (isRedirectResult(result)) {
2113
- let redirectNavigation = getLoaderRedirect(state, result);
2114
- await startRedirectNavigation(result, redirectNavigation);
2135
+ await startRedirectNavigation(state, result);
2115
2136
  return;
2116
2137
  } // Process any non-redirect errors thrown
2117
2138
 
@@ -2167,19 +2188,60 @@
2167
2188
  */
2168
2189
 
2169
2190
 
2170
- async function startRedirectNavigation(redirect, navigation, replace) {
2191
+ async function startRedirectNavigation(state, redirect, replace) {
2171
2192
  if (redirect.revalidate) {
2172
2193
  isRevalidationRequired = true;
2173
2194
  }
2174
2195
 
2175
- invariant(navigation.location, "Expected a location on the redirect navigation"); // There's no need to abort on redirects, since we don't detect the
2196
+ let redirectLocation = createLocation(state.location, redirect.location);
2197
+ invariant(redirectLocation, "Expected a location on the redirect navigation");
2198
+
2199
+ if (redirect.external && typeof window !== "undefined" && typeof window.location !== "undefined") {
2200
+ if (replace) {
2201
+ window.location.replace(redirect.location);
2202
+ } else {
2203
+ window.location.assign(redirect.location);
2204
+ }
2205
+
2206
+ return;
2207
+ } // There's no need to abort on redirects, since we don't detect the
2176
2208
  // redirect until the action/loaders have settled
2177
2209
 
2210
+
2178
2211
  pendingNavigationController = null;
2179
2212
  let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push;
2180
- await startNavigation(redirectHistoryAction, navigation.location, {
2181
- overrideNavigation: navigation
2182
- });
2213
+ let {
2214
+ formMethod,
2215
+ formAction,
2216
+ formEncType,
2217
+ formData
2218
+ } = state.navigation; // If this was a 307/308 submission we want to preserve the HTTP method and
2219
+ // re-submit the POST/PUT/PATCH/DELETE as a submission navigation to the
2220
+ // redirected location
2221
+
2222
+ if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isSubmissionMethod(formMethod) && formEncType && formData) {
2223
+ await startNavigation(redirectHistoryAction, redirectLocation, {
2224
+ submission: {
2225
+ formMethod,
2226
+ formAction: redirect.location,
2227
+ formEncType,
2228
+ formData
2229
+ }
2230
+ });
2231
+ } else {
2232
+ // Otherwise, we kick off a new loading navigation, preserving the
2233
+ // submission info for the duration of this navigation
2234
+ await startNavigation(redirectHistoryAction, redirectLocation, {
2235
+ overrideNavigation: {
2236
+ state: "loading",
2237
+ location: redirectLocation,
2238
+ formMethod: formMethod || undefined,
2239
+ formAction: formAction || undefined,
2240
+ formEncType: formEncType || undefined,
2241
+ formData: formData || undefined
2242
+ }
2243
+ });
2244
+ }
2183
2245
  }
2184
2246
 
2185
2247
  async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
@@ -2383,6 +2445,7 @@
2383
2445
  // Passthrough to history-aware createHref used by useHref so we get proper
2384
2446
  // hash-aware URLs in DOM paths
2385
2447
  createHref: to => init.history.createHref(to),
2448
+ encodeLocation: to => init.history.encodeLocation(to),
2386
2449
  getFetcher,
2387
2450
  deleteFetcher,
2388
2451
  dispose,
@@ -2395,11 +2458,10 @@
2395
2458
  //#region createStaticHandler
2396
2459
  ////////////////////////////////////////////////////////////////////////////////
2397
2460
 
2398
- const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
2399
- const validRequestMethods = new Set(["GET", "HEAD", ...validActionMethods]);
2400
- function unstable_createStaticHandler(routes) {
2461
+ function unstable_createStaticHandler(routes, opts) {
2401
2462
  invariant(routes.length > 0, "You must provide a non-empty routes array to unstable_createStaticHandler");
2402
2463
  let dataRoutes = convertRoutesToDataRoutes(routes);
2464
+ let basename = (opts ? opts.basename : null) || "/";
2403
2465
  /**
2404
2466
  * The query() method is intended for document requests, in which we want to
2405
2467
  * call an optional action and potentially multiple loaders for all nested
@@ -2422,16 +2484,20 @@
2422
2484
 
2423
2485
  async function query(request) {
2424
2486
  let url = new URL(request.url);
2487
+ let method = request.method.toLowerCase();
2425
2488
  let location = createLocation("", createPath(url), null, "default");
2426
- let matches = matchRoutes(dataRoutes, location);
2489
+ let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
2427
2490
 
2428
- if (!validRequestMethods.has(request.method)) {
2491
+ if (!isValidMethod(method) && method !== "head") {
2492
+ let error = getInternalRouterError(405, {
2493
+ method
2494
+ });
2429
2495
  let {
2430
2496
  matches: methodNotAllowedMatches,
2431
- route,
2432
- error
2433
- } = getMethodNotAllowedMatches(dataRoutes);
2497
+ route
2498
+ } = getShortCircuitMatches(dataRoutes);
2434
2499
  return {
2500
+ basename,
2435
2501
  location,
2436
2502
  matches: methodNotAllowedMatches,
2437
2503
  loaderData: {},
@@ -2444,12 +2510,15 @@
2444
2510
  actionHeaders: {}
2445
2511
  };
2446
2512
  } else if (!matches) {
2513
+ let error = getInternalRouterError(404, {
2514
+ pathname: location.pathname
2515
+ });
2447
2516
  let {
2448
2517
  matches: notFoundMatches,
2449
- route,
2450
- error
2451
- } = getNotFoundMatches(dataRoutes);
2518
+ route
2519
+ } = getShortCircuitMatches(dataRoutes);
2452
2520
  return {
2521
+ basename,
2453
2522
  location,
2454
2523
  matches: notFoundMatches,
2455
2524
  loaderData: {},
@@ -2473,7 +2542,8 @@
2473
2542
 
2474
2543
 
2475
2544
  return _extends({
2476
- location
2545
+ location,
2546
+ basename
2477
2547
  }, result);
2478
2548
  }
2479
2549
  /**
@@ -2489,35 +2559,42 @@
2489
2559
  * can do proper boundary identification in Remix where a thrown Response
2490
2560
  * must go to the Catch Boundary but a returned Response is happy-path.
2491
2561
  *
2492
- * One thing to note is that any Router-initiated thrown Response (such as a
2493
- * 404 or 405) will have a custom X-Remix-Router-Error: "yes" header on it
2494
- * in order to differentiate from responses thrown from user actions/loaders.
2562
+ * One thing to note is that any Router-initiated Errors that make sense
2563
+ * to associate with a status code will be thrown as an ErrorResponse
2564
+ * instance which include the raw Error, such that the calling context can
2565
+ * serialize the error as they see fit while including the proper response
2566
+ * code. Examples here are 404 and 405 errors that occur prior to reaching
2567
+ * any user-defined loaders.
2495
2568
  */
2496
2569
 
2497
2570
 
2498
2571
  async function queryRoute(request, routeId) {
2499
2572
  let url = new URL(request.url);
2573
+ let method = request.method.toLowerCase();
2500
2574
  let location = createLocation("", createPath(url), null, "default");
2501
- let matches = matchRoutes(dataRoutes, location);
2575
+ let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
2502
2576
 
2503
- if (!validRequestMethods.has(request.method)) {
2504
- throw createRouterErrorResponse(null, {
2505
- status: 405,
2506
- statusText: "Method Not Allowed"
2577
+ if (!isValidMethod(method) && method !== "head") {
2578
+ throw getInternalRouterError(405, {
2579
+ method
2507
2580
  });
2508
2581
  } else if (!matches) {
2509
- throw createRouterErrorResponse(null, {
2510
- status: 404,
2511
- statusText: "Not Found"
2582
+ throw getInternalRouterError(404, {
2583
+ pathname: location.pathname
2512
2584
  });
2513
2585
  }
2514
2586
 
2515
2587
  let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
2516
2588
 
2517
- if (!match) {
2518
- throw createRouterErrorResponse(null, {
2519
- status: 404,
2520
- statusText: "Not Found"
2589
+ if (routeId && !match) {
2590
+ throw getInternalRouterError(403, {
2591
+ pathname: location.pathname,
2592
+ routeId
2593
+ });
2594
+ } else if (!match) {
2595
+ // This should never hit I don't think?
2596
+ throw getInternalRouterError(404, {
2597
+ pathname: location.pathname
2521
2598
  });
2522
2599
  }
2523
2600
 
@@ -2546,7 +2623,7 @@
2546
2623
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2547
2624
 
2548
2625
  try {
2549
- if (validActionMethods.has(request.method)) {
2626
+ if (isSubmissionMethod(request.method.toLowerCase())) {
2550
2627
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), routeMatch != null);
2551
2628
  return result;
2552
2629
  }
@@ -2582,17 +2659,22 @@
2582
2659
  let result;
2583
2660
 
2584
2661
  if (!actionMatch.route.action) {
2662
+ let error = getInternalRouterError(405, {
2663
+ method: request.method,
2664
+ pathname: createURL(request.url).pathname,
2665
+ routeId: actionMatch.route.id
2666
+ });
2667
+
2585
2668
  if (isRouteRequest) {
2586
- throw createRouterErrorResponse(null, {
2587
- status: 405,
2588
- statusText: "Method Not Allowed"
2589
- });
2669
+ throw error;
2590
2670
  }
2591
2671
 
2592
- result = getMethodNotAllowedResult(request.url);
2672
+ result = {
2673
+ type: ResultType.error,
2674
+ error
2675
+ };
2593
2676
  } else {
2594
- result = await callLoaderOrAction("action", request, actionMatch, matches, undefined, // Basename not currently supported in static handlers
2595
- true, isRouteRequest);
2677
+ result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest);
2596
2678
 
2597
2679
  if (request.signal.aborted) {
2598
2680
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2621,20 +2703,7 @@
2621
2703
  // Note: This should only be non-Response values if we get here, since
2622
2704
  // isRouteRequest should throw any Response received in callLoaderOrAction
2623
2705
  if (isErrorResult(result)) {
2624
- let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2625
- return {
2626
- matches: [actionMatch],
2627
- loaderData: {},
2628
- actionData: null,
2629
- errors: {
2630
- [boundaryMatch.route.id]: result.error
2631
- },
2632
- // Note: statusCode + headers are unused here since queryRoute will
2633
- // return the raw Response or value
2634
- statusCode: 500,
2635
- loaderHeaders: {},
2636
- actionHeaders: {}
2637
- };
2706
+ throw result.error;
2638
2707
  }
2639
2708
 
2640
2709
  return {
@@ -2683,9 +2752,18 @@
2683
2752
  }
2684
2753
 
2685
2754
  async function loadRouteData(request, matches, routeMatch, pendingActionError) {
2686
- let isRouteRequest = routeMatch != null;
2755
+ let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
2756
+
2757
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
2758
+ throw getInternalRouterError(400, {
2759
+ method: request.method,
2760
+ pathname: createURL(request.url).pathname,
2761
+ routeId: routeMatch == null ? void 0 : routeMatch.route.id
2762
+ });
2763
+ }
2764
+
2687
2765
  let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
2688
- let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run
2766
+ let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
2689
2767
 
2690
2768
  if (matchesToLoad.length === 0) {
2691
2769
  return {
@@ -2697,8 +2775,7 @@
2697
2775
  };
2698
2776
  }
2699
2777
 
2700
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, undefined, // Basename not currently supported in static handlers
2701
- true, isRouteRequest))]);
2778
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest))]);
2702
2779
 
2703
2780
  if (request.signal.aborted) {
2704
2781
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2719,14 +2796,6 @@
2719
2796
  });
2720
2797
  }
2721
2798
 
2722
- function createRouterErrorResponse(body, init) {
2723
- return new Response(body, _extends({}, init, {
2724
- headers: _extends({}, init.headers, {
2725
- "X-Remix-Router-Error": "yes"
2726
- })
2727
- }));
2728
- }
2729
-
2730
2799
  return {
2731
2800
  dataRoutes,
2732
2801
  query,
@@ -2751,9 +2820,14 @@
2751
2820
  });
2752
2821
 
2753
2822
  return newContext;
2823
+ }
2824
+
2825
+ function isSubmissionNavigation(opts) {
2826
+ return opts != null && "formData" in opts;
2754
2827
  } // Normalize navigation options by converting formMethod=GET formData objects to
2755
2828
  // URLSearchParams so they behave identically to links with query params
2756
2829
 
2830
+
2757
2831
  function normalizeNavigateOptions(to, opts, isFetcher) {
2758
2832
  if (isFetcher === void 0) {
2759
2833
  isFetcher = false;
@@ -2761,14 +2835,23 @@
2761
2835
 
2762
2836
  let path = typeof to === "string" ? to : createPath(to); // Return location verbatim on non-submission navigations
2763
2837
 
2764
- if (!opts || !("formMethod" in opts) && !("formData" in opts)) {
2838
+ if (!opts || !isSubmissionNavigation(opts)) {
2765
2839
  return {
2766
2840
  path
2767
2841
  };
2842
+ }
2843
+
2844
+ if (opts.formMethod && !isValidMethod(opts.formMethod)) {
2845
+ return {
2846
+ path,
2847
+ error: getInternalRouterError(405, {
2848
+ method: opts.formMethod
2849
+ })
2850
+ };
2768
2851
  } // Create a Submission on non-GET navigations
2769
2852
 
2770
2853
 
2771
- if (opts.formMethod != null && opts.formMethod !== "get") {
2854
+ if (opts.formMethod && isSubmissionMethod(opts.formMethod)) {
2772
2855
  return {
2773
2856
  path,
2774
2857
  submission: {
@@ -2778,13 +2861,6 @@
2778
2861
  formData: opts.formData
2779
2862
  }
2780
2863
  };
2781
- } // No formData to flatten for GET submission
2782
-
2783
-
2784
- if (!opts.formData) {
2785
- return {
2786
- path
2787
- };
2788
2864
  } // Flatten submission onto URLSearchParams for GET submissions
2789
2865
 
2790
2866
 
@@ -2803,31 +2879,13 @@
2803
2879
  } catch (e) {
2804
2880
  return {
2805
2881
  path,
2806
- error: new ErrorResponse(400, "Bad Request", "Cannot submit binary form data using GET")
2882
+ error: getInternalRouterError(400)
2807
2883
  };
2808
2884
  }
2809
2885
 
2810
2886
  return {
2811
2887
  path: createPath(parsedPath)
2812
2888
  };
2813
- }
2814
-
2815
- function getLoaderRedirect(state, redirect) {
2816
- let {
2817
- formMethod,
2818
- formAction,
2819
- formEncType,
2820
- formData
2821
- } = state.navigation;
2822
- let navigation = {
2823
- state: "loading",
2824
- location: createLocation(state.location, redirect.location),
2825
- formMethod: formMethod || undefined,
2826
- formAction: formAction || undefined,
2827
- formEncType: formEncType || undefined,
2828
- formData: formData || undefined
2829
- };
2830
- return navigation;
2831
2889
  } // Filter out all routes below any caught error as they aren't going to
2832
2890
  // render so we don't need to load them
2833
2891
 
@@ -2928,6 +2986,10 @@
2928
2986
  }
2929
2987
 
2930
2988
  async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest) {
2989
+ if (basename === void 0) {
2990
+ basename = "/";
2991
+ }
2992
+
2931
2993
  if (isStaticRequest === void 0) {
2932
2994
  isStaticRequest = false;
2933
2995
  }
@@ -2953,6 +3015,7 @@
2953
3015
  request,
2954
3016
  params: match.params
2955
3017
  }), abortPromise]);
3018
+ invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
2956
3019
  } catch (e) {
2957
3020
  resultType = ResultType.error;
2958
3021
  result = e;
@@ -2963,26 +3026,31 @@
2963
3026
  if (result instanceof Response) {
2964
3027
  let status = result.status; // Process redirects
2965
3028
 
2966
- if (status >= 300 && status <= 399) {
3029
+ if (redirectStatusCodes.has(status)) {
2967
3030
  let location = result.headers.get("Location");
2968
- invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header"); // Support relative routing in redirects
3031
+ invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header"); // Check if this an external redirect that goes to a new origin
2969
3032
 
2970
- let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
2971
- let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
2972
- let requestPath = createURL(request.url).pathname;
2973
- let resolvedLocation = resolveTo(location, routePathnames, requestPath);
2974
- invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + result.headers.get("Location")); // Prepend the basename to the redirect location if we have one
3033
+ let external = createURL(location).origin !== createURL("/").origin; // Support relative routing in internal redirects
2975
3034
 
2976
- if (basename) {
2977
- let path = resolvedLocation.pathname;
2978
- resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
2979
- }
3035
+ if (!external) {
3036
+ let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
3037
+ let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
3038
+ let requestPath = createURL(request.url).pathname;
3039
+ let resolvedLocation = resolveTo(location, routePathnames, requestPath);
3040
+ invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + location); // Prepend the basename to the redirect location if we have one
3041
+
3042
+ if (basename) {
3043
+ let path = resolvedLocation.pathname;
3044
+ resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
3045
+ }
2980
3046
 
2981
- location = createPath(resolvedLocation); // Don't process redirects in the router during static requests requests.
3047
+ location = createPath(resolvedLocation);
3048
+ } // Don't process redirects in the router during static requests requests.
2982
3049
  // Instead, throw the Response and let the server handle it with an HTTP
2983
3050
  // redirect. We also update the Location header in place in this flow so
2984
3051
  // basename and relative routing is taken into account
2985
3052
 
3053
+
2986
3054
  if (isStaticRequest) {
2987
3055
  result.headers.set("Location", location);
2988
3056
  throw result;
@@ -2992,7 +3060,8 @@
2992
3060
  type: ResultType.redirect,
2993
3061
  status,
2994
3062
  location,
2995
- revalidate: result.headers.get("X-Remix-Revalidate") !== null
3063
+ revalidate: result.headers.get("X-Remix-Revalidate") !== null,
3064
+ external
2996
3065
  };
2997
3066
  } // For SSR single-route requests, we want to hand Responses back directly
2998
3067
  // without unwrapping. We do this with the QueryRouteResponse wrapper
@@ -3220,10 +3289,10 @@
3220
3289
  return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
3221
3290
  }
3222
3291
 
3223
- function getShortCircuitMatches(routes, status, statusText) {
3292
+ function getShortCircuitMatches(routes) {
3224
3293
  // Prefer a root layout route if present, otherwise shim in a route object
3225
3294
  let route = routes.find(r => r.index || !r.path || r.path === "/") || {
3226
- id: "__shim-" + status + "-route__"
3295
+ id: "__shim-error-route__"
3227
3296
  };
3228
3297
  return {
3229
3298
  matches: [{
@@ -3232,26 +3301,45 @@
3232
3301
  pathnameBase: "",
3233
3302
  route
3234
3303
  }],
3235
- route,
3236
- error: new ErrorResponse(status, statusText, null)
3304
+ route
3237
3305
  };
3238
3306
  }
3239
3307
 
3240
- function getNotFoundMatches(routes) {
3241
- return getShortCircuitMatches(routes, 404, "Not Found");
3242
- }
3308
+ function getInternalRouterError(status, _temp) {
3309
+ let {
3310
+ pathname,
3311
+ routeId,
3312
+ method,
3313
+ message
3314
+ } = _temp === void 0 ? {} : _temp;
3315
+ let statusText = "Unknown Server Error";
3316
+ let errorMessage = "Unknown @remix-run/router error";
3317
+
3318
+ if (status === 400) {
3319
+ statusText = "Bad Request";
3320
+
3321
+ if (method && pathname && routeId) {
3322
+ errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
3323
+ } else {
3324
+ errorMessage = "Cannot submit binary form data using GET";
3325
+ }
3326
+ } else if (status === 403) {
3327
+ statusText = "Forbidden";
3328
+ errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
3329
+ } else if (status === 404) {
3330
+ statusText = "Not Found";
3331
+ errorMessage = "No route matches URL \"" + pathname + "\"";
3332
+ } else if (status === 405) {
3333
+ statusText = "Method Not Allowed";
3243
3334
 
3244
- function getMethodNotAllowedMatches(routes) {
3245
- return getShortCircuitMatches(routes, 405, "Method Not Allowed");
3246
- }
3335
+ if (method && pathname && routeId) {
3336
+ errorMessage = "You made a " + method.toUpperCase() + " request to \"" + pathname + "\" but " + ("did not provide an `action` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
3337
+ } else if (method) {
3338
+ errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
3339
+ }
3340
+ }
3247
3341
 
3248
- function getMethodNotAllowedResult(path) {
3249
- let href = typeof path === "string" ? path : createPath(path);
3250
- 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 + "]"));
3251
- return {
3252
- type: ResultType.error,
3253
- error: new ErrorResponse(405, "Method Not Allowed", "")
3254
- };
3342
+ return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
3255
3343
  } // Find any returned redirect errors, starting from the lowest match
3256
3344
 
3257
3345
 
@@ -3302,6 +3390,14 @@
3302
3390
  return obj && obj.response instanceof Response && (obj.type === ResultType.data || ResultType.error);
3303
3391
  }
3304
3392
 
3393
+ function isValidMethod(method) {
3394
+ return validRequestMethods.has(method);
3395
+ }
3396
+
3397
+ function isSubmissionMethod(method) {
3398
+ return validActionMethods.has(method);
3399
+ }
3400
+
3305
3401
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
3306
3402
  for (let index = 0; index < results.length; index++) {
3307
3403
  let result = results[index];