Skip to content

Commit 259cbc1

Browse files
authored
Revert "Revert "Initial support for metadata (#44729)"" (#45113)
1 parent 0a5d272 commit 259cbc1

File tree

37 files changed

+2318
-45
lines changed

37 files changed

+2318
-45
lines changed

packages/next/src/build/webpack/loaders/next-app-loader.ts

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { sep } from 'path'
77
import { verifyRootLayout } from '../../../lib/verifyRootLayout'
88
import * as Log from '../../../build/output/log'
99
import { APP_DIR_ALIAS } from '../../../lib/constants'
10+
import { resolveFileBasedMetadataForLoader } from '../../../lib/metadata/resolve-metadata'
1011

1112
const FILE_TYPES = {
1213
layout: 'layout',
@@ -36,21 +37,26 @@ async function createTreeCodeFromPath({
3637
resolveParallelSegments,
3738
}: {
3839
pagePath: string
39-
resolve: (pathname: string) => Promise<string | undefined>
40+
resolve: (
41+
pathname: string,
42+
resolveDir?: boolean
43+
) => Promise<string | undefined>
4044
resolveParallelSegments: (
4145
pathname: string
4246
) => [key: string, segment: string][]
4347
}) {
4448
const splittedPath = pagePath.split(/[\\/]/)
4549
const appDirPrefix = splittedPath[0]
4650
const pages: string[] = []
51+
4752
let rootLayout: string | undefined
4853
let globalError: string | undefined
4954

5055
async function createSubtreePropsFromSegmentPath(
5156
segments: string[]
5257
): Promise<{
5358
treeCode: string
59+
treeMetadataCode: string
5460
}> {
5561
const segmentPath = segments.join('/')
5662

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

74+
let metadataCode = ''
75+
6876
for (const [parallelKey, parallelSegment] of parallelSegments) {
6977
if (parallelSegment === PAGE_SEGMENT) {
7078
const matchedPagePath = `${appDirPrefix}${segmentPath}/page`
7179
const resolvedPagePath = await resolve(matchedPagePath)
7280
if (resolvedPagePath) pages.push(resolvedPagePath)
7381

82+
metadataCode += `{
83+
type: 'page',
84+
layer: ${
85+
// There's an extra virtual segment.
86+
segments.length - 1
87+
},
88+
mod: () => import(/* webpackMode: "eager" */ ${JSON.stringify(
89+
resolvedPagePath
90+
)}),
91+
path: ${JSON.stringify(resolvedPagePath)},
92+
},`
93+
7494
// Use '' for segment as it's the page. There can't be a segment called '' so this is the safest way to add it.
7595
props[parallelKey] = `['', {}, {
7696
page: [() => import(/* webpackMode: "eager" */ ${JSON.stringify(
@@ -80,9 +100,8 @@ async function createTreeCodeFromPath({
80100
}
81101

82102
const parallelSegmentPath = segmentPath + '/' + parallelSegment
83-
const { treeCode: subtreeCode } = await createSubtreePropsFromSegmentPath(
84-
[...segments, parallelSegment]
85-
)
103+
const { treeCode: subtreeCode, treeMetadataCode: subTreeMetadataCode } =
104+
await createSubtreePropsFromSegmentPath([...segments, parallelSegment])
86105

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

123+
// Collect metadata for the layout
124+
if (layoutPath) {
125+
metadataCode += `{
126+
type: 'layout',
127+
layer: ${segments.length},
128+
mod: () => import(/* webpackMode: "eager" */ ${JSON.stringify(
129+
layoutPath
130+
)}),
131+
path: ${JSON.stringify(layoutPath)},
132+
},`
133+
}
134+
metadataCode += await resolveFileBasedMetadataForLoader(
135+
segments.length,
136+
(await resolve(`${appDirPrefix}${parallelSegmentPath}/`, true))!
137+
)
138+
metadataCode += subTreeMetadataCode
139+
140+
if (!rootLayout) {
141+
rootLayout = layoutPath
142+
}
143+
104144
if (!globalError) {
105145
globalError = await resolve(
106146
`${appDirPrefix}${parallelSegmentPath}/${GLOBAL_ERROR_FILE_TYPE}`
@@ -133,13 +173,16 @@ async function createTreeCodeFromPath({
133173
.map(([key, value]) => `${key}: ${value}`)
134174
.join(',\n')}
135175
}`,
176+
treeMetadataCode: metadataCode,
136177
}
137178
}
138179

139-
const { treeCode } = await createSubtreePropsFromSegmentPath([])
180+
const { treeCode, treeMetadataCode } =
181+
await createSubtreePropsFromSegmentPath([])
140182
return {
141183
treeCode: `const tree = ${treeCode}.children;`,
142-
pages,
184+
treeMetadataCode: `const metadata = [${treeMetadataCode}];`,
185+
pages: `const pages = ${JSON.stringify(pages)};`,
143186
rootLayout,
144187
globalError,
145188
}
@@ -197,7 +240,7 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
197240
const rest = path.slice(pathname.length + 1).split('/')
198241

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

215-
const resolver = async (pathname: string) => {
258+
const resolver = async (pathname: string, resolveDir?: boolean) => {
259+
if (resolveDir) {
260+
return createAbsolutePath(appDir, pathname)
261+
}
262+
216263
try {
217264
const resolved = await resolve(this.rootContext, pathname)
218265
this.addDependency(resolved)
@@ -230,12 +277,17 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
230277
}
231278
}
232279

233-
const { treeCode, pages, rootLayout, globalError } =
234-
await createTreeCodeFromPath({
235-
pagePath,
236-
resolve: resolver,
237-
resolveParallelSegments,
238-
})
280+
const {
281+
treeCode,
282+
treeMetadataCode,
283+
pages: pageListCode,
284+
rootLayout,
285+
globalError,
286+
} = await createTreeCodeFromPath({
287+
pagePath,
288+
resolve: resolver,
289+
resolveParallelSegments,
290+
})
239291

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

264316
const result = `
265317
export ${treeCode}
266-
export const pages = ${JSON.stringify(pages)}
318+
export ${treeMetadataCode}
319+
export ${pageListCode}
267320
268321
export { default as AppRouter } from 'next/dist/client/components/app-router'
269322
export { default as LayoutRouter } from 'next/dist/client/components/layout-router'

packages/next/src/build/webpack/plugins/flight-types-plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ interface IEntry {
6464
? "runtime?: 'nodejs' | 'experimental-edge' | 'edge'"
6565
: ''
6666
}
67+
metadata?: any
6768
}
6869
6970
// =============

packages/next/src/client/components/head.tsx

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { ResolvedMetadata } from './types/metadata-interface'
2+
3+
export const createDefaultMetadata = (): ResolvedMetadata => {
4+
return {
5+
viewport: 'width=device-width, initial-scale=1',
6+
7+
// Other values are all null
8+
metadataBase: null,
9+
title: null,
10+
description: null,
11+
applicationName: null,
12+
authors: null,
13+
generator: null,
14+
keywords: null,
15+
referrer: null,
16+
themeColor: null,
17+
colorScheme: null,
18+
creator: null,
19+
publisher: null,
20+
robots: null,
21+
alternates: {
22+
canonical: null,
23+
languages: {},
24+
},
25+
icons: null,
26+
openGraph: null,
27+
twitter: null,
28+
verification: {},
29+
appleWebApp: null,
30+
formatDetection: null,
31+
itunes: null,
32+
abstract: null,
33+
appLinks: null,
34+
archives: null,
35+
assets: null,
36+
bookmarks: null,
37+
category: null,
38+
classification: null,
39+
other: {},
40+
}
41+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { ResolvedMetadata } from '../types/metadata-interface'
2+
3+
import React from 'react'
4+
5+
export function ResolvedAlternatesMetadata({
6+
metadata,
7+
}: {
8+
metadata: ResolvedMetadata
9+
}) {
10+
return (
11+
<>
12+
{metadata.alternates.canonical ? (
13+
<link rel="canonical" href={metadata.alternates.canonical.toString()} />
14+
) : null}
15+
{Object.entries(metadata.alternates.languages).map(([locale, url]) =>
16+
url ? (
17+
<link
18+
key={locale}
19+
rel="alternate"
20+
hrefLang={locale}
21+
href={url.toString()}
22+
/>
23+
) : null
24+
)}
25+
{metadata.alternates.media
26+
? Object.entries(metadata.alternates.media).map(([media, url]) =>
27+
url ? (
28+
<link
29+
key={media}
30+
rel="alternate"
31+
media={media}
32+
href={url.toString()}
33+
/>
34+
) : null
35+
)
36+
: null}
37+
{metadata.alternates.types
38+
? Object.entries(metadata.alternates.types).map(([type, url]) =>
39+
url ? (
40+
<link
41+
key={type}
42+
rel="alternate"
43+
type={type}
44+
href={url.toString()}
45+
/>
46+
) : null
47+
)
48+
: null}
49+
</>
50+
)
51+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { ResolvedMetadata } from '../types/metadata-interface'
2+
3+
import React from 'react'
4+
import { Meta } from './utils'
5+
6+
export function ResolvedBasicMetadata({
7+
metadata,
8+
}: {
9+
metadata: ResolvedMetadata
10+
}) {
11+
return (
12+
<>
13+
<meta charSet="utf-8" />
14+
{metadata.title !== null ? (
15+
<title>{metadata.title.absolute}</title>
16+
) : null}
17+
<Meta name="description" content={metadata.description} />
18+
<Meta name="application-name" content={metadata.applicationName} />
19+
<Meta name="author" content={metadata.authors?.join(',')} />
20+
<Meta name="generator" content={metadata.generator} />
21+
<Meta name="keywords" content={metadata.keywords?.join(',')} />
22+
<Meta name="referrer" content={metadata.referrer} />
23+
<Meta name="theme-color" content={metadata.themeColor} />
24+
<Meta name="color-scheme" content={metadata.colorScheme} />
25+
<Meta name="viewport" content={metadata.viewport} />
26+
<Meta name="creator" content={metadata.creator} />
27+
<Meta name="publisher" content={metadata.publisher} />
28+
<Meta name="robots" content={metadata.robots} />
29+
<Meta name="abstract" content={metadata.abstract} />
30+
{metadata.archives
31+
? metadata.archives.map((archive) => (
32+
<link rel="archives" href={archive} key={archive} />
33+
))
34+
: null}
35+
{metadata.assets
36+
? metadata.assets.map((asset) => (
37+
<link rel="assets" href={asset} key={asset} />
38+
))
39+
: null}
40+
{metadata.bookmarks
41+
? metadata.bookmarks.map((bookmark) => (
42+
<link rel="bookmarks" href={bookmark} key={bookmark} />
43+
))
44+
: null}
45+
<Meta name="category" content={metadata.category} />
46+
<Meta name="classification" content={metadata.classification} />
47+
{Object.entries(metadata.other).map(([name, content]) => (
48+
<Meta
49+
key={name}
50+
name={name}
51+
content={Array.isArray(content) ? content.join(',') : content}
52+
/>
53+
))}
54+
</>
55+
)
56+
}

0 commit comments

Comments
 (0)