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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.0.3
2
+ * @remix-run/router v1.0.4-pre.0
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,55 @@
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
+ window.location.replace(redirect.location);
2201
+ return;
2202
+ } // There's no need to abort on redirects, since we don't detect the
2176
2203
  // redirect until the action/loaders have settled
2177
2204
 
2205
+
2178
2206
  pendingNavigationController = null;
2179
2207
  let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push;
2180
- await startNavigation(redirectHistoryAction, navigation.location, {
2181
- overrideNavigation: navigation
2182
- });
2208
+ let {
2209
+ formMethod,
2210
+ formAction,
2211
+ formEncType,
2212
+ formData
2213
+ } = state.navigation; // If this was a 307/308 submission we want to preserve the HTTP method and
2214
+ // re-submit the POST/PUT/PATCH/DELETE as a submission navigation to the
2215
+ // redirected location
2216
+
2217
+ if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isSubmissionMethod(formMethod) && formEncType && formData) {
2218
+ await startNavigation(redirectHistoryAction, redirectLocation, {
2219
+ submission: {
2220
+ formMethod,
2221
+ formAction: redirect.location,
2222
+ formEncType,
2223
+ formData
2224
+ }
2225
+ });
2226
+ } else {
2227
+ // Otherwise, we kick off a new loading navigation, preserving the
2228
+ // submission info for the duration of this navigation
2229
+ await startNavigation(redirectHistoryAction, redirectLocation, {
2230
+ overrideNavigation: {
2231
+ state: "loading",
2232
+ location: redirectLocation,
2233
+ formMethod: formMethod || undefined,
2234
+ formAction: formAction || undefined,
2235
+ formEncType: formEncType || undefined,
2236
+ formData: formData || undefined
2237
+ }
2238
+ });
2239
+ }
2183
2240
  }
2184
2241
 
2185
2242
  async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
@@ -2383,6 +2440,7 @@
2383
2440
  // Passthrough to history-aware createHref used by useHref so we get proper
2384
2441
  // hash-aware URLs in DOM paths
2385
2442
  createHref: to => init.history.createHref(to),
2443
+ encodeLocation: to => init.history.encodeLocation(to),
2386
2444
  getFetcher,
2387
2445
  deleteFetcher,
2388
2446
  dispose,
@@ -2395,11 +2453,10 @@
2395
2453
  //#region createStaticHandler
2396
2454
  ////////////////////////////////////////////////////////////////////////////////
2397
2455
 
