Skip to content

Commit fc901a8

Browse files
committed
[Fizz] Preload "suspensey" images (#27191)
Eventually we will treat images without `loading="lazy"` as suspensey meaning we will coordinate the reveal of boundaries when these images have loaded and ideally decoded. As a step in that direction this change prioritizes these images for preloading to ensure the highest chance that they are loaded before boundaries reveal (or initial paint). every img rendered that is non lazy loading will emit a preload just behind fonts. This change implements a new resource queue for high priority image preloads There are a number of scenarios where we end up putting a preload in this queue 1. If you render a non-lazy image and there are fewer than 10 high priority image preloads 2. if you render a non-lazy image with fetchPriority "high" 3. if you preload as "image" with fetchPriority "high" This means that by default we won't overrsaturate this queue with every img rendered on the page but the earlier encountered ones will go first. Essentially this is React's own implementation of fetchPriority="auto". If however you specify that the fetchPriority is higher then in theory an unlimited number of images can preload in this queue. This gives users some control over queuing while still providing a good default that does not require any opting into Additionally we use fetchPriority "low" as a signal that an image does not require preloading. This may end up being pointless if not using lazy (which also opts out of preloading) because it might delay initial paint but we'll start with this hueristic and consider changes in the future when we have more information DiffTrain build for [f359f9b](f359f9b)
1 parent e09bb3d commit fc901a8

File tree

7 files changed

+2188
-1579
lines changed

7 files changed

+2188
-1579
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
201becd3d294b38824930a818e3412c6e04ba2eb
1+
f359f9b41ac1a8127f5ba505e0c04675eee0d310

compiled/facebook-www/ReactDOMServer-dev.classic.js

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if (__DEV__) {
1919
var React = require("react");
2020
var ReactDOM = require("react-dom");
2121

22-
var ReactVersion = "18.3.0-www-classic-94960c5b";
22+
var ReactVersion = "18.3.0-www-classic-328214ad";
2323

2424
// This refers to a WWW module.
2525
var warningWWW = require("warning");
@@ -4501,6 +4501,80 @@ function pushStyleContents(target, props) {
45014501
return;
45024502
}
45034503

4504+
function getImagePreloadKey(href, imageSrcSet, imageSizes) {
4505+
var uniquePart = "";
4506+
4507+
if (typeof imageSrcSet === "string" && imageSrcSet !== "") {
4508+
uniquePart += "[" + imageSrcSet + "]";
4509+
4510+
if (typeof imageSizes === "string") {
4511+
uniquePart += "[" + imageSizes + "]";
4512+
}
4513+
} else {
4514+
uniquePart += "[][]" + href;
4515+
}
4516+
4517+
return getResourceKey("image", uniquePart);
4518+
}
4519+
4520+
function pushImg(target, props, resources) {
4521+
if (
4522+
props.loading !== "lazy" &&
4523+
typeof props.src === "string" &&
4524+
props.fetchPriority !== "low"
4525+
) {
4526+
// We have a suspensey image and ought to preload it to optimize the loading of display blocking
4527+
// resources.
4528+
var src = props.src,
4529+
imageSrcSet = props.imageSrcSet,
4530+
imageSizes = props.imageSizes;
4531+
var key = getImagePreloadKey(src, imageSrcSet, imageSizes);
4532+
var resource = resources.preloadsMap.get(key);
4533+
4534+
if (!resource) {
4535+
resource = {
4536+
type: "preload",
4537+
chunks: [],
4538+
state: NoState,
4539+
props: {
4540+
rel: "preload",
4541+
as: "image",
4542+
// There is a bug in Safari where imageSrcSet is not respected on preload links
4543+
// so we omit the href here if we have imageSrcSet b/c safari will load the wrong image.
4544+
// This harms older browers that do not support imageSrcSet by making their preloads not work
4545+
// but this population is shrinking fast and is already small so we accept this tradeoff.
4546+
href: imageSrcSet ? undefined : src,
4547+
imageSrcSet: imageSrcSet,
4548+
imageSizes: imageSizes,
4549+
crossOrigin: props.crossOrigin,
4550+
integrity: props.integrity,
4551+
type: props.type,
4552+
fetchPriority: props.fetchPriority,
4553+
referrerPolicy: props.referrerPolicy
4554+
}
4555+
};
4556+
resources.preloadsMap.set(key, resource);
4557+
4558+
{
4559+
markAsRenderedResourceDEV(resource, props);
4560+
}
4561+
4562+
pushLinkImpl(resource.chunks, resource.props);
4563+
}
4564+
4565+
if (
4566+
props.fetchPriority === "high" ||
4567+
resources.highImagePreloads.size < 10
4568+
) {
4569+
resources.highImagePreloads.add(resource);
4570+
} else {
4571+
resources.bulkPreloads.add(resource);
4572+
}
4573+
}
4574+
4575+
return pushSelfClosing(target, props, "img");
4576+
}
4577+
45044578
function pushSelfClosing(target, props, tag) {
45054579
target.push(startChunkForTag(tag));
45064580

@@ -5249,6 +5323,10 @@ function pushStartInstance(
52495323
case "pre": {
52505324
return pushStartPreformattedElement(target, props, type);
52515325
}
5326+
5327+
case "img": {
5328+
return pushImg(target, props, resources);
5329+
}
52525330
// Omitted close tags
52535331

52545332
case "base":
@@ -5257,7 +5335,6 @@ function pushStartInstance(
52575335
case "col":
52585336
case "embed":
52595337
case "hr":
5260-
case "img":
52615338
case "keygen":
52625339
case "param":
52635340
case "source":
@@ -6279,14 +6356,16 @@ function writePreamble(
62796356

62806357
preconnectChunks.length = 0;
62816358
resources.fontPreloads.forEach(flushResourceInPreamble, destination);
6282-
resources.fontPreloads.clear(); // Flush unblocked stylesheets by precedence
6359+
resources.fontPreloads.clear();
6360+
resources.highImagePreloads.forEach(flushResourceInPreamble, destination);
6361+
resources.highImagePreloads.clear(); // Flush unblocked stylesheets by precedence
62836362

62846363
resources.precedences.forEach(flushAllStylesInPreamble, destination);
62856364
resources.bootstrapScripts.forEach(flushResourceInPreamble, destination);
62866365
resources.scripts.forEach(flushResourceInPreamble, destination);
62876366
resources.scripts.clear();
6288-
resources.explicitPreloads.forEach(flushResourceInPreamble, destination);
6289-
resources.explicitPreloads.clear(); // Write embedding preloadChunks
6367+
resources.bulkPreloads.forEach(flushResourceInPreamble, destination);
6368+
resources.bulkPreloads.clear(); // Write embedding preloadChunks
62906369

62916370
var preloadChunks = responseState.preloadChunks;
62926371

@@ -6334,16 +6413,18 @@ function writeHoistables(destination, resources, responseState) {
63346413

63356414
preconnectChunks.length = 0;
63366415
resources.fontPreloads.forEach(flushResourceLate, destination);
6337-
resources.fontPreloads.clear(); // Preload any stylesheets. these will emit in a render instruction that follows this
6416+
resources.fontPreloads.clear();
6417+
resources.highImagePreloads.forEach(flushResourceInPreamble, destination);
6418+
resources.highImagePreloads.clear(); // Preload any stylesheets. these will emit in a render instruction that follows this
63386419
// but we want to kick off preloading as soon as possible
63396420

63406421
resources.precedences.forEach(preloadLateStyles, destination); // bootstrap scripts should flush above script priority but these can only flush in the preamble
63416422
// so we elide the code here for performance
63426423

63436424
resources.scripts.forEach(flushResourceLate, destination);
63446425
resources.scripts.clear();
6345-
resources.explicitPreloads.forEach(flushResourceLate, destination);
6346-
resources.explicitPreloads.clear(); // Write embedding preloadChunks
6426+
resources.bulkPreloads.forEach(flushResourceLate, destination);
6427+
resources.bulkPreloads.clear(); // Write embedding preloadChunks
63476428

63486429
var preloadChunks = responseState.preloadChunks;
63496430

@@ -6822,12 +6903,13 @@ function createResources() {
68226903
// cleared on flush
68236904
preconnects: new Set(),
68246905
fontPreloads: new Set(),
6906+
highImagePreloads: new Set(),
68256907
// usedImagePreloads: new Set(),
68266908
precedences: new Map(),
68276909
stylePrecedences: new Map(),
68286910
bootstrapScripts: new Set(),
68296911
scripts: new Set(),
6830-
explicitPreloads: new Set(),
6912+
bulkPreloads: new Set(),
68316913
// like a module global for currently rendering boundary
68326914
boundaryResources: null
68336915
};
@@ -7033,19 +7115,7 @@ function preload(href, options) {
70337115
// by varying the href. this is an edge case but it is the most correct behavior.
70347116
var imageSrcSet = options.imageSrcSet,
70357117
imageSizes = options.imageSizes;
7036-
var uniquePart = "";
7037-
7038-
if (typeof imageSrcSet === "string" && imageSrcSet !== "") {
7039-
uniquePart += "[" + imageSrcSet + "]";
7040-
7041-
if (typeof imageSizes === "string") {
7042-
uniquePart += "[" + imageSizes + "]";
7043-
}
7044-
} else {
7045-
uniquePart += "[][]" + href;
7046-
}
7047-
7048-
key = getResourceKey(as, uniquePart);
7118+
key = getImagePreloadKey(href, imageSrcSet, imageSizes);
70497119
} else {
70507120
key = getResourceKey(as, href);
70517121
}
@@ -7139,8 +7209,10 @@ function preload(href, options) {
71397209

71407210
if (as === "font") {
71417211
resources.fontPreloads.add(resource);
7212+
} else if (as === "image" && options.fetchPriority === "high") {
7213+
resources.highImagePreloads.add(resource);
71427214
} else {
7143-
resources.explicitPreloads.add(resource);
7215+
resources.bulkPreloads.add(resource);
71447216
}
71457217

71467218
flushResources(request);

0 commit comments

Comments
 (0)