mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[Fizz] Gate rel="expect" behind enableFizzBlockingRender (#33183)
Enabled in experimental channel. We know this is critical semantics to enforce at the HTML level since if you don't then you can't add explicit boundaries after the fact. However, this might have to go in a major release to allow for upgrading.
This commit is contained in:
parent
2bcf06b692
commit
b94603b955
|
|
@ -34,6 +34,7 @@ import {Children} from 'react';
|
|||
import {
|
||||
enableFizzExternalRuntime,
|
||||
enableSrcObject,
|
||||
enableFizzBlockingRender,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import type {
|
||||
|
|
@ -4146,18 +4147,23 @@ export function writeCompletedRoot(
|
|||
// we need to track the paint time of the shell so we know how much to throttle the reveal.
|
||||
writeShellTimeInstruction(destination, resumableState, renderState);
|
||||
}
|
||||
if (enableFizzBlockingRender) {
|
||||
const preamble = renderState.preamble;
|
||||
if (preamble.htmlChunks || preamble.headChunks) {
|
||||
// If we rendered the whole document, then we emitted a rel="expect" that needs a
|
||||
// matching target. Normally we use one of the bootstrap scripts for this but if
|
||||
// there are none, then we need to emit a tag to complete the shell.
|
||||
if ((resumableState.instructions & SentCompletedShellId) === NothingSent) {
|
||||
if (
|
||||
(resumableState.instructions & SentCompletedShellId) ===
|
||||
NothingSent
|
||||
) {
|
||||
writeChunk(destination, startChunkForTag('template'));
|
||||
writeCompletedShellIdAttribute(destination, resumableState);
|
||||
writeChunk(destination, endOfStartTag);
|
||||
writeChunk(destination, endChunkForTag('template'));
|
||||
}
|
||||
}
|
||||
}
|
||||
return writeBootstrap(destination, renderState);
|
||||
}
|
||||
|
||||
|
|
@ -5040,12 +5046,14 @@ function writeBlockingRenderInstruction(
|
|||
resumableState: ResumableState,
|
||||
renderState: RenderState,
|
||||
): void {
|
||||
if (enableFizzBlockingRender) {
|
||||
const idPrefix = resumableState.idPrefix;
|
||||
const shellId = '\u00AB' + idPrefix + 'R\u00BB';
|
||||
writeChunk(destination, blockingRenderChunkStart);
|
||||
writeChunk(destination, stringToChunk(escapeTextForBrowser(shellId)));
|
||||
writeChunk(destination, blockingRenderChunkEnd);
|
||||
}
|
||||
}
|
||||
|
||||
const completedShellIdAttributeStart = stringToPrecomputedChunk(' id="');
|
||||
|
||||
|
|
|
|||
|
|
@ -3590,7 +3590,9 @@ describe('ReactDOMFizzServer', () => {
|
|||
(gate(flags => flags.shouldUseFizzExternalRuntime)
|
||||
? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
|
||||
: '') +
|
||||
'<link rel="expect" href="#«R»" blocking="render">',
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render">'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -4523,7 +4525,15 @@ describe('ReactDOMFizzServer', () => {
|
|||
|
||||
// the html should be as-is
|
||||
expect(document.documentElement.innerHTML).toEqual(
|
||||
'<head><script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script><link rel="expect" href="#«R»" blocking="render"></head><body><p>hello world!</p><template id="«R»"></template></body>',
|
||||
'<head><script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render">'
|
||||
: '') +
|
||||
'</head><body><p>hello world!</p>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</body>',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -6512,7 +6522,14 @@ describe('ReactDOMFizzServer', () => {
|
|||
(gate(flags => flags.shouldUseFizzExternalRuntime)
|
||||
? '<script src="react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js" async=""></script>'
|
||||
: '') +
|
||||
'<link rel="expect" href="#«R»" blocking="render"></head><body><script>try { foo() } catch (e) {} ;</script><template id="«R»"></template></body></html>',
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render">'
|
||||
: '') +
|
||||
'</head><body><script>try { foo() } catch (e) {} ;</script>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -84,9 +84,15 @@ describe('ReactDOMFizzServerBrowser', () => {
|
|||
),
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
if (gate(flags => flags.enableFizzBlockingRender)) {
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
);
|
||||
} else {
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should emit bootstrap script src at the end', async () => {
|
||||
|
|
@ -529,7 +535,15 @@ describe('ReactDOMFizzServerBrowser', () => {
|
|||
|
||||
const result = await readResult(stream);
|
||||
expect(result).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/><title>foo</title></head><body>bar<template id="«R»"></template></body></html>',
|
||||
'<!DOCTYPE html><html><head>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render"/>'
|
||||
: '') +
|
||||
'<title>foo</title></head><body>bar' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -71,8 +71,14 @@ describe('ReactDOMFizzServerEdge', () => {
|
|||
setTimeout(resolve, 1);
|
||||
});
|
||||
|
||||
if (gate(flags => flags.enableFizzBlockingRender)) {
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body><main>hello</main><template id="«R»"></template></body></html>"`,
|
||||
);
|
||||
} else {
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head></head><body><main>hello</main></body></html>"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -78,9 +78,15 @@ describe('ReactDOMFizzServerNode', () => {
|
|||
pipe(writable);
|
||||
});
|
||||
// with Float, we emit empty heads if they are elided when rendering <html>
|
||||
if (gate(flags => flags.enableFizzBlockingRender)) {
|
||||
expect(output.result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
);
|
||||
} else {
|
||||
expect(output.result).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should emit bootstrap script src at the end', async () => {
|
||||
|
|
|
|||
|
|
@ -195,9 +195,15 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
|||
),
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
if (gate(flags => flags.enableFizzBlockingRender)) {
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
);
|
||||
} else {
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should emit bootstrap script src at the end', async () => {
|
||||
|
|
@ -1438,8 +1444,15 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
|||
expect(await readContent(content)).toBe(
|
||||
'<!DOCTYPE html><html lang="en"><head>' +
|
||||
'<link rel="stylesheet" href="my-style" data-precedence="high"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body>Hello<template id="«R»"></template></body></html>',
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render"/>'
|
||||
: '') +
|
||||
'</head>' +
|
||||
'<body>Hello' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -63,9 +63,15 @@ describe('ReactDOMFizzStaticNode', () => {
|
|||
</html>,
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
if (gate(flags => flags.enableFizzBlockingRender)) {
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head><body>hello world<template id="«R»"></template></body></html>"`,
|
||||
);
|
||||
} else {
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<!DOCTYPE html><html><head></head><body>hello world</body></html>"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
|
|
|
|||
|
|
@ -704,8 +704,14 @@ describe('ReactDOMFloat', () => {
|
|||
(gate(flags => flags.shouldUseFizzExternalRuntime)
|
||||
? '<script src="react-dom/unstable_server-external-runtime" async=""></script>'
|
||||
: '') +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/><title>foo</title></head>' +
|
||||
'<body>bar<template id="«R»"></template>',
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render"/>'
|
||||
: '') +
|
||||
'<title>foo</title></head>' +
|
||||
'<body>bar' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: ''),
|
||||
'</body></html>',
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,8 +34,15 @@ describe('ReactDOMFloat', () => {
|
|||
);
|
||||
|
||||
expect(result).toEqual(
|
||||
'<html><head><meta charSet="utf-8"/><link rel="expect" href="#«R»" blocking="render"/>' +
|
||||
'<title>title</title><script src="foo"></script></head><template id="«R»"></template></html>',
|
||||
'<html><head><meta charSet="utf-8"/>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render"/>'
|
||||
: '') +
|
||||
'<title>title</title><script src="foo"></script></head>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</html>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -78,14 +78,20 @@ describe('rendering React components at document', () => {
|
|||
root = ReactDOMClient.hydrateRoot(testDocument, <Root hello="world" />);
|
||||
});
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
'Hello world' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: ''),
|
||||
);
|
||||
|
||||
await act(() => {
|
||||
root.render(<Root hello="moon" />);
|
||||
});
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello moon' + '<template id="«R»"></template>',
|
||||
'Hello moon' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: ''),
|
||||
);
|
||||
|
||||
expect(body === testDocument.body).toBe(true);
|
||||
|
|
@ -112,7 +118,10 @@ describe('rendering React components at document', () => {
|
|||
root = ReactDOMClient.hydrateRoot(testDocument, <Root />);
|
||||
});
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
'Hello world' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: ''),
|
||||
);
|
||||
|
||||
const originalDocEl = testDocument.documentElement;
|
||||
|
|
@ -124,9 +133,15 @@ describe('rendering React components at document', () => {
|
|||
expect(testDocument.firstChild).toBe(originalDocEl);
|
||||
expect(testDocument.head).toBe(originalHead);
|
||||
expect(testDocument.body).toBe(originalBody);
|
||||
expect(originalBody.innerHTML).toBe('<template id="«R»"></template>');
|
||||
expect(originalBody.innerHTML).toBe(
|
||||
gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '',
|
||||
);
|
||||
expect(originalHead.innerHTML).toBe(
|
||||
'<link rel="expect" href="#«R»" blocking="render">',
|
||||
gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render">'
|
||||
: '',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -166,7 +181,10 @@ describe('rendering React components at document', () => {
|
|||
});
|
||||
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
'Hello world' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: ''),
|
||||
);
|
||||
|
||||
await act(() => {
|
||||
|
|
@ -174,7 +192,9 @@ describe('rendering React components at document', () => {
|
|||
});
|
||||
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'<template id="«R»"></template>' + 'Goodbye world',
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') + 'Goodbye world',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -205,7 +225,10 @@ describe('rendering React components at document', () => {
|
|||
});
|
||||
|
||||
expect(testDocument.body.innerHTML).toBe(
|
||||
'Hello world' + '<template id="«R»"></template>',
|
||||
'Hello world' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -341,7 +364,10 @@ describe('rendering React components at document', () => {
|
|||
expect(testDocument.body.innerHTML).toBe(
|
||||
favorSafetyOverHydrationPerf
|
||||
? 'Hello world'
|
||||
: 'Goodbye world<template id="«R»"></template>',
|
||||
: 'Goodbye world' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: ''),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1921,14 +1921,28 @@ describe('ReactFlightDOM', () => {
|
|||
expect(content1).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="preload" href="before1" as="style"/>' +
|
||||
'<link rel="preload" href="after1" as="style"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body><p>hello world</p><template id="«R»"></template></body></html>',
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render"/>'
|
||||
: '') +
|
||||
'</head>' +
|
||||
'<body><p>hello world</p>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</body></html>',
|
||||
);
|
||||
expect(content2).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="preload" href="before2" as="style"/>' +
|
||||
'<link rel="preload" href="after2" as="style"/>' +
|
||||
'<link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body><p>hello world</p><template id="«R»"></template></body></html>',
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render"/>'
|
||||
: '') +
|
||||
'</head>' +
|
||||
'<body><p>hello world</p>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1899,8 +1899,16 @@ describe('ReactFlightDOMBrowser', () => {
|
|||
}
|
||||
|
||||
expect(content).toEqual(
|
||||
'<!DOCTYPE html><html><head><link rel="expect" href="#«R»" blocking="render"/></head>' +
|
||||
'<body><p>hello world</p><template id="«R»"></template></body></html>',
|
||||
'<!DOCTYPE html><html><head>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<link rel="expect" href="#«R»" blocking="render"/>'
|
||||
: '') +
|
||||
'</head>' +
|
||||
'<body><p>hello world</p>' +
|
||||
(gate(flags => flags.enableFizzBlockingRender)
|
||||
? '<template id="«R»"></template>'
|
||||
: '') +
|
||||
'</body></html>',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ export const enableScrollEndPolyfill = __EXPERIMENTAL__;
|
|||
|
||||
export const enableSuspenseyImages = false;
|
||||
|
||||
export const enableFizzBlockingRender = __EXPERIMENTAL__; // rel="expect"
|
||||
|
||||
export const enableSrcObject = __EXPERIMENTAL__;
|
||||
|
||||
export const enableHydrationChangeEvent = __EXPERIMENTAL__;
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ export const enableViewTransition = false;
|
|||
export const enableGestureTransition = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFizzBlockingRender = true;
|
||||
export const enableSrcObject = false;
|
||||
export const enableHydrationChangeEvent = true;
|
||||
export const enableDefaultTransitionIndicator = false;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ export const enableFastAddPropertiesInDiffing = false;
|
|||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFizzBlockingRender = true;
|
||||
export const enableSrcObject = false;
|
||||
export const enableHydrationChangeEvent = false;
|
||||
export const enableDefaultTransitionIndicator = false;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ export const enableFastAddPropertiesInDiffing = true;
|
|||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFizzBlockingRender = true;
|
||||
export const enableSrcObject = false;
|
||||
export const enableHydrationChangeEvent = false;
|
||||
export const enableDefaultTransitionIndicator = false;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export const enableFastAddPropertiesInDiffing = false;
|
|||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFizzBlockingRender = true;
|
||||
export const enableSrcObject = false;
|
||||
export const enableHydrationChangeEvent = false;
|
||||
export const enableDefaultTransitionIndicator = false;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ export const enableFastAddPropertiesInDiffing = false;
|
|||
export const enableLazyPublicInstanceInFabric = false;
|
||||
export const enableScrollEndPolyfill = true;
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFizzBlockingRender = true;
|
||||
export const enableSrcObject = false;
|
||||
export const enableHydrationChangeEvent = false;
|
||||
export const enableDefaultTransitionIndicator = false;
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ export const enableLazyPublicInstanceInFabric = false;
|
|||
export const enableGestureTransition = false;
|
||||
|
||||
export const enableSuspenseyImages = false;
|
||||
export const enableFizzBlockingRender = true;
|
||||
export const enableSrcObject = false;
|
||||
export const enableHydrationChangeEvent = false;
|
||||
export const enableDefaultTransitionIndicator = false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user