Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nuxt): ensure all dir parts are present in component name #20779

Merged
merged 18 commits into from May 15, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 36 additions & 17 deletions packages/nuxt/src/components/scan.ts
Expand Up @@ -72,24 +72,8 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
}

/**
* Array of fileName parts splitted by case, / or -
*
* @example third-component -> ['third', 'component']
* @example AwesomeComponent -> ['Awesome', 'Component']
*/
const fileNameParts = splitByCase(fileName)

const componentNameParts: string[] = []

while (prefixParts.length &&
(prefixParts[0] || '').toLowerCase() !== (fileNameParts[0] || '').toLowerCase()
) {
componentNameParts.push(prefixParts.shift()!)
}

const componentName = pascalCase(componentNameParts) + pascalCase(fileNameParts)
const suffix = (mode !== 'all' ? `-${mode}` : '')
const componentName = resolveComponentName(fileName, prefixParts)

if (resolvedNames.has(componentName + suffix) || resolvedNames.has(componentName)) {
console.warn(`Two component files resolving to the same name \`${componentName}\`:\n` +
Expand Down Expand Up @@ -137,3 +121,38 @@ 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)

// e.g Item/Holder/Item/ItemHolderItem.vue -> ItemHolderItem
if (fileNameParts.join('').toLowerCase() === prefixParts.join('').toLowerCase()) {
return pascalCase(fileNameParts)
}

// e.g Item/Item/Item.vue -> Item
if ([...new Set(prefixParts)].length === 1 &&
(fileNameParts[0] || '').toLowerCase() === (prefixParts[0] || '').toLowerCase()) {
return pascalCase(fileNameParts)
}
danielroe marked this conversation as resolved.
Show resolved Hide resolved

const fileNamePartsContent = fileNameParts.join('').toLowerCase()
let index = prefixParts.length - 1
const matchedSuffix:string[] = []
while (index >= 0) {
matchedSuffix.unshift((prefixParts[index] || '').toLowerCase())
if (!fileNamePartsContent.startsWith(matchedSuffix.join(''))) {
index--
} else {
prefixParts.length = index
break
}
}
return pascalCase(prefixParts) + pascalCase(fileNameParts)
}
7 changes: 7 additions & 0 deletions packages/nuxt/test/fixture/components/same-name/same/Same.vue
@@ -0,0 +1,7 @@
<template>
<div>
This is Same name component!
</div>
</template>
<script setup>
</script>
39 changes: 37 additions & 2 deletions packages/nuxt/test/scan-components.test.ts
@@ -1,8 +1,8 @@
import { resolve } from 'node:path'
import { expect, it, vi } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import type { ComponentsDir } from 'nuxt/schema'

import { scanComponents } from '../src/components/scan'
import { resolveComponentName, scanComponents } from '../src/components/scan'

const fixtureDir = resolve(__dirname, 'fixture')
const rFixture = (...p: string[]) => resolve(fixtureDir, ...p)
Expand Down Expand Up @@ -192,6 +192,19 @@ const expectedComponents = [
preload: false,
priority: 1
},
{
chunkName: 'components/same-name-same',
export: 'default',
global: undefined,
island: undefined,
kebabName: 'same-name-same',
mode: 'all',
pascalName: 'SameNameSame',
prefetch: false,
preload: false,
priority: 1,
shortPath: 'components/same-name/same/Same.vue'
},
{
chunkName: 'components/some-glob',
export: 'default',
Expand Down Expand Up @@ -230,3 +243,25 @@ it('components:scanComponents', async () => {
}
expect(scannedComponents).deep.eq(expectedComponents)
})

const tests: Array<[string, string[], string]> = [
['WithClientOnlySetup', ['Client'], 'ClientWithClientOnlySetup'],
['Item', ['Item', 'Holder', 'Item'], 'ItemHolderItem'],
['ItemHolder', ['Item', 'Holder', 'Item'], 'ItemHolderItemHolder'],
['ItemHolderItem', ['Item', 'Holder', 'Item'], 'ItemHolderItem'],
['Item', ['Item'], 'Item'],
['Item', ['Item', 'Item'], 'Item'],
['ItemTest', ['Item', 'Test'], 'ItemTest'],
['ThingItemTest', ['Item', 'Thing'], 'ItemThingItemTest'],
['Item', ['Thing', 'Item'], 'ThingItem'],
['ThingItemTest', ['Item', 'Thing', 'Foo'], 'ItemThingFooThingItemTest'],
['ItemIn', ['Item', 'Holder', 'Item', 'In'], 'ItemHolderItemIn'],
['Item', ['Item', 'Holder', 'Test'], 'ItemHolderTestItem'],
['ItemHolderItem', ['Item', 'Holder', 'Item', 'Holder'], 'ItemHolderItemHolderItem']
]

describe('components:resolveComponentName', () => {
it.each(tests)('resolves %s with prefix parts %s and filename %s', (fileName, prefixParts: string[], result) => {
expect(resolveComponentName(fileName, prefixParts)).toBe(result)
})
})