Skip to content

Commit 796fa93

Browse files
authored
Merge pull request #24 from Eyas/direct-script
Support using <script> tags directly.
2 parents 38e3245 + a048a6c commit 796fa93

4 files changed

Lines changed: 204 additions & 41 deletions

File tree

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,39 @@ export function GraceHopper() {
5050
}
5151
```
5252

53+
### Directly creating `<script>` tags (for `next/head` and elsewhere)
54+
55+
Certain `<head>` management libraries require `<script>` tags to be directly
56+
included, rather than wrapped in a component. This includes NextJS's
57+
`next/head`, and `react-helmet`. With these, we can use the `jsonLdScriptProps`
58+
export to do the same thing:
59+
60+
```tsx
61+
import { Person } from "schema-dts";
62+
import { helmetJsonLdProp } from "react-schemaorg";
63+
import Head from "next/head";
64+
65+
export default function MyPage() {
66+
return (
67+
<Head>
68+
<script
69+
{...jsonLdScriptProps<Person>({
70+
"@context": "https://schema.org",
71+
"@type": "Person",
72+
name: "Grace Hopper",
73+
alternateName: "Grace Brewster Murray Hopper",
74+
alumniOf: {
75+
"@type": "CollegeOrUniversity",
76+
name: ["Yale University", "Vassar College"],
77+
},
78+
knowsAbout: ["Compilers", "Computer Science"],
79+
})}
80+
/>
81+
</Head>
82+
);
83+
}
84+
```
85+
5386
### [React Helmet](https://github.com/nfl/react-helmet) Usage
5487

5588
To set JSON-LD in React Helmet, you need to pass it to the `script={[...]}` prop

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
* limitations under the License.
1515
*/
1616

17-
export { helmetJsonLdProp, JsonLd } from "./json-ld";
17+
export { helmetJsonLdProp, JsonLd, jsonLdScriptProps } from "./json-ld";

src/json-ld.tsx

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,41 @@ export class JsonLd<T extends Thing> extends React.Component<
5050
}
5151
> {
5252
render() {
53-
return (
54-
<script
55-
type="application/ld+json"
56-
dangerouslySetInnerHTML={{
57-
__html: JSON.stringify(
58-
this.props.item,
59-
safeJsonLdReplacer,
60-
this.props.space
61-
),
62-
}}
63-
/>
64-
);
53+
return <script {...jsonLdScriptProps<T>(this.props.item, this.props)} />;
6554
}
6655
}
6756

57+
/**
58+
* Produces necessary props for a JSX <script> tag that includes JSON-LD.
59+
*
60+
* Can be used by spreading the props into a <script> JSX tag:
61+
*
62+
* ```tsx
63+
* <script {...jsonLdScriptProps<Person>({
64+
* "@context": "https://schema.org",
65+
* "@type": "Person",
66+
* name: "Grace Hopper",
67+
* alternateName: "Grace Brewster Murray Hopper",
68+
* alumniOf: {
69+
* "@type": "CollegeOrUniversity",
70+
* name: ["Yale University", "Vassar College"]
71+
* },
72+
* knowsAbout: ["Compilers", "Computer Science"]
73+
* })} />
74+
* ```
75+
*/
76+
export function jsonLdScriptProps<T extends Thing>(
77+
item: WithContext<T>,
78+
options: JsonLdOptions = {}
79+
): JSX.IntrinsicElements["script"] {
80+
return {
81+
type: "application/ld+json",
82+
dangerouslySetInnerHTML: {
83+
__html: JSON.stringify(item, safeJsonLdReplacer, options.space),
84+
},
85+
};
86+
}
87+
6888
/**
6989
* Produces a Helmet-style <script> prop for a given JSON-LD datum.
7090
*

test/jsonld_func_test.ts

Lines changed: 138 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,119 @@
1-
import {Person} from 'schema-dts';
1+
import { Person } from "schema-dts";
22

3-
import {helmetJsonLdProp} from '../src';
3+
import { helmetJsonLdProp, jsonLdScriptProps } from "../src";
44

5-
test('works', () => {
6-
expect(helmetJsonLdProp<Person>({
7-
'@context': 'https://schema.org',
8-
'@type': 'Person',
9-
})).toMatchInlineSnapshot(`
5+
test("works", () => {
6+
expect(
7+
helmetJsonLdProp<Person>({
8+
"@context": "https://schema.org",
9+
"@type": "Person",
10+
})
11+
).toMatchInlineSnapshot(`
1012
Object {
1113
"innerHTML": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\"}",
1214
"type": "application/ld+json",
1315
}
1416
`);
17+
18+
expect(
19+
jsonLdScriptProps<Person>({
20+
"@context": "https://schema.org",
21+
"@type": "Person",
22+
})
23+
).toMatchInlineSnapshot(`
24+
Object {
25+
"dangerouslySetInnerHTML": Object {
26+
"__html": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\"}",
27+
},
28+
"type": "application/ld+json",
29+
}
30+
`);
31+
32+
expect(
33+
jsonLdScriptProps<Person>(
34+
{
35+
"@context": "https://schema.org",
36+
"@type": "Person",
37+
},
38+
/* options=*/ {}
39+
)
40+
).toMatchInlineSnapshot(`
41+
Object {
42+
"dangerouslySetInnerHTML": Object {
43+
"__html": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\"}",
44+
},
45+
"type": "application/ld+json",
46+
}
47+
`);
48+
49+
expect(
50+
jsonLdScriptProps<Person>(
51+
{
52+
"@context": "https://schema.org",
53+
"@type": "Person",
54+
},
55+
/* options=*/ { space: 2 }
56+
)
57+
).toMatchInlineSnapshot(`
58+
Object {
59+
"dangerouslySetInnerHTML": Object {
60+
"__html": "{
61+
\\"@context\\": \\"https://schema.org\\",
62+
\\"@type\\": \\"Person\\"
63+
}",
64+
},
65+
"type": "application/ld+json",
66+
}
67+
`);
1568
});
1669

