Skip to content

Commit

Permalink
API updates for v2 meta arguments and types (remix-run#5785)
Browse files Browse the repository at this point in the history
  • Loading branch information
chaance authored and fernandojbf committed Mar 21, 2023
1 parent 0083e25 commit f968b9b
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 103 deletions.
37 changes: 35 additions & 2 deletions .changeset/meta-v2-enhancements.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
---
"@remix-run/cloudflare": minor
"@remix-run/deno": minor
"@remix-run/node": minor
"@remix-run/react": minor
"@remix-run/server-runtime": patch
"@remix-run/server-runtime": minor
---

Add support for generating `<script type='application/ld+json' />` and meta-related `<link />` tags to document head via the route `meta` function when using the `v2_meta` future flag
We have made a few changes to the API for route module `meta` functions when using the `future.v2_meta` flag. **These changes are _only_ breaking for users who have opted in.**

- `V2_HtmlMetaDescriptor` has been renamed to `V2_MetaDescriptor`
- The `meta` function's arguments have been simplified
- `parentsData` has been removed, as each route's loader data is available on the `data` property of its respective `match` object
```tsx
// before
export function meta({ parentsData }) {
return [
{ title: parentsData['routes/some-route'].title },
];
};
// after
export function meta({ matches }) {
return [
{ title: matches.find((match) => match.id === 'routes/some-route').data.title },
];
};
```
- The `route` property on route matches has been removed, as relevant match data is attached directly to the match object
```tsx
// before
export function meta({ parentsData }) {
let rootModule = matches.find(match => match.route.id === "root");
};
// after
export function meta({ matches }) {
let rootModule = matches.find(match => match.id === "root");
};
```
- Added support for generating `<script type='application/ld+json' />` and meta-related `<link />` tags to document head via the route `meta` function when using the `v2_meta` future flag
64 changes: 38 additions & 26 deletions docs/route/meta.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,25 +150,32 @@ export const meta: V2_MetaFunction = () => {
};
```

Meta functions return an array of `V2_HtmlMetaDescriptor` objects. These objects map one-to-one with normal HTML meta tags:
Meta functions return an array of `V2_MetaDescriptor` objects. These objects map one-to-one with HTML tags. So this meta function:

```tsx
const description = {
name: "description",
content: "This is my website description",
};
// becomes
<meta
name="description"
content="This is my website description"
/>;

const ogTitle = {
property: "og:title",
content: "My Website Title",
export const meta: V2_MetaFunction = () => {
return [
{
title: "Very cool app | Remix",
},
{
property: "og:title",
content: "Very cool app",
},
{
name: "description",
content: "This app is the best",
},
];
};
// becomes
<meta property="og:title" content="My Website Title" />;
```

…produces this HTML:

```html
<title>Very cool app | Remix</title>
<meta property="og:title" content="Very cool app" />;
<meta name="description" content="This app is the best" />
```

The one exception is the `title` tag since it's not a `<meta>` tag but acts as one.
Expand All @@ -187,14 +194,7 @@ const title = {

This is a list of the current route matches. You have access to many things, particularly the meta from the parent matches and data.

It's most useful for merging the parent meta into the child meta since the child meta value is what will be used:

```tsx
export const meta: V2_MetaFunction = ({ matches }) => {
let parentMeta = matches.map((match) => match.meta ?? []);
return [...parentMeta, { title: "Projects" }];
};
```
The interface for `matches` is similar to the return value of [`useMatches`][use-matches], but each match will include the output of its `meta` function. This is useful for [merging metadata across the route hierarchy](#md-merging-with-parent-meta).

## `data`

Expand Down Expand Up @@ -320,12 +320,23 @@ Usually you only need to add meta to what the parent has already defined. You ca

```tsx
export const meta: V2_MetaFunction = ({ matches }) => {
let parentMeta = matches.map((match) => match.meta ?? []);
let parentMeta = matches.flatMap(
(match) => match.meta ?? []
);
return [...parentMeta, { title: "Projects" }];
};
```

Note that this _will not_ override something like `title`. This is only additive.
Note that this _will not_ override something like `title`. This is only additive. If the inherited route meta includes a `title` tag, you can override with `Array.prototype.filter`:

```tsx
export const meta: V2_MetaFunction = ({ matches }) => {
let parentMeta = matches
.flatMap((match) => match.meta ?? [])
.filter((meta) => !("title" in meta));
return [...parentMeta, { title: "Projects" }];
};
```

### Meta Merging helper

Expand All @@ -343,3 +354,4 @@ If you can't avoid the merge problem with global meta or index routes, we've cre
[index-route]: ../guides/routing#index-routes
[merge-meta]: https://gist.github.com/ryanflorence/ec1849c6d690cfbffcb408ecd633e069
[url-params]: ../guides/routing#dynamic-segments
[use-matches]: ../hooks/use-matches
2 changes: 1 addition & 1 deletion integration/meta-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ test.describe("v2_meta", () => {

"app/routes/music.jsx": js`
export function meta({ data, matches }) {
let rootModule = matches.find(match => match.route.id === "root");
let rootModule = matches.find(match => match.id === "root");
let rootCharSet = rootModule.meta.find(meta => meta.charSet);
return [
rootCharSet,
Expand Down
7 changes: 5 additions & 2 deletions packages/remix-cloudflare/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export type {
HeadersFunction,
HtmlLinkDescriptor,
HtmlMetaDescriptor,
V2_HtmlMetaDescriptor,
JsonFunction,
LinkDescriptor,
LinksFunction,
Expand All @@ -66,14 +65,18 @@ export type {
MemoryUploadHandlerOptions,
MetaDescriptor,
MetaFunction,
V2_MetaFunction,
PageLinkDescriptor,
RequestHandler,
RouteComponent,
RouteHandle,
SerializeFrom,
ServerBuild,
ServerEntryModule,
V2_ServerRuntimeMetaArgs as V2_MetaArgs,
V2_ServerRuntimeMetaDescriptor as V2_MetaDescriptor,
// TODO: Remove in v2
V2_ServerRuntimeMetaDescriptor as V2_HtmlMetaDescriptor,
V2_ServerRuntimeMetaFunction as V2_MetaFunction,
Session,
SessionData,
SessionIdStorageStrategy,
Expand Down
7 changes: 5 additions & 2 deletions packages/remix-deno/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export type {
SerializeFrom,
ServerBuild,
ServerEntryModule,
V2_ServerRuntimeMetaArgs as V2_MetaArgs,
V2_ServerRuntimeMetaDescriptor as V2_MetaDescriptor,
// TODO: Remove in v2
V2_ServerRuntimeMetaDescriptor as V2_HtmlMetaDescriptor,
V2_ServerRuntimeMetaFunction as V2_MetaFunction,
Session,
SessionData,
SessionIdStorageStrategy,
Expand All @@ -70,6 +75,4 @@ export type {
UnsignFunction,
UploadHandler,
UploadHandlerPart,
V2_HtmlMetaDescriptor,
V2_MetaFunction,
} from "@remix-run/server-runtime";
7 changes: 5 additions & 2 deletions packages/remix-node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export type {
HeadersFunction,
HtmlLinkDescriptor,
HtmlMetaDescriptor,
V2_HtmlMetaDescriptor,
JsonFunction,
LinkDescriptor,
LinksFunction,
Expand All @@ -77,14 +76,18 @@ export type {
MemoryUploadHandlerOptions,
MetaDescriptor,
MetaFunction,
V2_MetaFunction,
PageLinkDescriptor,
RequestHandler,
RouteComponent,
RouteHandle,
SerializeFrom,
ServerBuild,
ServerEntryModule,
V2_ServerRuntimeMetaArgs as V2_MetaArgs,
V2_ServerRuntimeMetaDescriptor as V2_MetaDescriptor,
// TODO: Remove in v2
V2_ServerRuntimeMetaDescriptor as V2_HtmlMetaDescriptor,
V2_ServerRuntimeMetaFunction as V2_MetaFunction,
Session,
SessionData,
SessionIdStorageStrategy,
Expand Down
68 changes: 39 additions & 29 deletions packages/remix-react/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ import {
import type { HtmlLinkDescriptor, PrefetchPageDescriptor } from "./links";
import { createHtml, escapeHtml } from "./markup";
import type {
RouteMatchWithMeta,
V1_HtmlMetaDescriptor,
V2_HtmlMetaDescriptor,
V1_MetaFunction,
V2_MetaDescriptor,
V2_MetaFunction,
V2_MetaMatch,
V2_MetaMatches,
} from "./routeModules";
import type {
Transition,
Expand Down Expand Up @@ -568,12 +571,11 @@ function V1Meta() {
if (routeModule.meta) {
let routeMeta =
typeof routeModule.meta === "function"
? routeModule.meta({
? (routeModule.meta as V1_MetaFunction)({
data,
parentsData,
params,
location,
matches: undefined as any,
})
: routeModule.meta;
if (routeMeta && Array.isArray(routeMeta)) {
Expand Down Expand Up @@ -651,39 +653,28 @@ function V1Meta() {

function V2Meta() {
let { routeModules } = useRemixContext();
let { matches, loaderData } = useDataRouterStateContext();
let { matches: _matches, loaderData } = useDataRouterStateContext();
let location = useLocation();

let meta: V2_HtmlMetaDescriptor[] = [];
let leafMeta: V2_HtmlMetaDescriptor[] | null = null;
let parentsData: { [routeId: string]: AppData } = {};

let matchesWithMeta: RouteMatchWithMeta[] = matches.map((match) => ({
...match,
meta: [],
}));

let index = -1;
for (let match of matches) {
index++;
let routeId = match.route.id;
let meta: V2_MetaDescriptor[] = [];
let leafMeta: V2_MetaDescriptor[] | null = null;
let matches: V2_MetaMatches = [];
for (let i = 0; i < _matches.length; i++) {
let _match = _matches[i];
let routeId = _match.route.id;
let data = loaderData[routeId];
let params = match.params;

let params = _match.params;
let routeModule = routeModules[routeId];

let routeMeta: V2_HtmlMetaDescriptor[] | V1_HtmlMetaDescriptor | undefined =
[];
let routeMeta: V2_MetaDescriptor[] | V1_HtmlMetaDescriptor | undefined = [];

if (routeModule?.meta) {
routeMeta =
typeof routeModule.meta === "function"
? routeModule.meta({
? (routeModule.meta as V2_MetaFunction)({
data,
parentsData,
params,
location,
matches: matchesWithMeta,
matches,
})
: routeModule.meta;
} else if (leafMeta) {
Expand All @@ -697,7 +688,7 @@ function V2Meta() {
if (!Array.isArray(routeMeta)) {
throw new Error(
"The `v2_meta` API is enabled in the Remix config, but the route at " +
match.route.path +
_match.route.path +
" returns an invalid value. In v2, all route meta functions must " +
"return an array of meta objects." +
// TODO: Add link to the docs once they are written
Expand All @@ -706,9 +697,28 @@ function V2Meta() {
);
}

matchesWithMeta[index].meta = routeMeta;
let match: V2_MetaMatch = {
id: routeId,
data,
meta: routeMeta,
params: _match.params,
pathname: _match.pathname,
handle: _match.route.handle,
// TODO: Remove in v2. Only leaving it for now because we used it in
// examples and there's no reason to crash someone's build for one line.
// They'll get a TS error from the type updates anyway.
// @ts-expect-error
get route() {
console.warn(
"The meta function in " +
_match.route.path +
" accesses the `route` property on `matches`. This is deprecated and will be removed in Remix version 2. See"
);
return _match.route;
},
};
matches[i] = match;
meta = routeMeta;
parentsData[routeId] = data;
leafMeta = meta;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/remix-react/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export type { HtmlLinkDescriptor } from "./links";
export type {
CatchBoundaryComponent,
HtmlMetaDescriptor,
V2_MetaArgs,
V2_MetaDescriptor,
V2_MetaFunction,
RouteModules as UNSAFE_RouteModules,
ShouldReloadFunction,
} from "./routeModules";
Expand Down
Loading

0 comments on commit f968b9b

Please sign in to comment.