diff --git a/packages/nuxt/src/components/scan.ts b/packages/nuxt/src/components/scan.ts index a5093b9305..b4fe7d873c 100644 --- a/packages/nuxt/src/components/scan.ts +++ b/packages/nuxt/src/components/scan.ts @@ -8,6 +8,8 @@ import { hyphenate } from '@vue/shared' import { withTrailingSlash } from 'ufo' import type { Component, ComponentsDir } from 'nuxt/schema' +import { resolveComponentName } from '../core/utils' + /** * Scan the components inside different components folders * and return a unique list of components @@ -157,33 +159,6 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr return components } -export function resolveComponentName (fileName: string, prefixParts: string[]) { - /** - * Array of fileName parts splitted by case, / or - - * @example third-component -> ['third', 'component'] - * @example AwesomeComponent -> ['Awesome', 'Component'] - */ - const fileNameParts = splitByCase(fileName) - const fileNamePartsContent = fileNameParts.join('/').toLowerCase() - const componentNameParts: string[] = [...prefixParts] - let index = prefixParts.length - 1 - const matchedSuffix: string[] = [] - while (index >= 0) { - matchedSuffix.unshift(...splitByCase(prefixParts[index] || '').map(p => p.toLowerCase())) - const matchedSuffixContent = matchedSuffix.join('/') - if ((fileNamePartsContent === matchedSuffixContent || fileNamePartsContent.startsWith(matchedSuffixContent + '/')) || - // e.g Item/Item/Item.vue -> Item - (prefixParts[index].toLowerCase() === fileNamePartsContent && - prefixParts[index + 1] && - prefixParts[index] === prefixParts[index + 1])) { - componentNameParts.length = index - } - index-- - } - - return pascalCase(componentNameParts) + pascalCase(fileNameParts) -} - function warnAboutDuplicateComponent (componentName: string, filePath: string, duplicatePath: string) { logger.warn(`Two component files resolving to the same name \`${componentName}\`:\n` + `\n - ${filePath}` + diff --git a/packages/nuxt/src/core/utils/names.ts b/packages/nuxt/src/core/utils/names.ts index 9e9dff7512..f7ec2ba9fa 100644 --- a/packages/nuxt/src/core/utils/names.ts +++ b/packages/nuxt/src/core/utils/names.ts @@ -1,14 +1,43 @@ -import { basename, extname, normalize } from 'pathe' -import { kebabCase } from 'scule' +import { basename, dirname, extname, normalize, relative } from 'pathe' +import { kebabCase, pascalCase, splitByCase } from 'scule' import { withTrailingSlash } from 'ufo' export function getNameFromPath (path: string, relativeTo?: string) { const relativePath = relativeTo ? normalize(path).replace(withTrailingSlash(normalize(relativeTo)), '') : basename(path) - return kebabCase(relativePath.replace(/\/index\.\w+$/i, '').replace(/[\\/]+/g, '-').replace(extname(relativePath), '')).replace(/["']/g, '') + const prefixParts = splitByCase(dirname(relativePath)) + const fileName = basename(relativePath, extname(relativePath)) + return kebabCase(resolveComponentName(fileName.toLowerCase() === 'index' ? '' : fileName, prefixParts)).replace(/["']/g, '') } export function hasSuffix (path: string, suffix: string) { return basename(path).replace(extname(path), '').endsWith(suffix) } + +export function resolveComponentName (fileName: string, prefixParts: string[]) { + /** + * Array of fileName parts splitted by case, / or - + * @example third-component -> ['third', 'component'] + * @example AwesomeComponent -> ['Awesome', 'Component'] + */ + const fileNameParts = splitByCase(fileName) + const fileNamePartsContent = fileNameParts.join('/').toLowerCase() + const componentNameParts: string[] = [...prefixParts] + let index = prefixParts.length - 1 + const matchedSuffix: string[] = [] + while (index >= 0) { + matchedSuffix.unshift(...splitByCase(prefixParts[index] || '').map(p => p.toLowerCase())) + const matchedSuffixContent = matchedSuffix.join('/') + if ((fileNamePartsContent === matchedSuffixContent || fileNamePartsContent.startsWith(matchedSuffixContent + '/')) || + // e.g Item/Item/Item.vue -> Item + (prefixParts[index].toLowerCase() === fileNamePartsContent && + prefixParts[index + 1] && + prefixParts[index] === prefixParts[index + 1])) { + componentNameParts.length = index + } + index-- + } + + return pascalCase(componentNameParts) + pascalCase(fileNameParts) +} diff --git a/packages/nuxt/test/app.test.ts b/packages/nuxt/test/app.test.ts index bc92319335..00d7c577db 100644 --- a/packages/nuxt/test/app.test.ts +++ b/packages/nuxt/test/app.test.ts @@ -186,6 +186,7 @@ describe('resolveApp', () => { 'layouts/some/layout.vue', 'layouts/SomeOther.vue', 'layouts/SomeOther/Thing/Index.vue', + 'layouts/thing/thing/thing.vue', 'layouts/some.vue', 'layouts/SomeOther/layout.ts' ]) @@ -215,6 +216,10 @@ describe('resolveApp', () => { "file": "/layouts/SomeOther/Thing/Index.vue", "name": "some-other-thing", }, + "thing": { + "file": "/layouts/thing/thing/thing.vue", + "name": "thing", + }, } `) }) diff --git a/packages/nuxt/test/scan-components.test.ts b/packages/nuxt/test/scan-components.test.ts index 97cd60a472..47fad31b01 100644 --- a/packages/nuxt/test/scan-components.test.ts +++ b/packages/nuxt/test/scan-components.test.ts @@ -2,7 +2,8 @@ import { resolve } from 'node:path' import { describe, expect, it, vi } from 'vitest' import type { ComponentsDir } from 'nuxt/schema' -import { resolveComponentName, scanComponents } from '../src/components/scan' +import { scanComponents } from '../src/components/scan' +import { resolveComponentName } from '../src/core/utils' const fixtureDir = resolve(__dirname, 'fixture') const rFixture = (...p: string[]) => resolve(fixtureDir, ...p)