2398
- const validActionMethods = new Set(["POST", "PUT", "PATCH", "DELETE"]);
2399
- const validRequestMethods = new Set(["GET", "HEAD", ...validActionMethods]);
2400
- function unstable_createStaticHandler(routes) {
2456
+ function unstable_createStaticHandler(routes, opts) {
2401
2457
  invariant(routes.length > 0, "You must provide a non-empty routes array to unstable_createStaticHandler");
2402
2458
  let dataRoutes = convertRoutesToDataRoutes(routes);
2459
+ let basename = (opts ? opts.basename : null) || "/";
2403
2460
  /**
2404
2461
  * The query() method is intended for document requests, in which we want to
2405
2462
  * call an optional action and potentially multiple loaders for all nested
@@ -2422,16 +2479,20 @@
2422
2479
 
2423
2480
  async function query(request) {
2424
2481
  let url = new URL(request.url);
2482
+ let method = request.method.toLowerCase();
2425
2483
  let location = createLocation("", createPath(url), null, "default");
2426
- let matches = matchRoutes(dataRoutes, location);
2484
+ let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
2427
2485
 
2428
- if (!validRequestMethods.has(request.method)) {
2486
+ if (!isValidMethod(method) && method !== "head") {
2487
+ let error = getInternalRouterError(405, {
2488
+ method
2489
+ });
2429
2490
  let {
2430
2491
  matches: methodNotAllowedMatches,
2431
- route,
2432
- error
2433
- } = getMethodNotAllowedMatches(dataRoutes);
2492
+ route
2493
+ } = getShortCircuitMatches(dataRoutes);
2434
2494
  return {
2495
+ basename,
2435
2496
  location,
2436
2497
  matches: methodNotAllowedMatches,
2437
2498
  loaderData: {},
@@ -2444,12 +2505,15 @@
2444
2505
  actionHeaders: {}
2445
2506
  };
2446
2507
  } else if (!matches) {
2508
+ let error = getInternalRouterError(404, {
2509
+ pathname: location.pathname
2510
+ });
2447
2511
  let {
2448
2512
  matches: notFoundMatches,
2449
- route,
2450
- error
2451
- } = getNotFoundMatches(dataRoutes);
2513
+ route
2514
+ } = getShortCircuitMatches(dataRoutes);
2452
2515
  return {
2516
+ basename,
2453
2517
  location,
2454
2518
  matches: notFoundMatches,
2455
2519
  loaderData: {},
@@ -2473,7 +2537,8 @@
2473
2537
 
2474
2538
 
2475
2539
  return _extends({
2476
- location
2540
+ location,
2541
+ basename
2477
2542
  }, result);
2478
2543
  }
2479
2544
  /**
@@ -2489,35 +2554,42 @@
2489
2554
  * can do proper boundary identification in Remix where a thrown Response
2490
2555
  * must go to the Catch Boundary but a returned Response is happy-path.
2491
2556
  *
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.
2557
+ * One thing to note is that any Router-initiated Errors that make sense
2558
+ * to associate with a status code will be thrown as an ErrorResponse
2559
+ * instance which include the raw Error, such that the calling context can
2560
+ * serialize the error as they see fit while including the proper response
2561
+ * code. Examples here are 404 and 405 errors that occur prior to reaching
2562
+ * any user-defined loaders.
2495
2563
  */
2496
2564
 
2497
2565
 
2498
2566
  async function queryRoute(request, routeId) {
2499
2567
  let url = new URL(request.url);
2568
+ let method = request.method.toLowerCase();
2500
2569
  let location = createLocation("", createPath(url), null, "default");
2501
- let matches = matchRoutes(dataRoutes, location);
2570
+ let matches = matchRoutes(dataRoutes, location, basename); // SSR supports HEAD requests while SPA doesn't
2502
2571
 
2503
- if (!validRequestMethods.has(request.method)) {
2504
- throw createRouterErrorResponse(null, {
2505
- status: 405,
2506
- statusText: "Method Not Allowed"
2572
+ if (!isValidMethod(method) && method !== "head") {
2573
+ throw getInternalRouterError(405, {
2574
+ method
2507
2575
  });
2508
2576
  } else if (!matches) {
2509
- throw createRouterErrorResponse(null, {
2510
- status: 404,
2511
- statusText: "Not Found"
2577
+ throw getInternalRouterError(404, {
2578
+ pathname: location.pathname
2512
2579
  });
2513
2580
  }
2514
2581
 
2515
2582
  let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
2516
2583
 
2517
- if (!match) {
2518
- throw createRouterErrorResponse(null, {
2519
- status: 404,
2520
- statusText: "Not Found"
2584
+ if (routeId && !match) {
2585
+ throw getInternalRouterError(403, {
2586
+ pathname: location.pathname,
2587
+ routeId
2588
+ });
2589
+ } else if (!match) {
2590
+ // This should never hit I don't think?
2591
+ throw getInternalRouterError(404, {
2592
+ pathname: location.pathname
2521
2593
  });
2522
2594
  }
2523
2595
 
@@ -2546,7 +2618,7 @@
2546
2618
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2547
2619
 
2548
2620
  try {
2549
- if (validActionMethods.has(request.method)) {
2621
+ if (isSubmissionMethod(request.method.toLowerCase())) {
2550
2622
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), routeMatch != null);
2551
2623
  return result;
2552
2624
  }
@@ -2582,17 +2654,22 @@
2582
2654
  let result;
2583
2655
 
2584
2656
  if (!actionMatch.route.action) {
2657
+ let error = getInternalRouterError(405, {
2658
+ method: request.method,
2659
+ pathname: createURL(request.url).pathname,
2660
+ routeId: actionMatch.route.id
2661
+ });
2662
+
2585
2663
  if (isRouteRequest) {
2586
- throw createRouterErrorResponse(null, {
2587
- status: 405,
2588
- statusText: "Method Not Allowed"
2589
- });
2664
+ throw error;
2590
2665
  }
2591
2666
 
2592
- result = getMethodNotAllowedResult(request.url);
2667
+ result = {
2668
+ type: ResultType.error,
2669
+ error
2670
+ };
2593
2671
  } else {
2594
- result = await callLoaderOrAction("action", request, actionMatch, matches, undefined, // Basename not currently supported in static handlers
2595
- true, isRouteRequest);
2672
+ result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest);
2596
2673
 
2597
2674
  if (request.signal.aborted) {
2598
2675
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2621,20 +2698,7 @@
2621
2698
  // Note: This should only be non-Response values if we get here, since
2622
2699
  // isRouteRequest should throw any Response received in callLoaderOrAction
2623
2700
  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
- };
2701
+ throw result.error;
2638
2702
  }
2639
2703
 
2640
2704
  return {
@@ -2683,9 +2747,18 @@
2683
2747
  }
2684
2748
 
2685
2749
  async function loadRouteData(request, matches, routeMatch, pendingActionError) {
2686
- let isRouteRequest = routeMatch != null;
2750
+ let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
2751
+
2752
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
2753
+ throw getInternalRouterError(400, {
2754
+ method: request.method,
2755
+ pathname: createURL(request.url).pathname,
2756
+ routeId: routeMatch == null ? void 0 : routeMatch.route.id
2757
+ });
2758
+ }
2759
+
2687
2760
  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
2761
+ let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
2689
2762
 
2690
2763
  if (matchesToLoad.length === 0) {
2691
2764
  return {
@@ -2697,8 +2770,7 @@
2697
2770
  };
2698
2771
  }
2699
2772
 
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))]);
2773
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest))]);
2702
2774
 
2703
2775
  if (request.signal.aborted) {
2704
2776
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -2719,14 +2791,6 @@
2719
2791
  });
2720
2792
  }
