From eaeda4ee1e04e8f185df86351cca3b35e8ea632d Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Tue, 22 Oct 2024 21:32:46 +0900 Subject: [PATCH] feat(nuxt,schema): `pages:resolved` hook + scan meta post extend (#28861) --- docs/1.getting-started/12.upgrade.md | 40 +++++++++++++++++++ .../1.experimental-features.md | 2 + docs/2.guide/3.going-further/1.features.md | 1 + packages/nuxt/src/pages/module.ts | 2 +- packages/nuxt/src/pages/utils.ts | 15 +++++-- packages/schema/src/config/experimental.ts | 7 +++- packages/schema/src/types/hooks.ts | 11 ++++- .../basic/modules/page-extend/index.ts | 9 ++++- test/fixtures/basic/nuxt.config.ts | 4 +- 9 files changed, 79 insertions(+), 12 deletions(-) diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md index 68cb635dc9..642592192b 100644 --- a/docs/1.getting-started/12.upgrade.md +++ b/docs/1.getting-started/12.upgrade.md @@ -67,6 +67,7 @@ export default defineNuxtConfig({ // app: 'app' // }, // experimental: { + // scanPageMeta: 'after-resolve', // sharedPrerenderData: false, // compileTemplate: true, // resetAsyncDataToUndefined: true, @@ -236,6 +237,45 @@ export default defineNuxtConfig({ }) ``` +#### Scan Page Meta After Resolution + +🚦 **Impact Level**: Minimal + +##### What Changed + +We now scan page metadata (defined in `definePageMeta`) _after_ calling the `pages:extend` hook rather than before. + +##### Reasons for Change + +This was to allow scanning metadata for pages that users wanted to add in `pages:extend`. We still offer an opportunity to change or override page metadata in a new `pages:resolved` hook. + +##### Migration Steps + +If you want to override page metadata, do that in `pages:resolved` rather than in `pages:extend`. + +```diff + export default defineNuxtConfig({ + hooks: { +- 'pages:extend'(pages) { ++ 'pages:resolved'(pages) { + const myPage = pages.find(page => page.path === '/') + myPage.meta ||= {} + myPage.meta.layout = 'overridden-layout' + } + } + }) +``` + +Alternatively, you can revert to the previous behaviour with: + +```ts twoslash [nuxt.config.ts] +export default defineNuxtConfig({ + experimental: { + scanPageMeta: true + } +}) +``` + #### Shared Prerender Data 🚦 **Impact Level**: Medium 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 31bed1a8ae..57cd77803e 100644 --- a/docs/2.guide/3.going-further/1.experimental-features.md +++ b/docs/2.guide/3.going-further/1.experimental-features.md @@ -334,6 +334,8 @@ This option allows exposing some route metadata defined in `definePageMeta` at b This only works with static or strings/arrays rather than variables or conditional assignment. See [original issue](https://github.com/nuxt/nuxt/issues/24770) for more information and context. +It is also possible to scan page metadata only after all routes have been registered in `pages:extend`. Then another hook, `pages:resolved` will be called. To enable this behavior, set `scanPageMeta: 'after-resolve'`. + You can disable this feature if it causes issues in your project. ```ts twoslash [nuxt.config.ts] diff --git a/docs/2.guide/3.going-further/1.features.md b/docs/2.guide/3.going-further/1.features.md index 247df516e2..46150d86d3 100644 --- a/docs/2.guide/3.going-further/1.features.md +++ b/docs/2.guide/3.going-further/1.features.md @@ -61,6 +61,7 @@ export default defineNuxtConfig({ app: 'app' }, experimental: { + scanPageMeta: 'after-resolve', sharedPrerenderData: false, compileTemplate: true, resetAsyncDataToUndefined: true, diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index 074bce293a..e3eba25859 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -503,7 +503,7 @@ export default defineNuxtModule({ const { routes, imports } = normalizeRoutes(app.pages, new Set(), { serverComponentRuntime, clientComponentRuntime, - overrideMeta: nuxt.options.experimental.scanPageMeta, + overrideMeta: !!nuxt.options.experimental.scanPageMeta, }) return [...imports, `export default ${routes}`].join('\n') }, diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts index 4ccb9540ec..25ef9ead8a 100644 --- a/packages/nuxt/src/pages/utils.ts +++ b/packages/nuxt/src/pages/utils.ts @@ -64,18 +64,25 @@ export async function resolvePagesRoutes (): Promise { }) const pages = uniqueBy(allRoutes, 'path') - const shouldAugment = nuxt.options.experimental.scanPageMeta || nuxt.options.experimental.typedPages - if (shouldAugment) { + if (shouldAugment === false) { + await nuxt.callHook('pages:extend', pages) + return pages + } + + if (shouldAugment === 'after-resolve') { + await nuxt.callHook('pages:extend', pages) + await augmentPages(pages, nuxt.vfs) + } else { const augmentedPages = await augmentPages(pages, nuxt.vfs) await nuxt.callHook('pages:extend', pages) await augmentPages(pages, nuxt.vfs, augmentedPages) augmentedPages.clear() - } else { - await nuxt.callHook('pages:extend', pages) } + await nuxt.callHook('pages:resolved', pages) + return pages } diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts index 711a20b776..5743c313f0 100644 --- a/packages/schema/src/config/experimental.ts +++ b/packages/schema/src/config/experimental.ts @@ -297,8 +297,13 @@ export default defineUntypedSchema({ * This only works with static or strings/arrays rather than variables or conditional assignment. * * @see [Nuxt Issues #24770](https://github.com/nuxt/nuxt/issues/24770) + * @type {boolean | 'after-resolve'} */ - scanPageMeta: true, + scanPageMeta: { + async $resolve (val, get) { + return val ?? ((await get('future') as Record).compatibilityVersion === 4 ? 'after-resolve' : true) + }, + }, /** * Automatically share payload _data_ between pages that are prerendered. This can result in a significant diff --git a/packages/schema/src/types/hooks.ts b/packages/schema/src/types/hooks.ts index 7fd4b2aa5d..aa9287a82f 100644 --- a/packages/schema/src/types/hooks.ts +++ b/packages/schema/src/types/hooks.ts @@ -183,12 +183,19 @@ export interface NuxtHooks { 'builder:watch': (event: WatchEvent, path: string) => HookResult /** - * Called after pages routes are resolved. - * @param pages Array containing resolved pages + * Called after page routes are scanned from the file system. + * @param pages Array containing scanned pages * @returns Promise */ 'pages:extend': (pages: NuxtPage[]) => HookResult + /** + * Called after page routes have been augmented with scanned metadata. + * @param pages Array containing resolved pages + * @returns Promise + */ + 'pages:resolved': (pages: NuxtPage[]) => HookResult + /** * Called when resolving `app/router.options` files. It allows modifying the detected router options files * and adding new ones. diff --git a/test/fixtures/basic/modules/page-extend/index.ts b/test/fixtures/basic/modules/page-extend/index.ts index 60ef2c224c..a72c5a791a 100644 --- a/test/fixtures/basic/modules/page-extend/index.ts +++ b/test/fixtures/basic/modules/page-extend/index.ts @@ -13,13 +13,18 @@ export default defineNuxtModule({ name: 'page-extend', path: '/page-extend', file: resolver.resolve('../runtime/page.vue'), - }, { + }) + }) + + nuxt.hook('pages:resolved', (pages) => { + pages.push({ path: '/big-page-1', file: resolver.resolve('./pages/big-page.vue'), meta: { layout: false, }, - }, { + }, + { path: '/big-page-2', file: resolver.resolve('./pages/big-page.vue'), meta: { diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index 1920336133..e4cf482bf0 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -75,7 +75,7 @@ export default defineNuxtConfig({ _layout: page.meta?.layout, }, }) - nuxt.hook('pages:extend', (pages) => { + nuxt.hook('pages:resolved', (pages) => { const newPages = [] for (const page of pages) { if (routesToDuplicate.includes(page.path)) { @@ -88,7 +88,7 @@ export default defineNuxtConfig({ }, function (_options, nuxt) { // to check that page metadata is preserved - nuxt.hook('pages:extend', (pages) => { + nuxt.hook('pages:resolved', (pages) => { const customName = pages.find(page => page.name === 'some-custom-name') if (!customName) { throw new Error('Page with custom name not found') } if (customName.path !== '/some-custom-path') { throw new Error('Page path not extracted') }