-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Fog of War #9600
Conversation
🦋 Changeset detectedLatest commit: 287cb89 The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
data-discover={ | ||
!isAbsolute && discover === "render" ? "true" : undefined | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data-discover
opts into the manifest patching. It's enabled by default (discover="render"
), users can opt out via <Link discover="click">
/<NavLink discover="click">
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of a data-
attribute here instead of just react state is for a few reasons:
- Makes it easier to detect via
MutationObserver
and not a mess of React context/effects where Links "register" themselves with some parent context - It scales very well into an RSC world where you may just want to render
<a href="">
and not have to use a client component just to participate in fog of war - If we ever remove
Link
in favor of event delegation, it works there too
let nextMatches = React.useMemo(() => { | ||
if (navigation.location) { | ||
// FIXME: can probably use transitionManager `nextMatches` | ||
let matches = matchRoutes( | ||
router.routes, | ||
navigation.location, | ||
router.basename | ||
); | ||
invariant( | ||
matches, | ||
`No routes match path "${navigation.location.pathname}"` | ||
); | ||
return matches; | ||
} | ||
|
||
return []; | ||
}, [navigation.location, router.routes, router.basename]); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jacob-ebey This seems like dead code to me? It only returns a non-empty array when we're in the middle of a client-side navigation
(which implies we've already hydrated), but the routePreloads
it gets concatenated into below is only ever rendered when we're not hydrated. I think it's likely leftover from the TransitionManager
implementation and maybe before <PrefetchPageLinks>
?
let matches = matchServerRoutes(routes, url.pathname, _build.basename); | ||
if (matches && matches.length > 0) { | ||
Object.assign(params, matches[0].params); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this down so we don't bother matching on /__manifest
requests
let observer = new MutationObserver((records) => { | ||
records.forEach((r) => { | ||
[r.target, ...r.addedNodes].forEach((node) => { | ||
if (!isElement(node)) return; | ||
if (node.tagName === "A" && node.getAttribute("data-discover")) { | ||
registerPath(node.getAttribute("href")); | ||
} else if (node.tagName !== "A") { | ||
node | ||
.querySelectorAll("a[data-discover]") | ||
.forEach((el) => registerPath(el.getAttribute("href"))); | ||
} | ||
debouncedFetchPatches(); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic detects newly rendered links on navigations and internal route state changes (i.e., click this button to show a link) and batches them up via the debounced fetch
.querySelectorAll("a[data-discover]") | ||
.forEach((a) => registerPath(a.getAttribute("href"))); | ||
|
||
fetchPatches(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does the initial fetch for all routes rendered in the initial HTML payload
patchRoutesOnMiss: async ({ path, patch }) => { | ||
if (fogOfWar!.known404Paths.has(path)) return; | ||
await fetchAndApplyManifestPatches( | ||
[path], | ||
fogOfWar!.known404Paths, | ||
manifest, | ||
routeModules, | ||
future, | ||
isSpaMode, | ||
basename, | ||
patch | ||
); | ||
}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we click a link that doesn't match, try to fetch that path from the server during the loading phase. In an ideal scenario, this never runs because the pre-discovery logic all lands prior to link clicks.
if (lazyPaths.length === 0) { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short circuit there are no actively rendered paths to fetch - which means that they are all either matchable via the currently known routes, or they are a known 404 from a prior fetch.
🤖 Hello there, We just published version Thanks! |
🤖 Hello there, We just published version Thanks! |
Implements Fog of War (remix-run/react-router#11113) for Remix via the manifest
__remixManifest
is sent up on page load in an inline<script>
just like__remixContext
<Link>
components are batched up and a request is sent to the Remix server handler via a built-in/__manifest
endpoint to match paths on the server and send back relevant route manifest entries__remixManifest
and the client side routerLink
, we fall into thepatchRoutesOnMiss
handler which will load patches for that path during theloading
phaseRR Sibling PR: remix-run/react-router#11626
Closes #8423