2721
2793
 
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
2794
  return {
2731
2795
  dataRoutes,
2732
2796
  query,
@@ -2751,9 +2815,14 @@
2751
2815
  });
2752
2816
 
2753
2817
  return newContext;
2818
+ }
2819
+
2820
+ function isSubmissionNavigation(opts) {
2821
+ return opts != null && "formData" in opts;
2754
2822
  } // Normalize navigation options by converting formMethod=GET formData objects to
2755
2823
  // URLSearchParams so they behave identically to links with query params
2756
2824
 
2825
+
2757
2826
  function normalizeNavigateOptions(to, opts, isFetcher) {
2758
2827
  if (isFetcher === void 0) {
2759
2828
  isFetcher = false;
@@ -2761,14 +2830,23 @@
2761
2830
 
2762
2831
  let path = typeof to === "string" ? to : createPath(to); // Return location verbatim on non-submission navigations
2763
2832
 
2764
- if (!opts || !("formMethod" in opts) && !("formData" in opts)) {
2833
+ if (!opts || !isSubmissionNavigation(opts)) {
2765
2834
  return {
2766
2835
  path
2767
2836
  };
2837
+ }
2838
+
2839
+ if (opts.formMethod && !isValidMethod(opts.formMethod)) {
2840
+ return {
2841
+ path,
2842
+ error: getInternalRouterError(405, {
2843
+ method: opts.formMethod
2844
+ })
2845
+ };
2768
2846
  } // Create a Submission on non-GET navigations
2769
2847
 
2770
2848
 
2771
- if (opts.formMethod != null && opts.formMethod !== "get") {
2849
+ if (opts.formMethod && isSubmissionMethod(opts.formMethod)) {
2772
2850
  return {
2773
2851
  path,
2774
2852
  submission: {
@@ -2778,13 +2856,6 @@
2778
2856
  formData: opts.formData
2779
2857
  }
2780
2858
  };
2781
- } // No formData to flatten for GET submission
2782
-
2783
-
2784
- if (!opts.formData) {
2785
- return {
2786
- path
2787
- };
2788
2859
  } // Flatten submission onto URLSearchParams for GET submissions
2789
2860
 
2790
2861
 
@@ -2803,31 +2874,13 @@
2803
2874
  } catch (e) {
2804
2875
  return {
2805
2876
  path,
2806
- error: new ErrorResponse(400, "Bad Request", "Cannot submit binary form data using GET")
2877
+ error: getInternalRouterError(400)
2807
2878
  };
2808
2879
  }
2809
2880
 
2810
2881
  return {
2811
2882
  path: createPath(parsedPath)
2812
2883
  };
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
2884
  } // Filter out all routes below any caught error as they aren't going to
2832
2885
  // render so we don't need to load them
2833
2886
 
@@ -2928,6 +2981,10 @@
2928
2981
  }
2929
2982
 
2930
2983
  async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest) {
2984
+ if (basename === void 0) {
2985
+ basename = "/";
2986
+ }
2987
+
2931
2988
  if (isStaticRequest === void 0) {
2932
2989
  isStaticRequest = false;
2933
2990
  }
@@ -2953,6 +3010,7 @@
2953
3010
  request,
2954
3011
  params: match.params
2955
3012
  }), abortPromise]);
