Skip to content

Commit

Permalink
Support metadata icons field
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Jan 20, 2023
1 parent c30c14d commit 7fcbcda
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 43 deletions.
2 changes: 1 addition & 1 deletion packages/next/src/lib/metadata/generate/basic.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ResolvedMetadata } from '../types/metadata-interface'

import React from 'react'
import { Meta } from './utils'
import { Meta } from './ui'

export function ResolvedBasicMetadata({
metadata,
Expand Down
76 changes: 76 additions & 0 deletions packages/next/src/lib/metadata/generate/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { ResolvedMetadata } from '../types/metadata-interface'
import type { Icon, IconDescriptor } from '../types/metadata-types'

import React from 'react'
import { resolveAsArrayOrUndefined } from './utils'

const resolveUrl = (url: string | URL) =>
typeof url === 'string' ? url : url.toString()

function IconDescriptor({ icon }: { icon: IconDescriptor }) {
const { url, rel = 'icon', ...props } = icon

if (process.env.NODE_ENV !== 'production') {
if (!url) {
throw new Error('`url` in required for descriptor in `metadata.icons`')
}
}

return <link rel={rel} href={resolveUrl(url)} {...props} />
}

function IconLink({ rel, icon }: { rel?: string; icon: Icon }) {
if (typeof icon === 'object' && !(icon instanceof URL)) {
if (rel) icon.rel = rel
return <IconDescriptor icon={icon} />
} else {
const href = resolveUrl(icon)
return <link rel={rel} href={href} />
}
}

export function ResolvedIconsMetadata({
icons,
}: {
icons: ResolvedMetadata['icons']
}) {
if (!icons) return null

const shortcutList = resolveAsArrayOrUndefined(icons.shortcut)
const iconList = resolveAsArrayOrUndefined(icons.icon)
const appleList = resolveAsArrayOrUndefined(icons.apple)
const otherList = resolveAsArrayOrUndefined(icons.other)

return (
<>
{shortcutList
? shortcutList.map((icon, index) => (
<IconLink
key={`shortcut-${index}`}
rel="shortcut icon"
icon={icon}
/>
))
: null}
{iconList
? iconList.map((icon, index) => (
<IconLink key={`shortcut-${index}`} rel="icon" icon={icon} />
))
: null}
{appleList
? appleList.map((icon, index) => (
<IconLink
key={`apple-${index}`}
rel="apple-touch-icon"
icon={icon}
/>
))
: null}
{otherList
? otherList.map((icon, index) => (
<IconDescriptor key={`other-${index}`} icon={icon} />
))
: null}
</>
)
}
2 changes: 1 addition & 1 deletion packages/next/src/lib/metadata/generate/opengraph.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ResolvedMetadata } from '../types/metadata-interface'

import React from 'react'
import { Meta, MultiMeta } from './utils'
import { Meta, MultiMeta } from './ui'

export function ResolvedOpenGraphMetadata({
openGraph,
Expand Down
11 changes: 11 additions & 0 deletions packages/next/src/lib/metadata/generate/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function resolveAsArrayOrUndefined<T = any>(
value: T | T[] | undefined
): undefined | T[] {
if (typeof value === 'undefined' || value === null) {
return undefined
}
if (Array.isArray(value)) {
return value
}
return [value]
}
56 changes: 41 additions & 15 deletions packages/next/src/lib/metadata/resolve-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,11 @@ function merge(
break
}
case 'viewport': {
let content: string | null = null
const { viewport } = source
if (typeof viewport === 'string') {
content = viewport
} else if (viewport) {
content = ''
for (const viewportKey_ in viewPortKeys) {
const viewportKey = viewportKey_ as keyof Viewport
if (viewport[viewportKey]) {
if (content) content += ', '
content += `${viewPortKeys[viewportKey]}=${viewport[viewportKey]}`
}
}
}
target.viewport = content
target.viewport = resolveViewport(source.viewport)
break
}
case 'icons': {
target.icons = resolveIcons(source.icons)
break
}
default: {
Expand All @@ -121,6 +111,42 @@ function merge(
}
}

function resolveViewport(
viewport: Metadata['viewport']
): ResolvedMetadata['viewport'] {
let resolved: ResolvedMetadata['viewport'] = null

if (typeof viewport === 'string') {
resolved = viewport
} else if (viewport) {
resolved = ''
for (const viewportKey_ in viewPortKeys) {
const viewportKey = viewportKey_ as keyof Viewport
if (viewport[viewportKey]) {
if (resolved) resolved += ', '
resolved += `${viewPortKeys[viewportKey]}=${viewport[viewportKey]}`
}
}
}
return resolved
}

function resolveIcons(icons: Metadata['icons']): ResolvedMetadata['icons'] {
let resolved: ResolvedMetadata['icons'] = null
if (icons == null) {
resolved = null
} else {
if (Array.isArray(icons) || typeof icons === 'string') {
resolved = {
icon: icons,
}
} else {
resolved = icons
}
}
return resolved
}

export async function resolveMetadata(metadataItems: Item[]) {
const resolvedMetadata = createDefaultMetadata()

Expand Down
11 changes: 1 addition & 10 deletions packages/next/src/lib/metadata/resolve-opengraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
OpenGraph,
ResolvedOpenGraph,
} from './types/opengraph-types'
import { resolveAsArrayOrUndefined } from './generate/utils'

const OgTypFields = {
article: ['authors', 'tags'],
Expand All @@ -22,16 +23,6 @@ const OgTypFields = {
],
} as const

function resolveAsArrayOrUndefined<T = any>(value: T): undefined | any[] {
if (typeof value === 'undefined' || value === null) {
return undefined
}
if (Array.isArray(value)) {
return value
}
return [value]
}

function getFieldsByOgType(ogType: OpenGraphType | undefined) {
switch (ogType) {
case 'article':
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/lib/metadata/types/metadata-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export type Icons = {
// rel="apple-touch-icon"
apple?: Icon | Array<Icon>
// rel inferred from descriptor, defaults to "icon"
other?: Icon | Array<Icon>
other?: IconDescriptor | Array<IconDescriptor>
}

export type Verification = {
Expand Down
6 changes: 4 additions & 2 deletions packages/next/src/lib/metadata/ui.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react'

import type { ResolvedMetadata } from './types/metadata-interface'

import React from 'react'
import { ResolvedBasicMetadata } from './generate/basic'
import { ResolvedAlternatesMetadata } from './generate/alternate'
import { ResolvedOpenGraphMetadata } from './generate/opengraph'
import { resolveMetadata } from './resolve-metadata'
import { ResolvedIconsMetadata } from './generate/icons'

// Generate the actual React elements from the resolved metadata.
export async function Metadata({ metadata }: { metadata: any }) {
Expand All @@ -14,6 +15,7 @@ export async function Metadata({ metadata }: { metadata: any }) {
<ResolvedBasicMetadata metadata={resolved} />
<ResolvedAlternatesMetadata metadata={resolved} />
<ResolvedOpenGraphMetadata openGraph={resolved.openGraph} />
<ResolvedIconsMetadata icons={resolved.icons} />
</>
)
}
20 changes: 20 additions & 0 deletions test/e2e/app-dir/metadata/app/icons/descriptor/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default function page() {
return 'icons'
}

export const metadata = {
icons: {
icon: [{ url: '/icon.png' }, new URL('/icon.png', 'https://example.com')],
shortcut: ['/shortcut-icon.png'],
apple: [
{ url: '/apple-icon.png' },
{ url: '/apple-icon-x3.png', sizes: '180x180', type: 'image/png' },
],
other: [
{
rel: 'apple-touch-icon-precomposed',
url: '/apple-touch-icon-precomposed.png',
},
],
},
}
15 changes: 15 additions & 0 deletions test/e2e/app-dir/metadata/app/icons/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function page() {
return 'icons'
}

export const metadata = {
icons: {
icon: '/icon.png',
shortcut: '/shortcut-icon.png',
apple: '/apple-icon.png',
other: {
rel: 'apple-touch-icon-precomposed',
url: '/apple-touch-icon-precomposed.png',
},
},
}
7 changes: 7 additions & 0 deletions test/e2e/app-dir/metadata/app/icons/string/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function page() {
return 'icons'
}

export const metadata = {
icons: '/icon.png',
}

0 comments on commit 7fcbcda

Please sign in to comment.