-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(remix-react): Add array-syntax for meta
export
#2598
Conversation
276ff22
to
d0e23f0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of my suggestions?
packages/remix-react/components.tsx
Outdated
@@ -651,7 +651,7 @@ export function Meta() { | |||
let { matches, routeData, routeModules } = useRemixEntryContext(); | |||
let location = useLocation(); | |||
|
|||
let meta: HtmlMetaDescriptor = {}; | |||
let meta: HtmlMetaDescriptor[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's change meta
to a Map
type MetaMap = Map<string, HtmlMetaDescriptor>;
let meta: MetaMap = new Map();
* This function processes a meta descriptor and adds it to the meta array. | ||
* This converts the original object syntax to new array syntax. | ||
*/ | ||
export function processMeta( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change HtmlMetaDescriptor
to this:
export type HtmlMetaDescriptor = {
key?: string;
name?: string;
property?: string;
title?: string;
content?: string;
} & Record<string, string | string[] | null | undefined>;
and try this for processMeta
. it sets key
here
export function processMeta(
meta: MetaMap,
routeMeta: HtmlMetaDescriptor | HtmlMetaDescriptor[]
) {
let items: HtmlMetaDescriptor[] = Array.isArray(routeMeta)
? routeMeta
: Object.entries(routeMeta)
.map(([key, value]) => {
if (!value) return [];
let propertyName = key.startsWith("og:") ? "property" : "name";
return key === "title"
? { key: "title", content: assertString(value) }
: ["charset", "charSet"].includes(key)
? { key: "charset", content: assertString(value) }
: Array.isArray(value)
? value.map((content) => ({
key: `${key}.${content}`,
[propertyName]: key,
content,
}))
: { key, [propertyName]: key, content: value };
})
.flat();
items.forEach((item) => {
let [key, value] = computeKey(item);
// only set if key doesn't exist (i.e. let the furthest route win)
if (!meta.has(key)) {
meta.set(key, value);
}
});
}
other needed helpers:
function assertString(value: string | string[]): string {
if (typeof value !== "string") {
throw new Error("Expected string, got an array of strings");
}
return value;
}
function generateKey(item: HtmlMetaDescriptor) {
console.warn("No key provided to meta", item);
return JSON.stringify(item); // generate a reasonable key
}
function computeKey(item: HtmlMetaDescriptor): [string, HtmlMetaDescriptor] {
let {
name,
property,
title,
key = name ?? property ?? title ?? generateKey(item),
...rest
} = item;
return [key, { name, property, title, ...rest }];
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made these changes, but a couple of comments:
-
I was trying not to change
HtmlMetaDescriptor
since it's a public API. Your version no longer supports the extended object syntax (ie., httpEquiv). My understanding is that's going to be reverted anyway. -
The array syntax no longer supports the shorthand for
{ title: "my title"}
instead requiring{ key: "title", content"my title" }
. Same with charset. Probably need Ryan to weigh-in on this change. -
When you're merging meta map, you said only set if key doesn't exist. However,
processMap
is called from the root to the child route, so without subsequent sets, you can't override. I just removed the check.
With these changes, my local tests work. Still having issues with the integration test.
Object.assign(meta, routeMeta); | ||
if (routeMeta) { | ||
meta = processMeta(meta, routeMeta); | ||
} | ||
} | ||
|
||
parentsData[routeId] = data; | ||
} | ||
|
||
return ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here's the rendering part. The keys have been pre-computed in processMeta
return (
<>
{[...meta.entries()].map(([key, value]) => {
if (key === "title" && typeof value.content === "string") {
return <title key={key}>{value.content}</title>;
}
if (key === "charset" && typeof value.content === "string") {
return <meta key={key} charSet={value.content} />;
}
return <meta key={key} {...value} />;
})}
</>
);
25fd0ba
to
c969879
Compare
@kiliman Could you fix conflicts here please? |
Hello team, I want to mention a scenario I found, where open graph og:image accept multiple metatags with the same name, as described at: https://ogp.me/#array
Just want to mention that this PR might allow this open graph usage. Thank you. cc #4260 |
c969879
to
6d5eb0d
Compare
|
@machour I've rebased onto latest dev @felquis I added support for duplicate property names. It dedupes by property+content. If you need to override dupes, you can provide a unique key for each. remix/integration/meta-test.ts Lines 146 to 159 in 87594af
remix/integration/meta-test.ts Lines 253 to 260 in 87594af
|
@kiliman This seems like it could be error-prone across nested route boundaries. In the vast majority of cases you want your child route's meta to override the parent's. For this to work would users need to provide keys for every tag, or just tags that support duplicates for array values? I'm starting to wonder if we shouldn't hold off on this until v2. We're already planning some changes that incorporate this change, but if we continue auto-merging meta in nested routes I think array values introduce some potential gnarly edge cases that will be hard to work around. |
.map(([key, value]) => { | ||
if (!value) return []; | ||
|
||
let propertyName = key.startsWith("og:") ? "property" : "name"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will need to be updated. See #4445
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll review this. Basically the key should be either name
or property
value. The only time you need an explicit key
property is if you have duplicates, or there is no name/property like <meta http-equiv="refresh" />
@felquis Just so you know, you can already do this today with the existing API. You just need to pass an array to the tag's content. export function meta() {
return {
"og:image": [
"https://picsum.photos/300/300",
"https://picsum.photos/200/200",
],
};
} It does get a little weird with structured properties though, since we'd need to reorder those according to the protocol. Array values would help there, but it could get weird with nesting. |
If we're going to support having duplicate items (e.g. multiple export function meta() {
return {
"og:image": [
"https://picsum.photos/300/300",
"https://picsum.photos/200/200",
],
};
} The problem with this is that you can have multiple So I would either recommend adding keys to allow child routes to override, or add an explicit |
For all interested, we have an RFC that will address a lot of the issues mentioned in this thread. #4462 |
Closed in favor of #4610 |
This PR adds array-syntax to the
meta
export.