Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support metadata icons field #45105

Merged
merged 8 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 IconDescriptorLink({ 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`')
}
}
huozhi marked this conversation as resolved.
Show resolved Hide resolved

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 <IconDescriptorLink 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) => (
<IconDescriptorLink 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 @@ -48,6 +48,42 @@ type Item =
path?: string
}

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
}

// Merge the source metadata into the resolved target metadata.
function merge(
target: ResolvedMetadata,
Expand Down Expand Up @@ -94,21 +130,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 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',
}