Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fog of War #9600

Merged
merged 42 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d155a3f
POC of fog of war
brophdawg11 May 21, 2024
ab53086
Non-optimized loading for pathless/index routes
brophdawg11 May 23, 2024
7c5c7e9
Pin to RR experimental
brophdawg11 May 23, 2024
8590fe3
Fix TS errors around hasChildren
brophdawg11 May 23, 2024
ea2ef90
Sorry TS, need a build
brophdawg11 May 23, 2024
3fd0f53
First pass at remix fog of war prefetching
brophdawg11 May 29, 2024
02591eb
Move to patchRoutes approach
brophdawg11 May 31, 2024
0f191a5
Update approach to use an updater function for batching
brophdawg11 Jun 5, 2024
82578b2
Switch to data-attribute/mutation-observer approach
brophdawg11 Jun 5, 2024
bdeb2df
Update to latest RR experimental
brophdawg11 Jun 5, 2024
c4b10d0
Fix build issues
brophdawg11 Jun 5, 2024
a7ab460
Minor updates
brophdawg11 Jun 5, 2024
9c4d3f9
Update to latest RR experimental
brophdawg11 Jun 7, 2024
f32fee9
Code cleanups and use patch function
brophdawg11 Jun 7, 2024
b239db4
Start integraton tests
brophdawg11 Jun 7, 2024
e21e9aa
Add discover props and update tests
brophdawg11 Jun 10, 2024
922fd09
Cleanups
brophdawg11 Jun 10, 2024
c2a6fd7
fix TS issues
brophdawg11 Jun 10, 2024
7800ea0
Fix lint
brophdawg11 Jun 11, 2024
21befc9
Support basename
brophdawg11 Jun 11, 2024
84ac12c
Remove global opt out
brophdawg11 Jun 12, 2024
e3b9513
Update to latest RR
brophdawg11 Jun 12, 2024
2a12381
Add future flag and disable in spa mode
brophdawg11 Jun 12, 2024
8829152
Add build time error for fog of war + spa mode
brophdawg11 Jun 12, 2024
b2f9f46
Update fogOfWar singleton initialization
brophdawg11 Jun 12, 2024
6ce93be
Enable flag in E2E tests
brophdawg11 Jun 12, 2024
306af79
Listen for attribute changes
brophdawg11 Jun 12, 2024
3aaa19c
Add manifest preload back behind future flag
brophdawg11 Jun 13, 2024
3918d0a
Remove dead code for nextMatches preloading
brophdawg11 Jun 13, 2024
b638c4c
Minor updates
brophdawg11 Jun 13, 2024
ff915c9
Add tests for dynamic attribute update
brophdawg11 Jun 13, 2024
f68d251
Change discover prop to be render|none to match prefetch
brophdawg11 Jun 13, 2024
ab9b606
lint fixes
brophdawg11 Jun 13, 2024
0a5e3bc
Minor updates
brophdawg11 Jun 13, 2024
f55702c
Docs
brophdawg11 Jun 13, 2024
0dd903c
Changeset
brophdawg11 Jun 13, 2024
9569fed
Extract implementation to hook
brophdawg11 Jun 14, 2024
67ce35a
Cleanups
brophdawg11 Jun 14, 2024
5d07d72
Merge branch 'dev' into brophdawg11/fog-of-war
brophdawg11 Jun 14, 2024
a5205ce
Merge branch 'dev' into brophdawg11/fog-of-war
brophdawg11 Jun 14, 2024
ca303ee
Fix flakey tests
brophdawg11 Jun 14, 2024
287cb89
Merge branch 'dev' into brophdawg11/fog-of-war
brophdawg11 Jun 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/two-jars-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@remix-run/react": minor
"@remix-run/server-runtime": minor
---

Add support for Lazy Route Discovery (a.k.a. Fog of War)

