Skip to content

Commit

Permalink
Support all links as potential Resources
Browse files Browse the repository at this point in the history
every link that does not have an onLoad or onError prop and has a string href and string rel can be a resource

on the server some of these generic link resources are privileged such as preconnect and prefetch-dns where they will be ordered at a higher priority for flushing.

These generic link types key off rel, href, sizes, and media.

In addition to these changes. preloads can now work for any as type. If the type is not for a recognized resource type then they fall back to generic link resource behavior.
  • Loading branch information
gnoff committed Oct 21, 2022
1 parent 973b90b commit 9f7339a
Show file tree
Hide file tree
Showing 5 changed files with 371 additions and 50 deletions.
103 changes: 88 additions & 15 deletions packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals.js';
const {Dispatcher} = ReactDOMSharedInternals;
import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
import {
validateUnmatchedLinkResourceProps,
warnOnMissingHrefAndRel,
validatePreloadResourceDifference,
validateURLKeyedUpdatedProps,
validateStyleResourceDifference,
Expand Down Expand Up @@ -54,7 +54,7 @@ type StyleProps = {
'data-precedence': string,
[string]: mixed,
};
export type StyleResource = {
type StyleResource = {
type: 'style',

// Ref count for resource
Expand All @@ -79,7 +79,7 @@ type ScriptProps = {
src: string,
[string]: mixed,
};
export type ScriptResource = {
type ScriptResource = {
type: 'script',
src: string,
props: ScriptProps,
Expand All @@ -88,12 +88,10 @@ export type ScriptResource = {
root: FloatRoot,
};

export type HeadResource = TitleResource | MetaResource;

type TitleProps = {
[string]: mixed,
};
export type TitleResource = {
type TitleResource = {
type: 'title',
props: TitleProps,

Expand All @@ -105,7 +103,7 @@ export type TitleResource = {
type MetaProps = {
[string]: mixed,
};
export type MetaResource = {
type MetaResource = {
type: 'meta',
matcher: string,
property: ?string,
Expand All @@ -117,8 +115,23 @@ export type MetaResource = {
root: Document,
};

type LinkProps = {
href: string,
rel: string,
[string]: mixed,
};
type LinkResource = {
type: 'link',
props: LinkProps,

count: number,
instance: ?Element,
root: Document,
};

type Props = {[string]: mixed};

type HeadResource = TitleResource | MetaResource | LinkResource;
type Resource = StyleResource | ScriptResource | PreloadResource | HeadResource;

export type RootResources = {
Expand Down Expand Up @@ -617,8 +630,30 @@ export function getResource(
return null;
}
default: {
const {href, sizes, media} = pendingProps;
if (typeof rel === 'string' && typeof href === 'string') {
const sizeKey =
'::sizes:' + (typeof sizes === 'string' ? sizes : '');
const mediaKey =
'::media:' + (typeof media === 'string' ? media : '');
const key = 'rel:' + rel + '::href:' + href + sizeKey + mediaKey;
const headRoot = getDocumentFromRoot(resourceRoot);
const headResources = getResourcesFromRoot(headRoot).head;
let resource = headResources.get(key);
if (!resource) {
resource = {
type: 'link',
props: Object.assign({}, pendingProps),
count: 0,
instance: null,
root: headRoot,
};
headResources.set(key, resource);
}
return resource;
}
if (__DEV__) {
validateUnmatchedLinkResourceProps(pendingProps, currentProps);
warnOnMissingHrefAndRel(pendingProps, currentProps);
}
return null;
}
Expand Down Expand Up @@ -710,6 +745,7 @@ function scriptPropsFromRawProps(rawProps: ScriptQualifyingProps): ScriptProps {
export function acquireResource(resource: Resource): Instance {
switch (resource.type) {
case 'title':
case 'link':
case 'meta': {
return acquireHeadResource(resource);
}
Expand All @@ -732,6 +768,7 @@ export function acquireResource(resource: Resource): Instance {

export function releaseResource(resource: Resource): void {
switch (resource.type) {
case 'link':
case 'title':
case 'meta': {
return releaseHeadResource(resource);
Expand Down Expand Up @@ -1050,6 +1087,41 @@ function acquireHeadResource(resource: HeadResource): Instance {
insertResourceInstanceBefore(root, instance, insertBefore);
break;
}
case 'link': {
const linkProps: LinkProps = (props: any);
const limitedEscapedRel = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.rel,
);
const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.href,
);
let selector = `link[rel="${limitedEscapedRel}"][href="${limitedEscapedHref}"]`;
if (typeof linkProps.sizes === 'string') {
const limitedEscapedSizes = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.sizes,
);
selector += `[sizes="${limitedEscapedSizes}"]`;
}
if (typeof linkProps.media === 'string') {
const limitedEscapedMedia = escapeSelectorAttributeValueInsideDoubleQuotes(
linkProps.media,
);
selector += `[media="${limitedEscapedMedia}"]`;
}
const existingEl = root.querySelector(selector);
if (existingEl) {
instance = resource.instance = existingEl;
markNodeAsResource(instance);
return instance;
}
instance = resource.instance = createResourceInstance(
type,
props,
root,
);
insertResourceInstanceBefore(root, instance, null);
return instance;
}
default: {
throw new Error(
`acquireHeadResource encountered a resource type it did not expect: "${type}". This is a bug in React.`,
Expand Down Expand Up @@ -1265,26 +1337,27 @@ export function isHostResourceType(type: string, props: Props): boolean {
return true;
}
case 'link': {
const {onLoad, onError} = props;
if (onLoad || onError) {
return false;
}
switch (props.rel) {
case 'stylesheet': {
if (__DEV__) {
validateLinkPropsForStyleResource(props);
}
const {href, precedence, onLoad, onError, disabled} = props;
const {href, precedence, disabled} = props;
return (
typeof href === 'string' &&
typeof precedence === 'string' &&
!onLoad &&
!onError &&
disabled == null
);
}
case 'preload': {
const {href, onLoad, onError} = props;
return !onLoad && !onError && typeof href === 'string';
default: {
const {rel, href} = props;
return typeof href === 'string' && typeof rel === 'string';
}
}
return false;
}
case 'script': {
// We don't validate because it is valid to use async with onLoad/onError unlike combining
Expand Down
50 changes: 46 additions & 4 deletions packages/react-dom-bindings/src/server/ReactDOMFloatServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,20 @@ type MetaResource = {
flushed: boolean,
};

type LinkProps = {
href: string,
rel: string,
[string]: mixed,
};
type LinkResource = {
type: 'link',
props: LinkProps,

flushed: boolean,
};

export type Resource = PreloadResource | StyleResource | ScriptResource;
export type HeadResource = TitleResource | MetaResource;
export type HeadResource = TitleResource | MetaResource | LinkResource;

export type Resources = {
// Request local cache
Expand All @@ -101,6 +113,7 @@ export type Resources = {

// Flushing queues for Resource dependencies
charset: null | MetaResource,
preconnects: Set<LinkResource>,
fontPreloads: Set<PreloadResource>,
// usedImagePreloads: Set<PreloadResource>,
precedences: Map<string, Set<StyleResource>>,
Expand Down Expand Up @@ -131,6 +144,7 @@ export function createResources(): Resources {

// cleared on flush
charset: null,
preconnects: new Set(),
fontPreloads: new Set(),
// usedImagePreloads: new Set(),
precedences: new Map(),
Expand Down Expand Up @@ -697,10 +711,11 @@ export function resourcesFromLink(props: Props): boolean {
const resources = currentResources;

const {rel, href} = props;
if (!href || typeof href !== 'string') {
if (!href || typeof href !== 'string' || !rel || typeof rel !== 'string') {
return false;
}

let key = '';
switch (rel) {
case 'stylesheet': {
const {onLoad, onError, precedence, disabled} = props;
Expand Down Expand Up @@ -813,10 +828,37 @@ export function resourcesFromLink(props: Props): boolean {
return true;
}
}
return false;
break;
}
}
return false;
if (props.onLoad || props.onError) {
return false;
}

const sizes = typeof props.sizes === 'string' ? props.sizes : '';
const media = typeof props.media === 'string' ? props.media : '';
key =
'rel:' + rel + '::href:' + href + '::sizes:' + sizes + '::media:' + media;
let resource = resources.headsMap.get(key);
if (!resource) {
resource = {
type: 'link',
props: Object.assign({}, props),
flushed: false,
};
resources.headsMap.set(key, resource);
switch (rel) {
case 'preconnect':
case 'prefetch-dns': {
resources.preconnects.add(resource);
break;
}
default: {
resources.headResources.add(resource);
}
}
}
return true;
}

// Construct a resource from link props.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,7 @@ export function writeInitialResources(

const {
charset,
preconnects,
fontPreloads,
precedences,
usedStylePreloads,
Expand All @@ -2356,6 +2357,13 @@ export function writeInitialResources(
resources.charset = null;
}

preconnects.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
r.flushed = true;
});
preconnects.clear();

fontPreloads.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
Expand Down Expand Up @@ -2418,6 +2426,10 @@ export function writeInitialResources(
pushSelfClosing(target, r.props, 'meta', responseState);
break;
}
case 'link': {
pushLinkImpl(target, r.props, responseState);
break;
}
}
r.flushed = true;
});
Expand Down Expand Up @@ -2450,6 +2462,7 @@ export function writeImmediateResources(

const {
charset,
preconnects,
fontPreloads,
usedStylePreloads,
scripts,
Expand All @@ -2465,6 +2478,13 @@ export function writeImmediateResources(
resources.charset = null;
}

preconnects.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
r.flushed = true;
});
preconnects.clear();

fontPreloads.forEach(r => {
// font preload Resources should not already be flushed so we elide this check
pushLinkImpl(target, r.props, responseState);
Expand Down Expand Up @@ -2507,6 +2527,10 @@ export function writeImmediateResources(
pushSelfClosing(target, r.props, 'meta', responseState);
break;
}
case 'link': {
pushLinkImpl(target, r.props, responseState);
break;
}
}
r.flushed = true;
});
Expand Down
Loading

0 comments on commit 9f7339a

Please sign in to comment.