Skip to content

Commit

Permalink
Merge branch 'canary' into rsc-error-editor-links
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] committed Jan 24, 2023
2 parents 21e623b + f9c1046 commit 54d4831
Show file tree
Hide file tree
Showing 24 changed files with 443 additions and 91 deletions.
4 changes: 3 additions & 1 deletion .github/actions/next-stats-action/src/prepare/repo-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ module.exports = (actionInfo) => {
})

const pkgPaths = new Map()
const pkgs = await fs.readdir(path.join(repoDir, 'packages'))
const pkgs = (await fs.readdir(path.join(repoDir, 'packages'))).filter(
(item) => !item.startsWith('.')
)

pkgs.forEach((pkgDirname) => {
const { name } = require(path.join(
Expand Down
2 changes: 1 addition & 1 deletion lint-staged.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const eslint = new ESLint()
const isWin = process.platform === 'win32'

module.exports = {
'**/*.{js,jsx,ts,tsx}': (filenames) => {
'**/*.{js,jsx,mjs,ts,tsx,mts}': (filenames) => {
const escapedFileNames = filenames
.map((filename) => (isWin ? filename : escape([filename])))
.join(' ')
Expand Down
11 changes: 10 additions & 1 deletion packages/create-next-app/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@ export const installTemplate = async ({
/**
* Default dependencies.
*/
const dependencies = ['react', 'react-dom', 'next', '@next/font']
const dependencies = [
'react',
'react-dom',
`next${
process.env.NEXT_PRIVATE_TEST_VERSION
? `@${process.env.NEXT_PRIVATE_TEST_VERSION}`
: ''
}`,
'@next/font',
]
/**
* TypeScript projects will have type definitions and other devDependencies.
*/
Expand Down
12 changes: 9 additions & 3 deletions packages/next/src/client/use-intersection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import {
requestIdleCallback,
cancelIdleCallback,
Expand Down Expand Up @@ -101,12 +101,17 @@ export function useIntersection<T extends Element>({
const isDisabled: boolean = disabled || !hasIntersectionObserver

const [visible, setVisible] = useState(false)
const [element, setElement] = useState<T | null>(null)
// const [element, setElement] = useState<T | null>(null)
const elementRef = useRef<T | null>(null)
const setElement = useCallback((element: T | null) => {
elementRef.current = element
}, [])

useEffect(() => {
if (hasIntersectionObserver) {
if (isDisabled || visible) return

const element = elementRef.current
if (element && element.tagName) {
const unobserve = observe(
element,
Expand All @@ -122,7 +127,8 @@ export function useIntersection<T extends Element>({
return () => cancelIdleCallback(idleCallback)
}
}
}, [element, isDisabled, rootMargin, rootRef, visible])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDisabled, rootMargin, rootRef, visible, elementRef.current])

const resetVisible = useCallback(() => {
setVisible(false)
Expand Down
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 './meta'

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

import React from 'react'

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

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

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 = icons.shortcut
const iconList = icons.icon
const appleList = icons.apple
const otherList = 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 './meta'

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 | null
): undefined | T[] {
if (typeof value === 'undefined' || value === null) {
return undefined
}
if (Array.isArray(value)) {
return value
}
return [value]
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
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 }) {
if (!metadata) return null

const resolved: ResolvedMetadata = await resolveMetadata(metadata)
return (
<>
<ResolvedBasicMetadata metadata={resolved} />
<ResolvedAlternatesMetadata metadata={resolved} />
<ResolvedOpenGraphMetadata openGraph={resolved.openGraph} />
<ResolvedIconsMetadata icons={resolved.icons} />
</>
)
}
79 changes: 63 additions & 16 deletions packages/next/src/lib/metadata/resolve-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import type {
} from './types/metadata-interface'
import type { Viewport } from './types/extra-types'
import type { ResolvedTwitterMetadata } from './types/twitter-types'
import type { AbsoluteTemplateString } from './types/metadata-types'
import type {
AbsoluteTemplateString,
Icon,
IconDescriptor,
Icons,
} from './types/metadata-types'
import { createDefaultMetadata } from './default-metadata'
import { resolveOpenGraph } from './resolve-opengraph'
import { mergeTitle } from './resolve-title'
import { resolveAsArrayOrUndefined } from './generate/utils'

const viewPortKeys = {
width: 'width',
Expand Down Expand Up @@ -48,6 +54,57 @@ 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 isUrlIcon(icon: any): icon is string | URL {
return typeof icon === 'string' || icon instanceof URL
}

function resolveIcon(icon: Icon): IconDescriptor {
if (isUrlIcon(icon)) return { url: icon }
else if (Array.isArray(icon)) return icon
return icon
}

const IconKeys = ['icon', 'shortcut', 'apple', 'other'] as (keyof Icons)[]

function resolveIcons(icons: Metadata['icons']): ResolvedMetadata['icons'] {
if (!icons) {
return null
}

const resolved: ResolvedMetadata['icons'] = {}
if (Array.isArray(icons)) {
resolved.icon = icons.map(resolveIcon).filter(Boolean)
} else if (isUrlIcon(icons)) {
resolved.icon = [resolveIcon(icons)]
} else {
for (const key of IconKeys) {
const values = resolveAsArrayOrUndefined(icons[key])
if (values) resolved[key] = values.map(resolveIcon)
}
}
return resolved
}

// Merge the source metadata into the resolved target metadata.
function merge(
target: ResolvedMetadata,
Expand Down Expand Up @@ -94,21 +151,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
6 changes: 4 additions & 2 deletions packages/next/src/lib/metadata/types/metadata-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import type {
ColorSchemeEnum,
Icon,
Icons,
IconURL,
ReferrerEnum,
ResolvedIcons,
Robots,
TemplateString,
Verification,
Expand Down Expand Up @@ -56,7 +58,7 @@ export interface Metadata {

// Defaults to rel="icon" but the Icons type can be used
// to get more specific about rel types
icons?: null | Array<Icon> | Icons
icons?: null | IconURL | Array<Icon> | Icons

openGraph?: null | OpenGraph

Expand Down Expand Up @@ -145,7 +147,7 @@ export interface ResolvedMetadata {

// Defaults to rel="icon" but the Icons type can be used
// to get more specific about rel types
icons: null | Icons
icons: null | ResolvedIcons

openGraph: null | ResolvedOpenGraph

Expand Down
24 changes: 16 additions & 8 deletions packages/next/src/lib/metadata/types/metadata-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export type Robots = {
googleBot?: string | Robots
}

export type Icon = string | IconDescriptor | URL
export type IconURL = string | URL
export type Icon = IconURL | IconDescriptor
export type IconDescriptor = {
url: string | URL
type?: string
Expand All @@ -74,20 +75,27 @@ export type IconDescriptor = {
}
export type Icons = {
// rel="icon"
icon?: Icon | Array<Icon>
icon?: Icon | Icon[]
// rel="shortcut icon"
shortcut?: Icon | Array<Icon>
shortcut?: Icon | Icon[]
// rel="apple-touch-icon"
apple?: Icon | Array<Icon>
apple?: Icon | Icon[]
// rel inferred from descriptor, defaults to "icon"
other?: Icon | Array<Icon>
other?: IconDescriptor | IconDescriptor[]
}

export type Verification = {
google?: null | string | number | Array<string | number>
yahoo?: null | string | number | Array<string | number>
google?: null | string | number | (string | number)[]
yahoo?: null | string | number | (string | number)[]
// if you ad-hoc additional verification
other?: {
[name: string]: string | number | Array<string | number>
[name: string]: string | number | (string | number)[]
}
}

export type ResolvedIcons = {
icon?: IconDescriptor[]
shortcut?: IconDescriptor[]
apple?: IconDescriptor[]
other?: IconDescriptor[]
}

0 comments on commit 54d4831

Please sign in to comment.