Compare commits
1 Commits
pr34576
...
sync-nextj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6230622a1a |
@@ -2412,7 +2412,7 @@ export function getResource(
|
||||
if (!resource) {
|
||||
// We asserted this above but Flow can't figure out that the type satisfies
|
||||
const ownerDocument = getDocumentFromRoot(resourceRoot);
|
||||
resource = {
|
||||
resource = ({
|
||||
type: 'stylesheet',
|
||||
instance: null,
|
||||
count: 0,
|
||||
@@ -2420,15 +2420,34 @@ export function getResource(
|
||||
loading: NotLoaded,
|
||||
preload: null,
|
||||
},
|
||||
};
|
||||
}: StylesheetResource);
|
||||
styles.set(key, resource);
|
||||
const instance = ownerDocument.querySelector(
|
||||
getStylesheetSelectorFromKey(key),
|
||||
);
|
||||
if (instance) {
|
||||
const loadingState: ?Promise<mixed> = (instance: any)._p;
|
||||
if (loadingState) {
|
||||
// This instance is inserted as part of a boundary reveal and is not yet
|
||||
// loaded
|
||||
} else {
|
||||
// This instance is already loaded
|
||||
resource.instance = instance;
|
||||
resource.state.loading = Loaded | Inserted;
|
||||
}
|
||||
}
|
||||
|
||||
if (!preloadPropsMap.has(key)) {
|
||||
preloadStylesheet(
|
||||
ownerDocument,
|
||||
key,
|
||||
preloadPropsFromStylesheet(qualifiedProps),
|
||||
resource.state,
|
||||
);
|
||||
const preloadProps = preloadPropsFromStylesheet(qualifiedProps);
|
||||
preloadPropsMap.set(key, preloadProps);
|
||||
if (!instance) {
|
||||
preloadStylesheet(
|
||||
ownerDocument,
|
||||
key,
|
||||
preloadProps,
|
||||
resource.state,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentProps && currentResource === null) {
|
||||
@@ -2599,28 +2618,21 @@ function preloadStylesheet(
|
||||
preloadProps: PreloadProps,
|
||||
state: StylesheetState,
|
||||
) {
|
||||
preloadPropsMap.set(key, preloadProps);
|
||||
|
||||
if (!ownerDocument.querySelector(getStylesheetSelectorFromKey(key))) {
|
||||
// There is no matching stylesheet instance in the Document.
|
||||
// We will insert a preload now to kick off loading because
|
||||
// we expect this stylesheet to commit
|
||||
const preloadEl = ownerDocument.querySelector(
|
||||
getPreloadStylesheetSelectorFromKey(key),
|
||||
);
|
||||
if (preloadEl) {
|
||||
// If we find a preload already it was SSR'd and we won't have an actual
|
||||
// loading state to track. For now we will just assume it is loaded
|
||||
state.loading = Loaded;
|
||||
} else {
|
||||
const instance = ownerDocument.createElement('link');
|
||||
state.preload = instance;
|
||||
instance.addEventListener('load', () => (state.loading |= Loaded));
|
||||
instance.addEventListener('error', () => (state.loading |= Errored));
|
||||
setInitialProperties(instance, 'link', preloadProps);
|
||||
markNodeAsHoistable(instance);
|
||||
(ownerDocument.head: any).appendChild(instance);
|
||||
}
|
||||
const preloadEl = ownerDocument.querySelector(
|
||||
getPreloadStylesheetSelectorFromKey(key),
|
||||
);
|
||||
if (preloadEl) {
|
||||
// If we find a preload already it was SSR'd and we won't have an actual
|
||||
// loading state to track. For now we will just assume it is loaded
|
||||
state.loading = Loaded;
|
||||
} else {
|
||||
const instance = ownerDocument.createElement('link');
|
||||
state.preload = instance;
|
||||
instance.addEventListener('load', () => (state.loading |= Loaded));
|
||||
instance.addEventListener('error', () => (state.loading |= Errored));
|
||||
setInitialProperties(instance, 'link', preloadProps);
|
||||
markNodeAsHoistable(instance);
|
||||
(ownerDocument.head: any).appendChild(instance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ export function completeBoundaryWithStyles(
|
||||
const dependencies = [];
|
||||
let href, precedence, attr, loadingState, resourceEl, media;
|
||||
|
||||
function cleanupWith(cb) {
|
||||
this['_p'] = null;
|
||||
cb();
|
||||
}
|
||||
|
||||
// Sheets Mode
|
||||
let sheetMode = true;
|
||||
while (true) {
|
||||
@@ -80,18 +85,14 @@ export function completeBoundaryWithStyles(
|
||||
resourceEl.setAttribute(attr, stylesheetDescriptor[j++]);
|
||||
}
|
||||
loadingState = resourceEl['_p'] = new Promise((resolve, reject) => {
|
||||
resourceEl.onload = resolve;
|
||||
resourceEl.onerror = reject;
|
||||
resourceEl.onload = cleanupWith.bind(resourceEl, resolve);
|
||||
resourceEl.onerror = cleanupWith.bind(resourceEl, reject);
|
||||
});
|
||||
// Save this resource element so we can bailout if it is used again
|
||||
resourceMap.set(href, resourceEl);
|
||||
}
|
||||
media = resourceEl.getAttribute('media');
|
||||
if (
|
||||
loadingState &&
|
||||
loadingState['s'] !== 'l' &&
|
||||
(!media || window['matchMedia'](media).matches)
|
||||
) {
|
||||
if (loadingState && (!media || window['matchMedia'](media).matches)) {
|
||||
dependencies.push(loadingState);
|
||||
}
|
||||
if (avoidInsert) {
|
||||
|
||||
@@ -6,7 +6,7 @@ export const clientRenderBoundary =
|
||||
export const completeBoundary =
|
||||
'$RC=function(b,c,e){c=document.getElementById(c);c.parentNode.removeChild(c);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var d=a.data;if("/$"===d)if(0===f)break;else f--;else"$"!==d&&"$?"!==d&&"$!"!==d||f++}d=a.nextSibling;e.removeChild(a);a=d}while(a);for(;c.firstChild;)e.insertBefore(c.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};';
|
||||
export const completeBoundaryWithStyles =
|
||||
'$RM=new Map;\n$RR=function(r,t,w){for(var u=$RC,n=$RM,p=new Map,q=document,g,b,h=q.querySelectorAll("link[data-precedence],style[data-precedence]"),v=[],k=0;b=h[k++];)"not all"===b.getAttribute("media")?v.push(b):("LINK"===b.tagName&&n.set(b.getAttribute("href"),b),p.set(b.dataset.precedence,g=b));b=0;h=[];var l,a;for(k=!0;;){if(k){var f=w[b++];if(!f){k=!1;b=0;continue}var c=!1,m=0;var d=f[m++];if(a=n.get(d)){var e=a._p;c=!0}else{a=q.createElement("link");a.href=d;a.rel="stylesheet";for(a.dataset.precedence=\nl=f[m++];e=f[m++];)a.setAttribute(e,f[m++]);e=a._p=new Promise(function(x,y){a.onload=x;a.onerror=y});n.set(d,a)}d=a.getAttribute("media");!e||"l"===e.s||d&&!matchMedia(d).matches||h.push(e);if(c)continue}else{a=v[b++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=p.get(l)||g;c===g&&(g=a);p.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=q.head,c.insertBefore(a,c.firstChild))}Promise.all(h).then(u.bind(null,r,t,""),u.bind(null,r,t,"Resource failed to load"))};';
|
||||
'$RM=new Map;\n$RR=function(t,u,y){function v(n){this._p=null;n()}for(var w=$RC,p=$RM,q=new Map,r=document,g,b,h=r.querySelectorAll("link[data-precedence],style[data-precedence]"),x=[],k=0;b=h[k++];)"not all"===b.getAttribute("media")?x.push(b):("LINK"===b.tagName&&p.set(b.getAttribute("href"),b),q.set(b.dataset.precedence,g=b));b=0;h=[];var l,a;for(k=!0;;){if(k){var e=y[b++];if(!e){k=!1;b=0;continue}var c=!1,m=0;var d=e[m++];if(a=p.get(d)){var f=a._p;c=!0}else{a=r.createElement("link");a.href=\nd;a.rel="stylesheet";for(a.dataset.precedence=l=e[m++];f=e[m++];)a.setAttribute(f,e[m++]);f=a._p=new Promise(function(n,z){a.onload=v.bind(a,n);a.onerror=v.bind(a,z)});p.set(d,a)}d=a.getAttribute("media");!f||d&&!matchMedia(d).matches||h.push(f);if(c)continue}else{a=x[b++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=q.get(l)||g;c===g&&(g=a);q.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=r.head,c.insertBefore(a,c.firstChild))}Promise.all(h).then(w.bind(null,\nt,u,""),w.bind(null,t,u,"Resource failed to load"))};';
|
||||
export const completeSegment =
|
||||
'$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};';
|
||||
export const formReplaying =
|
||||
|
||||
@@ -47,6 +47,11 @@ export function completeBoundaryWithStyles(
|
||||
const dependencies = [];
|
||||
let href, precedence, attr, loadingState, resourceEl, media;
|
||||
|
||||
function cleanupWith(cb) {
|
||||
this['_p'] = null;
|
||||
cb();
|
||||
}
|
||||
|
||||
// Sheets Mode
|
||||
let sheetMode = true;
|
||||
while (true) {
|
||||
@@ -82,18 +87,14 @@ export function completeBoundaryWithStyles(
|
||||
resourceEl.setAttribute(attr, stylesheetDescriptor[j++]);
|
||||
}
|
||||
loadingState = resourceEl['_p'] = new Promise((resolve, reject) => {
|
||||
resourceEl.onload = resolve;
|
||||
resourceEl.onerror = reject;
|
||||
resourceEl.onload = cleanupWith.bind(resourceEl, resolve);
|
||||
resourceEl.onerror = cleanupWith.bind(resourceEl, reject);
|
||||
});
|
||||
// Save this resource element so we can bailout if it is used again
|
||||
resourceMap.set(href, resourceEl);
|
||||
}
|
||||
media = resourceEl.getAttribute('media');
|
||||
if (
|
||||
loadingState &&
|
||||
loadingState['s'] !== 'l' &&
|
||||
(!media || window['matchMedia'](media).matches)
|
||||
) {
|
||||
if (loadingState && (!media || window['matchMedia'](media).matches)) {
|
||||
dependencies.push(loadingState);
|
||||
}
|
||||
if (avoidInsert) {
|
||||
|
||||
@@ -3348,6 +3348,172 @@ body {
|
||||
);
|
||||
});
|
||||
|
||||
it('will assume stylesheets already in the document have loaded if it cannot confirm it is not yet loaded', async () => {
|
||||
await act(() => {
|
||||
renderToPipeableStream(
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="foo" data-precedence="default" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="foo" />
|
||||
</body>
|
||||
</html>,
|
||||
).pipe(writable);
|
||||
});
|
||||
|
||||
const root = ReactDOMClient.createRoot(document.querySelector('#foo'));
|
||||
|
||||
root.render(
|
||||
<div>
|
||||
<Suspense fallback="loading...">
|
||||
<link rel="stylesheet" href="foo" precedence="default" />
|
||||
hello world
|
||||
</Suspense>
|
||||
</div>,
|
||||
);
|
||||
|
||||
await waitForAll([]);
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="foo" data-precedence="default" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="foo">
|
||||
<div>hello world</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
});
|
||||
|
||||
it('will assume wait for loading stylesheets to load before continuing', async () => {
|
||||
let ssr = true;
|
||||
function Component() {
|
||||
if (ssr) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<link rel="stylesheet" href="foo" precedence="default" />
|
||||
<div>hello client</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
renderToPipeableStream(
|
||||
<html>
|
||||
<body>
|
||||
<div>
|
||||
<Suspense fallback="loading...">
|
||||
<BlockedOn value="reveal">
|
||||
<link rel="stylesheet" href="foo" precedence="default" />
|
||||
<div>hello world</div>
|
||||
</BlockedOn>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div>
|
||||
<Suspense fallback="loading 2...">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</div>
|
||||
</body>
|
||||
</html>,
|
||||
).pipe(writable);
|
||||
});
|
||||
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head />
|
||||
<body>
|
||||
<div>loading...</div>
|
||||
<div />
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
|
||||
await act(() => {
|
||||
resolveText('reveal');
|
||||
});
|
||||
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="foo" data-precedence="default" />
|
||||
</head>
|
||||
<body>
|
||||
<div>loading...</div>
|
||||
<div />
|
||||
<link rel="preload" href="foo" as="style" />
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
|
||||
ssr = false;
|
||||
|
||||
ReactDOMClient.hydrateRoot(
|
||||
document,
|
||||
<html>
|
||||
<body>
|
||||
<div>
|
||||
<Suspense fallback="loading...">
|
||||
<BlockedOn value="reveal">
|
||||
<link rel="stylesheet" href="foo" precedence="default" />
|
||||
<div>hello world</div>
|
||||
</BlockedOn>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div>
|
||||
<Suspense fallback="loading 2...">
|
||||
<Component />
|
||||
</Suspense>
|
||||
</div>
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
await waitForAll([]);
|
||||
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="foo" data-precedence="default" />
|
||||
</head>
|
||||
<body>
|
||||
<div>loading...</div>
|
||||
<div />
|
||||
<link rel="preload" href="foo" as="style" />
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
|
||||
await expect(async () => {
|
||||
loadStylesheets();
|
||||
}).toErrorDev([
|
||||
"Hydration failed because the server rendered HTML didn't match the client.",
|
||||
]);
|
||||
assertLog(['load stylesheet: foo']);
|
||||
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="foo" data-precedence="default" />
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div>hello world</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>hello client</div>
|
||||
</div>
|
||||
<link rel="preload" href="foo" as="style" />
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can suspend commits on more than one root for the same resource at the same time', async () => {
|
||||
document.body.innerHTML = '';
|
||||
const container1 = document.createElement('div');
|
||||
|
||||
Reference in New Issue
Block a user