Skip to content

Commit be4788e

Browse files
authored
Add metaV1 utility package (#5)
1 parent 8509225 commit be4788e

File tree

20 files changed

+994
-38
lines changed

20 files changed

+994
-38
lines changed

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
**/dist
55
example/build
66
example/.cache
7-
examples/public
7+
example/public/build

example/app/other/posts.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import { Outlet } from "@remix-run/react";
2+
import type { V2_MetaArgs } from "@remix-run/react";
3+
import { metaV1 } from "@remix-run/v1-meta";
24

3-
export default function () {
5+
export function meta(args: V2_MetaArgs) {
6+
return metaV1(args, {
7+
title: "Posts",
8+
description: "All the posts",
9+
"og:image": ["https://remix.run/logo.png", "https://remix.run/logo2.png"],
10+
});
11+
}
12+
13+
export default function Posts() {
414
return (
515
<div style={{ border: "2px solid red" }}>
616
<Outlet />

example/app/other/posts/$postId.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { Link, useParams } from "@remix-run/react";
2+
import type { V2_MetaArgs } from "@remix-run/react";
3+
import { metaV1 } from "@remix-run/v1-meta";
24

3-
export default function () {
5+
export function meta(args: V2_MetaArgs) {
6+
return metaV1(args, {
7+
title: `Post ${args.params.postId}`,
8+
description: "This is a post",
9+
});
10+
}
11+
12+
export default function Post() {
413
let params = useParams();
514
return (
615
<div>

example/app/other/posts/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Link } from "@remix-run/react";
22

3-
export default function () {
3+
export default function PostsIndex() {
44
return (
55
<div>
66
<h1>Posts</h1>

example/app/root.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { V2_MetaArgs } from "@remix-run/react";
12
import {
23
Links,
34
LiveReload,
@@ -6,13 +7,20 @@ import {
67
Scripts,
78
ScrollRestoration,
89
} from "@remix-run/react";
10+
import { metaV1 } from "@remix-run/v1-meta";
11+
12+
export function meta(args: V2_MetaArgs) {
13+
return metaV1(args, {
14+
charSet: "utf-8",
15+
viewport: "width=device-width,initial-scale=1",
16+
title: "Remix Example",
17+
});
18+
}
919

1020
export default function App() {
1121
return (
1222
<html lang="en">
1323
<head>
14-
<meta charSet="utf-8" />
15-
<meta name="viewport" content="width=device-width,initial-scale=1" />
1624
<Meta />
1725
<Links />
1826
</head>

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"devDependencies": {
1919
"@remix-run/dev": "1.15.0-pre.2",
2020
"@remix-run/eslint-config": "1.15.0-pre.2",
21+
"@remix-run/v1-meta": "workspace:*",
2122
"@remix-run/v1-route-convention": "workspace:*",
2223
"@types/react": "^18.0.30",
2324
"@types/react-dom": "^18.0.8",

example/remix.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ module.exports = {
66
// assetsBuildDirectory: "public/build",
77
// serverBuildPath: "build/index.js",
88
// publicPath: "/build/",
9-
future: { v2_routeConvention: true },
9+
future: {
10+
v2_meta: true,
11+
v2_routeConvention: true,
12+
},
1013
routes(defineRoutes) {
1114
return createRoutesFromFolders(defineRoutes, { routesDirectory: "other" });
1215
},

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"packages/*"
55
],
66
"scripts": {
7+
"dev": "pnpm run --recursive --if-present dev",
78
"watch": "pnpm run --recursive --if-present watch",
89
"build": "pnpm run --recursive --if-present build",
910
"test": "pnpm run --recursive --if-present test",
@@ -19,6 +20,7 @@
1920
"prettier": "^2.8.7",
2021
"tsup": "^6.7.0",
2122
"typescript": "^5.0.2",
22-
"vitest": "^0.29.7"
23+
"vite": "^4.2.1",
24+
"vitest": "^0.29.8"
2325
}
2426
}

packages/v1-meta/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# V1 Meta
2+
3+
```tsx
4+
import { metaV1 } from "@remix-run/v1-meta";
5+
6+
export function meta(args) {
7+
return metaV1(args, {
8+
title: "My App",
9+
description: "My App Description",
10+
});
11+
// return [
12+
// { charSet: "utf-8" }, // inherited!
13+
// { title: "My App" },
14+
// { name: "description", content: "My App Description" },
15+
// ];
16+
}
17+
```
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import { fromMetaData as _fromMetaData } from "../src/index";
2+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
3+
4+
let fromMetaData = _fromMetaData;
5+
if (import.meta.env.TEST_BUILD) {
6+
try {
7+
fromMetaData = require("../dist/index").fromMetaData;
8+
} catch (_) {}
9+
}
10+
11+
describe("fromMetaData", () => {
12+
let warn = console.warn;
13+
beforeEach(() => {
14+
console.warn = vi.fn();
15+
});
16+
afterEach(() => {
17+
console.warn = warn;
18+
});
19+
20+
it("does not create a descriptor from empty object", () => {
21+
let descriptors = fromMetaData({});
22+
expect(descriptors).toEqual([]);
23+
});
24+
25+
it("does not create a descriptor from 'undefined' value", () => {
26+
let descriptors = fromMetaData({ "og:type": undefined });
27+
expect(descriptors).toEqual([]);
28+
});
29+
30+
it("does not create a descriptor from 'null' value", () => {
31+
// @ts-expect-error
32+
let descriptors = fromMetaData({ title: null });
33+
expect(descriptors).toEqual([]);
34+
});
35+
36+
it("creates multiple descriptors", () => {
37+
let descriptors = fromMetaData({
38+
title: "hiya | my website",
39+
"og:title": "hiya",
40+
description: "my website",
41+
"og:image": [
42+
"https://example.com/image1.jpg",
43+
"https://example.com/image2.jpg",
44+
],
45+
});
46+
expect(descriptors).toEqual([
47+
{ title: "hiya | my website", key: expect.any(String) },
48+
{ property: "og:title", content: "hiya", key: expect.any(String) },
49+
{ name: "description", content: "my website", key: expect.any(String) },
50+
{
51+
property: "og:image",
52+
content: "https://example.com/image1.jpg",
53+
key: expect.any(String),
54+
},
55+
{
56+
property: "og:image",
57+
content: "https://example.com/image2.jpg",
58+
key: expect.any(String),
59+
},
60+
]);
61+
});
62+
63+
describe("title tags", () => {
64+
it("creates a 'title' descriptor from 'title' property", () => {
65+
let descriptors = fromMetaData({ title: "hello world" });
66+
expect(descriptors[0]).toEqual({
67+
title: "hello world",
68+
key: "title",
69+
});
70+
});
71+
});
72+
73+
describe("'charSet' meta tags", () => {
74+
it("creates a 'charSet' descriptor from 'charSet' property", () => {
75+
let descriptors = fromMetaData({ charSet: "utf-8" });
76+
expect(descriptors[0]).toEqual({
77+
charSet: "utf-8",
78+
key: "charSet",
79+
});
80+
});
81+
82+
it("creates a 'charSet' descriptor from 'charset' property", () => {
83+
let descriptors = fromMetaData({ charset: "utf-8" });
84+
expect(descriptors[0]).toEqual({ charSet: "utf-8", key: "charSet" });
85+
});
86+
});
87+
88+
describe("og:* meta tags", () => {
89+
it("creates an 'og:type' descriptor from 'og:type' property", () => {
90+
let descriptors = fromMetaData({ "og:type": "website" });
91+
expect(descriptors[0]).toEqual({
92+
property: "og:type",
93+
content: "website",
94+
key: expect.any(String),
95+
});
96+
});
97+
98+
it("creates an 'og:title' descriptor from 'og:title' property", () => {
99+
let descriptors = fromMetaData({ "og:title": "hello world" });
100+
expect(descriptors[0]).toEqual({
101+
property: "og:title",
102+
content: "hello world",
103+
key: expect.any(String),
104+
});
105+
});
106+
107+
it("creates an 'og:description' descriptor from 'og:description' property", () => {
108+
let descriptors = fromMetaData({ "og:description": "hello world" });
109+
expect(descriptors[0]).toEqual({
110+
property: "og:description",
111+
content: "hello world",
112+
key: expect.any(String),
113+
});
114+
});
115+
116+
it("creates an 'og:url' descriptor from 'og:url' property", () => {
117+
let descriptors = fromMetaData({ "og:url": "https://example.com" });
118+
expect(descriptors[0]).toEqual({
119+
property: "og:url",
120+
content: "https://example.com",
121+
key: expect.any(String),
122+
});
123+
});
124+
125+
it("creates multiple descriptors with array values", () => {
126+
let descriptors = fromMetaData({
127+
"og:image": [
128+
"https://example.com/image1.png",
129+
"https://example.com/image2.png",
130+
],
131+
});
132+
expect(descriptors).toEqual([
133+
{
134+
property: "og:image",
135+
content: "https://example.com/image1.png",
136+
key: expect.any(String),
137+
},
138+
{
139+
property: "og:image",
140+
content: "https://example.com/image2.png",
141+
key: expect.any(String),
142+
},
143+
]);
144+
});
145+
});
146+
147+
describe("fb:* meta tags", () => {
148+
it("creates a 'fb:app_id' descriptor from 'fb:app_id' property", () => {
149+
let descriptors = fromMetaData({ "fb:app_id": "123" });
150+
expect(descriptors[0]).toEqual({
151+
property: "fb:app_id",
152+
content: "123",
153+
key: expect.any(String),
154+
});
155+
});
156+
});
157+
158+
describe("twitter:* meta tags", () => {
159+
it("creates a 'twitter:card' descriptor from 'twitter:card' property", () => {
160+
let descriptors = fromMetaData({ "twitter:card": "summary" });
161+
expect(descriptors[0]).toEqual({
162+
name: "twitter:card",
163+
content: "summary",
164+
key: expect.any(String),
165+
});
166+
});
167+
168+
it("creates a 'twitter:site' descriptor from 'twitter:site' property", () => {
169+
let descriptors = fromMetaData({ "twitter:site": "@example" });
170+
expect(descriptors[0]).toEqual({
171+
name: "twitter:site",
172+
content: "@example",
173+
key: expect.any(String),
174+
});
175+
});
176+
177+
it("creates a 'twitter:title' descriptor from 'twitter:title' property", () => {
178+
let descriptors = fromMetaData({ "twitter:title": "hello world" });
179+
expect(descriptors[0]).toEqual({
180+
name: "twitter:title",
181+
content: "hello world",
182+
key: expect.any(String),
183+
});
184+
});
185+
});
186+
187+
describe("standard meta tags", () => {
188+
it("creates a 'description' descriptor from 'description' property", () => {
189+
let descriptors = fromMetaData({ description: "hello world" });
190+
expect(descriptors[0]).toEqual({
191+
name: "description",
192+
content: "hello world",
193+
key: expect.any(String),
194+
});
195+
});
196+
197+
it("creates a 'keywords' descriptor from 'keywords' property", () => {
198+
let descriptors = fromMetaData({ keywords: "hello,world" });
199+
expect(descriptors[0]).toEqual({
200+
name: "keywords",
201+
content: "hello,world",
202+
key: expect.any(String),
203+
});
204+
});
205+
206+
it("creates a 'viewport' descriptor from 'viewport' property", () => {
207+
let descriptors = fromMetaData({
208+
viewport: "width=device-width, initial-scale=1",
209+
});
210+
expect(descriptors[0]).toEqual({
211+
name: "viewport",
212+
content: "width=device-width, initial-scale=1",
213+
key: expect.any(String),
214+
});
215+
});
216+
217+
it("stringifies numeric value", () => {
218+
// @ts-expect-error
219+
let descriptors = fromMetaData({ description: 1 });
220+
expect(descriptors[0]).toEqual({
221+
name: "description",
222+
content: "1",
223+
key: expect.any(String),
224+
});
225+
});
226+
227+
it("does not create a descriptor if value is 'falsey'", () => {
228+
// This includes any 'falsey' value, including numbers. Ideally those
229+
// would be stringified and work just fine since that's how React handles
230+
// rendering falsey numeric values, but that's not how it works in v1 so
231+
// it works the same way here.
232+
// @ts-expect-error
233+
let descriptors = fromMetaData({ description: 0 });
234+
expect(descriptors).toEqual([]);
235+
});
236+
});
237+
238+
describe("arbitrary meta tags", () => {
239+
it("creates a descriptor from keys with object value", () => {
240+
let descriptors = fromMetaData({
241+
refresh: {
242+
httpEquiv: "refresh",
243+
content: "3;url=https://www.mozilla.org",
244+
},
245+
});
246+
expect(descriptors[0]).toEqual({
247+
httpEquiv: "refresh",
248+
content: "3;url=https://www.mozilla.org",
249+
key: expect.any(String),
250+
});
251+
});
252+
});
253+
});

0 commit comments

Comments
 (0)