-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
/
scan.ts
150 lines (129 loc) · 5.33 KB
/
scan.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import { basename, dirname, extname, join, relative } from 'pathe'
import { globby } from 'globby'
import { pascalCase, splitByCase } from 'scule'
import { isIgnored } from '@nuxt/kit'
// eslint-disable-next-line vue/prefer-import-from-vue
import { hyphenate } from '@vue/shared'
import { withTrailingSlash } from 'ufo'
import type { Component, ComponentsDir } from 'nuxt/schema'
/**
* 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[] = []
// Keep resolved path to avoid duplicates
const filePaths = new Set<string>()
// All scanned paths
const scannedPaths: string[] = []
for (const dir of dirs) {
// A map from resolved path to component name (used for making duplicate warning message)
const resolvedNames = new Map<string, string>()
const files = (await globby(dir.pattern!, { cwd: dir.path, ignore: dir.ignore })).sort()
for (const _file of files) {
const filePath = join(dir.path, _file)
if (scannedPaths.find(d => filePath.startsWith(withTrailingSlash(d))) || isIgnored(filePath)) {
continue
}
// Avoid duplicate paths
if (filePaths.has(filePath)) { continue }
filePaths.add(filePath)
/**
* 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(
dir.prefix ? splitByCase(dir.prefix) : [],
(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 third-components/Awesome.vue -> Awesome
*/
let fileName = basename(filePath, extname(filePath))
const island = /\.(island)(\.global)?$/.test(fileName) || dir.island
const global = /\.(global)(\.island)?$/.test(fileName) || dir.global
const mode = island ? 'server' : (fileName.match(/(?<=\.)(client|server)(\.global|\.island)*$/)?.[1] || 'all') as 'client' | 'server' | 'all'
fileName = fileName.replace(/(\.(client|server))?(\.global|\.island)*$/, '')
if (fileName.toLowerCase() === 'index') {
fileName = dir.pathPrefix === false ? basename(dirname(filePath)) : '' /* inherits from path */
}
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` +
`\n - ${filePath}` +
`\n - ${resolvedNames.get(componentName)}`
)
continue
}
resolvedNames.set(componentName + suffix, filePath)
const pascalName = pascalCase(componentName).replace(/["']/g, '')
const kebabName = hyphenate(componentName)
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: 1
}
if (typeof dir.extendComponent === 'function') {
component = (await dir.extendComponent(component)) || component
}
// Ignore component if component is already defined (with same mode)
if (!components.some(c => c.pascalName === component.pascalName && ['all', component.mode].includes(c.mode))) {
components.push(component)
}
}
scannedPaths.push(dir.path)
}
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)
}