diff --git a/packages/nuxt/src/core/app.ts b/packages/nuxt/src/core/app.ts index df70fd4934..7afdeec1b9 100644 --- a/packages/nuxt/src/core/app.ts +++ b/packages/nuxt/src/core/app.ts @@ -1,5 +1,5 @@ import { promises as fsp, mkdirSync, writeFileSync } from 'node:fs' -import { dirname, join, resolve } from 'pathe' +import { dirname, join, relative, resolve } from 'pathe' import { defu } from 'defu' import { compileTemplate, findPath, logger, normalizePlugin, normalizeTemplate, resolveAlias, resolveFiles, resolvePath, templateUtils, tryResolveModule } from '@nuxt/kit' import type { Nuxt, NuxtApp, NuxtPlugin, NuxtTemplate, ResolvedNuxtTemplate } from 'nuxt/schema' @@ -117,6 +117,11 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) { const layoutFiles = await resolveFiles(config.srcDir, `${layoutDir}/**/*{${nuxt.options.extensions.join(',')}}`) for (const file of layoutFiles) { const name = getNameFromPath(file, resolve(config.srcDir, layoutDir)) + if (!name) { + // Ignore files like `~/layouts/index.vue` which end up not having a name at all + logger.warn(`No layout name could not be resolved for \`~/${relative(nuxt.options.srcDir, file)}\`. Bear in mind that \`index\` is ignored for the purpose of creating a layout name.`) + continue + } app.layouts[name] = app.layouts[name] || { name, file } } } @@ -126,10 +131,15 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) { for (const config of reversedConfigs) { const middlewareDir = (config.rootDir === nuxt.options.rootDir ? nuxt.options : config).dir?.middleware || 'middleware' const middlewareFiles = await resolveFiles(config.srcDir, `${middlewareDir}/*{${nuxt.options.extensions.join(',')}}`) - app.middleware.push(...middlewareFiles.map((file) => { + for (const file of middlewareFiles) { const name = getNameFromPath(file) - return { name, path: file, global: hasSuffix(file, '.global') } - })) + if (!name) { + // Ignore files like `~/middleware/index.vue` which end up not having a name at all + logger.warn(`No middleware name could not be resolved for \`~/${relative(nuxt.options.srcDir, file)}\`. Bear in mind that \`index\` is ignored for the purpose of creating a middleware name.`) + continue + } + app.middleware.push({ name, path: file, global: hasSuffix(file, '.global') }) + } } // Resolve plugins, first extended layers and then base diff --git a/packages/nuxt/test/app.test.ts b/packages/nuxt/test/app.test.ts index ed92b85e7a..cb9075b6c5 100644 --- a/packages/nuxt/test/app.test.ts +++ b/packages/nuxt/test/app.test.ts @@ -80,6 +80,39 @@ describe('resolveApp', () => { `) }) + it('resolves layouts and middleware correctly', async () => { + const app = await getResolvedApp([ + 'middleware/index.ts', + 'middleware/auth/index.ts', + 'middleware/other.ts', + 'layouts/index.vue', + 'layouts/default/index.vue', + 'layouts/other.vue', + ]) + // Middleware are not resolved in a nested manner + expect(app.middleware.filter(m => m.path.startsWith(''))).toMatchInlineSnapshot(` + [ + { + "global": false, + "name": "other", + "path": "/middleware/other.ts", + }, + ] + `) + expect(app.layouts).toMatchInlineSnapshot(` + { + "default": { + "file": "/layouts/default/index.vue", + "name": "default", + }, + "other": { + "file": "/layouts/other.vue", + "name": "other", + }, + } + `) + }) + it('resolves layer plugins in correct order', async () => { const app = await getResolvedApp([ // layer 1