3013
+ 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
3014
  } catch (e) {
2957
3015
  resultType = ResultType.error;
2958
3016
  result = e;
@@ -2963,26 +3021,31 @@
2963
3021
  if (result instanceof Response) {
2964
3022
  let status = result.status; // Process redirects
2965
3023
 
2966
- if (status >= 300 && status <= 399) {
3024
+ if (redirectStatusCodes.has(status)) {
2967
3025
  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
3026
+ 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
3027
 
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
3028
+ let external = createURL(location).origin !== createURL("/").origin; // Support relative routing in internal redirects
2975
3029
 
2976
- if (basename) {
2977
- let path = resolvedLocation.pathname;
2978
- resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
2979
- }
3030
+ if (!external) {
3031
+ let activeMatches = matches.slice(0, matches.indexOf(match) + 1);
3032
+ let routePathnames = getPathContributingMatches(activeMatches).map(match => match.pathnameBase);
3033
+ let requestPath = createURL(request.url).pathname;
3034
+ let resolvedLocation = resolveTo(location, routePathnames, requestPath);
3035
+ invariant(createPath(resolvedLocation), "Unable to resolve redirect location: " + location); // Prepend the basename to the redirect location if we have one
3036
+
3037
+ if (basename) {
3038
+ let path = resolvedLocation.pathname;
3039
+ resolvedLocation.pathname = path === "/" ? basename : joinPaths([basename, path]);
3040
+ }
2980
3041
 
2981
- location = createPath(resolvedLocation); // Don't process redirects in the router during static requests requests.
3042
+ location = createPath(resolvedLocation);
3043
+ } // Don't process redirects in the router during static requests requests.
2982
3044
  // Instead, throw the Response and let the server handle it with an HTTP
2983
3045
  // redirect. We also update the Location header in place in this flow so
2984
3046
  // basename and relative routing is taken into account
2985
3047
 
3048
+
2986
3049
  if (isStaticRequest) {
2987
3050
  result.headers.set("Location", location);
2988
3051
  throw result;
@@ -2992,7 +3055,8 @@
2992
3055
  type: ResultType.redirect,
2993
3056
  status,
2994
3057
  location,
2995
- revalidate: result.headers.get("X-Remix-Revalidate") !== null
3058
+ revalidate: result.headers.get("X-Remix-Revalidate") !== null,
3059
+ external
2996
3060
  };
2997
3061
  } // For SSR single-route requests, we want to hand Responses back directly
2998
3062
  // without unwrapping. We do this with the QueryRouteResponse wrapper
@@ -3220,10 +3284,10 @@
3220
3284
  return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
3221
3285
  }
3222
3286
 
3223
- function getShortCircuitMatches(routes, status, statusText) {
3287
+ function getShortCircuitMatches(routes) {
3224
3288
  // Prefer a root layout route if present, otherwise shim in a route object
3225
3289
  let route = routes.find(r => r.index || !r.path || r.path === "/") || {
3226
- id: "__shim-" + status + "-route__"
3290
+ id: "__shim-error-route__"
3227
3291
  };
3228
3292
  return {
3229
3293
  matches: [{
@@ -3232,26 +3296,45 @@
3232
3296
  pathnameBase: "",
3233
3297
  route
3234
3298
  }],
3235
- route,
3236
- error: new ErrorResponse(status, statusText, null)
3299
+ route
3237
3300
  };
3238
3301
  }
3239
3302
 
3240
- function getNotFoundMatches(routes) {
3241
- return getShortCircuitMatches(routes, 404, "Not Found");
3242
- }
3303
+ function getInternalRouterError(status, _temp) {
3304
+ let {
3305
+ pathname,
3306
+ routeId,
3307
+ method,
3308
+ message
3309
+ } = _temp === void 0 ? {} : _temp;
3310
+ let statusText = "Unknown Server Error";
3311
+ let errorMessage = "Unknown @remix-run/router error";
3312
+
3313
+ if (status === 400) {
3314
+ statusText = "Bad Request";
3315
+
3316
+ if (method && pathname && routeId) {
3317
+ 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.";
3318
+ } else {
3319
+ errorMessage = "Cannot submit binary form data using GET";
3320
+ }
3321
+ } else if (status === 403) {
3322
+ statusText = "Forbidden";
3323
+ errorMessage = "Route \"" + routeId + "\" does not match URL \"" + pathname + "\"";
3324
+ } else if (status === 404) {
3325
+ statusText = "Not Found";
3326
+ errorMessage = "No route matches URL \"" + pathname + "\"";
3327
+ } else if (status === 405) {
3328
+ statusText = "Method Not Allowed";
3243
3329
 
3244
- function getMethodNotAllowedMatches(routes) {
3245
- return getShortCircuitMatches(routes, 405, "Method Not Allowed");
3246
- }
3330
+ if (method && pathname && routeId) {
3331
+ 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.";
3332
+ } else if (method) {
3333
+ errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
3334
+ }
3335
+ }
3247
3336
 
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
- };
3337
+ return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
3255
3338
  } // Find any returned redirect errors, starting from the lowest match
3256
3339
 
3257
3340
 
@@ -3302,6 +3385,14 @@
3302
3385
  return obj && obj.response instanceof Response && (obj.type === ResultType.data || ResultType.error);
3303
3386
  }
3304
3387
 
3388
+ function isValidMethod(method) {
3389
+ return validRequestMethods.has(method);
3390
+ }
3391
+
3392
+ function isSubmissionMethod(method) {
3393
+ return validActionMethods.has(method);
3394
+ }
3395
+
3305
3396
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
3306
3397
  for (let index = 0; index < results.length; index++) {
3307
3398
  let result = results[index];