Skip to content

Commit c53ca68

Browse files
committed
Currently we preload all scripts that are not hoisted. One of the original reasons for this is we stopped SSR rendering async scripts that had an onLoad/onError because we needed to be able to distinguish between Float scripts and non-Float scripts during hydration. Hydration has been refactored a bit and we can not get around this limitation so we can just emit the async script in place. However, sync and defer scripts are also preloaded. While this is sometimes desirable it is not universally so and there are issues with conveying priority properly (see fetchpriority) so with this change we remove the automatic preloading of non-Float scripts altogether.
1 parent 06f4826 commit c53ca68

File tree

6 files changed

+102
-228
lines changed

6 files changed

+102
-228
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,19 +1036,6 @@ export function bindInstance(
10361036

10371037
export const supportsHydration = true;
10381038

1039-
// With Resources, some HostComponent types will never be server rendered and need to be
1040-
// inserted without breaking hydration
1041-
export function isHydratableType(type: string, props: Props): boolean {
1042-
if (enableFloat) {
1043-
if (type === 'script') {
1044-
const {async, onLoad, onError} = (props: any);
1045-
return !(async && (onLoad || onError));
1046-
}
1047-
return true;
1048-
} else {
1049-
return true;
1050-
}
1051-
}
10521039
export function isHydratableText(text: string): boolean {
10531040
return text !== '';
10541041
}
@@ -1164,21 +1151,22 @@ export function canHydrateInstance(
11641151
// if we learn it is problematic
11651152
const srcAttr = element.getAttribute('src');
11661153
if (
1167-
srcAttr &&
1168-
element.hasAttribute('async') &&
1169-
!element.hasAttribute('itemprop')
1170-
) {
1171-
// This is an async script resource
1172-
break;
1173-
} else if (
11741154
srcAttr !== (anyProps.src == null ? null : anyProps.src) ||
11751155
element.getAttribute('type') !==
11761156
(anyProps.type == null ? null : anyProps.type) ||
11771157
element.getAttribute('crossorigin') !==
11781158
(anyProps.crossOrigin == null ? null : anyProps.crossOrigin)
11791159
) {
1180-
// This script is for a different src
1181-
break;
1160+
// This script is for a different src/type/crossOrigin. It may be a script resource
1161+
// or it may just be a mistmatch
1162+
if (
1163+
srcAttr &&
1164+
element.hasAttribute('async') &&
1165+
!element.hasAttribute('itemprop')
1166+
) {
1167+
// This is an async script resource
1168+
break;
1169+
}
11821170
}
11831171
return element;
11841172
}

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 78 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -2637,128 +2637,102 @@ function pushScript(
26372637
noscriptTagInScope: boolean,
26382638
): null {
26392639
if (enableFloat) {
2640+
const asyncProp = props.async;
26402641
if (
2642+
typeof props.src !== 'string' ||
2643+
!props.src ||
2644+
!(
2645+
asyncProp &&
2646+
typeof asyncProp !== 'function' &&
2647+
typeof asyncProp !== 'symbol'
2648+
) ||
2649+
props.onLoad ||
2650+
props.onError ||
26412651
insertionMode === SVG_MODE ||
26422652
noscriptTagInScope ||
2643-
props.itemProp != null ||
2644-
typeof props.src !== 'string' ||
2645-
!props.src
2653+
props.itemProp != null
26462654
) {
2647-
// This script will not be a resource nor can it be preloaded, we bailout early
2648-
// and emit it in place.
2655+
// This script will not be a resource, we bailout early and emit it in place.
26492656
return pushScriptImpl(target, props);
26502657
}
26512658

26522659
const src = props.src;
26532660
const key = getResourceKey('script', src);
2654-
if (props.async !== true || props.onLoad || props.onError) {
2655-
// we don't want to preload nomodule scripts
2656-
if (props.noModule !== true) {
2657-
// We can't resourcify scripts with load listeners. To avoid ambiguity with
2658-
// other Resourcified async scripts on the server we omit them from the server
2659-
// stream and expect them to be inserted during hydration on the client.
2660-
// We can still preload them however so the client can start fetching the script
2661-
// as soon as possible
2662-
let resource = resources.preloadsMap.get(key);
2663-
if (!resource) {
2664-
resource = {
2665-
type: 'preload',
2666-
chunks: [],
2667-
state: NoState,
2668-
props: preloadAsScriptPropsFromProps(props.src, props),
2669-
};
2670-
resources.preloadsMap.set(key, resource);
2671-
if (__DEV__) {
2672-
markAsImplicitResourceDEV(resource, props, resource.props);
2661+
// We can make this <script> into a ScriptResource
2662+
let resource = resources.scriptsMap.get(key);
2663+
if (__DEV__) {
2664+
const devResource = getAsResourceDEV(resource);
2665+
if (devResource) {
2666+
switch (devResource.__provenance) {
2667+
case 'rendered': {
2668+
const differenceDescription = describeDifferencesForScripts(
2669+
// Diff the props from the JSX element, not the derived resource props
2670+
props,
2671+
devResource.__originalProps,
2672+
);
2673+
if (differenceDescription) {
2674+
console.error(
2675+
'React encountered a <script async={true} src="%s" .../> that has props that conflict' +
2676+
' with another hoistable script with the same `src`. When rendering hoistable scripts (async scripts without any loading handlers)' +
2677+
' the props from the first encountered instance will be used and props from later instances will be ignored.' +
2678+
' Update the props on both <script async={true} .../> instance so they agree.%s',
2679+
src,
2680+
differenceDescription,
2681+
);
2682+
}
2683+
break;
26732684
}
2674-
resources.usedScripts.add(resource);
2675-
pushLinkImpl(resource.chunks, resource.props);
2676-
}
2677-
}
2678-
2679-
if (props.async !== true) {
2680-
// This is not an async script, we can preloaded it but it still needs to
2681-
// be emitted in place since it needs to hydrate on the client
2682-
pushScriptImpl(target, props);
2683-
return null;
2684-
}
2685-
} else {
2686-
// We can make this <script> into a ScriptResource
2687-
let resource = resources.scriptsMap.get(key);
2688-
if (__DEV__) {
2689-
const devResource = getAsResourceDEV(resource);
2690-
if (devResource) {
2691-
switch (devResource.__provenance) {
2692-
case 'rendered': {
2693-
const differenceDescription = describeDifferencesForScripts(
2685+
case 'preinit': {
2686+
const differenceDescription =
2687+
describeDifferencesForScriptOverPreinit(
26942688
// Diff the props from the JSX element, not the derived resource props
26952689
props,
2696-
devResource.__originalProps,
2690+
devResource.__propsEquivalent,
2691+
);
2692+
if (differenceDescription) {
2693+
console.error(
2694+
'React encountered a <script async={true} src="%s" .../> with props that conflict' +
2695+
' with the options provided to `ReactDOM.preinit("%s", { as: "script", ... })`. React will use the first props or preinitialization' +
2696+
' options encountered when rendering a hoistable script with a particular `src` and will ignore any newer props or' +
2697+
' options. The first instance of this script resource was created using the `ReactDOM.preinit()` function.' +
2698+
' Please note, `ReactDOM.preinit()` is modeled off of module import assertions capabilities and does not support' +
2699+
' arbitrary props. If you need to have props not included with the preinit options you will need to rely on rendering' +
2700+
' <script> tags only.%s',
2701+
src,
2702+
src,
2703+
differenceDescription,
26972704
);
2698-
if (differenceDescription) {
2699-
console.error(
2700-
'React encountered a <script async={true} src="%s" .../> that has props that conflict' +
2701-
' with another hoistable script with the same `src`. When rendering hoistable scripts (async scripts without any loading handlers)' +
2702-
' the props from the first encountered instance will be used and props from later instances will be ignored.' +
2703-
' Update the props on both <script async={true} .../> instance so they agree.%s',
2704-
src,
2705-
differenceDescription,
2706-
);
2707-
}
2708-
break;
2709-
}
2710-
case 'preinit': {
2711-
const differenceDescription =
2712-
describeDifferencesForScriptOverPreinit(
2713-
// Diff the props from the JSX element, not the derived resource props
2714-
props,
2715-
devResource.__propsEquivalent,
2716-
);
2717-
if (differenceDescription) {
2718-
console.error(
2719-
'React encountered a <script async={true} src="%s" .../> with props that conflict' +
2720-
' with the options provided to `ReactDOM.preinit("%s", { as: "script", ... })`. React will use the first props or preinitialization' +
2721-
' options encountered when rendering a hoistable script with a particular `src` and will ignore any newer props or' +
2722-
' options. The first instance of this script resource was created using the `ReactDOM.preinit()` function.' +
2723-
' Please note, `ReactDOM.preinit()` is modeled off of module import assertions capabilities and does not support' +
2724-
' arbitrary props. If you need to have props not included with the preinit options you will need to rely on rendering' +
2725-
' <script> tags only.%s',
2726-
src,
2727-
src,
2728-
differenceDescription,
2729-
);
2730-
}
2731-
break;
27322705
}
2706+
break;
27332707
}
27342708
}
27352709
}
2736-
if (!resource) {
2737-
resource = {
2738-
type: 'script',
2739-
chunks: [],
2740-
state: NoState,
2741-
props: null,
2742-
};
2743-
resources.scriptsMap.set(key, resource);
2744-
if (__DEV__) {
2745-
markAsRenderedResourceDEV(resource, props);
2746-
}
2747-
// Add to the script flushing queue
2748-
resources.scripts.add(resource);
2749-
2750-
let scriptProps = props;
2751-
const preloadResource = resources.preloadsMap.get(key);
2752-
if (preloadResource) {
2753-
// If we already had a preload we don't want that resource to flush directly.
2754-
// We let the newly created resource govern flushing.
2755-
preloadResource.state |= Blocked;
2756-
scriptProps = {...props};
2757-
adoptPreloadPropsForScriptProps(scriptProps, preloadResource.props);
2758-
}
2759-
// encode the tag as Chunks
2760-
pushScriptImpl(resource.chunks, scriptProps);
2710+
}
2711+
if (!resource) {
2712+
resource = {
2713+
type: 'script',
2714+
chunks: [],
2715+
state: NoState,
2716+
props: null,
2717+
};
2718+
resources.scriptsMap.set(key, resource);
2719+
if (__DEV__) {
2720+
markAsRenderedResourceDEV(resource, props);
27612721
}
2722+
// Add to the script flushing queue
2723+
resources.scripts.add(resource);
2724+
2725+
let scriptProps = props;
2726+
const preloadResource = resources.preloadsMap.get(key);
2727+
if (preloadResource) {
2728+
// If we already had a preload we don't want that resource to flush directly.
2729+
// We let the newly created resource govern flushing.
2730+
preloadResource.state |= Blocked;
2731+
scriptProps = {...props};
2732+
adoptPreloadPropsForScriptProps(scriptProps, preloadResource.props);
2733+
}
2734+
// encode the tag as Chunks
2735+
pushScriptImpl(resource.chunks, scriptProps);
27622736
}
27632737

27642738
if (textEmbedded) {
@@ -4234,9 +4208,6 @@ export function writePreamble(
42344208
resources.scripts.forEach(flushResourceInPreamble, destination);
42354209
resources.scripts.clear();
42364210

4237-
resources.usedScripts.forEach(flushResourceInPreamble, destination);
4238-
resources.usedScripts.clear();
4239-
42404211
resources.explicitStylesheetPreloads.forEach(
42414212
flushResourceInPreamble,
42424213
destination,
@@ -4314,9 +4285,6 @@ export function writeHoistables(
43144285
resources.scripts.forEach(flushResourceLate, destination);
43154286
resources.scripts.clear();
43164287

4317-
resources.usedScripts.forEach(flushResourceLate, destination);
4318-
resources.usedScripts.clear();
4319-
43204288
resources.explicitStylesheetPreloads.forEach(flushResourceLate, destination);
43214289
resources.explicitStylesheetPreloads.clear();
43224290

@@ -4862,7 +4830,6 @@ export type Resources = {
48624830
precedences: Map<string, Set<StyleResource>>,
48634831
stylePrecedences: Map<string, StyleTagResource>,
48644832
scripts: Set<ScriptResource>,
4865-
usedScripts: Set<PreloadResource>,
48664833
explicitStylesheetPreloads: Set<PreloadResource>,
48674834
// explicitImagePreloads: Set<PreloadResource>,
48684835
explicitScriptPreloads: Set<PreloadResource>,
@@ -4889,7 +4856,6 @@ export function createResources(): Resources {
48894856
precedences: new Map(),
48904857
stylePrecedences: new Map(),
48914858
scripts: new Set(),
4892-
usedScripts: new Set(),
48934859
explicitStylesheetPreloads: new Set(),
48944860
// explicitImagePreloads: new Set(),
48954861
explicitScriptPreloads: new Set(),
@@ -5469,19 +5435,6 @@ function preloadAsStylePropsFromProps(href: string, props: any): PreloadProps {
54695435
};
54705436
}
54715437

5472-
function preloadAsScriptPropsFromProps(href: string, props: any): PreloadProps {
5473-
return {
5474-
rel: 'preload',
5475-
as: 'script',
5476-
href,
5477-
crossOrigin: props.crossOrigin,
5478-
fetchPriority: props.fetchPriority,
5479-
integrity: props.integrity,
5480-
nonce: props.nonce,
5481-
referrerPolicy: props.referrerPolicy,
5482-
};
5483-
}
5484-
54855438
function stylesheetPropsFromPreinitOptions(
54865439
href: string,
54875440
precedence: string,
@@ -5598,29 +5551,6 @@ function markAsImperativeResourceDEV(
55985551
}
55995552
}
56005553

5601-
function markAsImplicitResourceDEV(
5602-
resource: Resource,
5603-
underlyingProps: any,
5604-
impliedProps: any,
5605-
): void {
5606-
if (__DEV__) {
5607-
const devResource: ImplicitResourceDEV = (resource: any);
5608-
if (typeof devResource.__provenance === 'string') {
5609-
console.error(
5610-
'Resource already marked for DEV type. This is a bug in React.',
5611-
);
5612-
}
5613-
devResource.__provenance = 'implicit';
5614-
devResource.__underlyingProps = underlyingProps;
5615-
devResource.__impliedProps = impliedProps;
5616-
} else {
5617-
// eslint-disable-next-line react-internal/prod-error-codes
5618-
throw new Error(
5619-
'markAsImplicitResourceDEV was included in a production build. This is a bug in React.',
5620-
);
5621-
}
5622-
}
5623-
56245554
function getAsResourceDEV(
56255555
resource: null | void | Resource,
56265556
): null | ResourceDEV {

0 commit comments

Comments
 (0)