From facea99f0e11b3164ecdfa6e9fb0daa967de468d Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Wed, 29 Mar 2023 13:16:47 +0200 Subject: [PATCH] Expose metadata types (#47630) ### What? - Besides existing `Metadata` type, expose `ResolvingMetadata` and `ResolvedMetadata` to `'next'`. - Group `Robots` / `Sitemap` / `Manifest` these dynamic data routes types as `MetadataRoute` type and expose to `'next'` ### Why? Allow users to type the more API easily, and grouping them to avoid type conflicts (e.g. we have `Robots` in metadata interface field, `MetadataRoute.Robots` can avoid conflicts) ### How? Closes NEXT-908 --- .../metadata/resolve-route-data.test.ts | 8 +++--- .../loaders/metadata/resolve-route-data.ts | 25 +++++++++---------- .../lib/metadata/types/metadata-interface.ts | 13 +++++++--- packages/next/types/index.d.ts | 8 ++++-- .../metadata-dynamic-routes/app/manifest.ts | 4 ++- .../metadata-dynamic-routes/app/robots.ts | 4 ++- .../metadata-dynamic-routes/app/sitemap.ts | 4 ++- .../app/type-checking/generate-async/page.tsx | 4 ++- 8 files changed, 44 insertions(+), 26 deletions(-) diff --git a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts index a83999efc7d0..f24da86a19bc 100644 --- a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts +++ b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.test.ts @@ -1,4 +1,4 @@ -import type { Robots } from '../../../../lib/metadata/types/metadata-interface' +import type { MetadataRoute } from '../../../../lib/metadata/types/metadata-interface' import { resolveRobots, resolveSitemap } from './resolve-route-data' describe('resolveRouteData', () => { @@ -30,7 +30,7 @@ describe('resolveRouteData', () => { }) it('should error with ts when specify both wildcard userAgent and specific userAgent', () => { - const data1: Robots = { + const data1: MetadataRoute['robots'] = { rules: [ // @ts-expect-error userAgent is required for Array { @@ -43,14 +43,14 @@ describe('resolveRouteData', () => { ], } - const data2: Robots = { + const data2: MetadataRoute['robots'] = { rules: { // Can skip userAgent for single Robots allow: '/', }, } - const data3: Robots = { + const data3: MetadataRoute['robots'] = { rules: { allow: '/' }, } diff --git a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts index d9589370a7f5..a149dc1755d6 100644 --- a/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts +++ b/packages/next/src/build/webpack/loaders/metadata/resolve-route-data.ts @@ -1,12 +1,8 @@ -import type { - Robots, - Sitemap, -} from '../../../../lib/metadata/types/metadata-interface' -import type { Manifest } from '../../../../lib/metadata/types/manifest-types' +import type { MetadataRoute } from '../../../../lib/metadata/types/metadata-interface' import { resolveArray } from '../../../../lib/metadata/generate/utils' // convert robots data to txt string -export function resolveRobots(data: Robots): string { +export function resolveRobots(data: MetadataRoute['robots']): string { let content = '' const rules = Array.isArray(data.rules) ? data.rules : [data.rules] for (const rule of rules) { @@ -47,7 +43,7 @@ export function resolveRobots(data: Robots): string { // TODO-METADATA: support multi sitemap files // convert sitemap data to xml string -export function resolveSitemap(data: Sitemap): string { +export function resolveSitemap(data: MetadataRoute['sitemap']): string { let content = '' content += '\n' content += '\n' @@ -67,22 +63,25 @@ export function resolveSitemap(data: Sitemap): string { return content } -export function resolveManifest(data: Manifest): string { +export function resolveManifest(data: MetadataRoute['manifest']): string { return JSON.stringify(data) } export function resolveRouteData( - data: Robots | Sitemap | Manifest, - fileType: 'robots' | 'sitemap' | 'manifest' + data: + | MetadataRoute['robots'] + | MetadataRoute['sitemap'] + | MetadataRoute['manifest'], + fileType: keyof MetadataRoute ): string { if (fileType === 'robots') { - return resolveRobots(data as Robots) + return resolveRobots(data as MetadataRoute['robots']) } if (fileType === 'sitemap') { - return resolveSitemap(data as Sitemap) + return resolveSitemap(data as MetadataRoute['sitemap']) } if (fileType === 'manifest') { - return resolveManifest(data as Manifest) + return resolveManifest(data as MetadataRoute['manifest']) } return '' } diff --git a/packages/next/src/lib/metadata/types/metadata-interface.ts b/packages/next/src/lib/metadata/types/metadata-interface.ts index bf4dd99bf278..23762a6a5cb5 100644 --- a/packages/next/src/lib/metadata/types/metadata-interface.ts +++ b/packages/next/src/lib/metadata/types/metadata-interface.ts @@ -28,6 +28,7 @@ import type { Verification, ThemeColorDescriptor, } from './metadata-types' +import type { Manifest as ManifestFile } from './manifest-types' import type { OpenGraph, ResolvedOpenGraph } from './opengraph-types' import type { ResolvedTwitterMetadata, Twitter } from './twitter-types' @@ -554,10 +555,16 @@ type RobotsFile = { host?: string } -type Sitemap = Array<{ +type SitemapFile = Array<{ url: string lastModified?: string | Date }> -export type ResolvingMetadata = Promise -export { Metadata, ResolvedMetadata, RobotsFile as Robots, Sitemap } +type ResolvingMetadata = Promise +type MetadataRoute = { + robots: RobotsFile + sitemap: SitemapFile + manifest: ManifestFile +} + +export { Metadata, ResolvedMetadata, ResolvingMetadata, MetadataRoute } diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index 711cbacf434d..d2afeeab06a5 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -28,8 +28,12 @@ export type ServerRuntime = 'nodejs' | 'experimental-edge' | 'edge' | undefined // @ts-ignore This path is generated at build time and conflicts otherwise export { NextConfig } from '../dist/server/config' -// @ts-ignore This path is generated at build time and conflicts otherwise -export type { Metadata } from '../dist/lib/metadata/types/metadata-interface' +export type { + Metadata, + MetadataRoute, + ResolvedMetadata, + ResolvingMetadata, // @ts-ignore This path is generated at build time and conflicts otherwise +} from '../dist/lib/metadata/types/metadata-interface' // Extend the React types with missing properties declare module 'react' { diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/manifest.ts b/test/e2e/app-dir/metadata-dynamic-routes/app/manifest.ts index 736cffed86b6..cff52e74ae12 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/app/manifest.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/app/manifest.ts @@ -1,4 +1,6 @@ -export default function manifest() { +import { MetadataRoute } from 'next' + +export default function manifest(): MetadataRoute['manifest'] { return { name: 'Next.js App', short_name: 'Next.js App', diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts b/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts index 749881098a1e..80436c28dc90 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/app/robots.ts @@ -1,4 +1,6 @@ -export default function robots() { +import type { MetadataRoute } from 'next' + +export default function robots(): MetadataRoute['robots'] { return { rules: [ { diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts b/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts index 7b1426aa7ea4..7e609558e30c 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/app/sitemap.ts @@ -1,4 +1,6 @@ -export default function sitemap() { +import { MetadataRoute } from 'next' + +export default function sitemap(): MetadataRoute['sitemap'] { return [ { url: 'https://example.com', diff --git a/test/e2e/app-dir/metadata/app/type-checking/generate-async/page.tsx b/test/e2e/app-dir/metadata/app/type-checking/generate-async/page.tsx index d17bc146fd29..3eac399debb7 100644 --- a/test/e2e/app-dir/metadata/app/type-checking/generate-async/page.tsx +++ b/test/e2e/app-dir/metadata/app/type-checking/generate-async/page.tsx @@ -1,8 +1,10 @@ +import type { ResolvingMetadata } from 'next' + export default function Page() { return null } -export async function generateMetadata() { +export async function generateMetadata(_, __: ResolvingMetadata) { return { title: 'foo', }