17-
test('escapes JSON-LD-illegal chars', () => {
18-
expect(helmetJsonLdProp<Person>({
19-
'@context': 'https://schema.org',
20-
'@type': 'Person',
21-
name: 'Foo</script>',
22-
})).toMatchInlineSnapshot(`
70+
test("escapes JSON-LD-illegal chars", () => {
71+
expect(
72+
helmetJsonLdProp<Person>({
73+
"@context": "https://schema.org",
74+
"@type": "Person",
75+
name: "Foo</script>",
76+
})
77+
).toMatchInlineSnapshot(`
2378
Object {
2479
"innerHTML": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\",\\"name\\":\\"Foo&lt;/script&gt;\\"}",
2580
"type": "application/ld+json",
2681
}
2782
`);
83+
84+
expect(
85+
jsonLdScriptProps<Person>({
86+
"@context": "https://schema.org",
87+
"@type": "Person",
88+
name: "Foo</script>",
89+
})
90+
).toMatchInlineSnapshot(`
91+
Object {
92+
"dangerouslySetInnerHTML": Object {
93+
"__html": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\",\\"name\\":\\"Foo&lt;/script&gt;\\"}",
94+
},
95+
"type": "application/ld+json",
96+
}
97+
`);
2898
});
2999

30-
test('escapes JSON-LD-illegal chars', () => {
31-
expect(helmetJsonLdProp<Person>(
32-
{
33-
'@context': 'https://schema.org',
34-
'@type': 'Person',
35-
name: ['Foo</script>', null!, undefined!],
36-
knows: [],
37-
knowsAbout: {
38-
'@type': 'CreativeWork',
39-
name: 'Foo',
40-
copyrightYear: 2020,
41-
},
42-
},
43-
{space: 2}))
44-
.toMatchInlineSnapshot(`
100+
test("escapes JSON-LD-illegal chars", () => {
101+
expect(
102+
helmetJsonLdProp<Person>(
103+
{
104+
"@context": "https://schema.org",
105+
"@type": "Person",
106+
name: ["Foo</script>", null!, undefined!],
107+
knows: [],
108+
knowsAbout: {
109+
"@type": "CreativeWork",
110+
name: "Foo",
111+
copyrightYear: 2020,
112+
},
113+
},
114+
{ space: 2 }
115+
)
116+
).toMatchInlineSnapshot(`
45117
Object {
46118
"innerHTML": "{
47119
\\"@context\\": \\"https://schema.org\\",
@@ -61,4 +133,42 @@ test('escapes JSON-LD-illegal chars', () => {
61133
"type": "application/ld+json",
62134
}
63135
`);
136+
137+
expect(
138+
jsonLdScriptProps<Person>(
139+
{
140+
"@context": "https://schema.org",
141+
"@type": "Person",
142+
name: ["Foo</script>", null!, undefined!],
143+
knows: [],
144+
knowsAbout: {
145+
"@type": "CreativeWork",
146+
name: "Foo",
147+
copyrightYear: 2020,
148+
},
149+
},
150+
{ space: 2 }
151+
)
152+
).toMatchInlineSnapshot(`
153+
Object {
154+
"dangerouslySetInnerHTML": Object {
155+
"__html": "{
156+
\\"@context\\": \\"https://schema.org\\",
157+
\\"@type\\": \\"Person\\",
158+
\\"name\\": [
159+
\\"Foo&lt;/script&gt;\\",
160+
null,
161+
null
162+
],
163+
\\"knows\\": [],
164+
\\"knowsAbout\\": {
165+
\\"@type\\": \\"CreativeWork\\",
166+
\\"name\\": \\"Foo\\",
167+
\\"copyrightYear\\": 2020
168+
}
169+
}",
170+
},
171+
"type": "application/ld+json",
172+
}
173+
`);
64174
});

0 commit comments

Comments
 (0)