- RFC: https://github.com/remix-run/react-router/discussions/11113
- Docs: https://remix.run/docs/guides/fog-of-war
15 changes: 15 additions & 0 deletions docs/components/link.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ You can also pass a `Partial<Path>` value:
/>
```

### `discover`

Defines the route discovery behavior when using [`future.unstable_fogOfWar`][fog-of-war].

```tsx
<>
<Link /> {/* defaults to "render" */}
<Link discover="none" />
</>
```

- **render** - default, discover the route when the link renders
- **none** - don't eagerly discover, only discover if the link is clicked

### `prefetch`

Defines the data and module prefetching behavior for the link.
Expand Down Expand Up @@ -222,3 +236,4 @@ Please note that this API is marked unstable and may be subject to breaking chan
[document-start-view-transition]: https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition
[use-view-transition-state]: ../hooks/use-view-transition-state
[relativesplatpath]: ../hooks/use-resolved-path#splat-paths
[fog-of-war]: ../guides/fog-of-war
55 changes: 55 additions & 0 deletions docs/guides/fog-of-war.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Fog of War
---

# Fog Of War

Remix introduced support for "Fog of War" ([RFC][rfc]) behind the [`future.unstable_fogOfWar`][future-flags] flag in [`v2.10.0`][2.10.0] which allows you to opt-into this behavior which will become the default in Remix v3.

## Overview

When you enable Fog of War, Remix will no longer send a full route manifest on initial load. Previously this would be loaded through a JS file on initial load (i.e., `/assets/manifest-[hash]].js`). The manifest does not contain the route module implementations, but rather just their paths and some meta information on their imports and whether they have a server side `loader`/`action`, etc. Having this full manifest up-front allows Remix to do synchronous client-side route matching on Link clicks and kick off the loads for route modules and data imediately. For small-to-medium-sized apps, loading the full manifest up-front is usually not prohibitive as it is highly cacheable and gzips down quite well. However, at scale we found that this manifest could grow large enough to impact some performance metrics.

The "Fog of War" approach solves this problem by only sending up the initially-rendered routes in the initial manifest, and then loads additional routes as needed as the user navigates around the application and patches them into the manifest. Over time, the manifest grows to include only portions of the app the user navigated to.

### Eager Route Discovery

There is a tradeoff with this type of lazy-route discovery though in that Remix can no longer perform synchronous route matching on link clicks, which can lead to waterfalls.

Without prefetching in the current architecture, clicking a link would look something like this:

```
click /a
|-- load route module -->
|-- load route data -->
render /a
```

In the Fog of War architecture, clicking a link can introduce a waterfall:

```
click /a
|-- discover route -->
|-- load route module -->
|-- load route data -->
render /a
```

As we all know, Remix hates waterfalls, so the Fog of War feature implements an optimization to avoid them in the vast majority of cases. By default, all [`<Link>`][link] and [`<NavLink>`][navlink] components rendered on the page will be batched up and eagerly "discovered" via a request to the server. This request will match all current link paths on the server and send back all required route manifest entries. Under the vast majority of cases, this request will complete prior to the user clicking any links (since users don't usually click links in the first few hundred milliseconds) and the manifest will be patched before any links are clicked. Then, when a link is clicked, Remix is able to do synchronous client-side matching as if the Fog of War behavior wasn't even present.

If you wish to opt-out of this eager route discovery on a per-link basis, you can do that via the [`discover="none"`][link-discover] prop (the default value is `discover="render"`).

### Notable Changes

- When this feature is enabled, the route manifest in `window.__remixManifest.routes` will only contain the minimal required routes on initial SSR, and routes will be added to it dynamically as the user navigates around
- The Remix handler now has a new internal `__manifest` endpoint through which it will fetch manifest patches
- ⚠️ This is considered an internal implementation detail and is not intended to be requested by application code.

## Details

[rfc]: https://github.com/remix-run/react-router/discussions/11113
[future-flags]: ../file-conventions/remix-config#future
[2.10.0]: https://github.com/remix-run/remix/blob/main/CHANGELOG.md#v2100
[link]: ../components/link
[navlink]: ../components/nav-link
[link-discover]: ../components/link#discover
2 changes: 2 additions & 0 deletions docs/start/future-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ The following future flags currently exist in Remix v2 and will become the defau
- **`v3_relativeSplatPath`**: Fixes buggy relative path resolution in splat routes. Please see the [React Router docs][relativesplatpath] for more information.
- **`v3_throwAbortReason`**: When a server-side request is aborted, Remix will throw the `request.signal.reason` instead of an error such as `new Error("query() call aborted...")`
- **`unstable_singleFetch`**: Opt into [Single Fetch][single-fetch] behavior
- **`unstable_fogOfWar`**: Opt into [Fog of War][fog-of-war] behavior

## Summary

Expand All @@ -83,3 +84,4 @@ Our development strategy focuses on gradual feature adoption and seamless versio
[use-fetcher]: ../hooks/use-fetcher
[relativesplatpath]: https://reactrouter.com/en/main/hooks/use-resolved-path#splat-paths
[single-fetch]: ../guides/single-fetch
[fog-of-war]: ../guides/fog-of-war
4 changes: 1 addition & 3 deletions integration/error-data-request-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ test.describe("ErrorBoundary", () => {
fixture.requestData("/loader-return-json", "routes/loader-return-json", {
method: "TRACE",
})
).rejects.toThrowError(
`Failed to construct 'Request': 'TRACE' HTTP method is unsupported.`
);
).rejects.toThrow(/'TRACE' HTTP method is unsupported\./);
});

test("returns a 403 x-remix-error on a data fetch GET to a bad path", async () => {
Expand Down
Loading