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

Revert "Revert "Revert "Revert "Initial support for metadata (#44729)"" (#45113)"" #45196

Merged
merged 3 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
83 changes: 68 additions & 15 deletions packages/next/src/build/webpack/loaders/next-app-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { sep } from 'path'
import { verifyRootLayout } from '../../../lib/verifyRootLayout'
import * as Log from '../../../build/output/log'
import { APP_DIR_ALIAS } from '../../../lib/constants'
import { resolveFileBasedMetadataForLoader } from '../../../lib/metadata/resolve-metadata'

const FILE_TYPES = {
layout: 'layout',
Expand Down Expand Up @@ -36,21 +37,26 @@ async function createTreeCodeFromPath({
resolveParallelSegments,
}: {
pagePath: string
resolve: (pathname: string) => Promise<string | undefined>
resolve: (
pathname: string,
resolveDir?: boolean
) => Promise<string | undefined>
resolveParallelSegments: (
pathname: string
) => [key: string, segment: string][]
}) {
const splittedPath = pagePath.split(/[\\/]/)
const appDirPrefix = splittedPath[0]
const pages: string[] = []

let rootLayout: string | undefined
let globalError: string | undefined

async function createSubtreePropsFromSegmentPath(
segments: string[]
): Promise<{
treeCode: string
treeMetadataCode: string
}> {
const segmentPath = segments.join('/')

Expand All @@ -65,12 +71,26 @@ async function createTreeCodeFromPath({
parallelSegments.push(...resolveParallelSegments(segmentPath))
}

let metadataCode = ''

for (const [parallelKey, parallelSegment] of parallelSegments) {
if (parallelSegment === PAGE_SEGMENT) {
const matchedPagePath = `${appDirPrefix}${segmentPath}/page`
const resolvedPagePath = await resolve(matchedPagePath)
if (resolvedPagePath) pages.push(resolvedPagePath)

metadataCode += `{
type: 'page',
layer: ${
// There's an extra virtual segment.
segments.length - 1
},
mod: () => import(/* webpackMode: "eager" */ ${JSON.stringify(
resolvedPagePath
)}),
path: ${JSON.stringify(resolvedPagePath)},
},`

// Use '' for segment as it's the page. There can't be a segment called '' so this is the safest way to add it.
props[parallelKey] = `['', {}, {
page: [() => import(/* webpackMode: "eager" */ ${JSON.stringify(
Expand All @@ -80,9 +100,8 @@ async function createTreeCodeFromPath({
}

const parallelSegmentPath = segmentPath + '/' + parallelSegment
const { treeCode: subtreeCode } = await createSubtreePropsFromSegmentPath(
[...segments, parallelSegment]
)
const { treeCode: subtreeCode, treeMetadataCode: subTreeMetadataCode } =
await createSubtreePropsFromSegmentPath([...segments, parallelSegment])

// `page` is not included here as it's added above.
const filePaths = await Promise.all(
Expand All @@ -101,6 +120,27 @@ async function createTreeCodeFromPath({
rootLayout = layoutPath
}

// Collect metadata for the layout
if (layoutPath) {
metadataCode += `{
type: 'layout',
layer: ${segments.length},
mod: () => import(/* webpackMode: "eager" */ ${JSON.stringify(
layoutPath
)}),
path: ${JSON.stringify(layoutPath)},
},`
}
metadataCode += await resolveFileBasedMetadataForLoader(
segments.length,
(await resolve(`${appDirPrefix}${parallelSegmentPath}/`, true))!
)
metadataCode += subTreeMetadataCode

if (!rootLayout) {
rootLayout = layoutPath
}

if (!globalError) {
globalError = await resolve(
`${appDirPrefix}${parallelSegmentPath}/${GLOBAL_ERROR_FILE_TYPE}`
Expand Down Expand Up @@ -133,13 +173,16 @@ async function createTreeCodeFromPath({
.map(([key, value]) => `${key}: ${value}`)
.join(',\n')}
}`,
treeMetadataCode: metadataCode,
}
}

const { treeCode } = await createSubtreePropsFromSegmentPath([])
const { treeCode, treeMetadataCode } =
await createSubtreePropsFromSegmentPath([])
return {
treeCode: `const tree = ${treeCode}.children;`,
pages,
treeMetadataCode: `const metadata = [${treeMetadataCode}];`,
pages: `const pages = ${JSON.stringify(pages)};`,
rootLayout,
globalError,
}
Expand Down Expand Up @@ -197,7 +240,7 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
const rest = path.slice(pathname.length + 1).split('/')

let matchedSegment = rest[0]
// It is the actual page, mark it sepcially.
// It is the actual page, mark it specially.
if (rest.length === 1 && matchedSegment === 'page') {
matchedSegment = PAGE_SEGMENT
}
Expand All @@ -212,7 +255,11 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
return Object.entries(matched)
}

const resolver = async (pathname: string) => {
const resolver = async (pathname: string, resolveDir?: boolean) => {
if (resolveDir) {
return createAbsolutePath(appDir, pathname)
}

try {
const resolved = await resolve(this.rootContext, pathname)
this.addDependency(resolved)
Expand All @@ -230,12 +277,17 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
}
}

const { treeCode, pages, rootLayout, globalError } =
await createTreeCodeFromPath({
pagePath,
resolve: resolver,
resolveParallelSegments,
})
const {
treeCode,
treeMetadataCode,
pages: pageListCode,
rootLayout,
globalError,
} = await createTreeCodeFromPath({
pagePath,
resolve: resolver,
resolveParallelSegments,
})

if (!rootLayout) {
const errorMessage = `${chalk.bold(
Expand Down Expand Up @@ -263,7 +315,8 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{

const result = `
export ${treeCode}
export const pages = ${JSON.stringify(pages)}
export ${treeMetadataCode}
export ${pageListCode}

export { default as AppRouter } from 'next/dist/client/components/app-router'
export { default as LayoutRouter } from 'next/dist/client/components/layout-router'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ interface IEntry {
? "runtime?: 'nodejs' | 'experimental-edge' | 'edge'"
: ''
}
metadata?: any
}

// =============
Expand Down
10 changes: 0 additions & 10 deletions packages/next/src/client/components/head.tsx

This file was deleted.

41 changes: 41 additions & 0 deletions packages/next/src/lib/metadata/default-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { ResolvedMetadata } from './types/metadata-interface'

export const createDefaultMetadata = (): ResolvedMetadata => {
return {
viewport: 'width=device-width, initial-scale=1',

// Other values are all null
metadataBase: null,
title: null,
description: null,
applicationName: null,
authors: null,
generator: null,
keywords: null,
referrer: null,
themeColor: null,
colorScheme: null,
creator: null,
publisher: null,
robots: null,
alternates: {
canonical: null,
languages: {},
},
icons: null,
openGraph: null,
twitter: null,
verification: {},
appleWebApp: null,
formatDetection: null,
itunes: null,
abstract: null,
appLinks: null,
archives: null,
assets: null,
bookmarks: null,
category: null,
classification: null,
other: {},
}
}
51 changes: 51 additions & 0 deletions packages/next/src/lib/metadata/generate/alternate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { ResolvedMetadata } from '../types/metadata-interface'

import React from 'react'

export function ResolvedAlternatesMetadata({
metadata,
}: {
metadata: ResolvedMetadata
}) {
return (
<>
{metadata.alternates.canonical ? (
<link rel="canonical" href={metadata.alternates.canonical.toString()} />
) : null}
{Object.entries(metadata.alternates.languages).map(([locale, url]) =>
url ? (
<link
key={locale}
rel="alternate"
hrefLang={locale}
href={url.toString()}
/>
) : null
)}
{metadata.alternates.media
? Object.entries(metadata.alternates.media).map(([media, url]) =>
url ? (
<link
key={media}
rel="alternate"
media={media}
href={url.toString()}
/>
) : null
)
: null}
{metadata.alternates.types
? Object.entries(metadata.alternates.types).map(([type, url]) =>
url ? (
<link
key={type}
rel="alternate"
type={type}
href={url.toString()}
/>
) : null
)
: null}
</>
)
}
56 changes: 56 additions & 0 deletions packages/next/src/lib/metadata/generate/basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { ResolvedMetadata } from '../types/metadata-interface'

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

export function ResolvedBasicMetadata({
metadata,
}: {
metadata: ResolvedMetadata
}) {
return (
<>
<meta charSet="utf-8" />
{metadata.title !== null ? (
<title>{metadata.title.absolute}</title>
) : null}
<Meta name="description" content={metadata.description} />
<Meta name="application-name" content={metadata.applicationName} />
<Meta name="author" content={metadata.authors?.join(',')} />
<Meta name="generator" content={metadata.generator} />
<Meta name="keywords" content={metadata.keywords?.join(',')} />
<Meta name="referrer" content={metadata.referrer} />
<Meta name="theme-color" content={metadata.themeColor} />
<Meta name="color-scheme" content={metadata.colorScheme} />
<Meta name="viewport" content={metadata.viewport} />
<Meta name="creator" content={metadata.creator} />
<Meta name="publisher" content={metadata.publisher} />
<Meta name="robots" content={metadata.robots} />
<Meta name="abstract" content={metadata.abstract} />
{metadata.archives
? metadata.archives.map((archive) => (
<link rel="archives" href={archive} key={archive} />
))
: null}
{metadata.assets
? metadata.assets.map((asset) => (
<link rel="assets" href={asset} key={asset} />
))
: null}
{metadata.bookmarks
? metadata.bookmarks.map((bookmark) => (
<link rel="bookmarks" href={bookmark} key={bookmark} />
))
: null}
<Meta name="category" content={metadata.category} />
<Meta name="classification" content={metadata.classification} />
{Object.entries(metadata.other).map(([name, content]) => (
<Meta
key={name}
name={name}
content={Array.isArray(content) ? content.join(',') : content}
/>
))}
</>
)
}