mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[Float] Support script preloads (#25432)
* support script preloads * gates
This commit is contained in:
parent
65b3449c89
commit
618388bc32
|
|
@ -29,7 +29,7 @@ import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostCo
|
||||||
|
|
||||||
// The resource types we support. currently they match the form for the as argument.
|
// The resource types we support. currently they match the form for the as argument.
|
||||||
// In the future this may need to change, especially when modules / scripts are supported
|
// In the future this may need to change, especially when modules / scripts are supported
|
||||||
type ResourceType = 'style' | 'font';
|
type ResourceType = 'style' | 'font' | 'script';
|
||||||
|
|
||||||
type PreloadProps = {
|
type PreloadProps = {
|
||||||
rel: 'preload',
|
rel: 'preload',
|
||||||
|
|
@ -150,7 +150,7 @@ function getDocumentFromRoot(root: FloatRoot): Document {
|
||||||
// ReactDOM.Preload
|
// ReactDOM.Preload
|
||||||
// --------------------------------------
|
// --------------------------------------
|
||||||
type PreloadAs = ResourceType;
|
type PreloadAs = ResourceType;
|
||||||
type PreloadOptions = {as: PreloadAs, crossOrigin?: string};
|
type PreloadOptions = {as: PreloadAs, crossOrigin?: string, integrity?: string};
|
||||||
function preload(href: string, options: PreloadOptions) {
|
function preload(href: string, options: PreloadOptions) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
validatePreloadArguments(href, options);
|
validatePreloadArguments(href, options);
|
||||||
|
|
@ -194,6 +194,7 @@ function preloadPropsFromPreloadOptions(
|
||||||
rel: 'preload',
|
rel: 'preload',
|
||||||
as,
|
as,
|
||||||
crossOrigin: as === 'font' ? '' : options.crossOrigin,
|
crossOrigin: as === 'font' ? '' : options.crossOrigin,
|
||||||
|
integrity: options.integrity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -832,7 +833,7 @@ export function isHostResourceType(type: string, props: Props): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isResourceAsType(as: mixed): boolean {
|
function isResourceAsType(as: mixed): boolean {
|
||||||
return as === 'style' || as === 'font';
|
return as === 'style' || as === 'font' || as === 'script';
|
||||||
}
|
}
|
||||||
|
|
||||||
// When passing user input into querySelector(All) the embedded string must not alter
|
// When passing user input into querySelector(All) the embedded string must not alter
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import {
|
||||||
|
|
||||||
type Props = {[string]: mixed};
|
type Props = {[string]: mixed};
|
||||||
|
|
||||||
type ResourceType = 'style' | 'font';
|
type ResourceType = 'style' | 'font' | 'script';
|
||||||
|
|
||||||
type PreloadProps = {
|
type PreloadProps = {
|
||||||
rel: 'preload',
|
rel: 'preload',
|
||||||
|
|
@ -123,7 +123,7 @@ export const ReactDOMServerDispatcher = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type PreloadAs = ResourceType;
|
type PreloadAs = ResourceType;
|
||||||
type PreloadOptions = {as: PreloadAs, crossOrigin?: string};
|
type PreloadOptions = {as: PreloadAs, crossOrigin?: string, integrity?: string};
|
||||||
function preload(href: string, options: PreloadOptions) {
|
function preload(href: string, options: PreloadOptions) {
|
||||||
if (!currentResources) {
|
if (!currentResources) {
|
||||||
// While we expect that preload calls are primarily going to be observed
|
// While we expect that preload calls are primarily going to be observed
|
||||||
|
|
@ -248,6 +248,7 @@ function preloadPropsFromPreloadOptions(
|
||||||
rel: 'preload',
|
rel: 'preload',
|
||||||
as,
|
as,
|
||||||
crossOrigin: as === 'font' ? '' : options.crossOrigin,
|
crossOrigin: as === 'font' ? '' : options.crossOrigin,
|
||||||
|
integrity: options.integrity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,6 +527,7 @@ export function resourcesFromLink(props: Props): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (as) {
|
switch (as) {
|
||||||
|
case 'script':
|
||||||
case 'style':
|
case 'style':
|
||||||
case 'font': {
|
case 'font': {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
|
|
||||||
|
|
@ -16,44 +16,42 @@ export function validateUnmatchedLinkResourceProps(
|
||||||
currentProps: ?Props,
|
currentProps: ?Props,
|
||||||
) {
|
) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
if (pendingProps.rel !== 'font' && pendingProps.rel !== 'style') {
|
if (currentProps != null) {
|
||||||
if (currentProps != null) {
|
const originalResourceName =
|
||||||
const originalResourceName =
|
typeof currentProps.href === 'string'
|
||||||
typeof currentProps.href === 'string'
|
? `Resource with href "${currentProps.href}"`
|
||||||
? `Resource with href "${currentProps.href}"`
|
: 'Resource';
|
||||||
: 'Resource';
|
const originalRelStatement = getValueDescriptorExpectingEnumForWarning(
|
||||||
const originalRelStatement = getValueDescriptorExpectingEnumForWarning(
|
currentProps.rel,
|
||||||
currentProps.rel,
|
);
|
||||||
);
|
const pendingRelStatement = getValueDescriptorExpectingEnumForWarning(
|
||||||
const pendingRelStatement = getValueDescriptorExpectingEnumForWarning(
|
pendingProps.rel,
|
||||||
pendingProps.rel,
|
);
|
||||||
);
|
const pendingHrefStatement =
|
||||||
const pendingHrefStatement =
|
typeof pendingProps.href === 'string'
|
||||||
typeof pendingProps.href === 'string'
|
? ` and the updated href is "${pendingProps.href}"`
|
||||||
? ` and the updated href is "${pendingProps.href}"`
|
: '';
|
||||||
: '';
|
console.error(
|
||||||
console.error(
|
'A <link> previously rendered as a %s but was updated with a rel type that is not' +
|
||||||
'A <link> previously rendered as a %s but was updated with a rel type that is not' +
|
' valid for a Resource type. Generally Resources are not expected to ever have updated' +
|
||||||
' valid for a Resource type. Generally Resources are not expected to ever have updated' +
|
' props however in some limited circumstances it can be valid when changing the href.' +
|
||||||
' props however in some limited circumstances it can be valid when changing the href.' +
|
' When React encounters props that invalidate the Resource it is the same as not rendering' +
|
||||||
' When React encounters props that invalidate the Resource it is the same as not rendering' +
|
' a Resource at all. valid rel types for Resources are "stylesheet" and "preload". The previous' +
|
||||||
' a Resource at all. valid rel types for Resources are "font" and "style". The previous' +
|
' rel for this instance was %s. The updated rel is %s%s.',
|
||||||
' rel for this instance was %s. The updated rel is %s%s.',
|
originalResourceName,
|
||||||
originalResourceName,
|
originalRelStatement,
|
||||||
originalRelStatement,
|
pendingRelStatement,
|
||||||
pendingRelStatement,
|
pendingHrefStatement,
|
||||||
pendingHrefStatement,
|
);
|
||||||
);
|
} else {
|
||||||
} else {
|
const pendingRelStatement = getValueDescriptorExpectingEnumForWarning(
|
||||||
const pendingRelStatement = getValueDescriptorExpectingEnumForWarning(
|
pendingProps.rel,
|
||||||
pendingProps.rel,
|
);
|
||||||
);
|
console.error(
|
||||||
console.error(
|
'A <link> is rendering as a Resource but has an invalid rel property. The rel encountered is %s.' +
|
||||||
'A <link> is rendering as a Resource but has an invalid rel property. The rel encountered is %s.' +
|
' This is a bug in React.',
|
||||||
' This is a bug in React.',
|
pendingRelStatement,
|
||||||
pendingRelStatement,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -517,6 +515,7 @@ export function validatePreloadArguments(href: mixed, options: mixed) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'script':
|
||||||
case 'style': {
|
case 'style': {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -529,7 +528,7 @@ export function validatePreloadArguments(href: mixed, options: mixed) {
|
||||||
' Please use one of the following valid values instead: %s. The href for the preload call where this' +
|
' Please use one of the following valid values instead: %s. The href for the preload call where this' +
|
||||||
' warning originated is "%s".',
|
' warning originated is "%s".',
|
||||||
typeOfAs,
|
typeOfAs,
|
||||||
'"style" and "font"',
|
'"style", "font", or "script"',
|
||||||
href,
|
href,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -557,7 +556,6 @@ export function validatePreinitArguments(href: mixed, options: mixed) {
|
||||||
} else {
|
} else {
|
||||||
const as = options.as;
|
const as = options.as;
|
||||||
switch (as) {
|
switch (as) {
|
||||||
case 'font':
|
|
||||||
case 'style': {
|
case 'style': {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ describe('ReactDOMFloat', () => {
|
||||||
' valid for a Resource type. Generally Resources are not expected to ever have updated' +
|
' valid for a Resource type. Generally Resources are not expected to ever have updated' +
|
||||||
' props however in some limited circumstances it can be valid when changing the href.' +
|
' props however in some limited circumstances it can be valid when changing the href.' +
|
||||||
' When React encounters props that invalidate the Resource it is the same as not rendering' +
|
' When React encounters props that invalidate the Resource it is the same as not rendering' +
|
||||||
' a Resource at all. valid rel types for Resources are "font" and "style". The previous' +
|
' a Resource at all. valid rel types for Resources are "stylesheet" and "preload". The previous' +
|
||||||
' rel for this instance was "stylesheet". The updated rel is "author" and the updated href is "bar".',
|
' rel for this instance was "stylesheet". The updated rel is "author" and the updated href is "bar".',
|
||||||
);
|
);
|
||||||
expect(getVisibleChildren(document)).toEqual(
|
expect(getVisibleChildren(document)).toEqual(
|
||||||
|
|
@ -407,6 +407,97 @@ describe('ReactDOMFloat', () => {
|
||||||
</html>,
|
</html>,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @gate enableFloat
|
||||||
|
it('supports script preloads', async () => {
|
||||||
|
function ServerApp() {
|
||||||
|
ReactDOM.preload('foo', {as: 'script', integrity: 'foo hash'});
|
||||||
|
ReactDOM.preload('bar', {
|
||||||
|
as: 'script',
|
||||||
|
crossOrigin: 'use-credentials',
|
||||||
|
integrity: 'bar hash',
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<link rel="preload" href="baz" as="script" />
|
||||||
|
<head>
|
||||||
|
<title>hi</title>
|
||||||
|
</head>
|
||||||
|
<body>foo</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function ClientApp() {
|
||||||
|
ReactDOM.preload('foo', {as: 'script', integrity: 'foo hash'});
|
||||||
|
ReactDOM.preload('qux', {as: 'script'});
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>hi</title>
|
||||||
|
</head>
|
||||||
|
<body>foo</body>
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
href="quux"
|
||||||
|
as="script"
|
||||||
|
crossOrigin=""
|
||||||
|
integrity="quux hash"
|
||||||
|
/>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await actIntoEmptyDocument(() => {
|
||||||
|
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<ServerApp />);
|
||||||
|
pipe(writable);
|
||||||
|
});
|
||||||
|
expect(getVisibleChildren(document)).toEqual(
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="preload" as="script" href="foo" integrity="foo hash" />
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
as="script"
|
||||||
|
href="bar"
|
||||||
|
crossorigin="use-credentials"
|
||||||
|
integrity="bar hash"
|
||||||
|
/>
|
||||||
|
<link rel="preload" as="script" href="baz" />
|
||||||
|
<title>hi</title>
|
||||||
|
</head>
|
||||||
|
<body>foo</body>
|
||||||
|
</html>,
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDOMClient.hydrateRoot(document, <ClientApp />);
|
||||||
|
expect(Scheduler).toFlushWithoutYielding();
|
||||||
|
|
||||||
|
expect(getVisibleChildren(document)).toEqual(
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="preload" as="script" href="foo" integrity="foo hash" />
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
as="script"
|
||||||
|
href="bar"
|
||||||
|
crossorigin="use-credentials"
|
||||||
|
integrity="bar hash"
|
||||||
|
/>
|
||||||
|
<link rel="preload" as="script" href="baz" />
|
||||||
|
<title>hi</title>
|
||||||
|
<link rel="preload" as="script" href="qux" />
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
as="script"
|
||||||
|
href="quux"
|
||||||
|
crossorigin=""
|
||||||
|
integrity="quux hash"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>foo</body>
|
||||||
|
</html>,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ReactDOM.preinit as style', () => {
|
describe('ReactDOM.preinit as style', () => {
|
||||||
|
|
@ -2885,7 +2976,11 @@ describe('ReactDOMFloat', () => {
|
||||||
(mockError, scenarioNumber) => {
|
(mockError, scenarioNumber) => {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
||||||
makeArgs('undefined', '"style" and "font"', 'foo'),
|
makeArgs(
|
||||||
|
'undefined',
|
||||||
|
'"style", "font", or "script"',
|
||||||
|
'foo',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
expect(mockError).not.toHaveBeenCalled();
|
expect(mockError).not.toHaveBeenCalled();
|
||||||
|
|
@ -2898,7 +2993,7 @@ describe('ReactDOMFloat', () => {
|
||||||
(mockError, scenarioNumber) => {
|
(mockError, scenarioNumber) => {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
||||||
makeArgs('null', '"style" and "font"', 'bar'),
|
makeArgs('null', '"style", "font", or "script"', 'bar'),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
expect(mockError).not.toHaveBeenCalled();
|
expect(mockError).not.toHaveBeenCalled();
|
||||||
|
|
@ -2913,7 +3008,7 @@ describe('ReactDOMFloat', () => {
|
||||||
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
||||||
makeArgs(
|
makeArgs(
|
||||||
'something with type "number"',
|
'something with type "number"',
|
||||||
'"style" and "font"',
|
'"style", "font", or "script"',
|
||||||
'baz',
|
'baz',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -2930,7 +3025,7 @@ describe('ReactDOMFloat', () => {
|
||||||
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
||||||
makeArgs(
|
makeArgs(
|
||||||
'something with type "object"',
|
'something with type "object"',
|
||||||
'"style" and "font"',
|
'"style", "font", or "script"',
|
||||||
'qux',
|
'qux',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -2945,7 +3040,7 @@ describe('ReactDOMFloat', () => {
|
||||||
(mockError, scenarioNumber) => {
|
(mockError, scenarioNumber) => {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
expect(mockError.mock.calls[scenarioNumber]).toEqual(
|
||||||
makeArgs('"bar"', '"style" and "font"', 'quux'),
|
makeArgs('"bar"', '"style", "font", or "script"', 'quux'),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
expect(mockError).not.toHaveBeenCalled();
|
expect(mockError).not.toHaveBeenCalled();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user