Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,39 @@ export function GraceHopper() {
}
```

### Directly creating `<script>` tags (for `next/head` and elsewhere)

Certain `<head>` management libraries require `<script>` tags to be directly
included, rather than wrapped in a component. This includes NextJS's
`next/head`, and `react-helmet`. With these, we can use the `jsonLdScriptProps`
export to do the same thing:

```tsx
import { Person } from "schema-dts";
import { helmetJsonLdProp } from "react-schemaorg";
import Head from "next/head";

export default function MyPage() {
return (
<Head>
<script
{...jsonLdScriptProps<Person>({
"@context": "https://schema.org",
"@type": "Person",
name: "Grace Hopper",
alternateName: "Grace Brewster Murray Hopper",
alumniOf: {
"@type": "CollegeOrUniversity",
name: ["Yale University", "Vassar College"],
},
knowsAbout: ["Compilers", "Computer Science"],
})}
/>
</Head>
);
}
```

### [React Helmet](https://github.com/nfl/react-helmet) Usage

To set JSON-LD in React Helmet, you need to pass it to the `script={[...]}` prop
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
* limitations under the License.
*/

export { helmetJsonLdProp, JsonLd } from "./json-ld";
export { helmetJsonLdProp, JsonLd, jsonLdScriptProps } from "./json-ld";
44 changes: 32 additions & 12 deletions src/json-ld.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,41 @@ export class JsonLd<T extends Thing> extends React.Component<
}
> {
render() {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(
this.props.item,
safeJsonLdReplacer,
this.props.space
),
}}
/>
);
return <script {...jsonLdScriptProps<T>(this.props.item, this.props)} />;
}
}

/**
* Produces necessary props for a JSX <script> tag that includes JSON-LD.
*
* Can be used by spreading the props into a <script> JSX tag:
*
* ```tsx
* <script {...jsonLdScriptProps<Person>({
* "@context": "https://schema.org",
* "@type": "Person",
* name: "Grace Hopper",
* alternateName: "Grace Brewster Murray Hopper",
* alumniOf: {
* "@type": "CollegeOrUniversity",
* name: ["Yale University", "Vassar College"]
* },
* knowsAbout: ["Compilers", "Computer Science"]
* })} />
* ```
*/
export function jsonLdScriptProps<T extends Thing>(
item: WithContext<T>,
options: JsonLdOptions = {}
): JSX.IntrinsicElements["script"] {
return {
type: "application/ld+json",
dangerouslySetInnerHTML: {
__html: JSON.stringify(item, safeJsonLdReplacer, options.space),
},
};
}

/**
* Produces a Helmet-style <script> prop for a given JSON-LD datum.
*
Expand Down
166 changes: 138 additions & 28 deletions test/jsonld_func_test.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,119 @@
import {Person} from 'schema-dts';
import { Person } from "schema-dts";

import {helmetJsonLdProp} from '../src';
import { helmetJsonLdProp, jsonLdScriptProps } from "../src";

test('works', () => {
expect(helmetJsonLdProp<Person>({
'@context': 'https://schema.org',
'@type': 'Person',
})).toMatchInlineSnapshot(`
test("works", () => {
expect(
helmetJsonLdProp<Person>({
"@context": "https://schema.org",
"@type": "Person",
})
).toMatchInlineSnapshot(`
Object {
"innerHTML": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\"}",
"type": "application/ld+json",
}
`);

expect(
jsonLdScriptProps<Person>({
"@context": "https://schema.org",
"@type": "Person",
})
).toMatchInlineSnapshot(`
Object {
"dangerouslySetInnerHTML": Object {
"__html": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\"}",
},
"type": "application/ld+json",
}
`);

expect(
jsonLdScriptProps<Person>(
{
"@context": "https://schema.org",
"@type": "Person",
},
/* options=*/ {}
)
).toMatchInlineSnapshot(`
Object {
"dangerouslySetInnerHTML": Object {
"__html": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\"}",
},
"type": "application/ld+json",
}
`);

expect(
jsonLdScriptProps<Person>(
{
"@context": "https://schema.org",
"@type": "Person",
},
/* options=*/ { space: 2 }
)
).toMatchInlineSnapshot(`
Object {
"dangerouslySetInnerHTML": Object {
"__html": "{
\\"@context\\": \\"https://schema.org\\",
\\"@type\\": \\"Person\\"
}",
},
"type": "application/ld+json",
}
`);
});

test('escapes JSON-LD-illegal chars', () => {
expect(helmetJsonLdProp<Person>({
'@context': 'https://schema.org',
'@type': 'Person',
name: 'Foo</script>',
})).toMatchInlineSnapshot(`
test("escapes JSON-LD-illegal chars", () => {
expect(
helmetJsonLdProp<Person>({
"@context": "https://schema.org",
"@type": "Person",
name: "Foo</script>",
})
).toMatchInlineSnapshot(`
Object {
"innerHTML": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\",\\"name\\":\\"Foo&lt;/script&gt;\\"}",
"type": "application/ld+json",
}
`);

expect(
jsonLdScriptProps<Person>({
"@context": "https://schema.org",
"@type": "Person",
name: "Foo</script>",
})
).toMatchInlineSnapshot(`
Object {
"dangerouslySetInnerHTML": Object {
"__html": "{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Person\\",\\"name\\":\\"Foo&lt;/script&gt;\\"}",
},
"type": "application/ld+json",
}
`);
});

test('escapes JSON-LD-illegal chars', () => {
expect(helmetJsonLdProp<Person>(
{
'@context': 'https://schema.org',
'@type': 'Person',
name: ['Foo</script>', null!, undefined!],
knows: [],
knowsAbout: {
'@type': 'CreativeWork',
name: 'Foo',
copyrightYear: 2020,
},
},
{space: 2}))
.toMatchInlineSnapshot(`
test("escapes JSON-LD-illegal chars", () => {
expect(
helmetJsonLdProp<Person>(
{
"@context": "https://schema.org",
"@type": "Person",
name: ["Foo</script>", null!, undefined!],
knows: [],
knowsAbout: {
"@type": "CreativeWork",
name: "Foo",
copyrightYear: 2020,
},
},
{ space: 2 }
)
).toMatchInlineSnapshot(`
Object {
"innerHTML": "{
\\"@context\\": \\"https://schema.org\\",
Expand All @@ -61,4 +133,42 @@ test('escapes JSON-LD-illegal chars', () => {
"type": "application/ld+json",
}
`);

expect(
jsonLdScriptProps<Person>(
{
"@context": "https://schema.org",
"@type": "Person",
name: ["Foo</script>", null!, undefined!],
knows: [],
knowsAbout: {
"@type": "CreativeWork",
name: "Foo",
copyrightYear: 2020,
},
},
{ space: 2 }
)
).toMatchInlineSnapshot(`
Object {
"dangerouslySetInnerHTML": Object {
"__html": "{
\\"@context\\": \\"https://schema.org\\",
\\"@type\\": \\"Person\\",
\\"name\\": [
\\"Foo&lt;/script&gt;\\",
null,
null
],
\\"knows\\": [],
\\"knowsAbout\\": {
\\"@type\\": \\"CreativeWork\\",
\\"name\\": \\"Foo\\",
\\"copyrightYear\\": 2020
}
}",
},
"type": "application/ld+json",
}
`);
});