feat(nuxt): scanComponents named exports

This commit is contained in:
Julien Huang 2024-05-10 15:24:44 +02:00
parent 416d76ab80
commit 7ce0b8754f
3 changed files with 117 additions and 62 deletions

View File

@ -6,6 +6,7 @@ import { isIgnored, logger, useNuxt } from '@nuxt/kit'
import { withTrailingSlash } from 'ufo' import { withTrailingSlash } from 'ufo'
import type { Component, ComponentsDir } from 'nuxt/schema' import type { Component, ComponentsDir } from 'nuxt/schema'
import { resolveModuleExportNames } from 'mlly'
import { resolveComponentNameSegments } from '../core/utils' 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))) : [], (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 * In case we have index as filename the component become the parent path
* @example third-components/index.vue -> third-component * @example third-components/index.vue -> third-component
* if not take the filename * if not take the filename
* @example third-components/Awesome.vue -> Awesome * @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 island = /\.(island)(\.global)?$/.test(fileName) || dir.island
const global = /\.(global)(\.island)?$/.test(fileName) || dir.global const global = /\.(global)(\.island)?$/.test(fileName) || dir.global
@ -92,17 +94,17 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */ fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
} }
const suffix = (mode !== 'all' ? `-${mode}` : '') const getComponents = async (exportName: string): Promise<Component | null> => {
const componentNameSegments = resolveComponentNameSegments(fileName.replace(/["']/g, ''), prefixParts) const componentNameSegment = exportName === 'default' ? defaultComponentNameSegments : resolveComponentNameSegments(exportName, defaultComponentNameSegments)
const pascalName = pascalCase(componentNameSegments) const pascalName = pascalCase(componentNameSegment)
if (resolvedNames.has(pascalName + suffix) || resolvedNames.has(pascalName)) { if (resolvedNames.has(pascalName + suffix) || resolvedNames.has(pascalName)) {
warnAboutDuplicateComponent(pascalName, filePath, resolvedNames.get(pascalName) || resolvedNames.get(pascalName + suffix)!) warnAboutDuplicateComponent(pascalName, filePath, resolvedNames.get(pascalName) || resolvedNames.get(pascalName + suffix)!)
continue return null
} }
resolvedNames.set(pascalName + suffix, filePath) resolvedNames.set(pascalName + suffix, filePath)
const kebabName = kebabCase(componentNameSegments) const kebabName = kebabCase(componentNameSegment)
const shortPath = relative(srcDir, filePath) const shortPath = relative(srcDir, filePath)
const chunkName = 'components/' + kebabName + suffix const chunkName = 'components/' + kebabName + suffix
@ -119,7 +121,7 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
kebabName, kebabName,
chunkName, chunkName,
shortPath, shortPath,
export: 'default', export: exportName,
// by default, give priority to scanned components // by default, give priority to scanned components
priority: dir.priority ?? 1, priority: dir.priority ?? 1,
} }
@ -131,7 +133,7 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
// Ignore files like `~/components/index.vue` which end up not having a name at all // Ignore files like `~/components/index.vue` which end up not having a name at all
if (!pascalName) { if (!pascalName) {
logger.warn(`Component did not resolve to a file name in \`~/${relative(srcDir, filePath)}\`.`) logger.warn(`Component did not resolve to a file name in \`~/${relative(srcDir, filePath)}\`.`)
continue return null
} }
const existingComponent = components.find(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode)) const existingComponent = components.find(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))
@ -149,10 +151,21 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
warnAboutDuplicateComponent(pascalName, filePath, existingComponent.filePath) warnAboutDuplicateComponent(pascalName, filePath, existingComponent.filePath)
} }
continue return null
}
return component
} }
components.push(component) const suffix = (mode !== 'all' ? `-${mode}` : '')
const defaultComponentNameSegments = resolveComponentNameSegments(fileName.replace(/["']/g, ''), prefixParts)
const componentsDefinitions = fileExt === '.vue'
? [await getComponents('default')]
: await Promise.all((await resolveModuleExportNames(filePath, {
extensions: ['.ts', '.js', '.tsx', '.jsx'],
})).map(getComponents))
components.push(...componentsDefinitions.filter<Component>((c): c is Component => Boolean(c)))
} }
scannedPaths.push(dir.path) scannedPaths.push(dir.path)
} }

View File

@ -0,0 +1,13 @@
import { defineComponent } from 'vue'
export const NamedExport = defineComponent({
setup () {
return () => 'hello'
},
})
export default defineComponent({
setup () {
return () => 'default'
},
})

View File

@ -47,8 +47,9 @@ const dirs: ComponentsDir[] = [
enabled: true, enabled: true,
extensions: [ extensions: [
'vue', 'vue',
'ts',
], ],
pattern: '**/*.{vue,}', pattern: '**/*.{vue,ts}',
ignore: [ ignore: [
'**/*.stories.{js,ts,jsx,tsx}', '**/*.stories.{js,ts,jsx,tsx}',
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}', '**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
@ -61,8 +62,9 @@ const dirs: ComponentsDir[] = [
enabled: true, enabled: true,
extensions: [ extensions: [
'vue', 'vue',
'ts',
], ],
pattern: '**/*.{vue,}', pattern: '**/*.{vue,ts}',
ignore: [ ignore: [
'**/*.stories.{js,ts,jsx,tsx}', '**/*.stories.{js,ts,jsx,tsx}',
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}', '**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
@ -74,10 +76,11 @@ const dirs: ComponentsDir[] = [
path: rFixture('components'), path: rFixture('components'),
extensions: [ extensions: [
'vue', 'vue',
'ts',
], ],
prefix: 'nuxt', prefix: 'nuxt',
enabled: true, enabled: true,
pattern: '**/*.{vue,}', pattern: '**/*.{vue,ts}',
ignore: [ ignore: [
'**/*.stories.{js,ts,jsx,tsx}', '**/*.stories.{js,ts,jsx,tsx}',
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}', '**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
@ -127,6 +130,32 @@ const expectedComponents = [
preload: false, preload: false,
priority: 1, 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', mode: 'client',
pascalName: 'Nuxt3', pascalName: 'Nuxt3',