mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 23:22:02 +00:00
chore(nuxt3): add tests, comments and example for components scan (#1455)
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
parent
0a74940bf6
commit
6b873f15bc
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Awesome Component
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -1,5 +1,15 @@
|
|||||||
import { defineNuxtConfig } from 'nuxt3'
|
import { defineNuxtConfig } from 'nuxt3'
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
vite: false
|
vite: false,
|
||||||
|
components: {
|
||||||
|
dirs: [
|
||||||
|
'~/components',
|
||||||
|
{
|
||||||
|
path: '~/other-components-folder',
|
||||||
|
extensions: ['vue'],
|
||||||
|
prefix: 'nuxt'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Awesome Component
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -40,6 +40,10 @@ export interface ScanDir {
|
|||||||
* Prefix component name by it's path.
|
* Prefix component name by it's path.
|
||||||
*/
|
*/
|
||||||
pathPrefix?: boolean
|
pathPrefix?: boolean
|
||||||
|
/**
|
||||||
|
* Ignore scanning this directory if set to `true`
|
||||||
|
*/
|
||||||
|
enabled?: boolean
|
||||||
/**
|
/**
|
||||||
* Level is used to define a hint when overwriting the components which have the same name in two different directories.
|
* Level is used to define a hint when overwriting the components which have the same name in two different directories.
|
||||||
*/
|
*/
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { basename, extname, join, dirname, relative } from 'pathe'
|
import { basename, extname, join, dirname, relative } from 'pathe'
|
||||||
import globby from 'globby'
|
import globby from 'globby'
|
||||||
import { pascalCase, splitByCase } from 'scule'
|
import { pascalCase, splitByCase } from 'scule'
|
||||||
import type { ScanDir, Component } from '@nuxt/kit'
|
import type { ScanDir, Component, ComponentsDir } from '@nuxt/kit'
|
||||||
|
|
||||||
export function sortDirsByPathLength ({ path: pathA }: ScanDir, { path: pathB }: ScanDir): number {
|
export function sortDirsByPathLength ({ path: pathA }: ScanDir, { path: pathB }: ScanDir): number {
|
||||||
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
return pathB.split(/[\\/]/).filter(Boolean).length - pathA.split(/[\\/]/).filter(Boolean).length
|
||||||
@ -13,12 +13,26 @@ function hyphenate (str: string):string {
|
|||||||
return str.replace(/\B([A-Z])/g, '-$1').toLowerCase()
|
return str.replace(/\B([A-Z])/g, '-$1').toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<Component[]> {
|
/**
|
||||||
|
* Scan the components inside different components folders
|
||||||
|
* and return a unique list of components
|
||||||
|
*
|
||||||
|
* @param dirs all folders where components are defined
|
||||||
|
* @param srcDir src path of your app
|
||||||
|
* @returns {Promise} Component found promise
|
||||||
|
*/
|
||||||
|
export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Promise<Component[]> {
|
||||||
|
// All scanned components
|
||||||
const components: Component[] = []
|
const components: Component[] = []
|
||||||
|
|
||||||
|
// Keep resolved path to avoid duplicates
|
||||||
const filePaths = new Set<string>()
|
const filePaths = new Set<string>()
|
||||||
|
|
||||||
|
// All scanned paths
|
||||||
const scannedPaths: string[] = []
|
const scannedPaths: string[] = []
|
||||||
|
|
||||||
for (const dir of dirs.sort(sortDirsByPathLength)) {
|
for (const dir of dirs.sort(sortDirsByPathLength)) {
|
||||||
|
// A map from resolved path to component name (used for making duplicate warning message)
|
||||||
const resolvedNames = new Map<string, string>()
|
const resolvedNames = new Map<string, string>()
|
||||||
|
|
||||||
for (const _file of await globby(dir.pattern!, { cwd: dir.path, ignore: dir.ignore })) {
|
for (const _file of await globby(dir.pattern!, { cwd: dir.path, ignore: dir.ignore })) {
|
||||||
@ -28,18 +42,42 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid duplicate paths
|
||||||
if (filePaths.has(filePath)) { continue }
|
if (filePaths.has(filePath)) { continue }
|
||||||
|
|
||||||
filePaths.add(filePath)
|
filePaths.add(filePath)
|
||||||
|
|
||||||
// Resolve componentName
|
/**
|
||||||
|
* Create an array of prefixes base on the prefix config
|
||||||
|
* Empty prefix will be an empty array
|
||||||
|
*
|
||||||
|
* @example prefix: 'nuxt' -> ['nuxt']
|
||||||
|
* @example prefix: 'nuxt-test' -> ['nuxt', 'test']
|
||||||
|
*/
|
||||||
const prefixParts = ([] as string[]).concat(
|
const prefixParts = ([] as string[]).concat(
|
||||||
dir.prefix ? splitByCase(dir.prefix) : [],
|
dir.prefix ? splitByCase(dir.prefix) : [],
|
||||||
(dir.pathPrefix !== false) ? splitByCase(relative(dir.path, dirname(filePath))) : []
|
(dir.pathPrefix !== false) ? splitByCase(relative(dir.path, dirname(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 thid-components/Awesome.vue -> Awesome
|
||||||
|
*/
|
||||||
let fileName = basename(filePath, extname(filePath))
|
let fileName = basename(filePath, extname(filePath))
|
||||||
|
|
||||||
if (fileName.toLowerCase() === 'index') {
|
if (fileName.toLowerCase() === 'index') {
|
||||||
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
|
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 fileNameParts = splitByCase(fileName)
|
||||||
|
|
||||||
const componentNameParts: string[] = []
|
const componentNameParts: string[] = []
|
||||||
@ -53,7 +91,6 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
|
|||||||
const componentName = pascalCase(componentNameParts) + pascalCase(fileNameParts)
|
const componentName = pascalCase(componentNameParts) + pascalCase(fileNameParts)
|
||||||
|
|
||||||
if (resolvedNames.has(componentName)) {
|
if (resolvedNames.has(componentName)) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn(`Two component files resolving to the same name \`${componentName}\`:\n` +
|
console.warn(`Two component files resolving to the same name \`${componentName}\`:\n` +
|
||||||
`\n - ${filePath}` +
|
`\n - ${filePath}` +
|
||||||
`\n - ${resolvedNames.get(componentName)}`
|
`\n - ${resolvedNames.get(componentName)}`
|
||||||
@ -92,7 +129,6 @@ export async function scanComponents (dirs: ScanDir[], srcDir: string): Promise<
|
|||||||
components.push(component)
|
components.push(component)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scannedPaths.push(dir.path)
|
scannedPaths.push(dir.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
packages/nuxt3/test/fixture/components/HelloWorld.vue
Normal file
5
packages/nuxt3/test/fixture/components/HelloWorld.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
This is HelloWorld component!
|
||||||
|
</div>
|
||||||
|
</template>
|
5
packages/nuxt3/test/fixture/components/Nuxt3.vue
Normal file
5
packages/nuxt3/test/fixture/components/Nuxt3.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<b style="color: #00C58E">
|
||||||
|
From Nuxt 3
|
||||||
|
</b>
|
||||||
|
</template>
|
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Awesome Component
|
||||||
|
</div>
|
||||||
|
</template>
|
102
packages/nuxt3/test/scan-components.test.ts
Normal file
102
packages/nuxt3/test/scan-components.test.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { resolve } from 'path'
|
||||||
|
import { ComponentsDir } from '@nuxt/kit'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { scanComponents } from '../src/components/scan'
|
||||||
|
|
||||||
|
const fixtureDir = resolve(__dirname, 'fixture')
|
||||||
|
const rFixture = (...p) => resolve(fixtureDir, ...p)
|
||||||
|
|
||||||
|
const dirs: ComponentsDir[] = [
|
||||||
|
{
|
||||||
|
path: rFixture('components'),
|
||||||
|
level: 0,
|
||||||
|
enabled: true,
|
||||||
|
extensions: [
|
||||||
|
'vue'
|
||||||
|
],
|
||||||
|
pattern: '**/*.{vue,}',
|
||||||
|
ignore: [
|
||||||
|
'**/*.stories.{js,ts,jsx,tsx}',
|
||||||
|
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
|
||||||
|
'**/*.d.ts'
|
||||||
|
],
|
||||||
|
transpile: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: rFixture('components'),
|
||||||
|
level: 0,
|
||||||
|
enabled: true,
|
||||||
|
extensions: [
|
||||||
|
'vue'
|
||||||
|
],
|
||||||
|
pattern: '**/*.{vue,}',
|
||||||
|
ignore: [
|
||||||
|
'**/*.stories.{js,ts,jsx,tsx}',
|
||||||
|
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
|
||||||
|
'**/*.d.ts'
|
||||||
|
],
|
||||||
|
transpile: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: rFixture('components'),
|
||||||
|
extensions: [
|
||||||
|
'vue'
|
||||||
|
],
|
||||||
|
prefix: 'nuxt',
|
||||||
|
level: 0,
|
||||||
|
enabled: true,
|
||||||
|
pattern: '**/*.{vue,}',
|
||||||
|
ignore: [
|
||||||
|
'**/*.stories.{js,ts,jsx,tsx}',
|
||||||
|
'**/*{M,.m,-m}ixin.{js,ts,jsx,tsx}',
|
||||||
|
'**/*.d.ts'
|
||||||
|
],
|
||||||
|
transpile: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const expectedComponents = [
|
||||||
|
{
|
||||||
|
pascalName: 'HelloWorld',
|
||||||
|
kebabName: 'hello-world',
|
||||||
|
chunkName: 'components/hello-world',
|
||||||
|
shortPath: 'components/HelloWorld.vue',
|
||||||
|
export: 'default',
|
||||||
|
global: undefined,
|
||||||
|
level: 0,
|
||||||
|
prefetch: false,
|
||||||
|
preload: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pascalName: 'Nuxt3',
|
||||||
|
kebabName: 'nuxt3',
|
||||||
|
chunkName: 'components/nuxt3',
|
||||||
|
shortPath: 'components/Nuxt3.vue',
|
||||||
|
export: 'default',
|
||||||
|
global: undefined,
|
||||||
|
level: 0,
|
||||||
|
prefetch: false,
|
||||||
|
preload: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pascalName: 'ParentFolder',
|
||||||
|
kebabName: 'parent-folder',
|
||||||
|
chunkName: 'components/parent-folder',
|
||||||
|
shortPath: 'components/parent-folder/index.vue',
|
||||||
|
export: 'default',
|
||||||
|
global: undefined,
|
||||||
|
level: 0,
|
||||||
|
prefetch: false,
|
||||||
|
preload: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const srcDir = rFixture('.')
|
||||||
|
|
||||||
|
it('components:scanComponents', async () => {
|
||||||
|
const scannedComponents = await scanComponents(dirs, srcDir)
|
||||||
|
for (const c of scannedComponents) {
|
||||||
|
delete c.filePath
|
||||||
|
}
|
||||||
|
expect(scannedComponents).deep.eq(expectedComponents)
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user