fix: share component path resolution utility

This commit is contained in:
Daniel Roe 2023-10-16 22:20:11 +01:00
parent db9cc66a3e
commit 53b3617d2b
4 changed files with 41 additions and 31 deletions

View File

@ -8,6 +8,8 @@ import { hyphenate } from '@vue/shared'
import { withTrailingSlash } from 'ufo' import { withTrailingSlash } from 'ufo'
import type { Component, ComponentsDir } from 'nuxt/schema' import type { Component, ComponentsDir } from 'nuxt/schema'
import { resolveComponentName } from '../core/utils'
/** /**
* Scan the components inside different components folders * Scan the components inside different components folders
* and return a unique list of components * and return a unique list of components
@ -157,33 +159,6 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
return components 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) { function warnAboutDuplicateComponent (componentName: string, filePath: string, duplicatePath: string) {
logger.warn(`Two component files resolving to the same name \`${componentName}\`:\n` + logger.warn(`Two component files resolving to the same name \`${componentName}\`:\n` +
`\n - ${filePath}` + `\n - ${filePath}` +

View File

@ -1,14 +1,43 @@
import { basename, extname, normalize } from 'pathe' import { basename, dirname, extname, normalize, relative } from 'pathe'
import { kebabCase } from 'scule' import { kebabCase, pascalCase, splitByCase } from 'scule'
import { withTrailingSlash } from 'ufo' import { withTrailingSlash } from 'ufo'
export function getNameFromPath (path: string, relativeTo?: string) { export function getNameFromPath (path: string, relativeTo?: string) {
const relativePath = relativeTo const relativePath = relativeTo
? normalize(path).replace(withTrailingSlash(normalize(relativeTo)), '') ? normalize(path).replace(withTrailingSlash(normalize(relativeTo)), '')
: basename(path) : 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) { export function hasSuffix (path: string, suffix: string) {
return basename(path).replace(extname(path), '').endsWith(suffix) 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)
}

View File

@ -186,6 +186,7 @@ describe('resolveApp', () => {
'layouts/some/layout.vue', 'layouts/some/layout.vue',
'layouts/SomeOther.vue', 'layouts/SomeOther.vue',
'layouts/SomeOther/Thing/Index.vue', 'layouts/SomeOther/Thing/Index.vue',
'layouts/thing/thing/thing.vue',
'layouts/some.vue', 'layouts/some.vue',
'layouts/SomeOther/layout.ts' 'layouts/SomeOther/layout.ts'
]) ])
@ -215,6 +216,10 @@ describe('resolveApp', () => {
"file": "<rootDir>/layouts/SomeOther/Thing/Index.vue", "file": "<rootDir>/layouts/SomeOther/Thing/Index.vue",
"name": "some-other-thing", "name": "some-other-thing",
}, },
"thing": {
"file": "<rootDir>/layouts/thing/thing/thing.vue",
"name": "thing",
},
} }
`) `)
}) })

View File

@ -2,7 +2,8 @@ import { resolve } from 'node:path'
import { describe, expect, it, vi } from 'vitest' import { describe, expect, it, vi } from 'vitest'
import type { ComponentsDir } from 'nuxt/schema' 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 fixtureDir = resolve(__dirname, 'fixture')
const rFixture = (...p: string[]) => resolve(fixtureDir, ...p) const rFixture = (...p: string[]) => resolve(fixtureDir, ...p)