Skip to content

Commit c6d651f

Browse files
Remove DOM mutation overhead from fog-of-war prefetch (#12731)
* fix(perf): remove DOM mutation overhead from fog-of-war prefetch In a real-world website I am testing this against, this reduces the fog-of-war's blocking JS during SPA transitions from 100ms to 1.2ms. --------- Co-authored-by: Jason Miller <[email protected]>
1 parent 5ffd5da commit c6d651f

File tree

3 files changed

+22
-36
lines changed

3 files changed

+22
-36
lines changed

.changeset/moody-comics-exist.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Optimize Lazy Route Discovery path discovery to favor a single `querySelectorAll` call at the `body` level instead of many calls at the sub-tree level

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
- david-crespo
8080
- dcblair
8181
- decadentsavant
82+
- developit
8283
- dgrijuela
8384
- DigitalNaut
8485
- dmitrytarassov

packages/react-router/lib/dom/ssr/fog-of-war.ts

Lines changed: 16 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,23 @@ export function useFogOFWarDiscovery(
116116
if (!path) {
117117
return;
118118
}
119-
let url = new URL(path, window.location.origin);
120-
if (!discoveredPaths.has(url.pathname)) {
121-
nextPaths.add(url.pathname);
119+
// optimization: use the already-parsed pathname from links
120+
let pathname =
121+
el.tagName === "A"
122+
? (el as HTMLAnchorElement).pathname
123+
: new URL(path, window.location.origin).pathname;
124+
if (!discoveredPaths.has(pathname)) {
125+
nextPaths.add(pathname);
122126
}
123127
}
124128

125129
// Register and fetch patches for all initially-rendered links/forms
126130
async function fetchPatches() {
131+
// re-check/update registered links
132+
document
133+
.querySelectorAll("a[data-discover], form[data-discover]")
134+
.forEach(registerElement);
135+
127136
let lazyPaths = Array.from(nextPaths.keys()).filter((path) => {
128137
if (discoveredPaths.has(path)) {
129138
nextPaths.delete(path);
@@ -150,43 +159,14 @@ export function useFogOFWarDiscovery(
150159
}
151160
}
152161

153-
// Register and fetch patches for all initially-rendered links
154-
document.body
155-
.querySelectorAll("a[data-discover], form[data-discover]")
156-
.forEach((el) => registerElement(el));
162+
let debouncedFetchPatches = debounce(fetchPatches, 100);
157163

164+
// scan and fetch initial links
158165
fetchPatches();
159166

160167
// Setup a MutationObserver to fetch all subsequently rendered links/form
161-
let debouncedFetchPatches = debounce(fetchPatches, 100);
162-
163-
function isElement(node: Node): node is Element {
164-
return node.nodeType === Node.ELEMENT_NODE;
165-
}
166-
167-
let observer = new MutationObserver((records) => {
168-
let elements = new Set<Element>();
169-
records.forEach((r) => {
170-
[r.target, ...r.addedNodes].forEach((node) => {
171-
if (!isElement(node)) return;
172-
if (node.tagName === "A" && node.getAttribute("data-discover")) {
173-
elements.add(node);
174-
} else if (
175-
node.tagName === "FORM" &&
176-
node.getAttribute("data-discover")
177-
) {
178-
elements.add(node);
179-
}
180-
if (node.tagName !== "A") {
181-
node
182-
.querySelectorAll("a[data-discover], form[data-discover]")
183-
.forEach((el) => elements.add(el));
184-
}
185-
});
186-
});
187-
elements.forEach((el) => registerElement(el));
188-
debouncedFetchPatches();
189-
});
168+
// It just schedules a full scan since that's faster than checking subtrees
169+
let observer = new MutationObserver(() => debouncedFetchPatches());
190170

191171
observer.observe(document.documentElement, {
192172
subtree: true,

0 commit comments

Comments
 (0)