mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Float] add support for scripts and other enhancements (#25480)
* float enhance!!! Support preinit as script Support resources from async scripts Support saving the precedence place when rendering the shell There was a significant change to the flushing order of resources which follows the general principal of... 1. stuff that blocks display 2. stuff that we know will be used 3. stuff that was explicitly preloaded As a consequence if you preinit a style now it won't automatically flush in the shell unless you actually depend on it in your tree. To avoid races with precedence order we now emit a tag that saves the place amongst the precedence hierarchy so late insertions still end up where they were intended There is also a novel hydration pathway for certain tags. If you render an async script with an onLoad or onError it will always treat it like an insertion rather than a hydration. * restore preinit style flushing behavior and nits
This commit is contained in:
@@ -7,7 +7,11 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {FloatRoot, StyleResource} from './ReactDOMFloatClient';
|
||||
import type {
|
||||
FloatRoot,
|
||||
StyleResource,
|
||||
ScriptResource,
|
||||
} from './ReactDOMFloatClient';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import type {ReactScopeInstance} from 'shared/ReactTypes';
|
||||
import type {
|
||||
@@ -48,7 +52,7 @@ const internalContainerInstanceKey = '__reactContainer$' + randomKey;
|
||||
const internalEventHandlersKey = '__reactEvents$' + randomKey;
|
||||
const internalEventHandlerListenersKey = '__reactListeners$' + randomKey;
|
||||
const internalEventHandlesSetKey = '__reactHandles$' + randomKey;
|
||||
const internalRootNodeStylesSetKey = '__reactStyles$' + randomKey;
|
||||
const internalRootNodeResourcesKey = '__reactResources$' + randomKey;
|
||||
|
||||
export function detachDeletedInstance(node: Instance): void {
|
||||
// TODO: This function is only called on host components. I don't think all of
|
||||
@@ -278,10 +282,15 @@ export function doesTargetHaveEventHandle(
|
||||
return eventHandles.has(eventHandle);
|
||||
}
|
||||
|
||||
export function getStylesFromRoot(root: FloatRoot): Map<string, StyleResource> {
|
||||
let styles = (root: any)[internalRootNodeStylesSetKey];
|
||||
if (!styles) {
|
||||
styles = (root: any)[internalRootNodeStylesSetKey] = new Map();
|
||||
export function getResourcesFromRoot(
|
||||
root: FloatRoot,
|
||||
): {styles: Map<string, StyleResource>, scripts: Map<string, ScriptResource>} {
|
||||
let resources = (root: any)[internalRootNodeResourcesKey];
|
||||
if (!resources) {
|
||||
resources = (root: any)[internalRootNodeResourcesKey] = {
|
||||
styles: new Map(),
|
||||
scripts: new Map(),
|
||||
};
|
||||
}
|
||||
return styles;
|
||||
return resources;
|
||||
}
|
||||
|
||||
+235
-46
@@ -15,15 +15,16 @@ import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
|
||||
import {
|
||||
validateUnmatchedLinkResourceProps,
|
||||
validatePreloadResourceDifference,
|
||||
validateHrefKeyedUpdatedProps,
|
||||
validateURLKeyedUpdatedProps,
|
||||
validateStyleResourceDifference,
|
||||
validateScriptResourceDifference,
|
||||
validateLinkPropsForStyleResource,
|
||||
validateLinkPropsForPreloadResource,
|
||||
validatePreloadArguments,
|
||||
validatePreinitArguments,
|
||||
} from '../shared/ReactDOMResourceValidation';
|
||||
import {createElement, setInitialProperties} from './ReactDOMComponent';
|
||||
import {getStylesFromRoot} from './ReactDOMComponentTree';
|
||||
import {getResourcesFromRoot} from './ReactDOMComponentTree';
|
||||
import {HTML_NAMESPACE} from '../shared/DOMNamespaces';
|
||||
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
|
||||
|
||||
@@ -33,7 +34,6 @@ type ResourceType = 'style' | 'font' | 'script';
|
||||
|
||||
type PreloadProps = {
|
||||
rel: 'preload',
|
||||
as: ResourceType,
|
||||
href: string,
|
||||
[string]: mixed,
|
||||
};
|
||||
@@ -48,7 +48,7 @@ type PreloadResource = {
|
||||
type StyleProps = {
|
||||
rel: 'stylesheet',
|
||||
href: string,
|
||||
'data-rprec': string,
|
||||
'data-precedence': string,
|
||||
[string]: mixed,
|
||||
};
|
||||
export type StyleResource = {
|
||||
@@ -72,10 +72,22 @@ export type StyleResource = {
|
||||
instance: ?Element,
|
||||
root: FloatRoot,
|
||||
};
|
||||
type ScriptProps = {
|
||||
src: string,
|
||||
[string]: mixed,
|
||||
};
|
||||
export type ScriptResource = {
|
||||
type: 'script',
|
||||
src: string,
|
||||
props: ScriptProps,
|
||||
|
||||
instance: ?Element,
|
||||
root: FloatRoot,
|
||||
};
|
||||
|
||||
type Props = {[string]: mixed};
|
||||
|
||||
type Resource = StyleResource | PreloadResource;
|
||||
type Resource = StyleResource | ScriptResource | PreloadResource;
|
||||
|
||||
// Brief on purpose due to insertion by script when streaming late boundaries
|
||||
// s = Status
|
||||
@@ -202,11 +214,12 @@ function preloadPropsFromPreloadOptions(
|
||||
// ReactDOM.preinit
|
||||
// --------------------------------------
|
||||
|
||||
type PreinitAs = 'style';
|
||||
type PreinitAs = 'style' | 'script';
|
||||
type PreinitOptions = {
|
||||
as: PreinitAs,
|
||||
crossOrigin?: string,
|
||||
precedence?: string,
|
||||
crossOrigin?: string,
|
||||
integrity?: string,
|
||||
};
|
||||
function preinit(href: string, options: PreinitOptions) {
|
||||
if (__DEV__) {
|
||||
@@ -243,7 +256,7 @@ function preinit(href: string, options: PreinitOptions) {
|
||||
|
||||
switch (as) {
|
||||
case 'style': {
|
||||
const styleResources = getStylesFromRoot(resourceRoot);
|
||||
const styleResources = getResourcesFromRoot(resourceRoot).styles;
|
||||
const precedence = options.precedence || 'default';
|
||||
let resource = styleResources.get(href);
|
||||
if (resource) {
|
||||
@@ -270,6 +283,28 @@ function preinit(href: string, options: PreinitOptions) {
|
||||
);
|
||||
}
|
||||
acquireResource(resource);
|
||||
return;
|
||||
}
|
||||
case 'script': {
|
||||
const src = href;
|
||||
const scriptResources = getResourcesFromRoot(resourceRoot).scripts;
|
||||
let resource = scriptResources.get(src);
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
const latestProps = scriptPropsFromPreinitOptions(src, options);
|
||||
validateScriptResourceDifference(resource.props, latestProps);
|
||||
}
|
||||
} else {
|
||||
const resourceProps = scriptPropsFromPreinitOptions(src, options);
|
||||
resource = createScriptResource(
|
||||
scriptResources,
|
||||
resourceRoot,
|
||||
src,
|
||||
resourceProps,
|
||||
);
|
||||
}
|
||||
acquireResource(resource);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -285,6 +320,7 @@ function preloadPropsFromPreinitOptions(
|
||||
rel: 'preload',
|
||||
as,
|
||||
crossOrigin: as === 'font' ? '' : options.crossOrigin,
|
||||
integrity: options.integrity,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -296,11 +332,23 @@ function stylePropsFromPreinitOptions(
|
||||
return {
|
||||
rel: 'stylesheet',
|
||||
href,
|
||||
'data-rprec': precedence,
|
||||
'data-precedence': precedence,
|
||||
crossOrigin: options.crossOrigin,
|
||||
};
|
||||
}
|
||||
|
||||
function scriptPropsFromPreinitOptions(
|
||||
src: string,
|
||||
options: PreinitOptions,
|
||||
): ScriptProps {
|
||||
return {
|
||||
src,
|
||||
async: true,
|
||||
crossOrigin: options.crossOrigin,
|
||||
integrity: options.integrity,
|
||||
};
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// Resources from render
|
||||
// --------------------------------------
|
||||
@@ -314,7 +362,11 @@ type StyleQualifyingProps = {
|
||||
type PreloadQualifyingProps = {
|
||||
rel: 'preload',
|
||||
href: string,
|
||||
as: ResourceType,
|
||||
[string]: mixed,
|
||||
};
|
||||
type ScriptQualifyingProps = {
|
||||
src: string,
|
||||
async: true,
|
||||
[string]: mixed,
|
||||
};
|
||||
|
||||
@@ -335,13 +387,15 @@ export function getResource(
|
||||
const {rel} = pendingProps;
|
||||
switch (rel) {
|
||||
case 'stylesheet': {
|
||||
const styleResources = getStylesFromRoot(resourceRoot);
|
||||
const styleResources = getResourcesFromRoot(resourceRoot).styles;
|
||||
let didWarn;
|
||||
if (__DEV__) {
|
||||
if (currentProps) {
|
||||
didWarn = validateHrefKeyedUpdatedProps(
|
||||
didWarn = validateURLKeyedUpdatedProps(
|
||||
pendingProps,
|
||||
currentProps,
|
||||
'style',
|
||||
'href',
|
||||
);
|
||||
}
|
||||
if (!didWarn) {
|
||||
@@ -360,7 +414,7 @@ export function getResource(
|
||||
if (!didWarn) {
|
||||
const latestProps = stylePropsFromRawProps(styleRawProps);
|
||||
if ((resource: any)._dev_preload_props) {
|
||||
adoptPreloadProps(
|
||||
adoptPreloadPropsForStyle(
|
||||
latestProps,
|
||||
(resource: any)._dev_preload_props,
|
||||
);
|
||||
@@ -387,8 +441,8 @@ export function getResource(
|
||||
if (__DEV__) {
|
||||
validateLinkPropsForPreloadResource(pendingProps);
|
||||
}
|
||||
const {href, as} = pendingProps;
|
||||
if (typeof href === 'string' && isResourceAsType(as)) {
|
||||
const {href} = pendingProps;
|
||||
if (typeof href === 'string') {
|
||||
// We've asserted all the specific types for PreloadQualifyingProps
|
||||
const preloadRawProps: PreloadQualifyingProps = (pendingProps: any);
|
||||
let resource = preloadResources.get(href);
|
||||
@@ -424,6 +478,49 @@ export function getResource(
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'script': {
|
||||
const scriptResources = getResourcesFromRoot(resourceRoot).scripts;
|
||||
let didWarn;
|
||||
if (__DEV__) {
|
||||
if (currentProps) {
|
||||
didWarn = validateURLKeyedUpdatedProps(
|
||||
pendingProps,
|
||||
currentProps,
|
||||
'script',
|
||||
'src',
|
||||
);
|
||||
}
|
||||
}
|
||||
const {src, async} = pendingProps;
|
||||
if (async && typeof src === 'string') {
|
||||
const scriptRawProps: ScriptQualifyingProps = (pendingProps: any);
|
||||
let resource = scriptResources.get(src);
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
if (!didWarn) {
|
||||
const latestProps = scriptPropsFromRawProps(scriptRawProps);
|
||||
if ((resource: any)._dev_preload_props) {
|
||||
adoptPreloadPropsForScript(
|
||||
latestProps,
|
||||
(resource: any)._dev_preload_props,
|
||||
);
|
||||
}
|
||||
validateScriptResourceDifference(resource.props, latestProps);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const resourceProps = scriptPropsFromRawProps(scriptRawProps);
|
||||
resource = createScriptResource(
|
||||
scriptResources,
|
||||
resourceRoot,
|
||||
src,
|
||||
resourceProps,
|
||||
);
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
default: {
|
||||
throw new Error(
|
||||
`getResource encountered a resource type it did not expect: "${type}". this is a bug in React.`,
|
||||
@@ -440,12 +537,17 @@ function preloadPropsFromRawProps(
|
||||
|
||||
function stylePropsFromRawProps(rawProps: StyleQualifyingProps): StyleProps {
|
||||
const props: StyleProps = Object.assign({}, rawProps);
|
||||
props['data-rprec'] = rawProps.precedence;
|
||||
props['data-precedence'] = rawProps.precedence;
|
||||
props.precedence = null;
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
function scriptPropsFromRawProps(rawProps: ScriptQualifyingProps): ScriptProps {
|
||||
const props: ScriptProps = Object.assign({}, rawProps);
|
||||
return props;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// Resource Reconciliation
|
||||
// --------------------------------------
|
||||
@@ -455,6 +557,9 @@ export function acquireResource(resource: Resource): Instance {
|
||||
case 'style': {
|
||||
return acquireStyleResource(resource);
|
||||
}
|
||||
case 'script': {
|
||||
return acquireScriptResource(resource);
|
||||
}
|
||||
case 'preload': {
|
||||
return resource.instance;
|
||||
}
|
||||
@@ -558,7 +663,7 @@ function createStyleResource(
|
||||
// the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload
|
||||
// and a stylesheet the stylesheet will make a new request even if the preload had already loaded
|
||||
const preloadProps = hint.props;
|
||||
adoptPreloadProps(resource.props, hint.props);
|
||||
adoptPreloadPropsForStyle(resource.props, hint.props);
|
||||
if (__DEV__) {
|
||||
(resource: any)._dev_preload_props = preloadProps;
|
||||
}
|
||||
@@ -568,7 +673,7 @@ function createStyleResource(
|
||||
return resource;
|
||||
}
|
||||
|
||||
function adoptPreloadProps(
|
||||
function adoptPreloadPropsForStyle(
|
||||
styleProps: StyleProps,
|
||||
preloadProps: PreloadProps,
|
||||
): void {
|
||||
@@ -576,7 +681,6 @@ function adoptPreloadProps(
|
||||
styleProps.crossOrigin = preloadProps.crossOrigin;
|
||||
if (styleProps.referrerPolicy == null)
|
||||
styleProps.referrerPolicy = preloadProps.referrerPolicy;
|
||||
if (styleProps.media == null) styleProps.media = preloadProps.media;
|
||||
if (styleProps.title == null) styleProps.title = preloadProps.title;
|
||||
}
|
||||
|
||||
@@ -610,6 +714,63 @@ function preloadPropsFromStyleProps(props: StyleProps): PreloadProps {
|
||||
};
|
||||
}
|
||||
|
||||
function createScriptResource(
|
||||
scriptResources: Map<string, ScriptResource>,
|
||||
root: FloatRoot,
|
||||
src: string,
|
||||
props: ScriptProps,
|
||||
): ScriptResource {
|
||||
if (__DEV__) {
|
||||
if (scriptResources.has(src)) {
|
||||
console.error(
|
||||
'createScriptResource was called when a script Resource matching the same src already exists. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const limitedEscapedSrc = escapeSelectorAttributeValueInsideDoubleQuotes(src);
|
||||
const existingEl = root.querySelector(
|
||||
`script[async][src="${limitedEscapedSrc}"]`,
|
||||
);
|
||||
const resource = {
|
||||
type: 'script',
|
||||
src,
|
||||
props,
|
||||
root,
|
||||
instance: existingEl || null,
|
||||
};
|
||||
scriptResources.set(src, resource);
|
||||
|
||||
if (!existingEl) {
|
||||
const hint = preloadResources.get(src);
|
||||
if (hint) {
|
||||
// If a preload for this style Resource already exists there are certain props we want to adopt
|
||||
// on the style Resource, primarily focussed on making sure the style network pathways utilize
|
||||
// the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload
|
||||
// and a stylesheet the stylesheet will make a new request even if the preload had already loaded
|
||||
const preloadProps = hint.props;
|
||||
adoptPreloadPropsForScript(props, hint.props);
|
||||
if (__DEV__) {
|
||||
(resource: any)._dev_preload_props = preloadProps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
function adoptPreloadPropsForScript(
|
||||
scriptProps: ScriptProps,
|
||||
preloadProps: PreloadProps,
|
||||
): void {
|
||||
if (scriptProps.crossOrigin == null)
|
||||
scriptProps.crossOrigin = preloadProps.crossOrigin;
|
||||
if (scriptProps.referrerPolicy == null)
|
||||
scriptProps.referrerPolicy = preloadProps.referrerPolicy;
|
||||
if (scriptProps.integrity == null)
|
||||
scriptProps.referrerPolicy = preloadProps.integrity;
|
||||
}
|
||||
|
||||
function createPreloadResource(
|
||||
ownerDocument: Document,
|
||||
href: string,
|
||||
@@ -623,7 +784,7 @@ function createPreloadResource(
|
||||
);
|
||||
if (!element) {
|
||||
element = createResourceInstance('link', props, ownerDocument);
|
||||
insertPreloadInstance(element, ownerDocument);
|
||||
insertResourceInstance(element, ownerDocument);
|
||||
}
|
||||
return {
|
||||
type: 'preload',
|
||||
@@ -641,7 +802,7 @@ function acquireStyleResource(resource: StyleResource): Instance {
|
||||
props.href,
|
||||
);
|
||||
const existingEl = root.querySelector(
|
||||
`link[rel="stylesheet"][data-rprec][href="${limitedEscapedHref}"]`,
|
||||
`link[rel="stylesheet"][data-precedence][href="${limitedEscapedHref}"]`,
|
||||
);
|
||||
if (existingEl) {
|
||||
resource.instance = existingEl;
|
||||
@@ -685,6 +846,31 @@ function acquireStyleResource(resource: StyleResource): Instance {
|
||||
return resource.instance;
|
||||
}
|
||||
|
||||
function acquireScriptResource(resource: ScriptResource): Instance {
|
||||
if (!resource.instance) {
|
||||
const {props, root} = resource;
|
||||
const limitedEscapedSrc = escapeSelectorAttributeValueInsideDoubleQuotes(
|
||||
props.src,
|
||||
);
|
||||
const existingEl = root.querySelector(
|
||||
`script[async][src="${limitedEscapedSrc}"]`,
|
||||
);
|
||||
if (existingEl) {
|
||||
resource.instance = existingEl;
|
||||
} else {
|
||||
const instance = createResourceInstance(
|
||||
'script',
|
||||
resource.props,
|
||||
getDocumentFromRoot(root),
|
||||
);
|
||||
|
||||
insertResourceInstance(instance, getDocumentFromRoot(root));
|
||||
resource.instance = instance;
|
||||
}
|
||||
}
|
||||
return resource.instance;
|
||||
}
|
||||
|
||||
function attachLoadListeners(instance: Instance, resource: StyleResource) {
|
||||
const listeners = {};
|
||||
listeners.load = onResourceLoad.bind(
|
||||
@@ -749,12 +935,14 @@ function insertStyleInstance(
|
||||
precedence: string,
|
||||
root: FloatRoot,
|
||||
): void {
|
||||
const nodes = root.querySelectorAll('link[rel="stylesheet"][data-rprec]');
|
||||
const nodes = root.querySelectorAll(
|
||||
'link[rel="stylesheet"][data-precedence]',
|
||||
);
|
||||
const last = nodes.length ? nodes[nodes.length - 1] : null;
|
||||
let prior = last;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
const nodePrecedence = node.dataset.rprec;
|
||||
const nodePrecedence = node.dataset.precedence;
|
||||
if (nodePrecedence === precedence) {
|
||||
prior = node;
|
||||
} else if (prior !== last) {
|
||||
@@ -780,21 +968,27 @@ function insertStyleInstance(
|
||||
}
|
||||
}
|
||||
|
||||
function insertPreloadInstance(
|
||||
function insertResourceInstance(
|
||||
instance: Instance,
|
||||
ownerDocument: Document,
|
||||
): void {
|
||||
if (!ownerDocument.contains(instance)) {
|
||||
const parent = ownerDocument.head;
|
||||
if (parent) {
|
||||
parent.appendChild(instance);
|
||||
} else {
|
||||
throw new Error(
|
||||
'While attempting to insert a Resource, React expected the Document to contain' +
|
||||
' a head element but it was not found.',
|
||||
if (__DEV__) {
|
||||
if (instance.tagName === 'LINK' && (instance: any).rel === 'stylesheet') {
|
||||
console.error(
|
||||
'insertResourceInstance was called with a stylesheet. Stylesheets must be' +
|
||||
' inserted with insertStyleInstance instead. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
}
|
||||
const parent = ownerDocument.head;
|
||||
if (parent) {
|
||||
parent.appendChild(instance);
|
||||
} else {
|
||||
throw new Error(
|
||||
'While attempting to insert a Resource, React expected the Document to contain' +
|
||||
' a head element but it was not found.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function isHostResourceType(type: string, props: Props): boolean {
|
||||
@@ -815,27 +1009,22 @@ export function isHostResourceType(type: string, props: Props): boolean {
|
||||
);
|
||||
}
|
||||
case 'preload': {
|
||||
if (__DEV__) {
|
||||
validateLinkPropsForStyleResource(props);
|
||||
}
|
||||
const {href, as, onLoad, onError} = props;
|
||||
return (
|
||||
!onLoad &&
|
||||
!onError &&
|
||||
typeof href === 'string' &&
|
||||
isResourceAsType(as)
|
||||
);
|
||||
const {href, onLoad, onError} = props;
|
||||
return !onLoad && !onError && typeof href === 'string';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case 'script': {
|
||||
// We don't validate because it is valid to use async with onLoad/onError unlike combining
|
||||
// precedence with these for style resources
|
||||
const {src, async, onLoad, onError} = props;
|
||||
return (async: any) && typeof src === 'string' && !onLoad && !onError;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isResourceAsType(as: mixed): boolean {
|
||||
return as === 'style' || as === 'font' || as === 'script';
|
||||
}
|
||||
|
||||
// When passing user input into querySelector(All) the embedded string must not alter
|
||||
// the semantics of the query. This escape function is safe to use when we know the
|
||||
// provided value is going to be wrapped in double quotes as part of an attribute selector
|
||||
|
||||
@@ -794,6 +794,20 @@ export function bindInstance(
|
||||
|
||||
export const supportsHydration = true;
|
||||
|
||||
// With Resources, some HostComponent types will never be server rendered and need to be
|
||||
// inserted without breaking hydration
|
||||
export function isHydratable(type: string, props: Props): boolean {
|
||||
if (enableFloat) {
|
||||
if (type === 'script') {
|
||||
const {async, onLoad, onError} = (props: any);
|
||||
return !(async && (onLoad || onError));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function canHydrateInstance(
|
||||
instance: HydratableInstance,
|
||||
type: string,
|
||||
@@ -889,12 +903,26 @@ function getNextHydratable(node) {
|
||||
const rel = linkEl.rel;
|
||||
if (
|
||||
rel === 'preload' ||
|
||||
(rel === 'stylesheet' && linkEl.hasAttribute('data-rprec'))
|
||||
(rel === 'stylesheet' && linkEl.hasAttribute('data-precedence'))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'STYLE': {
|
||||
const styleEl: HTMLStyleElement = (element: any);
|
||||
if (styleEl.hasAttribute('data-precedence')) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'SCRIPT': {
|
||||
const scriptEl: HTMLScriptElement = (element: any);
|
||||
if (scriptEl.hasAttribute('async')) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'HTML':
|
||||
case 'HEAD':
|
||||
case 'BODY': {
|
||||
@@ -908,14 +936,31 @@ function getNextHydratable(node) {
|
||||
} else if (enableFloat) {
|
||||
if (nodeType === ELEMENT_NODE) {
|
||||
const element: Element = (node: any);
|
||||
if (element.tagName === 'LINK') {
|
||||
const linkEl: HTMLLinkElement = (element: any);
|
||||
const rel = linkEl.rel;
|
||||
if (
|
||||
rel === 'preload' ||
|
||||
(rel === 'stylesheet' && linkEl.hasAttribute('data-rprec'))
|
||||
) {
|
||||
continue;
|
||||
switch (element.tagName) {
|
||||
case 'LINK': {
|
||||
const linkEl: HTMLLinkElement = (element: any);
|
||||
const rel = linkEl.rel;
|
||||
if (
|
||||
rel === 'preload' ||
|
||||
(rel === 'stylesheet' && linkEl.hasAttribute('data-precedence'))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'STYLE': {
|
||||
const styleEl: HTMLStyleElement = (element: any);
|
||||
if (styleEl.hasAttribute('data-precedence')) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'SCRIPT': {
|
||||
const scriptEl: HTMLScriptElement = (element: any);
|
||||
if (scriptEl.hasAttribute('async')) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
+279
-104
@@ -11,6 +11,8 @@ import {
|
||||
validatePreloadResourceDifference,
|
||||
validateStyleResourceDifference,
|
||||
validateStyleAndHintProps,
|
||||
validateScriptResourceDifference,
|
||||
validateScriptAndHintProps,
|
||||
validateLinkPropsForStyleResource,
|
||||
validateLinkPropsForPreloadResource,
|
||||
validatePreloadArguments,
|
||||
@@ -38,7 +40,7 @@ type PreloadResource = {
|
||||
type StyleProps = {
|
||||
rel: 'stylesheet',
|
||||
href: string,
|
||||
'data-rprec': string,
|
||||
'data-precedence': string,
|
||||
[string]: mixed,
|
||||
};
|
||||
type StyleResource = {
|
||||
@@ -50,22 +52,44 @@ type StyleResource = {
|
||||
flushed: boolean,
|
||||
inShell: boolean, // flushedInShell
|
||||
hint: PreloadResource,
|
||||
set: Set<StyleResource>, // the precedence set this resource should be flushed in
|
||||
};
|
||||
|
||||
export type Resource = PreloadResource | StyleResource;
|
||||
type ScriptProps = {
|
||||
src: string,
|
||||
[string]: mixed,
|
||||
};
|
||||
type ScriptResource = {
|
||||
type: 'script',
|
||||
src: string,
|
||||
props: ScriptProps,
|
||||
|
||||
flushed: boolean,
|
||||
hint: PreloadResource,
|
||||
};
|
||||
|
||||
export type Resource = PreloadResource | StyleResource | ScriptResource;
|
||||
|
||||
export type Resources = {
|
||||
// Request local cache
|
||||
preloadsMap: Map<string, PreloadResource>,
|
||||
stylesMap: Map<string, StyleResource>,
|
||||
scriptsMap: Map<string, ScriptResource>,
|
||||
|
||||
// Flushing queues for Resource dependencies
|
||||
explicitPreloads: Set<PreloadResource>,
|
||||
implicitPreloads: Set<PreloadResource>,
|
||||
fontPreloads: Set<PreloadResource>,
|
||||
// usedImagePreloads: Set<PreloadResource>,
|
||||
precedences: Map<string, Set<StyleResource>>,
|
||||
usedStylePreloads: Set<PreloadResource>,
|
||||
scripts: Set<ScriptResource>,
|
||||
usedScriptPreloads: Set<PreloadResource>,
|
||||
explicitStylePreloads: Set<PreloadResource>,
|
||||
// explicitImagePreloads: Set<PreloadResource>,
|
||||
explicitScriptPreloads: Set<PreloadResource>,
|
||||
|
||||
// Module-global-like reference for current boundary resources
|
||||
boundaryResources: ?BoundaryResources,
|
||||
...
|
||||
};
|
||||
|
||||
// @TODO add bootstrap script to implicit preloads
|
||||
@@ -74,11 +98,18 @@ export function createResources(): Resources {
|
||||
// persistent
|
||||
preloadsMap: new Map(),
|
||||
stylesMap: new Map(),
|
||||
scriptsMap: new Map(),
|
||||
|
||||
// cleared on flush
|
||||
explicitPreloads: new Set(),
|
||||
implicitPreloads: new Set(),
|
||||
fontPreloads: new Set(),
|
||||
// usedImagePreloads: new Set(),
|
||||
precedences: new Map(),
|
||||
usedStylePreloads: new Set(),
|
||||
scripts: new Set(),
|
||||
usedScriptPreloads: new Set(),
|
||||
explicitStylePreloads: new Set(),
|
||||
// explicitImagePreloads: new Set(),
|
||||
explicitScriptPreloads: new Set(),
|
||||
|
||||
// like a module global for currently rendering boundary
|
||||
boundaryResources: null,
|
||||
@@ -91,13 +122,6 @@ export function createBoundaryResources(): BoundaryResources {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
export function mergeBoundaryResources(
|
||||
target: BoundaryResources,
|
||||
source: BoundaryResources,
|
||||
) {
|
||||
source.forEach(resource => target.add(resource));
|
||||
}
|
||||
|
||||
let currentResources: null | Resources = null;
|
||||
const currentResourcesStack = [];
|
||||
|
||||
@@ -134,6 +158,7 @@ function preload(href: string, options: PreloadOptions) {
|
||||
// simply return and do not warn.
|
||||
return;
|
||||
}
|
||||
const resources = currentResources;
|
||||
if (__DEV__) {
|
||||
validatePreloadArguments(href, options);
|
||||
}
|
||||
@@ -144,8 +169,7 @@ function preload(href: string, options: PreloadOptions) {
|
||||
options !== null
|
||||
) {
|
||||
const as = options.as;
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
let resource = currentResources.preloadsMap.get(href);
|
||||
let resource = resources.preloadsMap.get(href);
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
const originallyImplicit =
|
||||
@@ -160,23 +184,35 @@ function preload(href: string, options: PreloadOptions) {
|
||||
}
|
||||
} else {
|
||||
resource = createPreloadResource(
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
currentResources,
|
||||
resources,
|
||||
href,
|
||||
as,
|
||||
preloadPropsFromPreloadOptions(href, as, options),
|
||||
);
|
||||
}
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
captureExplicitPreloadResourceDependency(currentResources, resource);
|
||||
switch (as) {
|
||||
case 'font': {
|
||||
resources.fontPreloads.add(resource);
|
||||
break;
|
||||
}
|
||||
case 'style': {
|
||||
resources.explicitStylePreloads.add(resource);
|
||||
break;
|
||||
}
|
||||
case 'script': {
|
||||
resources.explicitScriptPreloads.add(resource);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PreinitAs = 'style';
|
||||
type PreinitAs = 'style' | 'script';
|
||||
type PreinitOptions = {
|
||||
as: PreinitAs,
|
||||
precedence?: string,
|
||||
crossOrigin?: string,
|
||||
integrity?: string,
|
||||
};
|
||||
function preinit(href: string, options: PreinitOptions) {
|
||||
if (!currentResources) {
|
||||
@@ -188,6 +224,7 @@ function preinit(href: string, options: PreinitOptions) {
|
||||
// simply return and do not warn.
|
||||
return;
|
||||
}
|
||||
const resources = currentResources;
|
||||
if (__DEV__) {
|
||||
validatePreinitArguments(href, options);
|
||||
}
|
||||
@@ -200,38 +237,48 @@ function preinit(href: string, options: PreinitOptions) {
|
||||
const as = options.as;
|
||||
switch (as) {
|
||||
case 'style': {
|
||||
const precedence = options.precedence || 'default';
|
||||
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
let resource = currentResources.stylesMap.get(href);
|
||||
let resource = resources.stylesMap.get(href);
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
const latestProps = stylePropsFromPreinitOptions(
|
||||
href,
|
||||
precedence,
|
||||
resource.precedence,
|
||||
options,
|
||||
);
|
||||
validateStyleResourceDifference(resource.props, latestProps);
|
||||
}
|
||||
} else {
|
||||
const precedence = options.precedence || 'default';
|
||||
const resourceProps = stylePropsFromPreinitOptions(
|
||||
href,
|
||||
precedence,
|
||||
options,
|
||||
);
|
||||
resource = createStyleResource(
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
currentResources,
|
||||
resources,
|
||||
href,
|
||||
precedence,
|
||||
resourceProps,
|
||||
);
|
||||
}
|
||||
resource.set.add(resource);
|
||||
resources.explicitStylePreloads.add(resource.hint);
|
||||
|
||||
// Do not associate preinit style resources with any specific boundary regardless of where it is called
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
captureStyleResourceDependency(currentResources, null, resource);
|
||||
|
||||
return;
|
||||
}
|
||||
case 'script': {
|
||||
const src = href;
|
||||
let resource = resources.scriptsMap.get(src);
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
const latestProps = scriptPropsFromPreinitOptions(src, options);
|
||||
validateScriptResourceDifference(resource.props, latestProps);
|
||||
}
|
||||
} else {
|
||||
const scriptProps = scriptPropsFromPreinitOptions(src, options);
|
||||
resource = createScriptResource(resources, src, scriptProps);
|
||||
resources.scripts.add(resource);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -286,6 +333,20 @@ function preloadAsStylePropsFromProps(
|
||||
};
|
||||
}
|
||||
|
||||
function preloadAsScriptPropsFromProps(
|
||||
href: string,
|
||||
props: Props | ScriptProps,
|
||||
): PreloadProps {
|
||||
return {
|
||||
rel: 'preload',
|
||||
as: 'script',
|
||||
href,
|
||||
crossOrigin: props.crossOrigin,
|
||||
integrity: props.integrity,
|
||||
referrerPolicy: props.referrerPolicy,
|
||||
};
|
||||
}
|
||||
|
||||
function createPreloadResource(
|
||||
resources: Resources,
|
||||
href: string,
|
||||
@@ -320,7 +381,7 @@ function stylePropsFromRawProps(
|
||||
const props: StyleProps = Object.assign({}, rawProps);
|
||||
props.href = href;
|
||||
props.rel = 'stylesheet';
|
||||
props['data-rprec'] = precedence;
|
||||
props['data-precedence'] = precedence;
|
||||
delete props.precedence;
|
||||
|
||||
return props;
|
||||
@@ -334,7 +395,7 @@ function stylePropsFromPreinitOptions(
|
||||
return {
|
||||
rel: 'stylesheet',
|
||||
href,
|
||||
'data-rprec': precedence,
|
||||
'data-precedence': precedence,
|
||||
crossOrigin: options.crossOrigin,
|
||||
};
|
||||
}
|
||||
@@ -352,7 +413,15 @@ function createStyleResource(
|
||||
);
|
||||
}
|
||||
}
|
||||
const {stylesMap, preloadsMap} = resources;
|
||||
const {stylesMap, preloadsMap, precedences} = resources;
|
||||
|
||||
// If this is the first time we've seen this precedence we encode it's position in our set even though
|
||||
// we don't add the resource to this set yet
|
||||
let precedenceSet = precedences.get(precedence);
|
||||
if (!precedenceSet) {
|
||||
precedenceSet = new Set();
|
||||
precedences.set(precedence, precedenceSet);
|
||||
}
|
||||
|
||||
let hint = preloadsMap.get(href);
|
||||
if (hint) {
|
||||
@@ -360,16 +429,11 @@ function createStyleResource(
|
||||
// on the style Resource, primarily focussed on making sure the style network pathways utilize
|
||||
// the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload
|
||||
// and a stylesheet the stylesheet will make a new request even if the preload had already loaded
|
||||
const preloadProps = hint.props;
|
||||
if (props.crossOrigin == null) props.crossOrigin = preloadProps.crossOrigin;
|
||||
if (props.referrerPolicy == null)
|
||||
props.referrerPolicy = preloadProps.referrerPolicy;
|
||||
if (props.media == null) props.media = preloadProps.media;
|
||||
if (props.title == null) props.title = preloadProps.title;
|
||||
adoptPreloadPropsForStyleProps(props, hint.props);
|
||||
|
||||
if (__DEV__) {
|
||||
validateStyleAndHintProps(
|
||||
preloadProps,
|
||||
hint.props,
|
||||
props,
|
||||
(hint: any)._dev_implicit_construction,
|
||||
);
|
||||
@@ -385,7 +449,7 @@ function createStyleResource(
|
||||
if (__DEV__) {
|
||||
(hint: any)._dev_implicit_construction = true;
|
||||
}
|
||||
captureImplicitPreloadResourceDependency(resources, hint);
|
||||
resources.explicitStylePreloads.add(hint);
|
||||
}
|
||||
|
||||
const resource = {
|
||||
@@ -396,47 +460,107 @@ function createStyleResource(
|
||||
inShell: false,
|
||||
props,
|
||||
hint,
|
||||
set: precedenceSet,
|
||||
};
|
||||
stylesMap.set(href, resource);
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
function captureStyleResourceDependency(
|
||||
resources: Resources,
|
||||
boundaryResources: ?BoundaryResources,
|
||||
styleResource: StyleResource,
|
||||
function adoptPreloadPropsForStyleProps(
|
||||
resourceProps: StyleProps,
|
||||
preloadProps: PreloadProps,
|
||||
): void {
|
||||
const {precedences} = resources;
|
||||
const {precedence} = styleResource;
|
||||
if (resourceProps.crossOrigin == null)
|
||||
resourceProps.crossOrigin = preloadProps.crossOrigin;
|
||||
if (resourceProps.referrerPolicy == null)
|
||||
resourceProps.referrerPolicy = preloadProps.referrerPolicy;
|
||||
if (resourceProps.title == null) resourceProps.title = preloadProps.title;
|
||||
}
|
||||
|
||||
if (boundaryResources) {
|
||||
boundaryResources.add(styleResource);
|
||||
if (!precedences.has(precedence)) {
|
||||
precedences.set(precedence, new Set());
|
||||
function scriptPropsFromPreinitOptions(
|
||||
src: string,
|
||||
options: PreinitOptions,
|
||||
): ScriptProps {
|
||||
return {
|
||||
src,
|
||||
async: true,
|
||||
crossOrigin: options.crossOrigin,
|
||||
integrity: options.integrity,
|
||||
};
|
||||
}
|
||||
|
||||
function scriptPropsFromRawProps(src: string, rawProps: Props): ScriptProps {
|
||||
const props = Object.assign({}, rawProps);
|
||||
props.src = src;
|
||||
return props;
|
||||
}
|
||||
|
||||
function createScriptResource(
|
||||
resources: Resources,
|
||||
src: string,
|
||||
props: ScriptProps,
|
||||
): ScriptResource {
|
||||
if (__DEV__) {
|
||||
if (resources.scriptsMap.has(src)) {
|
||||
console.error(
|
||||
'createScriptResource was called when a script Resource matching the same src already exists. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
}
|
||||
const {scriptsMap, preloadsMap} = resources;
|
||||
|
||||
let hint = preloadsMap.get(src);
|
||||
if (hint) {
|
||||
// If a preload for this style Resource already exists there are certain props we want to adopt
|
||||
// on the style Resource, primarily focussed on making sure the style network pathways utilize
|
||||
// the preload pathways. For instance if you have diffreent crossOrigin attributes for a preload
|
||||
// and a stylesheet the stylesheet will make a new request even if the preload had already loaded
|
||||
adoptPreloadPropsForScriptProps(props, hint.props);
|
||||
|
||||
if (__DEV__) {
|
||||
validateScriptAndHintProps(
|
||||
hint.props,
|
||||
props,
|
||||
(hint: any)._dev_implicit_construction,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let set = precedences.get(precedence);
|
||||
if (!set) {
|
||||
set = new Set();
|
||||
precedences.set(precedence, set);
|
||||
const preloadResourceProps = preloadAsScriptPropsFromProps(src, props);
|
||||
hint = createPreloadResource(
|
||||
resources,
|
||||
src,
|
||||
'script',
|
||||
preloadResourceProps,
|
||||
);
|
||||
if (__DEV__) {
|
||||
(hint: any)._dev_implicit_construction = true;
|
||||
}
|
||||
set.add(styleResource);
|
||||
resources.explicitScriptPreloads.add(hint);
|
||||
}
|
||||
|
||||
const resource = {
|
||||
type: 'script',
|
||||
src,
|
||||
flushed: false,
|
||||
props,
|
||||
hint,
|
||||
};
|
||||
scriptsMap.set(src, resource);
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
function captureExplicitPreloadResourceDependency(
|
||||
resources: Resources,
|
||||
preloadResource: PreloadResource,
|
||||
function adoptPreloadPropsForScriptProps(
|
||||
resourceProps: ScriptProps,
|
||||
preloadProps: PreloadProps,
|
||||
): void {
|
||||
resources.explicitPreloads.add(preloadResource);
|
||||
}
|
||||
|
||||
function captureImplicitPreloadResourceDependency(
|
||||
resources: Resources,
|
||||
preloadResource: PreloadResource,
|
||||
): void {
|
||||
resources.implicitPreloads.add(preloadResource);
|
||||
if (resourceProps.crossOrigin == null)
|
||||
resourceProps.crossOrigin = preloadProps.crossOrigin;
|
||||
if (resourceProps.referrerPolicy == null)
|
||||
resourceProps.referrerPolicy = preloadProps.referrerPolicy;
|
||||
if (resourceProps.integrity == null)
|
||||
resourceProps.integrity = preloadProps.integrity;
|
||||
}
|
||||
|
||||
// Construct a resource from link props.
|
||||
@@ -446,6 +570,8 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
'"currentResources" was expected to exist. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
const resources = currentResources;
|
||||
|
||||
const {rel, href} = props;
|
||||
if (!href || typeof href !== 'string') {
|
||||
return false;
|
||||
@@ -467,11 +593,11 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
validateLinkPropsForStyleResource(props);
|
||||
}
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
let preloadResource = currentResources.preloadsMap.get(href);
|
||||
let preloadResource = resources.preloadsMap.get(href);
|
||||
if (!preloadResource) {
|
||||
preloadResource = createPreloadResource(
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
currentResources,
|
||||
resources,
|
||||
href,
|
||||
'style',
|
||||
preloadAsStylePropsFromProps(href, props),
|
||||
@@ -479,17 +605,14 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
if (__DEV__) {
|
||||
(preloadResource: any)._dev_implicit_construction = true;
|
||||
}
|
||||
resources.usedStylePreloads.add(preloadResource);
|
||||
}
|
||||
captureImplicitPreloadResourceDependency(
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
currentResources,
|
||||
preloadResource,
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
// We are able to convert this link element to a resource exclusively. We construct the relevant Resource
|
||||
// and return true indicating that this link was fully consumed.
|
||||
let resource = currentResources.stylesMap.get(href);
|
||||
let resource = resources.stylesMap.get(href);
|
||||
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
const resourceProps = stylePropsFromRawProps(
|
||||
@@ -497,6 +620,7 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
precedence,
|
||||
props,
|
||||
);
|
||||
adoptPreloadPropsForStyleProps(resourceProps, resource.hint.props);
|
||||
validateStyleResourceDifference(resource.props, resourceProps);
|
||||
}
|
||||
} else {
|
||||
@@ -508,24 +632,18 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
precedence,
|
||||
resourceProps,
|
||||
);
|
||||
resources.usedStylePreloads.add(resource.hint);
|
||||
}
|
||||
if (resources.boundaryResources) {
|
||||
resources.boundaryResources.add(resource);
|
||||
} else {
|
||||
resource.set.add(resource);
|
||||
}
|
||||
captureStyleResourceDependency(
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
currentResources,
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
currentResources.boundaryResources,
|
||||
resource,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case 'preload': {
|
||||
const {as, onLoad, onError} = props;
|
||||
if (onLoad || onError) {
|
||||
// these props signal an opt-out of Resource semantics. We don't warn because there is no
|
||||
// conflicting opt-in like there is with Style Resources
|
||||
return false;
|
||||
}
|
||||
const {as} = props;
|
||||
switch (as) {
|
||||
case 'script':
|
||||
case 'style':
|
||||
@@ -533,8 +651,7 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
if (__DEV__) {
|
||||
validateLinkPropsForPreloadResource(props);
|
||||
}
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
let resource = currentResources.preloadsMap.get(href);
|
||||
let resource = resources.preloadsMap.get(href);
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
const originallyImplicit =
|
||||
@@ -549,15 +666,26 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
}
|
||||
} else {
|
||||
resource = createPreloadResource(
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
currentResources,
|
||||
resources,
|
||||
href,
|
||||
as,
|
||||
preloadPropsFromRawProps(href, as, props),
|
||||
);
|
||||
switch (as) {
|
||||
case 'script': {
|
||||
resources.explicitScriptPreloads.add(resource);
|
||||
break;
|
||||
}
|
||||
case 'style': {
|
||||
resources.explicitStylePreloads.add(resource);
|
||||
break;
|
||||
}
|
||||
case 'font': {
|
||||
resources.fontPreloads.add(resource);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
captureExplicitPreloadResourceDependency(currentResources, resource);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -567,12 +695,65 @@ export function resourcesFromLink(props: Props): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Construct a resource from link props.
|
||||
export function resourcesFromScript(props: Props): boolean {
|
||||
if (!currentResources) {
|
||||
throw new Error(
|
||||
'"currentResources" was expected to exist. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
const resources = currentResources;
|
||||
const {src, async, onLoad, onError} = props;
|
||||
if (!src || typeof src !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (async) {
|
||||
if (onLoad || onError) {
|
||||
if (__DEV__) {
|
||||
// validate
|
||||
}
|
||||
let preloadResource = resources.preloadsMap.get(src);
|
||||
if (!preloadResource) {
|
||||
preloadResource = createPreloadResource(
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
resources,
|
||||
src,
|
||||
'script',
|
||||
preloadAsScriptPropsFromProps(src, props),
|
||||
);
|
||||
if (__DEV__) {
|
||||
(preloadResource: any)._dev_implicit_construction = true;
|
||||
}
|
||||
resources.usedScriptPreloads.add(preloadResource);
|
||||
}
|
||||
} else {
|
||||
let resource = resources.scriptsMap.get(src);
|
||||
if (resource) {
|
||||
if (__DEV__) {
|
||||
const latestProps = scriptPropsFromRawProps(src, props);
|
||||
adoptPreloadPropsForScriptProps(latestProps, resource.hint.props);
|
||||
validateScriptResourceDifference(resource.props, latestProps);
|
||||
}
|
||||
} else {
|
||||
const resourceProps = scriptPropsFromRawProps(src, props);
|
||||
resource = createScriptResource(resources, src, resourceProps);
|
||||
resources.scripts.add(resource);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function hoistResources(
|
||||
resources: Resources,
|
||||
source: BoundaryResources,
|
||||
): void {
|
||||
if (resources.boundaryResources) {
|
||||
mergeBoundaryResources(resources.boundaryResources, source);
|
||||
const currentBoundaryResources = resources.boundaryResources;
|
||||
if (currentBoundaryResources) {
|
||||
source.forEach(resource => currentBoundaryResources.add(resource));
|
||||
source.clear();
|
||||
}
|
||||
}
|
||||
@@ -581,12 +762,6 @@ export function hoistResourcesToRoot(
|
||||
resources: Resources,
|
||||
boundaryResources: BoundaryResources,
|
||||
): void {
|
||||
boundaryResources.forEach(resource => {
|
||||
// all precedences are set upon discovery. so we know we will have a set here
|
||||
const set: Set<StyleResource> = (resources.precedences.get(
|
||||
resource.precedence,
|
||||
): any);
|
||||
set.add(resource);
|
||||
});
|
||||
boundaryResources.forEach(resource => resource.set.add(resource));
|
||||
boundaryResources.clear();
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
prepareToRenderResources,
|
||||
finishRenderingResources,
|
||||
resourcesFromLink,
|
||||
resourcesFromScript,
|
||||
ReactDOMServerDispatcher,
|
||||
} from './ReactDOMFloatServer';
|
||||
export {
|
||||
@@ -1349,6 +1350,26 @@ function pushStartHtml(
|
||||
return pushStartGenericElement(target, props, tag, responseState);
|
||||
}
|
||||
|
||||
function pushStartScript(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
props: Object,
|
||||
responseState: ResponseState,
|
||||
textEmbedded: boolean,
|
||||
): ReactNodeList {
|
||||
if (enableFloat && resourcesFromScript(props)) {
|
||||
if (textEmbedded) {
|
||||
// This link follows text but we aren't writing a tag. while not as efficient as possible we need
|
||||
// to be safe and assume text will follow by inserting a textSeparator
|
||||
target.push(textSeparator);
|
||||
}
|
||||
// We have converted this link exclusively to a resource and no longer
|
||||
// need to emit it
|
||||
return null;
|
||||
}
|
||||
|
||||
return pushStartGenericElement(target, props, 'script', responseState);
|
||||
}
|
||||
|
||||
function pushStartGenericElement(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
props: Object,
|
||||
@@ -1625,6 +1646,8 @@ export function pushStartInstance(
|
||||
return pushStartTitle(target, props, responseState);
|
||||
case 'link':
|
||||
return pushLink(target, props, responseState, textEmbedded);
|
||||
case 'script':
|
||||
return pushStartScript(target, props, responseState, textEmbedded);
|
||||
// Newline eating tags
|
||||
case 'listing':
|
||||
case 'pre': {
|
||||
@@ -2235,57 +2258,90 @@ function escapeJSObjectForInstructionScripts(input: Object): string {
|
||||
});
|
||||
}
|
||||
|
||||
const precedencePlaceholderStart = stringToPrecomputedChunk(
|
||||
'<style data-precedence="',
|
||||
);
|
||||
const precedencePlaceholderEnd = stringToPrecomputedChunk('"></style>');
|
||||
|
||||
export function writeInitialResources(
|
||||
destination: Destination,
|
||||
resources: Resources,
|
||||
responseState: ResponseState,
|
||||
): boolean {
|
||||
const explicitPreloadsTarget = [];
|
||||
const remainingTarget = [];
|
||||
function flushLinkResource(resource) {
|
||||
if (!resource.flushed) {
|
||||
pushLinkImpl(target, resource.props, responseState);
|
||||
resource.flushed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const {precedences, explicitPreloads, implicitPreloads} = resources;
|
||||
const target = [];
|
||||
|
||||
const {
|
||||
fontPreloads,
|
||||
precedences,
|
||||
usedStylePreloads,
|
||||
scripts,
|
||||
usedScriptPreloads,
|
||||
explicitStylePreloads,
|
||||
explicitScriptPreloads,
|
||||
} = resources;
|
||||
|
||||
fontPreloads.forEach(r => {
|
||||
// font preload Resources should not already be flushed so we elide this check
|
||||
pushLinkImpl(target, r.props, responseState);
|
||||
r.flushed = true;
|
||||
});
|
||||
fontPreloads.clear();
|
||||
|
||||
// Flush stylesheets first by earliest precedence
|
||||
precedences.forEach(precedenceResources => {
|
||||
precedenceResources.forEach(resource => {
|
||||
// resources should not already be flushed so we elide this check
|
||||
pushLinkImpl(remainingTarget, resource.props, responseState);
|
||||
resource.flushed = true;
|
||||
resource.inShell = true;
|
||||
resource.hint.flushed = true;
|
||||
});
|
||||
});
|
||||
|
||||
explicitPreloads.forEach(resource => {
|
||||
if (!resource.flushed) {
|
||||
pushLinkImpl(explicitPreloadsTarget, resource.props, responseState);
|
||||
resource.flushed = true;
|
||||
precedences.forEach((p, precedence) => {
|
||||
if (p.size) {
|
||||
p.forEach(r => {
|
||||
// resources should not already be flushed so we elide this check
|
||||
pushLinkImpl(target, r.props, responseState);
|
||||
r.flushed = true;
|
||||
r.inShell = true;
|
||||
r.hint.flushed = true;
|
||||
});
|
||||
p.clear();
|
||||
} else {
|
||||
target.push(
|
||||
precedencePlaceholderStart,
|
||||
escapeTextForBrowser(stringToChunk(precedence)),
|
||||
precedencePlaceholderEnd,
|
||||
);
|
||||
}
|
||||
});
|
||||
explicitPreloads.clear();
|
||||
|
||||
implicitPreloads.forEach(resource => {
|
||||
if (!resource.flushed) {
|
||||
pushLinkImpl(remainingTarget, resource.props, responseState);
|
||||
resource.flushed = true;
|
||||
}
|
||||
usedStylePreloads.forEach(flushLinkResource);
|
||||
usedStylePreloads.clear();
|
||||
|
||||
scripts.forEach(r => {
|
||||
// should never be flushed already
|
||||
pushStartGenericElement(target, r.props, 'script', responseState);
|
||||
pushEndInstance(target, target, 'script', r.props);
|
||||
r.flushed = true;
|
||||
r.hint.flushed = true;
|
||||
});
|
||||
implicitPreloads.clear();
|
||||
scripts.clear();
|
||||
|
||||
usedScriptPreloads.forEach(flushLinkResource);
|
||||
usedScriptPreloads.clear();
|
||||
|
||||
explicitStylePreloads.forEach(flushLinkResource);
|
||||
explicitStylePreloads.clear();
|
||||
|
||||
explicitScriptPreloads.forEach(flushLinkResource);
|
||||
explicitScriptPreloads.clear();
|
||||
|
||||
let i;
|
||||
let r = true;
|
||||
for (i = 0; i < explicitPreloadsTarget.length - 1; i++) {
|
||||
writeChunk(destination, explicitPreloadsTarget[i]);
|
||||
for (i = 0; i < target.length - 1; i++) {
|
||||
writeChunk(destination, target[i]);
|
||||
}
|
||||
if (i < explicitPreloadsTarget.length) {
|
||||
r = writeChunkAndReturn(destination, explicitPreloadsTarget[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < remainingTarget.length - 1; i++) {
|
||||
writeChunk(destination, remainingTarget[i]);
|
||||
}
|
||||
if (i < remainingTarget.length) {
|
||||
r = writeChunkAndReturn(destination, remainingTarget[i]);
|
||||
if (i < target.length) {
|
||||
r = writeChunkAndReturn(destination, target[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -2295,33 +2351,61 @@ export function writeImmediateResources(
|
||||
resources: Resources,
|
||||
responseState: ResponseState,
|
||||
): boolean {
|
||||
const {explicitPreloads, implicitPreloads} = resources;
|
||||
function flushLinkResource(resource) {
|
||||
if (!resource.flushed) {
|
||||
pushLinkImpl(target, resource.props, responseState);
|
||||
resource.flushed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const target = [];
|
||||
|
||||
explicitPreloads.forEach(resource => {
|
||||
if (!resource.flushed) {
|
||||
pushLinkImpl(target, resource.props, responseState);
|
||||
resource.flushed = true;
|
||||
}
|
||||
});
|
||||
explicitPreloads.clear();
|
||||
const {
|
||||
fontPreloads,
|
||||
usedStylePreloads,
|
||||
scripts,
|
||||
usedScriptPreloads,
|
||||
explicitStylePreloads,
|
||||
explicitScriptPreloads,
|
||||
} = resources;
|
||||
|
||||
implicitPreloads.forEach(resource => {
|
||||
if (!resource.flushed) {
|
||||
pushLinkImpl(target, resource.props, responseState);
|
||||
resource.flushed = true;
|
||||
}
|
||||
fontPreloads.forEach(r => {
|
||||
// font preload Resources should not already be flushed so we elide this check
|
||||
pushLinkImpl(target, r.props, responseState);
|
||||
r.flushed = true;
|
||||
});
|
||||
implicitPreloads.clear();
|
||||
fontPreloads.clear();
|
||||
|
||||
let i = 0;
|
||||
for (; i < target.length - 1; i++) {
|
||||
usedStylePreloads.forEach(flushLinkResource);
|
||||
usedStylePreloads.clear();
|
||||
|
||||
scripts.forEach(r => {
|
||||
// should never be flushed already
|
||||
pushStartGenericElement(target, r.props, 'script', responseState);
|
||||
pushEndInstance(target, target, 'script', r.props);
|
||||
r.flushed = true;
|
||||
r.hint.flushed = true;
|
||||
});
|
||||
scripts.clear();
|
||||
|
||||
usedScriptPreloads.forEach(flushLinkResource);
|
||||
usedScriptPreloads.clear();
|
||||
|
||||
explicitStylePreloads.forEach(flushLinkResource);
|
||||
explicitStylePreloads.clear();
|
||||
|
||||
explicitScriptPreloads.forEach(flushLinkResource);
|
||||
explicitScriptPreloads.clear();
|
||||
|
||||
let i;
|
||||
let r = true;
|
||||
for (i = 0; i < target.length - 1; i++) {
|
||||
writeChunk(destination, target[i]);
|
||||
}
|
||||
if (i < target.length) {
|
||||
return writeChunkAndReturn(destination, target[i]);
|
||||
r = writeChunkAndReturn(destination, target[i]);
|
||||
}
|
||||
return false;
|
||||
return r;
|
||||
}
|
||||
|
||||
function hasStyleResourceDependencies(
|
||||
@@ -2434,7 +2518,7 @@ function writeStyleResourceDependency(
|
||||
case 'href':
|
||||
case 'rel':
|
||||
case 'precedence':
|
||||
case 'data-rprec': {
|
||||
case 'data-precedence': {
|
||||
break;
|
||||
}
|
||||
case 'children':
|
||||
|
||||
+5
-3
@@ -57,9 +57,11 @@ export function completeBoundaryWithStyles(
|
||||
let lastResource, node;
|
||||
|
||||
// Seed the precedence list with existing resources
|
||||
const nodes = thisDocument.querySelectorAll('link[data-rprec]');
|
||||
const nodes = thisDocument.querySelectorAll(
|
||||
'link[data-precedence],style[data-precedence]',
|
||||
);
|
||||
for (let i = 0; (node = nodes[i++]); ) {
|
||||
precedences.set(node.dataset['rprec'], (lastResource = node));
|
||||
precedences.set(node.dataset['precedence'], (lastResource = node));
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
@@ -89,7 +91,7 @@ export function completeBoundaryWithStyles(
|
||||
resourceEl = thisDocument.createElement('link');
|
||||
resourceEl.href = href;
|
||||
resourceEl.rel = 'stylesheet';
|
||||
resourceEl.dataset['rprec'] = precedence = style[j++];
|
||||
resourceEl.dataset['precedence'] = precedence = style[j++];
|
||||
while ((attr = style[j++])) {
|
||||
resourceEl.setAttribute(attr, style[j++]);
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@ 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(p,q,v){function r(l){this.s=l}for(var t=$RC,u=$RM,m=new Map,n=document,g,e,f=n.querySelectorAll("link[data-rprec]"),d=0;e=f[d++];)m.set(e.dataset.rprec,g=e);e=0;f=[];for(var c,h,b,a;c=v[e++];){var k=0;h=c[k++];if(b=u.get(h))"l"!==b.s&&f.push(b);else{a=n.createElement("link");a.href=h;a.rel="stylesheet";for(a.dataset.rprec=d=c[k++];b=c[k++];)a.setAttribute(b,c[k++]);b=a._p=new Promise(function(l,w){a.onload=l;a.onerror=w});b.then(r.bind(b,"l"),r.bind(b,"e"));u.set(h,\nb);f.push(b);c=m.get(d)||g;c===g&&(g=a);m.set(d,a);c?c.parentNode.insertBefore(a,c.nextSibling):(d=n.head,d.insertBefore(a,d.firstChild))}}Promise.all(f).then(t.bind(null,p,q,""),t.bind(null,p,q,"Resource failed to load"))};';
|
||||
'$RM=new Map;\n$RR=function(p,q,v){function r(l){this.s=l}for(var t=$RC,u=$RM,m=new Map,n=document,g,e,f=n.querySelectorAll("link[data-precedence],style[data-precedence]"),d=0;e=f[d++];)m.set(e.dataset.precedence,g=e);e=0;f=[];for(var c,h,b,a;c=v[e++];){var k=0;h=c[k++];if(b=u.get(h))"l"!==b.s&&f.push(b);else{a=n.createElement("link");a.href=h;a.rel="stylesheet";for(a.dataset.precedence=d=c[k++];b=c[k++];)a.setAttribute(b,c[k++]);b=a._p=new Promise(function(l,w){a.onload=l;a.onerror=w});b.then(r.bind(b,\n"l"),r.bind(b,"e"));u.set(h,b);f.push(b);c=m.get(d)||g;c===g&&(g=a);m.set(d,a);c?c.parentNode.insertBefore(a,c.nextSibling):(d=n.head,d.insertBefore(a,d.firstChild))}}Promise.all(f).then(t.bind(null,p,q,""),t.bind(null,p,q,"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)};';
|
||||
|
||||
@@ -115,6 +115,7 @@ export function validatePreloadResourceDifference(
|
||||
if (missingProps || extraProps || differentProps) {
|
||||
warnDifferentProps(
|
||||
href,
|
||||
'href',
|
||||
originalWarningName,
|
||||
latestWarningName,
|
||||
extraProps,
|
||||
@@ -156,7 +157,7 @@ export function validateStyleResourceDifference(
|
||||
const originalValue = originalProps[propName];
|
||||
|
||||
if (propValue != null && propValue !== originalValue) {
|
||||
propName = propName === 'data-rprec' ? 'precedence' : propName;
|
||||
propName = propName === 'data-precedence' ? 'precedence' : propName;
|
||||
if (originalValue == null) {
|
||||
extraProps = extraProps || {};
|
||||
extraProps[propName] = propValue;
|
||||
@@ -173,6 +174,7 @@ export function validateStyleResourceDifference(
|
||||
if (missingProps || extraProps || differentProps) {
|
||||
warnDifferentProps(
|
||||
href,
|
||||
'href',
|
||||
originalWarningName,
|
||||
latestWarningName,
|
||||
extraProps,
|
||||
@@ -183,6 +185,58 @@ export function validateStyleResourceDifference(
|
||||
}
|
||||
}
|
||||
|
||||
export function validateScriptResourceDifference(
|
||||
originalProps: any,
|
||||
latestProps: any,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const {src} = originalProps;
|
||||
// eslint-disable-next-line no-labels
|
||||
const originalWarningName = getResourceNameForWarning(
|
||||
'script',
|
||||
originalProps,
|
||||
false,
|
||||
);
|
||||
const latestWarningName = getResourceNameForWarning(
|
||||
'script',
|
||||
latestProps,
|
||||
false,
|
||||
);
|
||||
let extraProps = null;
|
||||
let differentProps = null;
|
||||
|
||||
for (const propName in latestProps) {
|
||||
const propValue = latestProps[propName];
|
||||
const originalValue = originalProps[propName];
|
||||
|
||||
if (propValue != null && propValue !== originalValue) {
|
||||
if (originalValue == null) {
|
||||
extraProps = extraProps || {};
|
||||
extraProps[propName] = propValue;
|
||||
} else {
|
||||
differentProps = differentProps || {};
|
||||
differentProps[propName] = {
|
||||
original: originalValue,
|
||||
latest: propValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extraProps || differentProps) {
|
||||
warnDifferentProps(
|
||||
src,
|
||||
'src',
|
||||
originalWarningName,
|
||||
latestWarningName,
|
||||
extraProps,
|
||||
null,
|
||||
differentProps,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validateStyleAndHintProps(
|
||||
preloadProps: any,
|
||||
styleProps: any,
|
||||
@@ -205,7 +259,7 @@ export function validateStyleAndHintProps(
|
||||
if (preloadProps.as !== 'style') {
|
||||
console.error(
|
||||
'While creating a %s for href "%s" a %s for this same href was found. When preloading a stylesheet the' +
|
||||
' "as" prop must be of type "style". This most likely ocurred by rending a preload link with an incorrect' +
|
||||
' "as" prop must be of type "style". This most likely ocurred by rendering a preload link with an incorrect' +
|
||||
' "as" prop or by calling ReactDOM.preload with an incorrect "as" option.',
|
||||
latestWarningName,
|
||||
href,
|
||||
@@ -252,6 +306,86 @@ export function validateStyleAndHintProps(
|
||||
if (missingProps || extraProps || differentProps) {
|
||||
warnDifferentProps(
|
||||
href,
|
||||
'href',
|
||||
originalWarningName,
|
||||
latestWarningName,
|
||||
extraProps,
|
||||
missingProps,
|
||||
differentProps,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validateScriptAndHintProps(
|
||||
preloadProps: any,
|
||||
scriptProps: any,
|
||||
implicitPreload: boolean,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
const {href} = preloadProps;
|
||||
|
||||
const originalWarningName = getResourceNameForWarning(
|
||||
'preload',
|
||||
preloadProps,
|
||||
implicitPreload,
|
||||
);
|
||||
const latestWarningName = getResourceNameForWarning(
|
||||
'script',
|
||||
scriptProps,
|
||||
false,
|
||||
);
|
||||
|
||||
if (preloadProps.as !== 'script') {
|
||||
console.error(
|
||||
'While creating a %s for href "%s" a %s for this same url was found. When preloading a script the' +
|
||||
' "as" prop must be of type "script". This most likely ocurred by rendering a preload link with an incorrect' +
|
||||
' "as" prop or by calling ReactDOM.preload with an incorrect "as" option.',
|
||||
latestWarningName,
|
||||
href,
|
||||
originalWarningName,
|
||||
);
|
||||
}
|
||||
|
||||
let missingProps = null;
|
||||
let extraProps = null;
|
||||
let differentProps = null;
|
||||
|
||||
for (const propName in scriptProps) {
|
||||
const scriptValue = scriptProps[propName];
|
||||
const preloadValue = preloadProps[propName];
|
||||
switch (propName) {
|
||||
// Check for difference on specific props that cross over or influence
|
||||
// the relationship between the preload and stylesheet
|
||||
case 'crossOrigin':
|
||||
case 'referrerPolicy':
|
||||
case 'integrity': {
|
||||
if (
|
||||
preloadValue !== scriptValue &&
|
||||
!(preloadValue == null && scriptValue == null)
|
||||
) {
|
||||
if (scriptValue == null) {
|
||||
missingProps = missingProps || {};
|
||||
missingProps[propName] = preloadValue;
|
||||
} else if (preloadValue == null) {
|
||||
extraProps = extraProps || {};
|
||||
extraProps[propName] = scriptValue;
|
||||
} else {
|
||||
differentProps = differentProps || {};
|
||||
differentProps[propName] = {
|
||||
original: preloadValue,
|
||||
latest: scriptValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missingProps || extraProps || differentProps) {
|
||||
warnDifferentProps(
|
||||
href,
|
||||
'href',
|
||||
originalWarningName,
|
||||
latestWarningName,
|
||||
extraProps,
|
||||
@@ -263,7 +397,8 @@ export function validateStyleAndHintProps(
|
||||
}
|
||||
|
||||
function warnDifferentProps(
|
||||
href: string,
|
||||
url: string,
|
||||
urlPropKey: string,
|
||||
originalName: string,
|
||||
latestName: string,
|
||||
extraProps: ?{[string]: any},
|
||||
@@ -274,7 +409,7 @@ function warnDifferentProps(
|
||||
const juxtaposedNameStatement =
|
||||
latestName === originalName
|
||||
? 'an earlier instance of this Resource'
|
||||
: `a ${originalName} with the same href`;
|
||||
: `a ${originalName} with the same ${urlPropKey}`;
|
||||
|
||||
let comparisonStatement = '';
|
||||
if (missingProps !== null && typeof missingProps === 'object') {
|
||||
@@ -294,12 +429,14 @@ function warnDifferentProps(
|
||||
}
|
||||
|
||||
console.error(
|
||||
'A %s with href "%s" has props that disagree with those found on %s. Resources always use the props' +
|
||||
'A %s with %s "%s" has props that disagree with those found on %s. Resources always use the props' +
|
||||
' that were provided the first time they are encountered so any differences will be ignored. Please' +
|
||||
' update Resources that share an href to have props that agree. The differences are described below.%s',
|
||||
' update Resources that share an %s to have props that agree. The differences are described below.%s',
|
||||
latestName,
|
||||
href,
|
||||
urlPropKey,
|
||||
url,
|
||||
juxtaposedNameStatement,
|
||||
urlPropKey,
|
||||
comparisonStatement,
|
||||
);
|
||||
}
|
||||
@@ -315,6 +452,9 @@ function getResourceNameForWarning(
|
||||
case 'style': {
|
||||
return 'style Resource';
|
||||
}
|
||||
case 'script': {
|
||||
return 'script Resource';
|
||||
}
|
||||
case 'preload': {
|
||||
if (implicit) {
|
||||
return `preload for a ${props.as} Resource`;
|
||||
@@ -326,15 +466,17 @@ function getResourceNameForWarning(
|
||||
return 'Resource';
|
||||
}
|
||||
|
||||
export function validateHrefKeyedUpdatedProps(
|
||||
export function validateURLKeyedUpdatedProps(
|
||||
pendingProps: Props,
|
||||
currentProps: Props,
|
||||
resourceType: 'style' | 'script' | 'href',
|
||||
urlPropKey: 'href' | 'src',
|
||||
): boolean {
|
||||
if (__DEV__) {
|
||||
// This function should never be called if we don't have hrefs so we don't bother considering
|
||||
// This function should never be called if we don't have /srcs so we don't bother considering
|
||||
// Whether they are null or undefined
|
||||
if (pendingProps.href === currentProps.href) {
|
||||
// If we have the same href we need all other props to be the same
|
||||
if (pendingProps[urlPropKey] === currentProps[urlPropKey]) {
|
||||
// If we have the same href/src we need all other props to be the same
|
||||
let missingProps;
|
||||
let extraProps;
|
||||
let differentProps;
|
||||
@@ -366,7 +508,7 @@ export function validateHrefKeyedUpdatedProps(
|
||||
}
|
||||
if (missingProps || extraProps || differentProps) {
|
||||
const latestWarningName = getResourceNameForWarning(
|
||||
'style',
|
||||
resourceType,
|
||||
currentProps,
|
||||
false,
|
||||
);
|
||||
@@ -388,14 +530,17 @@ export function validateHrefKeyedUpdatedProps(
|
||||
}
|
||||
}
|
||||
console.error(
|
||||
'A %s with href "%s" recieved new props with different values from the props used' +
|
||||
'A %s with %s "%s" recieved new props with different values from the props used' +
|
||||
' when this Resource was first rendered. React will only use the props provided when' +
|
||||
' this resource was first rendered until a new href is provided. Unlike conventional' +
|
||||
' this resource was first rendered until a new %s is provided. Unlike conventional' +
|
||||
' DOM elements, Resources instances do not have a one to one correspondence with Elements' +
|
||||
' in the DOM and as such, every instance of a Resource for a single Resource identifier' +
|
||||
' (href) must have props that agree with each other. The differences are described below.%s',
|
||||
' (%s) must have props that agree with each other. The differences are described below.%s',
|
||||
latestWarningName,
|
||||
currentProps.href,
|
||||
urlPropKey,
|
||||
currentProps[urlPropKey],
|
||||
urlPropKey,
|
||||
urlPropKey,
|
||||
comparisonStatement,
|
||||
);
|
||||
return true;
|
||||
@@ -556,7 +701,8 @@ export function validatePreinitArguments(href: mixed, options: mixed) {
|
||||
} else {
|
||||
const as = options.as;
|
||||
switch (as) {
|
||||
case 'style': {
|
||||
case 'style':
|
||||
case 'script': {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -565,8 +711,8 @@ export function validatePreinitArguments(href: mixed, options: mixed) {
|
||||
const typeOfAs = getValueDescriptorExpectingEnumForWarning(as);
|
||||
console.error(
|
||||
'ReactDOM.preinit() expected the second argument to be an options argument containing at least an "as" property' +
|
||||
' specifying the Resource type. It found %s instead. Currently, the only valid resource type for preinit is "style".' +
|
||||
' The href for the preinit call where this warning originated is "%s".',
|
||||
' specifying the Resource type. It found %s instead. Currently, valid resource types for for preinit are "style"' +
|
||||
' and "script". The href for the preinit call where this warning originated is "%s".',
|
||||
typeOfAs,
|
||||
href,
|
||||
);
|
||||
|
||||
+483
-249
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@ describe('react-dom-server-rendering-stub', () => {
|
||||
}
|
||||
const html = ReactDOMFizzServer.renderToString(<App />);
|
||||
expect(html).toEqual(
|
||||
'<link href="foo" rel="preload" as="style"/><link rel="stylesheet" href="bar" data-rprec="default"/><div>foo</div>',
|
||||
'<link rel="stylesheet" href="bar" data-precedence="default"/><link href="foo" rel="preload" as="style"/><div>foo</div>',
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ function shim(...args: any): empty {
|
||||
// Hydration (when unsupported)
|
||||
export type SuspenseInstance = mixed;
|
||||
export const supportsHydration = false;
|
||||
export const isHydratable = shim;
|
||||
export const canHydrateInstance = shim;
|
||||
export const canHydrateTextInstance = shim;
|
||||
export const canHydrateSuspenseInstance = shim;
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
NoFlags,
|
||||
DidCapture,
|
||||
} from './ReactFiberFlags';
|
||||
import {enableHostSingletons} from 'shared/ReactFeatureFlags';
|
||||
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
createFiberFromHostInstanceForDeletion,
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
shouldSetTextContent,
|
||||
supportsHydration,
|
||||
supportsSingletons,
|
||||
isHydratable,
|
||||
canHydrateInstance,
|
||||
canHydrateTextInstance,
|
||||
canHydrateSuspenseInstance,
|
||||
@@ -438,6 +439,13 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void {
|
||||
if (!isHydrating) {
|
||||
return;
|
||||
}
|
||||
if (enableFloat && !isHydratable(fiber.type, fiber.pendingProps)) {
|
||||
// This fiber never hydrates from the DOM and always does an insert
|
||||
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
|
||||
isHydrating = false;
|
||||
hydrationParentFiber = fiber;
|
||||
return;
|
||||
}
|
||||
let nextInstance = nextHydratableInstance;
|
||||
if (!nextInstance) {
|
||||
if (shouldClientRenderOnMismatch(fiber)) {
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
NoFlags,
|
||||
DidCapture,
|
||||
} from './ReactFiberFlags';
|
||||
import {enableHostSingletons} from 'shared/ReactFeatureFlags';
|
||||
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {
|
||||
createFiberFromHostInstanceForDeletion,
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
shouldSetTextContent,
|
||||
supportsHydration,
|
||||
supportsSingletons,
|
||||
isHydratable,
|
||||
canHydrateInstance,
|
||||
canHydrateTextInstance,
|
||||
canHydrateSuspenseInstance,
|
||||
@@ -438,6 +439,13 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void {
|
||||
if (!isHydrating) {
|
||||
return;
|
||||
}
|
||||
if (enableFloat && !isHydratable(fiber.type, fiber.pendingProps)) {
|
||||
// This fiber never hydrates from the DOM and always does an insert
|
||||
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
|
||||
isHydrating = false;
|
||||
hydrationParentFiber = fiber;
|
||||
return;
|
||||
}
|
||||
let nextInstance = nextHydratableInstance;
|
||||
if (!nextInstance) {
|
||||
if (shouldClientRenderOnMismatch(fiber)) {
|
||||
|
||||
@@ -130,6 +130,7 @@ export const cloneHiddenTextInstance = $$$hostConfig.cloneHiddenTextInstance;
|
||||
// Hydration
|
||||
// (optional)
|
||||
// -------------------
|
||||
export const isHydratable = $$$hostConfig.isHydratable;
|
||||
export const canHydrateInstance = $$$hostConfig.canHydrateInstance;
|
||||
export const canHydrateTextInstance = $$$hostConfig.canHydrateTextInstance;
|
||||
export const canHydrateSuspenseInstance =
|
||||
|
||||
@@ -72,7 +72,7 @@ async function main() {
|
||||
// couldn't figure it out. Good enough for now. This only affects the inline
|
||||
// Fizz runtime, and should break immediately if there were a mistake, so I'm
|
||||
// not too worried about it.
|
||||
outputCode = outputCode.replaceAll(
|
||||
outputCode = outputCode.replace(
|
||||
/window\.(\$[A-z0-9_]*)/g,
|
||||
(_, variableName) => variableName
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user