diff --git a/docs/3.api/6.advanced/1.hooks.md b/docs/3.api/6.advanced/1.hooks.md index 79dcc6e963..b0f407f1f9 100644 --- a/docs/3.api/6.advanced/1.hooks.md +++ b/docs/3.api/6.advanced/1.hooks.md @@ -50,6 +50,7 @@ Hook | Arguments | Description `builder:generateApp` | `options` | Called before generating the app. `builder:watch` | `event, path` | Called at build time in development when the watcher spots a change to a file or directory in the project. `pages:extend` | `pages` | Called after pages routes are resolved. +`pages:routerOptions` | `{ files: Array<{ path: string, optional?: boolean }> }` | Called when resolving `router.options` files. `server:devHandler` | `handler` | Called when the dev middleware is being registered on the Nitro dev server. `imports:sources` | `presets` | Called at setup allowing modules to extend sources. `imports:extend` | `imports` | Called at setup allowing modules to extend imports. diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts index ed25eeb0cf..95c256a438 100644 --- a/packages/nuxt/src/pages/module.ts +++ b/packages/nuxt/src/pages/module.ts @@ -26,11 +26,27 @@ export default defineNuxtModule({ }, async setup (_options, nuxt) { const useExperimentalTypedPages = nuxt.options.experimental.typedPages - + const runtimeDir = resolve(distDir, 'pages/runtime') const pagesDirs = nuxt.options._layers.map( layer => resolve(layer.config.srcDir, (layer.config.rootDir === nuxt.options.rootDir ? nuxt.options : layer.config).dir?.pages || 'pages') ) + async function resolveRouterOptions () { + const context = { + files: [] as Array<{ path: string, optional?: boolean }> + } + // Add default options + context.files.push({ path: resolve(runtimeDir, 'router.options'), optional: true }) + + await Promise.all(nuxt.options._layers.map(async layer => { + const path = await findPath(resolve(layer.config.srcDir, 'app/router.options')) + if (path) { context.files.push({ path }) } + })) + + await nuxt.callHook('pages:routerOptions', context) + return context.files + } + // Disable module (and use universal router) if pages dir do not exists or user has disabled it const isNonEmptyDir = (dir: string) => existsSync(dir) && readdirSync(dir).length const userPreference = nuxt.options.pages @@ -38,7 +54,8 @@ export default defineNuxtModule({ if (typeof userPreference === 'boolean') { return userPreference } - if (nuxt.options._layers.some(layer => existsSync(resolve(layer.config.srcDir, 'app/router.options.ts')))) { + const routerOptionsFiles = await resolveRouterOptions() + if (routerOptionsFiles.filter(p => !p.optional).length > 0) { return true } if (pagesDirs.some(dir => isNonEmptyDir(dir))) { @@ -176,8 +193,6 @@ export default defineNuxtModule({ }) } - const runtimeDir = resolve(distDir, 'pages/runtime') - // Add $router types nuxt.hook('prepare:types', ({ references }) => { references.push({ types: useExperimentalTypedPages ? 'vue-router/auto' : 'vue-router' }) @@ -392,18 +407,13 @@ export default defineNuxtModule({ filename: 'router.options.mjs', getContents: async () => { // Scan and register app/router.options files - const routerOptionsFiles = (await Promise.all(nuxt.options._layers.map( - async layer => await findPath(resolve(layer.config.srcDir, 'app/router.options')) - ))).filter(Boolean) as string[] - - // Add default options - routerOptionsFiles.push(resolve(runtimeDir, 'router.options')) + const routerOptionsFiles = await resolveRouterOptions() const configRouterOptions = genObjectFromRawEntries(Object.entries(nuxt.options.router.options) .map(([key, value]) => [key, genString(value as string)])) return [ - ...routerOptionsFiles.map((file, index) => genImport(file, `routerOptions${index}`)), + ...routerOptionsFiles.map((file, index) => genImport(file.path, `routerOptions${index}`)), `const configRouterOptions = ${configRouterOptions}`, 'export default {', '...configRouterOptions,', diff --git a/packages/schema/src/types/hooks.ts b/packages/schema/src/types/hooks.ts index e6e7fad28d..1a562d750a 100644 --- a/packages/schema/src/types/hooks.ts +++ b/packages/schema/src/types/hooks.ts @@ -170,6 +170,17 @@ export interface NuxtHooks { */ 'pages:extend': (pages: NuxtPage[]) => HookResult + /** + * Called when resolving `app/router.options` files. It allows modifying the detected router options files + * and adding new ones. + * + * Adding a router options file will switch on page-based routing, unless `optional` is set, in which case + * it will only apply when page-based routing is already enabled. + * @param context An object with `files` containing an array of router options files. + * @returns Promise + */ + 'pages:routerOptions': (context: { files: Array<{ path: string, optional?: boolean }> }) => HookResult + /** * Called when the dev middleware is being registered on the Nitro dev server. * @param handler the Vite or Webpack event handler @@ -335,11 +346,11 @@ export interface NuxtHooks { * @returns Promise */ 'webpack:config': (webpackConfigs: Configuration[]) => HookResult - /** - * Allows to read the resolved webpack config - * @param webpackConfigs Configs objects to be pushed to the compiler - * @returns Promise - */ + /** + * Allows to read the resolved webpack config + * @param webpackConfigs Configs objects to be pushed to the compiler + * @returns Promise + */ 'webpack:configResolved': (webpackConfigs: Readonly[]) => HookResult /** * Called right before compilation.