Skip to content

Commit

Permalink
support icon and apple-touch-icon as resources
Browse files Browse the repository at this point in the history
  • Loading branch information
gnoff committed Oct 21, 2022
1 parent 701f08a commit cc45100
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 7 deletions.
76 changes: 70 additions & 6 deletions packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
Original file line number Diff line number Diff line change
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 @@ -616,6 +629,28 @@ export function getResource(
}
return null;
}
case 'icon':
case 'apple-touch-icon': {
const {href} = pendingProps;
if (typeof href === 'string') {
const key = rel + href;
const headRoot = getDocumentFromRoot(resourceRoot);
const headResources = getResourcesFromRoot(resourceRoot).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;
}
return null;
}
default: {
if (__DEV__) {
validateUnmatchedLinkResourceProps(pendingProps, currentProps);
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 Down Expand Up @@ -1050,6 +1086,30 @@ 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,
);
const existingEl = root.querySelector(
`link[rel="${limitedEscapedRel}"][href="${limitedEscapedHref}"]`,
);
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 @@ -1283,6 +1343,10 @@ export function isHostResourceType(type: string, props: Props): boolean {
const {href, onLoad, onError} = props;
return !onLoad && !onError && typeof href === 'string';
}
case 'icon':
case 'apple-touch-icon': {
return true;
}
}
return false;
}
Expand Down
29 changes: 28 additions & 1 deletion 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 Down Expand Up @@ -815,6 +827,21 @@ export function resourcesFromLink(props: Props): boolean {
}
return false;
}
case 'icon':
case 'apple-touch-icon': {
const key = rel + href;
let resource = resources.headsMap.get(key);
if (!resource) {
resource = {
type: 'link',
props: Object.assign({}, props),
flushed: false,
};
resources.headsMap.set(key, resource);
resources.headResources.add(resource);
}
return true;
}
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,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 @@ -2507,6 +2511,10 @@ export function writeImmediateResources(
pushSelfClosing(target, r.props, 'meta', responseState);
break;
}
case 'link': {
pushLinkImpl(target, r.props, responseState);
break;
}
}
r.flushed = true;
});
Expand Down
54 changes: 54 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFloat-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,60 @@ describe('ReactDOMFloat', () => {
});

describe('head resources', () => {
// @gate enableFloat
it('can render icons and apple-touch-icons as resources', async () => {
await actIntoEmptyDocument(() => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<>
<html>
<head />
<body>
<link rel="icon" href="foo" />
<div>hello world</div>
</body>
</html>
<link rel="apple-touch-icon" href="foo" />
</>,
);
pipe(writable);
});
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<link rel="icon" href="foo" />
<link rel="apple-touch-icon" href="foo" />
</head>
<body>
<div>hello world</div>
</body>
</html>,
);

ReactDOMClient.hydrateRoot(
document,
<html>
<link rel="apple-touch-icon" href="foo" />
<head />
<body>
<link rel="icon" href="foo" />
<div>hello world</div>
</body>
</html>,
);
expect(Scheduler).toFlushWithoutYielding();
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<link rel="icon" href="foo" />
<link rel="apple-touch-icon" href="foo" />
</head>
<body>
<div>hello world</div>
</body>
</html>,
);
});

// @gate enableFloat
it('can hydrate the right instances for deeply nested structured metas', async () => {
await actIntoEmptyDocument(() => {
Expand Down

0 comments on commit cc45100

Please sign in to comment.