@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.
package/dist/router.js CHANGED
@@ -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
  *
@@ -116,8 +116,13 @@ function createMemoryHistory(options) {
116
116
  return typeof to === "string" ? to : createPath(to);
117
117
  },
118
118
 
119
- encodeLocation(location) {
120
- return location;
119
+ encodeLocation(to) {
120
+ let path = typeof to === "string" ? parsePath(to) : to;
121
+ return {
122
+ pathname: path.pathname || "",
123
+ search: path.search || "",
124
+ hash: path.hash || ""
125
+ };
121
126
  },
122
127
 
123
128
  push(to, state) {
@@ -449,14 +454,14 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
449
454
  return createHref(window, to);
450
455
  },
451
456
 
452
- encodeLocation(location) {
457
+ encodeLocation(to) {
453
458
  // Encode a Location the same way window.location would
454
- let url = createURL(createPath(location));
455
- return _extends({}, location, {
459
+ let url = createURL(typeof to === "string" ? to : createPath(to));
460
+ return {
456
461
  pathname: url.pathname,
457
462
  search: url.search,
458
463
  hash: url.hash
459
- });
464
+ };
460
465
  },
461
466
 
462
467
  push,
@@ -1217,10 +1222,21 @@ const redirect = function redirect(url, init) {
1217
1222
  */
1218
1223
 
1219
1224
  class ErrorResponse {
1220
- constructor(status, statusText, data) {
1225
+ constructor(status, statusText, data, internal) {
1226
+ if (internal === void 0) {
1227
+ internal = false;
1228
+ }
1229
+
1221
1230
  this.status = status;
1222
1231
  this.statusText = statusText || "";
1223
- this.data = data;
1232
+ this.internal = internal;
1233
+
1234
+ if (data instanceof Error) {
1235
+ this.data = data.toString();
1236
+ this.error = data;
1237
+ } else {
1238
+ this.data = data;
1239
+ }
1224
1240
  }
1225
1241
 
1226
1242
  }
@@ -1233,6 +1249,12 @@ function isRouteErrorResponse(e) {
1233
1249
  return e instanceof ErrorResponse;
1234
1250
  }
1235
1251
 
1252
+ const validActionMethodsArr = ["post", "put", "patch", "delete"];
1253
+ const validActionMethods = new Set(validActionMethodsArr);
1254
+ const validRequestMethodsArr = ["get", ...validActionMethodsArr];
1255
+ const validRequestMethods = new Set(validRequestMethodsArr);
1256
+ const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
1257
+ const redirectPreserveMethodStatusCodes = new Set([307, 308]);
1236
1258
  const IDLE_NAVIGATION = {
1237
1259
  state: "idle",
1238
1260
  location: undefined,
@@ -1283,11 +1305,13 @@ function createRouter(init) {
1283
1305
  if (initialMatches == null) {
1284
1306
  // If we do not match a user-provided-route, fall back to the root
1285
1307
  // to allow the error boundary to take over
1308
+ let error = getInternalRouterError(404, {
1309
+ pathname: init.history.location.pathname
1310
+ });
1286
1311
  let {
1287
1312
  matches,
1288
- route,
1289
- error
1290
- } = getNotFoundMatches(dataRoutes);
1313
+ route
1314
+ } = getShortCircuitMatches(dataRoutes);
1291
1315
  initialMatches = matches;
1292
1316
  initialErrors = {
1293
1317
  [route.id]: error
@@ -1463,7 +1487,7 @@ function createRouter(init) {
1463
1487
  // the same encoding we'd get from a history.pushState/window.location read
1464
1488
  // without having to touch history
1465
1489
 
1466
- location = init.history.encodeLocation(location);
1490
+ location = _extends({}, location, init.history.encodeLocation(location));
1467
1491
  let historyAction = (opts && opts.replace) === true || submission != null ? Action.Replace : Action.Push;
1468
1492
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1469
1493
  return await startNavigation(historyAction, location, {
@@ -1527,11 +1551,13 @@ function createRouter(init) {
1527
1551
  let matches = matchRoutes(dataRoutes, location, init.basename); // Short circuit with a 404 on the root error boundary if we match nothing
1528
1552
 
1529
1553
  if (!matches) {
1554
+ let error = getInternalRouterError(404, {
1555
+ pathname: location.pathname
1556
+ });
1530
1557
  let {
1531
1558
  matches: notFoundMatches,
1532
- route,
1533
- error
1534
- } = getNotFoundMatches(dataRoutes); // Cancel all pending deferred on 404s since we don't keep any routes
1559
+ route
1560
+ } = getShortCircuitMatches(dataRoutes); // Cancel all pending deferred on 404s since we don't keep any routes
1535
1561
 
1536
1562
  cancelActiveDeferreds();
1537
1563
  completeNavigation(location, {
@@ -1627,7 +1653,14 @@ function createRouter(init) {
1627
1653
  let actionMatch = getTargetMatch(matches, location);
1628
1654
 
1629
1655
  if (!actionMatch.route.action) {
1630
- result = getMethodNotAllowedResult(location);
1656
+ result = {
1657
+ type: ResultType.error,
1658
+ error: getInternalRouterError(405, {
1659
+ method: request.method,
1660
+ pathname: location.pathname,
1661
+ routeId: actionMatch.route.id
1662
+ })
1663
+ };
1631
1664
  } else {
1632
1665
  result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
1633
1666
 
@@ -1639,12 +1672,7 @@ function createRouter(init) {
1639
1672
  }
1640
1673
 
1641
1674
  if (isRedirectResult(result)) {
1642
- let redirectNavigation = _extends({
1643
- state: "loading",
1644
- location: createLocation(state.location, result.location)
1645
- }, submission);
1646
-
1647
- await startRedirectNavigation(result, redirectNavigation, opts && opts.replace);
1675
+ await startRedirectNavigation(state, result, opts && opts.replace === true);
1648
1676
  return {
1649
1677
  shortCircuited: true
1650
1678
  };
@@ -1771,8 +1799,7 @@ function createRouter(init) {
1771
1799
  let redirect = findRedirect(results);
1772
1800
 
1773
1801
  if (redirect) {
1774
- let redirectNavigation = getLoaderRedirect(state, redirect);
1775
- await startRedirectNavigation(redirect, redirectNavigation, replace);
1802
+ await startRedirectNavigation(state, redirect, replace);
1776
1803
  return {
1777
1804
  shortCircuited: true
1778
1805
  };
@@ -1818,7 +1845,9 @@ function createRouter(init) {
1818
1845
  let matches = matchRoutes(dataRoutes, href, init.basename);
1819
1846
 
1820
1847
  if (!matches) {
1821
- setFetcherError(key, routeId, new ErrorResponse(404, "Not Found", null));
1848
+ setFetcherError(key, routeId, getInternalRouterError(404, {
1849
+ pathname: href
1850
+ }));
1822
1851
  return;
1823
1852
  }
1824
1853
 
@@ -1846,9 +1875,11 @@ function createRouter(init) {
1846
1875
  fetchLoadMatches.delete(key);
1847
1876
 
1848
1877
  if (!match.route.action) {
1849
- let {
1850
- error
1851
- } = getMethodNotAllowedResult(path);
1878
+ let error = getInternalRouterError(405, {
1879
+ method: submission.formMethod,
1880
+ pathname: path,
1881
+ routeId: routeId
1882
+ });
1852
1883
  setFetcherError(key, routeId, error);
1853
1884
  return;
1854
1885
  } // Put this fetcher into it's submitting state
@@ -1896,14 +1927,7 @@ function createRouter(init) {
1896
1927
  updateState({
1897
1928
  fetchers: new Map(state.fetchers)
1898
1929
  });
1899
-
1900
- let redirectNavigation = _extends({
1901
- state: "loading",
1902
- location: createLocation(state.location, actionResult.location)
1903
- }, submission);
1904
-
1905
- await startRedirectNavigation(actionResult, redirectNavigation);
1906
- return;
1930
+ return startRedirectNavigation(state, actionResult);
1907
1931
  } // Process any non-redirect errors thrown
1908
1932
 
1909
1933
 
@@ -1977,9 +2001,7 @@ function createRouter(init) {
1977
2001
  let redirect = findRedirect(results);
1978
2002
 
1979
2003
  if (redirect) {
1980
- let redirectNavigation = getLoaderRedirect(state, redirect);
1981
- await startRedirectNavigation(redirect, redirectNavigation);
1982
- return;
2004
+ return startRedirectNavigation(state, redirect);
1983
2005
  } // Process and commit output from loaders
1984
2006
 
1985
2007
 
@@ -2064,8 +2086,7 @@ function createRouter(init) {
2064
2086
 
2065
2087
 
2066
2088
  if (isRedirectResult(result)) {
2067
- let redirectNavigation = getLoaderRedirect(state, result);
2068
- await startRedirectNavigation(result, redirectNavigation);
2089
+ await startRedirectNavigation(state, result);
2069
2090
  return;
2070
2091
  } // Process any non-redirect errors thrown
2071
2092
 
@@ -2121,19 +2142,60 @@ function createRouter(init) {
2121
2142
  */
2122
2143
 
2123
2144
 
2124
- async function startRedirectNavigation(redirect, navigation, replace) {
2145
+ async function startRedirectNavigation(state, redirect, replace) {
2125
2146
  if (redirect.revalidate) {
2126
2147
  isRevalidationRequired = true;
2127
2148
  }
2128
2149
 
2129
- invariant(navigation.location, "Expected a location on the redirect navigation"); // There's no need to abort on redirects, since we don't detect the
2150
+ let redirectLocation = createLocation(state.location, redirect.location);
2151
+ invariant(redirectLocation, "Expected a location on the redirect navigation");
2152
+
2153
+ if (redirect.external && typeof window !== "undefined" && typeof window.location !== "undefined") {
2154
+ if (replace) {
2155
+ window.location.replace(redirect.location);
2156
+ } else {
2157
+ window.location.assign(redirect.location);
2158
+ }
2159
+
2160
+ return;
2161
+ } // There's no need to abort on redirects, since we don't detect the
2130
2162
  // redirect until the action/loaders have settled
2131
2163
 
2164
+
2132
2165
  pendingNavigationController = null;
2133
2166
  let redirectHistoryAction = replace === true ? Action.Replace : Action.Push;
2134
- await startNavigation(redirectHistoryAction, navigation.location, {
2135
- overrideNavigation: navigation
2136
- });
2167
+ let {
2168
+ formMethod,
2169
+ formAction,
2170
+ formEncType,
2171
+ formData
2172
+ } = state.navigation; // If this was a 307/308 submission we want to preserve the HTTP method and
2173
+ // re-submit the POST/PUT/PATCH/DELETE as a submission navigation to the
2174
+ // redirected location
2175
+
2176
+ if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isSubmissionMethod(formMethod) && formEncType && formData) {
2177
+ await startNavigation(redirectHistoryAction, redirectLocation, {
2178
+ submission: {
2179
+ formMethod,
2180
+ formAction: redirect.location,
2181
+ formEncType,
2182
+ formData
2183
+ }
2184
+ });
2185
+ } else {
2186
+ // Otherwise, we kick off a new loading navigation, preserving the
2187
+ // submission info for the duration of this navigation
2188
+ await startNavigation(redirectHistoryAction, redirectLocation, {
2189
+ overrideNavigation: {
2190
+ state: "loading",
2191
+ location: redirectLocation,
2192
+ formMethod: formMethod || undefined,
2193
+ formAction: formAction || undefined,
2194
+ formEncType: formEncType || undefined,
2195
+ formData: formData || undefined
2196
+ }
2197
+ });
2198
+ }
2137
2199
  }
2138
2200
 
2139
2201
  async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
@@ -2337,6 +2399,7 @@ function createRouter(init) {
2337
2399
  // Passthrough to history-aware createHref used by useHref so we get proper
2338
2400
  // hash-aware URLs in DOM paths
2339
2401
  createHref: to => init.history.createHref(to),
2402
+ encodeLocation: to => init.history.encodeLocation(to),
2340
2403
  getFetcher,
2341
2404
  deleteFetcher,
2342
2405
  dispose,
@@ -2349,11 +2412,10 @@ function createRouter(init) {
2349
2412
  //#region createStaticHandler
2350
2413
  ////////////////////////////////////////////////////////////////////////////////
2351
2414
 
2352
- const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
2353
- const validRequestMethods = new Set(["GET", "HEAD", ...validActionMethods]);
2354
- function unstable_createStaticHandler(routes) {
2415
+ function unstable_createStaticHandler(routes, opts) {
2355
2416
  invariant(routes.length > 0, "You must provide a non-empty routes array to unstable_createStaticHandler");
2356
2417
  let dataRoutes = convertRoutesToDataRoutes(routes);
2418
+ let basename = (opts ? opts.basename : null) || "/";
2357
2419
  /**
2358
2420
  * The query() method is intended for document requests, in which we want to
2359
2421
  * call an optional action and potentially multiple loaders for all nested
@@ -2376,16 +2438,20 @@ function unstable_createStaticHandler(routes) {
2376
2438
 
2377
2439
  async function query(request) {
2378
2440
  let url = new URL(request.url);
2441
+ let method = request.method.toLowerCase();
2379
2442
  let location = createLocation("", createPath(url), null, "default");
2380
- let matches = matchRoutes(dataRoutes, location);
2443
+ let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
2381
2444
 
2382
- if (!validRequestMethods.has(request.method)) {
2445
+ if (!isValidMethod(method) && method !== "head") {
2446
+ let error = getInternalRouterError(405, {
2447
+ method
2448
+ });
2383
2449
  let {
2384
2450
  matches: methodNotAllowedMatches,
2385
- route,
2386
- error
2387
- } = getMethodNotAllowedMatches(dataRoutes);
2451
+ route
2452
+ } = getShortCircuitMatches(dataRoutes);
2388
2453
  return {
2454
+ basename,
2389
2455
  location,
2390
2456
  matches: methodNotAllowedMatches,
2391
2457
  loaderData: {},
@@ -2398,12 +2464,15 @@ function unstable_createStaticHandler(routes) {
2398
2464
  actionHeaders: {}
2399
2465
  };
2400
2466
  } else if (!matches) {
2467
+ let error = getInternalRouterError(404, {
2468
+ pathname: location.pathname
2469
+ });
2401
2470
  let {
2402
2471
  matches: notFoundMatches,
2403
- route,
2404
- error
2405
- } = getNotFoundMatches(dataRoutes);
2472
+ route
2473
+ } = getShortCircuitMatches(dataRoutes);
2406
2474
  return {
2475
+ basename,
2407
2476
  location,
2408
2477
  matches: notFoundMatches,
2409
2478
  loaderData: {},
@@ -2427,7 +2496,8 @@ function unstable_createStaticHandler(routes) {
2427
2496
 
2428
2497
 
2429
2498
  return _extends({
2430
- location
2499
+ location,
2500
+ basename
2431
2501
  }, result);
2432
2502
  }
2433
2503
  /**
@@ -2443,35 +2513,42 @@ function unstable_createStaticHandler(routes) {
2443
2513
  * can do proper boundary identification in Remix where a thrown Response
2444
2514
  * must go to the Catch Boundary but a returned Response is happy-path.
2445
2515
  *
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.
2516
+ * One thing to note is that any Router-initiated Errors that make sense
2517
+ * to associate with a status code will be thrown as an ErrorResponse
2518
+ * instance which include the raw Error, such that the calling context can
2519
+ * serialize the error as they see fit while including the proper response
2520
+ * code. Examples here are 404 and 405 errors that occur prior to reaching
2521
+ * any user-defined loaders.
2449
2522
  */
2450
2523
 
2451
2524
 
2452
2525
  async function queryRoute(request, routeId) {
2453
2526
  let url = new URL(request.url);
2527
+ let method = request.method.toLowerCase();
2454
2528
  let location = createLocation("", createPath(url), null, "default");
2455
- let matches = matchRoutes(dataRoutes, location);
2529
+ let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
2456
2530
 
2457
- if (!validRequestMethods.has(request.method)) {
2458
- throw createRouterErrorResponse(null, {
2459
- status: 405,
2460
- statusText: "Method Not Allowed"
2531
+ if (!isValidMethod(method) && method !== "head") {
2532
+ throw getInternalRouterError(405, {
2533
+ method
2461
2534
  });
2462
2535
  } else if (!matches) {
2463
- throw createRouterErrorResponse(null, {
2464
- status: 404,
2465
- statusText: "Not Found"
2536
+ throw getInternalRouterError(404, {
2537
+ pathname: location.pathname
2466
2538
  });
2467
2539
  }
2468
2540
 
2469
2541
  let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
2470
2542
 
2471
- if (!match) {
2472
- throw createRouterErrorResponse(null, {
2473
- status: 404,
2474
- statusText: "Not Found"
2543
+ if (routeId && !match) {
2544
+ throw getInternalRouterError(403, {
2545
+ pathname: location.pathname,
2546
+ routeId
2547
+ });
2548
+ } else if (!match) {
2549
+ // This should never hit I don't think?
2550
+ throw getInternalRouterError(404, {
2551
+ pathname: location.pathname
2475
2552
  });
2476
2553
  }
2477
2554
 
@@ -2500,7 +2577,7 @@ function unstable_createStaticHandler(routes) {
2500
2577
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2501
2578
 
2502
2579
  try {
2503
- if (validActionMethods.has(request.method)) {
2580
+ if (isSubmissionMethod(request.method.toLowerCase())) {
2504
2581
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), routeMatch != null);
2505
2582
  return result;
2506
2583
  }
@@ -2536,17 +2613,22 @@ function unstable_createStaticHandler(routes) {
2536
2613
  let result;
2537
2614
 
2538
2615
  if (!actionMatch.route.action) {
2616
+ let error = getInternalRouterError(405, {
2617
+ method: request.method,
2618
+ pathname: createURL(request.url).pathname,
2619
+ routeId: actionMatch.route.id
2620
+ });
2621
+
2539
2622
  if (isRouteRequest) {
2540
- throw createRouterErrorResponse(null, {
2541
- status: 405,
2542
- statusText: "Method Not Allowed"
2543
- });
2623
+ throw error;
2544
2624
  }
2545
2625
 
2546
- result = getMethodNotAllowedResult(request.url);
2626
+ result = {
2627
+ type: ResultType.error,
2628
+ error
2629
+ };
2547
2630
  } else {
2548
- result = await callLoaderOrAction("action", request, actionMatch, matches, undefined, // Basename not currently supported in static handlers
2549
- true, isRouteRequest);
2631
+ result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest);
2550
2632
 
2551
2633
  if (request.signal.aborted) {
2552
2634
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2575,20 +2657,7 @@ function unstable_createStaticHandler(routes) {
2575
2657
  // Note: This should only be non-Response values if we get here, since
2576
2658
  // isRouteRequest should throw any Response received in callLoaderOrAction
2577
2659
  if (isErrorResult(result)) {
2578
- let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
2579
- return {
2580
- matches: [actionMatch],
2581
- loaderData: {},
2582
- actionData: null,
2583
- errors: {
2584
- [boundaryMatch.route.id]: result.error
2585
- },
2586
- // Note: statusCode + headers are unused here since queryRoute will
2587
- // return the raw Response or value
2588
- statusCode: 500,
2589
- loaderHeaders: {},
2590
- actionHeaders: {}
2591
- };
2660
+ throw result.error;
2592
2661
  }
2593
2662
 
2594
2663
  return {
@@ -2637,9 +2706,18 @@ function unstable_createStaticHandler(routes) {
2637
2706
  }
2638
2707
 
2639
2708
  async function loadRouteData(request, matches, routeMatch, pendingActionError) {
2640
- let isRouteRequest = routeMatch != null;
2709
+ let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
2710
+
2711
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
2712
+ throw getInternalRouterError(400, {
2713
+ method: request.method,
2714
+ pathname: createURL(request.url).pathname,
2715
+ routeId: routeMatch == null ? void 0 : routeMatch.route.id
2716
+ });
2717
+ }
2718
+
2641
2719
  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
2720
+ let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
2643
2721
 
2644
2722
  if (matchesToLoad.length === 0) {
2645
2723
  return {
@@ -2651,8 +2729,7 @@ function unstable_createStaticHandler(routes) {
2651
2729
  };
2652
2730
  }
2653
2731
 
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))]);
2732
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest))]);
2656
2733
 
2657
2734
  if (request.signal.aborted) {
2658
2735
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2673,14 +2750,6 @@ function unstable_createStaticHandler(routes) {
2673
2750
  });
2674
2751
  }
2675
2752
 
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
- }));
2682
- }
2683
-
2684
2753
  return {
2685
2754
  dataRoutes,
2686
2755
  query,
@@ -2705,9 +2774,14 @@ function getStaticContextFromError(routes, context, error) {
2705
2774
  });
2706
2775
 
2707
2776
  return newContext;
2777
+ }
2778
+
2779
+ function isSubmissionNavigation(opts) {
2780
+ return opts != null && "formData" in opts;
2708
2781
  } // Normalize navigation options by converting formMethod=GET formData objects to
2709
2782
  // URLSearchParams so they behave identically to links with query params
2710
2783
 
2784
+
2711
2785
  function normalizeNavigateOptions(to, opts, isFetcher) {
2712
2786
  if (isFetcher === void 0) {
2713
2787
  isFetcher = false;
@@ -2715,14 +2789,23 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2715
2789
 
2716
2790
  let path = typeof to === "string" ? to : createPath(to); // Return location verbatim on non-submission navigations
2717
2791
 
2718
- if (!opts || !("formMethod" in opts) && !("formData" in opts)) {
2792
+ if (!opts || !isSubmissionNavigation(opts)) {
2719
2793
  return {
2720
2794
  path
2721
2795
  };
2796
+ }
2797
+
2798
+ if (opts.formMethod && !isValidMethod(opts.formMethod)) {
2799
+ return {
2800
+ path,
2801
+ error: getInternalRouterError(405, {
2802
+ method: opts.formMethod
2803
+ })
2804
+ };
2722
2805
  } // Create a Submission on non-GET navigations
2723
2806
 
2724
2807
 
2725
- if (opts.formMethod != null && opts.formMethod !== "get") {
2808
+ if (opts.formMethod && isSubmissionMethod(opts.formMethod)) {
2726
2809
  return {
2727
2810
  path,
2728
2811
  submission: {
@@ -2732,13 +2815,6 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2732
2815
  formData: opts.formData
2733
2816
  }
2734
2817
  };
2735
- } // No formData to flatten for GET submission
2736
-
2737
-
2738
- if (!opts.formData) {
2739
- return {
2740
- path
2741
- };
2742
2818
  } // Flatten submission onto URLSearchParams for GET submissions
2743
2819
 
2744
2820
 
@@ -2757,31 +2833,13 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2757
2833
  } catch (e) {
2758
2834
  return {
2759
2835
  path,
2760
- error: new ErrorResponse(400, "Bad Request", "Cannot submit binary form data using GET")
2836
+ error: getInternalRouterError(400)
2761
2837
  };
2762
2838
  }
2763
2839
 
2764
2840
  return {
2765
2841
  path: createPath(parsedPath)
2766
2842
  };
2767
- }
2768
-
2769
- function getLoaderRedirect(state, redirect) {
2770
- let {
2771
- formMethod,
2772
- formAction,
2773
- formEncType,
2774
- formData
2775
- } = state.navigation;
2776
- let navigation = {
2777
- state: "loading",
2778
- location: createLocation(state.location, redirect.location),
2779
- formMethod: formMethod || undefined,
2780
- formAction: formAction || undefined,
2781
- formEncType: formEncType || undefined,
2782
- formData: formData || undefined
2783
- };
2784
- return navigation;
2785
2843
  } // Filter out all routes below any caught error as they aren't going to
2786
2844
  // render so we don't need to load them
2787
2845
 
@@ -2882,6 +2940,10 @@ function shouldRevalidateLoader(currentLocation, currentMatch, submission, locat
2882
2940
  }
2883
2941
 
2884
2942
  async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest) {
2943
+ if (basename === void 0) {
2944
+ basename = "/";
2945
+ }
2946
+
2885
2947
  if (isStaticRequest === void 0) {
2886
2948
  isStaticRequest = false;
2887
2949
  }
@@ -2907,6 +2969,7 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
2907
2969
  request,
2908
2970
  params: match.params
2909
2971
  }), abortPromise]);
2972
+ 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`.");
2910
2973
  } catch (e) {
2911
2974
  resultType = ResultType.error;
2912
2975
  result = e;
@@ -2917,26 +2980,31 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
2917
2980
  if (result instanceof Response) {
2918
2981
  let status = result.status; // Process redirects
2919
2982
 
2920
- if (status >= 300 && status <= 399) {
2983
+ if (redirectStatusCodes.has(status)) {
2921
2984
  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
2985
+ 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
2923
2986
 
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
2987
+ let external = createURL(location).origin !== createURL("/").origin; // Support relative routing in internal redirects
2929
2988
 
2930
- if (basename) {
2931
- let path = resolvedLocation.pathname;
2932
- resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
2933
- }
2989
+ if (!external) {
2990
+ let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
2991
+ let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
2992
+ let requestPath = createURL(request.url).pathname;
2993
+ let resolvedLocation = resolveTo(location, routePathnames, requestPath);
2994
+ invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + location); // Prepend the basename to the redirect location if we have one
2995
+
2996
+ if (basename) {
2997
+ let path = resolvedLocation.pathname;
2998
+ resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
2999
+ }
2934
3000
 
2935
- location = createPath(resolvedLocation); // Don't process redirects in the router during static requests requests.
3001
+ location = createPath(resolvedLocation);
3002
+ } // Don't process redirects in the router during static requests requests.
2936
3003
  // Instead, throw the Response and let the server handle it with an HTTP
2937
3004
  // redirect. We also update the Location header in place in this flow so
2938
3005
  // basename and relative routing is taken into account
2939
3006
 
3007
+
2940
3008
  if (isStaticRequest) {
2941
3009
  result.headers.set("Location", location);
2942
3010
  throw result;
@@ -2946,7 +3014,8 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
2946
3014
  type: ResultType.redirect,
2947
3015
  status,
2948
3016
  location,
2949
- revalidate: result.headers.get("X-Remix-Revalidate") !== null
3017
+ revalidate: result.headers.get("X-Remix-Revalidate") !== null,
3018
+ external
2950
3019
  };
2951
3020
  } // For SSR single-route requests, we want to hand Responses back directly
2952
3021
  // without unwrapping. We do this with the QueryRouteResponse wrapper
@@ -3174,10 +3243,10 @@ function findNearestBoundary(matches, routeId) {
3174
3243
  return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
3175
3244
  }
3176
3245
 
3177
- function getShortCircuitMatches(routes, status, statusText) {
3246
+ function getShortCircuitMatches(routes) {
3178
3247
  // Prefer a root layout route if present, otherwise shim in a route object
3179
3248
  let route = routes.find(r => r.index || !r.path || r.path === "/") || {
3180
- id: "__shim-" + status + "-route__"
3249
+ id: "__shim-error-route__"
3181
3250
  };
3182
3251
  return {
3183
3252
  matches: [{
@@ -3186,26 +3255,45 @@ function getShortCircuitMatches(routes, status, statusText) {
3186
3255
  pathnameBase: "",
3187
3256
  route
3188
3257
  }],
3189
- route,
3190
- error: new ErrorResponse(status, statusText, null)
3258
+ route
3191
3259
  };
3192
3260
  }
3193
3261
 
3194
- function getNotFoundMatches(routes) {
3195
- return getShortCircuitMatches(routes, 404, "Not Found");
3196
- }
3262
+ function getInternalRouterError(status, _temp) {
3263
+ let {
3264
+ pathname,
3265
+ routeId,
3266
+ method,
3267
+ message
3268
+ } = _temp === void 0 ? {} : _temp;
3269
+ let statusText = "Unknown Server Error";
3270
+ let errorMessage = "Unknown @remix-run/router error";
3271
+
3272
+ if (status === 400) {
3273
+ statusText = "Bad Request";
3274
+
3275
+ if (method && pathname && routeId) {
3276
+ 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.";
3277
+ } else {
3278
+ errorMessage = "Cannot submit binary form data using GET";
3279
+ }
3280
+ } else if (status === 403) {
3281
+ statusText = "Forbidden";
3282
+ errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
3283
+ } else if (status === 404) {
3284
+ statusText = "Not Found";
3285
+ errorMessage = "No route matches URL \"" + pathname + "\"";
3286
+ } else if (status === 405) {
3287
+ statusText = "Method Not Allowed";
3197
3288
 
3198
- function getMethodNotAllowedMatches(routes) {
3199
- return getShortCircuitMatches(routes, 405, "Method Not Allowed");
3200
- }
3289
+ if (method && pathname && routeId) {
3290
+ 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.";
3291
+ } else if (method) {
3292
+ errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
3293
+ }
3294
+ }
3201
3295
 
3202
- function getMethodNotAllowedResult(path) {
3203
- let href = typeof path === "string" ? path : createPath(path);
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 + "]"));
3205
- return {
3206
- type: ResultType.error,
3207
- error: new ErrorResponse(405, "Method Not Allowed", "")
3208
- };
3296
+ return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
3209
3297
  } // Find any returned redirect errors, starting from the lowest match
3210
3298
 
3211
3299
 
@@ -3256,6 +3344,14 @@ function isQueryRouteResponse(obj) {
3256
3344
  return obj && obj.response instanceof Response && (obj.type === ResultType.data || ResultType.error);
3257
3345
  }
3258
3346
 
3347
+ function isValidMethod(method) {
3348
+ return validRequestMethods.has(method);
3349
+ }
3350
+
3351
+ function isSubmissionMethod(method) {
3352
+ return validActionMethods.has(method);
3353
+ }
3354
+
3259
3355
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
3260
3356
  for (let index = 0; index < results.length; index++) {
3261
3357
  let result = results[index];