mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nuxt): scanComponents named exports
This commit is contained in:
parent
416d76ab80
commit
7ce0b8754f
@ -6,6 +6,7 @@ import { isIgnored, logger, useNuxt } from '@nuxt/kit'
|
||||
import { withTrailingSlash } from 'ufo'
|
||||
import type { Component, ComponentsDir } from 'nuxt/schema'
|
||||
|
||||
import { resolveModuleExportNames } from 'mlly'
|
||||
import { resolveComponentNameSegments } from '../core/utils'
|
||||
|
||||
/**
|
||||
@ -75,13 +76,14 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
||||
(dir.pathPrefix !== false) ? splitByCase(relative(dir.path, dirname(filePath))) : [],
|
||||
)
|
||||
|
||||
const fileExt = extname(filePath)
|
||||
/**
|
||||
* In case we have index as filename the component become the parent path
|
||||
* @example third-components/index.vue -> third-component
|
||||
* if not take the filename
|
||||
* @example third-components/Awesome.vue -> Awesome
|
||||
*/
|
||||
let fileName = basename(filePath, extname(filePath))
|
||||
let fileName = basename(filePath, fileExt)
|
||||
|
||||
const island = /\.(island)(\.global)?$/.test(fileName) || dir.island
|
||||
const global = /\.(global)(\.island)?$/.test(fileName) || dir.global
|
||||
@ -92,67 +94,78 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
|
||||
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
|
||||
}
|
||||
|
||||
const getComponents = async (exportName: string): Promise<Component | null> => {
|
||||
const componentNameSegment = exportName === 'default' ? defaultComponentNameSegments : resolveComponentNameSegments(exportName, defaultComponentNameSegments)
|
||||
const pascalName = pascalCase(componentNameSegment)
|
||||
|
||||
if (resolvedNames.has(pascalName + suffix) || resolvedNames.has(pascalName)) {
|
||||
warnAboutDuplicateComponent(pascalName, filePath, resolvedNames.get(pascalName) || resolvedNames.get(pascalName + suffix)!)
|
||||
return null
|
||||
}
|
||||
resolvedNames.set(pascalName + suffix, filePath)
|
||||
|
||||
const kebabName = kebabCase(componentNameSegment)
|
||||
const shortPath = relative(srcDir, filePath)
|
||||
const chunkName = 'components/' + kebabName + suffix
|
||||
|
||||
let component: Component = {
|
||||
// inheritable from directory configuration
|
||||
mode,
|
||||
global,
|
||||
island,
|
||||
prefetch: Boolean(dir.prefetch),
|
||||
preload: Boolean(dir.preload),
|
||||
// specific to the file
|
||||
filePath,
|
||||
pascalName,
|
||||
kebabName,
|
||||
chunkName,
|
||||
shortPath,
|
||||
export: exportName,
|
||||
// by default, give priority to scanned components
|
||||
priority: dir.priority ?? 1,
|
||||
}
|
||||
|
||||
if (typeof dir.extendComponent === 'function') {
|
||||
component = (await dir.extendComponent(component)) || component
|
||||
}
|
||||
|
||||
// Ignore files like `~/components/index.vue` which end up not having a name at all
|
||||
if (!pascalName) {
|
||||
logger.warn(`Component did not resolve to a file name in \`~/${relative(srcDir, filePath)}\`.`)
|
||||
return null
|
||||
}
|
||||
|
||||
const existingComponent = components.find(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))
|
||||
// Ignore component if component is already defined (with same mode)
|
||||
if (existingComponent) {
|
||||
const existingPriority = existingComponent.priority ?? 0
|
||||
const newPriority = component.priority ?? 0
|
||||
|
||||
// Replace component if priority is higher
|
||||
if (newPriority > existingPriority) {
|
||||
components.splice(components.indexOf(existingComponent), 1, component)
|
||||
}
|
||||
// Warn if a user-defined (or prioritized) component conflicts with a previously scanned component
|
||||
if (newPriority > 0 && newPriority === existingPriority) {
|
||||
warnAboutDuplicateComponent(pascalName, filePath, existingComponent.filePath)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
return component
|
||||
}
|
||||
|
||||
const suffix = (mode !== 'all' ? `-${mode}` : '')
|
||||
const componentNameSegments = resolveComponentNameSegments(fileName.replace(/["']/g, ''), prefixParts)
|
||||
const pascalName = pascalCase(componentNameSegments)
|
||||
const defaultComponentNameSegments = resolveComponentNameSegments(fileName.replace(/["']/g, ''), prefixParts)
|
||||
|
||||
if (resolvedNames.has(pascalName + suffix) || resolvedNames.has(pascalName)) {
|
||||
warnAboutDuplicateComponent(pascalName, filePath, resolvedNames.get(pascalName) || resolvedNames.get(pascalName + suffix)!)
|
||||
continue
|
||||
}
|
||||
resolvedNames.set(pascalName + suffix, filePath)
|
||||
const componentsDefinitions = fileExt === '.vue'
|
||||
? [await getComponents('default')]
|
||||
: await Promise.all((await resolveModuleExportNames(filePath, {
|
||||
extensions: ['.ts', '.js', '.tsx', '.jsx'],
|
||||
})).map(getComponents))
|
||||
|
||||
const kebabName = kebabCase(componentNameSegments)
|
||||
const shortPath = relative(srcDir, filePath)
|
||||
const chunkName = 'components/' + kebabName + suffix
|
||||
|
||||
let component: Component = {
|
||||
// inheritable from directory configuration
|
||||
mode,
|
||||
global,
|
||||
island,
|
||||
prefetch: Boolean(dir.prefetch),
|
||||
preload: Boolean(dir.preload),
|
||||
// specific to the file
|
||||
filePath,
|
||||
pascalName,
|
||||
kebabName,
|
||||
chunkName,
|
||||
shortPath,
|
||||
export: 'default',
|
||||
// by default, give priority to scanned components
|
||||
priority: dir.priority ?? 1,
|
||||
}
|
||||
|
||||
if (typeof dir.extendComponent === 'function') {
|
||||
component = (await dir.extendComponent(component)) || component
|
||||
}
|
||||
|
||||
// Ignore files like `~/components/index.vue` which end up not having a name at all
|
||||
if (!pascalName) {
|
||||
logger.warn(`Component did not resolve to a file name in \`~/${relative(srcDir, filePath)}\`.`)
|
||||
continue
|
||||
}
|
||||
|
||||
const existingComponent = components.find(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))
|
||||
// Ignore component if component is already defined (with same mode)
|
||||
if (existingComponent) {
|
||||
const existingPriority = existingComponent.priority ?? 0
|
||||
const newPriority = component.priority ?? 0
|
||||
|
||||
// Replace component if priority is higher
|
||||
if (newPriority > existingPriority) {
|
||||
components.splice(components.indexOf(existingComponent), 1, component)
|
||||
}
|
||||
// Warn if a user-defined (or prioritized) component conflicts with a previously scanned component
|
||||
if (newPriority > 0 && newPriority === existingPriority) {
|
||||
warnAboutDuplicateComponent(pascalName, filePath, existingComponent.filePath)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
components.push(component)
|
||||
components.push(...componentsDefinitions.filter<Component>((c): c is Component => Boolean(c)))
|
||||
}
|
||||
scannedPaths.push(dir.path)
|
||||
}
|
||||
|
13
packages/nuxt/test/fixture/components/Named.ts
Normal file
13
packages/nuxt/test/fixture/components/Named.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export const NamedExport = defineComponent({
|
||||
setup () {
|
||||
return () => 'hello'
|
||||
},
|
||||
})
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return () => 'default'
|
||||
},
|
||||
})
|
@ -47,8 +47,9 @@ const dirs: ComponentsDir[] = [
|
||||
enabled: true,
|
||||
extensions: [
|
||||
'vue',
|
||||
'ts',
|
||||
],
|
||||
pattern: '**/*.{vue,}',
|
||||
pattern: '**/*.{vue,ts}',
|
||||
ignore: [
|
||||
'**/*.stories.{js,ts,jsx,tsx}',
|
||||
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
|
||||
@ -61,8 +62,9 @@ const dirs: ComponentsDir[] = [
|
||||
enabled: true,
|
||||
extensions: [
|
||||
'vue',
|
||||
'ts',
|
||||
],
|
||||
pattern: '**/*.{vue,}',
|
||||
pattern: '**/*.{vue,ts}',
|
||||
ignore: [
|
||||
'**/*.stories.{js,ts,jsx,tsx}',
|
||||
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
|
||||
@ -74,10 +76,11 @@ const dirs: ComponentsDir[] = [
|
||||
path: rFixture('components'),
|
||||
extensions: [
|
||||
'vue',
|
||||
'ts',
|
||||
],
|
||||
prefix: 'nuxt',
|
||||
enabled: true,
|
||||
pattern: '**/*.{vue,}',
|
||||
pattern: '**/*.{vue,ts}',
|
||||
ignore: [
|
||||
'**/*.stories.{js,ts,jsx,tsx}',
|
||||
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
|
||||
@ -127,6 +130,32 @@ const expectedComponents = [
|
||||
preload: false,
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
chunkName: 'components/named-export',
|
||||
export: 'NamedExport',
|
||||
global: undefined,
|
||||
island: undefined,
|
||||
kebabName: 'named-export',
|
||||
mode: 'all',
|
||||
pascalName: 'NamedExport',
|
||||
prefetch: false,
|
||||
preload: false,
|
||||
priority: 1,
|
||||
shortPath: 'components/Named.ts',
|
||||
},
|
||||
{
|
||||
chunkName: 'components/named',
|
||||
export: 'default',
|
||||
global: undefined,
|
||||
island: undefined,
|
||||
kebabName: 'named',
|
||||
mode: 'all',
|
||||
pascalName: 'Named',
|
||||
prefetch: false,
|
||||
preload: false,
|
||||
priority: 1,
|
||||
shortPath: 'components/Named.ts',
|
||||
},
|
||||
{
|
||||
mode: 'client',
|
||||
pascalName: 'Nuxt3',
|
||||
|
Loading…
Reference in New Issue
Block a user