From 16ef9be9035e6d36135df1148de86fe031e0d13b Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Thu, 28 Nov 2024 03:57:15 +1100 Subject: [PATCH] feat(nuxt): experimental `extraPageMetaExtractionKeys` (#30015) --- .../1.experimental-features.md | 29 +++++++++++++++++++ packages/nuxt/src/pages/utils.ts | 19 +++++++----- packages/nuxt/test/page-metadata.test.ts | 18 ++++++++++++ packages/schema/src/config/experimental.ts | 10 +++++++ 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/docs/2.guide/3.going-further/1.experimental-features.md b/docs/2.guide/3.going-further/1.experimental-features.md index 40d8c5bebf..35280ba7ea 100644 --- a/docs/2.guide/3.going-further/1.experimental-features.md +++ b/docs/2.guide/3.going-further/1.experimental-features.md @@ -395,6 +395,35 @@ In addition, any changes to files within `srcDir` will trigger a rebuild of the A maximum of 10 cache tarballs are kept. :: +## extraPageMetaExtractionKeys + +The `definePageMeta()` macro is a useful way to collect build-time meta about pages. Nuxt itself provides a set list of supported keys which is used to power some of the internal features such as redirects, page aliases and custom paths. + +This option allows passing additional keys to extract from the page metadata when using `scanPageMeta`. + +```vue + +``` + +```ts +export default defineNuxtConfig({ + experimental: { + extraPageMetaExtractionKeys: ['foo'], + }, + hooks: { + 'pages:resolved' (ctx) { + // ✅ foo is available + }, + }, +}) +``` + +This allows modules to access additional metadata from the page metadata in the build context. If you are using this within a module, it's recommended also to [augment the `NuxtPage` types with your keys](/docs/guide/directory-structure/pages#typing-custom-metadata). + ## normalizeComponentNames Ensure that auto-generated Vue component names match the full component name diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts index 27aa91c398..0b68904979 100644 --- a/packages/nuxt/src/pages/utils.ts +++ b/packages/nuxt/src/pages/utils.ts @@ -71,13 +71,14 @@ export async function resolvePagesRoutes (): Promise { return pages } + const augmentCtx = { extraExtractionKeys: nuxt.options.experimental.extraPageMetaExtractionKeys } if (shouldAugment === 'after-resolve') { await nuxt.callHook('pages:extend', pages) - await augmentPages(pages, nuxt.vfs) + await augmentPages(pages, nuxt.vfs, augmentCtx) } else { - const augmentedPages = await augmentPages(pages, nuxt.vfs) + const augmentedPages = await augmentPages(pages, nuxt.vfs, augmentCtx) await nuxt.callHook('pages:extend', pages) - await augmentPages(pages, nuxt.vfs, { pagesToSkip: augmentedPages }) + await augmentPages(pages, nuxt.vfs, { pagesToSkip: augmentedPages, ...augmentCtx }) augmentedPages?.clear() } @@ -158,13 +159,15 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate interface AugmentPagesContext { pagesToSkip?: Set augmentedPages?: Set + extraExtractionKeys?: string[] } + export async function augmentPages (routes: NuxtPage[], vfs: Record, ctx: AugmentPagesContext = {}) { ctx.augmentedPages ??= new Set() for (const route of routes) { if (route.file && !ctx.pagesToSkip?.has(route.file)) { const fileContent = route.file in vfs ? vfs[route.file]! : fs.readFileSync(await resolvePath(route.file), 'utf-8') - const routeMeta = await getRouteMeta(fileContent, route.file) + const routeMeta = await getRouteMeta(fileContent, route.file, ctx.extraExtractionKeys) if (route.meta) { routeMeta.meta = { ...routeMeta.meta, ...route.meta } } @@ -196,12 +199,12 @@ export function extractScriptContent (html: string) { } const PAGE_META_RE = /definePageMeta\([\s\S]*?\)/ -const extractionKeys = ['name', 'path', 'props', 'alias', 'redirect'] as const +const defaultExtractionKeys = ['name', 'path', 'props', 'alias', 'redirect'] as const const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const const pageContentsCache: Record = {} const metaCache: Record>> = {} -export async function getRouteMeta (contents: string, absolutePath: string): Promise>> { +export async function getRouteMeta (contents: string, absolutePath: string, extraExtractionKeys: string[] = []): Promise>> { // set/update pageContentsCache, invalidate metaCache on cache mismatch if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) { pageContentsCache[absolutePath] = contents @@ -221,6 +224,8 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro const extractedMeta = {} as Partial> + const extractionKeys = new Set([...defaultExtractionKeys, ...extraExtractionKeys as Array]) + for (const script of scriptBlocks) { if (!PAGE_META_RE.test(script.code)) { continue @@ -295,7 +300,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro continue } const name = property.key.type === 'Identifier' ? property.key.name : String(property.value) - if (!(extractionKeys as unknown as string[]).includes(name)) { + if (!extractionKeys.has(name as keyof NuxtPage)) { dynamicProperties.add('meta') break } diff --git a/packages/nuxt/test/page-metadata.test.ts b/packages/nuxt/test/page-metadata.test.ts index 6206e07c08..cdd299c415 100644 --- a/packages/nuxt/test/page-metadata.test.ts +++ b/packages/nuxt/test/page-metadata.test.ts @@ -142,6 +142,24 @@ describe('page metadata', () => { } `) }) + + it('should extract configured extra meta', async () => { + const meta = await getRouteMeta(` + + `, filePath, ['bar', 'foo']) + + expect(meta).toMatchInlineSnapshot(` + { + "bar": true, + "foo": "bar", + } + `) + }) }) describe('normalizeRoutes', () => { diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts index 00ae9e2e0c..506ddb6481 100644 --- a/packages/schema/src/config/experimental.ts +++ b/packages/schema/src/config/experimental.ts @@ -308,6 +308,16 @@ export default defineUntypedSchema({ }, }, + /** + * Configure additional keys to extract from the page metadata when using `scanPageMeta`. + * + * This allows modules to access additional metadata from the page metadata. It's recommended + * to augment the NuxtPage types with your keys. + * + * @type {string[]} + */ + extraPageMetaExtractionKeys: [], + /** * Automatically share payload _data_ between pages that are prerendered. This can result in a significant * performance improvement when prerendering sites that use `useAsyncData` or `useFetch` and fetch the same