Skip to content

Commit

Permalink
feat(nuxt): experimental extraPageMetaExtractionKeys (nuxt#30015)
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw authored and Kamsou committed Feb 5, 2025
1 parent 5b282a0 commit bcfd835
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 7 deletions.
29 changes: 29 additions & 0 deletions docs/2.guide/3.going-further/1.experimental-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<script lang="ts" setup>
definePageMeta({
foo: 'bar'
})
</script>
```

```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
Expand Down
19 changes: 12 additions & 7 deletions packages/nuxt/src/pages/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
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()
}

Expand Down Expand Up @@ -158,13 +159,15 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
interface AugmentPagesContext {
pagesToSkip?: Set<string>
augmentedPages?: Set<string>
extraExtractionKeys?: string[]
}

export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, 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 }
}
Expand Down Expand Up @@ -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<string, string> = {}
const metaCache: Record<string, Partial<Record<keyof NuxtPage, any>>> = {}
export async function getRouteMeta (contents: string, absolutePath: string): Promise<Partial<Record<keyof NuxtPage, any>>> {
export async function getRouteMeta (contents: string, absolutePath: string, extraExtractionKeys: string[] = []): Promise<Partial<Record<keyof NuxtPage, any>>> {
// set/update pageContentsCache, invalidate metaCache on cache mismatch
if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) {
pageContentsCache[absolutePath] = contents
Expand All @@ -221,6 +224,8 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro

const extractedMeta = {} as Partial<Record<keyof NuxtPage, any>>

const extractionKeys = new Set<keyof NuxtPage>([...defaultExtractionKeys, ...extraExtractionKeys as Array<keyof NuxtPage>])

for (const script of scriptBlocks) {
if (!PAGE_META_RE.test(script.code)) {
continue
Expand Down Expand Up @@ -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
}
Expand Down
18 changes: 18 additions & 0 deletions packages/nuxt/test/page-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ describe('page metadata', () => {
}
`)
})

it('should extract configured extra meta', async () => {
const meta = await getRouteMeta(`
<script setup>
definePageMeta({
foo: 'bar',
bar: true,
})
</script>
`, filePath, ['bar', 'foo'])

expect(meta).toMatchInlineSnapshot(`
{
"bar": true,
"foo": "bar",
}
`)
})
})

describe('normalizeRoutes', () => {
Expand Down
10 changes: 10 additions & 0 deletions packages/schema/src/config/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit bcfd835

Please sign in to comment.