feat(nuxt): experimental extraPageMetaExtractionKeys (#30015)

This commit is contained in:
Harlan Wilton 2024-11-28 03:57:15 +11:00 committed by GitHub
parent 0f3e1e9442
commit 16ef9be903
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 69 additions and 7 deletions

View File

@ -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. 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 ## normalizeComponentNames
Ensure that auto-generated Vue component names match the full component name Ensure that auto-generated Vue component names match the full component name

View File

@ -71,13 +71,14 @@ export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
return pages return pages
} }
const augmentCtx = { extraExtractionKeys: nuxt.options.experimental.extraPageMetaExtractionKeys }
if (shouldAugment === 'after-resolve') { if (shouldAugment === 'after-resolve') {
await nuxt.callHook('pages:extend', pages) await nuxt.callHook('pages:extend', pages)
await augmentPages(pages, nuxt.vfs) await augmentPages(pages, nuxt.vfs, augmentCtx)
} else { } else {
const augmentedPages = await augmentPages(pages, nuxt.vfs) const augmentedPages = await augmentPages(pages, nuxt.vfs, augmentCtx)
await nuxt.callHook('pages:extend', pages) await nuxt.callHook('pages:extend', pages)
await augmentPages(pages, nuxt.vfs, { pagesToSkip: augmentedPages }) await augmentPages(pages, nuxt.vfs, { pagesToSkip: augmentedPages, ...augmentCtx })
augmentedPages?.clear() augmentedPages?.clear()
} }
@ -158,13 +159,15 @@ export function generateRoutesFromFiles (files: ScannedFile[], options: Generate
interface AugmentPagesContext { interface AugmentPagesContext {
pagesToSkip?: Set<string> pagesToSkip?: Set<string>
augmentedPages?: Set<string> augmentedPages?: Set<string>
extraExtractionKeys?: string[]
} }
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, ctx: AugmentPagesContext = {}) { export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, ctx: AugmentPagesContext = {}) {
ctx.augmentedPages ??= new Set() ctx.augmentedPages ??= new Set()
for (const route of routes) { for (const route of routes) {
if (route.file && !ctx.pagesToSkip?.has(route.file)) { 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 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) { if (route.meta) {
routeMeta.meta = { ...routeMeta.meta, ...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 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 DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const
const pageContentsCache: Record<string, string> = {} const pageContentsCache: Record<string, string> = {}
const metaCache: Record<string, Partial<Record<keyof NuxtPage, any>>> = {} 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 // set/update pageContentsCache, invalidate metaCache on cache mismatch
if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) { if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) {
pageContentsCache[absolutePath] = contents pageContentsCache[absolutePath] = contents
@ -221,6 +224,8 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
const extractedMeta = {} as Partial<Record<keyof NuxtPage, any>> 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) { for (const script of scriptBlocks) {
if (!PAGE_META_RE.test(script.code)) { if (!PAGE_META_RE.test(script.code)) {
continue continue
@ -295,7 +300,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
continue continue
} }
const name = property.key.type === 'Identifier' ? property.key.name : String(property.value) 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') dynamicProperties.add('meta')
break break
} }

View File

@ -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', () => { describe('normalizeRoutes', () => {

View File

@ -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 * 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 * performance improvement when prerendering sites that use `useAsyncData` or `useFetch` and fetch the same