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 all 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
45 changes: 28 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,30 @@ 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((prefixParts[index] || '').toLowerCase())
if (fileNamePartsContent.startsWith(matchedSuffix.join('')) ||
// 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)
}
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'],
['ItemHolderItem', ['Item', 'Holder', 'Item'], 'ItemHolderItem'],
['Item', ['Item'], 'Item'],
['Item', ['Item', 'Item'], 'Item'],
['ItemTest', ['Item', 'Test'], 'ItemTest'],
['ThingItemTest', ['Item', 'Thing'], 'ItemThingItemTest'],
['Item', ['Thing', 'Item'], 'ThingItem'],
['Item', ['Item', 'Holder', 'Item'], 'ItemHolderItem'],
['ItemHolder', ['Item', 'Holder', 'Item'], 'ItemHolderItemHolder'],
['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)
})
})