Skip to content
This repository was archived by the owner on Mar 3, 2023. It is now read-only.

Commit 4d028f8

Browse files
authored
starting work on betting TS (#140)
1 parent da5970f commit 4d028f8

File tree

1 file changed

+73
-48
lines changed

1 file changed

+73
-48
lines changed

packages/react/src/seo.ts

Lines changed: 73 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
1-
// eslint-disable-next-line eslint-comments/disable-enable-pair
2-
/* eslint-disable @typescript-eslint/no-explicit-any */
3-
import {Maybe} from './storefront-api-types.js';
4-
import {WithContext} from 'schema-dts';
5-
6-
export interface BaseSeo {
7-
description?: any;
8-
media?: any;
9-
title?: any;
10-
titleTemplate?: any;
11-
url?: any;
12-
handle?: any;
13-
ldJson?: any;
14-
alternates?: any[];
15-
}
1+
import type {Maybe} from './storefront-api-types.js';
2+
import type {WithContext, Thing} from 'schema-dts';
3+
import type {ComponentPropsWithoutRef, HTMLAttributes} from 'react';
164

175
export interface Seo {
186
/**
@@ -202,9 +190,9 @@ export type SeoMedia = {
202190

203191
export type TagKey = 'title' | 'base' | 'meta' | 'link' | 'script';
204192

205-
export interface HeadTag {
193+
export interface CustomHeadTagObject {
206194
tag: TagKey;
207-
props: Record<string, any>;
195+
props: Record<string, unknown>;
208196
children?: string;
209197
key: string;
210198
}
@@ -223,39 +211,43 @@ export type SchemaType =
223211
* pairs well with the SEO component in `@shopify/hydrogen` when building a Hydrogen Remix app, but can be used on its
224212
* own if you want to generate the tags yourself.
225213
*/
226-
export function generateSeoTags<T extends BaseSeo = Seo>(input: T) {
227-
const output: HeadTag[] = [];
228-
let ldJson: WithContext<any> = {
214+
export function generateSeoTags<T extends Seo = Seo>(
215+
seoInput: T
216+
): CustomHeadTagObject[] {
217+
const output: CustomHeadTagObject[] = [];
218+
219+
// https://github.com/google/schema-dts/issues/98
220+
let ldJson: WithContext<Exclude<Thing, string>> = {
229221
'@context': 'https://schema.org',
230222
'@type': 'Thing',
231223
};
232224

233-
for (const tag of Object.keys(input)) {
234-
const values = Array.isArray(input[tag as keyof T])
235-
? (input[tag as keyof T] as [keyof T][])
236-
: [input[tag as keyof T]];
225+
for (const seoKey of Object.keys(seoInput) as (keyof T)[]) {
226+
const values = Array.isArray(seoInput[seoKey])
227+
? (seoInput[seoKey] as T[keyof T][])
228+
: [seoInput[seoKey]];
237229

238230
const tags = values.map((value) => {
239-
const tagResults: any[] = [];
231+
const tagResults: CustomHeadTagObject[] = [];
240232

241233
if (!value) {
242234
return tagResults;
243235
}
244236

245-
switch (tag) {
246-
case 'title':
247-
// eslint-disable-next-line no-case-declarations
248-
const title = renderTitle(input.titleTemplate, value as string);
237+
switch (seoKey) {
238+
case 'title': {
239+
const title = renderTitle(seoInput?.titleTemplate, value);
249240

250241
tagResults.push(
251-
generateTag('title', title),
242+
generateTag('title', {title}),
252243
generateTag('meta', {property: 'og:title', content: title}),
253244
generateTag('meta', {name: 'twitter:title', content: title})
254245
);
255246

256247
ldJson.name = title;
257248

258249
break;
250+
}
259251

260252
case 'description':
261253
tagResults.push(
@@ -291,9 +283,8 @@ export function generateSeoTags<T extends BaseSeo = Seo>(input: T) {
291283
ldJson = {...ldJson, ...value};
292284
break;
293285

294-
case 'media':
295-
// eslint-disable-next-line no-case-declarations
296-
const values: any = Array.isArray(value) ? value : [value];
286+
case 'media': {
287+
const values = Array.isArray(value) ? value : [value];
297288

298289
for (const media of values) {
299290
if (typeof media === 'string') {
@@ -337,19 +328,16 @@ export function generateSeoTags<T extends BaseSeo = Seo>(input: T) {
337328
}
338329
}
339330
break;
331+
}
340332

341-
case 'alternates':
342-
// eslint-disable-next-line no-case-declarations
333+
case 'alternates': {
343334
const alternates = Array.isArray(value) ? value : [value];
344335

345336
for (const alternate of alternates) {
346337
const {
347-
// @ts-expect-error untyped
348338
language,
349-
// @ts-expect-error untyped
350339
media,
351340
url,
352-
// @ts-expect-error untyped
353341
default: defaultLang,
354342
} = alternate as Seo['alternates'][0];
355343

@@ -368,6 +356,7 @@ export function generateSeoTags<T extends BaseSeo = Seo>(input: T) {
368356
}
369357

370358
break;
359+
}
371360
}
372361

373362
return tagResults;
@@ -402,24 +391,56 @@ export function generateSeoTags<T extends BaseSeo = Seo>(input: T) {
402391
.flat();
403392
}
404393

405-
function generateTag<T extends HeadTag>(
406-
tagName: T['tag'],
407-
input: any,
394+
type MetaTagProps =
395+
| ComponentPropsWithoutRef<'title'>
396+
| ComponentPropsWithoutRef<'base'>
397+
| ComponentPropsWithoutRef<'meta'>
398+
| ComponentPropsWithoutRef<'link'>
399+
| ComponentPropsWithoutRef<'script'>;
400+
401+
function generateTag(
402+
tagName: 'title',
403+
input: ComponentPropsWithoutRef<'title'>,
404+
group?: string
405+
): CustomHeadTagObject;
406+
function generateTag(
407+
tagName: 'base',
408+
input: ComponentPropsWithoutRef<'base'>,
409+
group?: string
410+
): CustomHeadTagObject;
411+
function generateTag(
412+
tagName: 'meta',
413+
input: ComponentPropsWithoutRef<'meta'>,
414+
group?: string
415+
): CustomHeadTagObject;
416+
function generateTag(
417+
tagName: 'link',
418+
input: ComponentPropsWithoutRef<'link'>,
419+
group?: string
420+
): CustomHeadTagObject;
421+
function generateTag(
422+
tagName: 'script',
423+
input: ComponentPropsWithoutRef<'script'>,
424+
group?: string
425+
): CustomHeadTagObject;
426+
function generateTag(
427+
tagName: TagKey,
428+
input: MetaTagProps,
408429
group?: string
409-
): T | T[] {
410-
const tag = {tag: tagName, props: {}} as T;
430+
): CustomHeadTagObject {
431+
const tag: CustomHeadTagObject = {tag: tagName, props: {}, key: ''};
411432

412433
// title tags don't have props so move to children
413434
if (tagName === 'title') {
414-
tag.children = input;
435+
tag.children = JSON.stringify(input);
415436
tag.key = generateKey(tag);
416437

417438
return tag;
418439
}
419440

420441
// also move the input children to children and delete it
421442
if (tagName === 'script') {
422-
tag.children = input.children;
443+
tag.children = JSON.stringify(input.children);
423444

424445
delete input.children;
425446
}
@@ -437,7 +458,7 @@ function generateTag<T extends HeadTag>(
437458
return tag;
438459
}
439460

440-
function generateKey(tag: HeadTag, group?: string) {
461+
function generateKey(tag: CustomHeadTagObject, group?: string) {
441462
const {tag: tagName, props} = tag;
442463

443464
if (tagName === 'title') {
@@ -469,8 +490,12 @@ function generateKey(tag: HeadTag, group?: string) {
469490
return `${tagName}-${props.type}`;
470491
}
471492

472-
function renderTitle<T extends HeadTag['children']>(
473-
template?: string | ((title?: string) => string | undefined),
493+
function renderTitle<T extends CustomHeadTagObject['children']>(
494+
template?:
495+
| string
496+
| ((title?: string) => string | undefined)
497+
| undefined
498+
| null,
474499
title?: T
475500
): string | undefined {
476501
if (!template) {

0 commit comments

Comments
 (0)