forked from nuxt/framework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
scan.ts
147 lines (124 loc) · 5.08 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
import { basename, extname, join, dirname, relative, resolve } from 'pathe'
import { globby } from 'globby'
import { pascalCase, splitByCase } from 'scule'
import type { Component, ComponentsDir } from '@nuxt/schema'
import { isIgnored } from '@nuxt/kit'
import { hyphenate } from '@vue/shared'
import { withTrailingSlash } from 'ufo'
import { distDir } from '../dirs'
/**
* 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 global = /\.(global)$/.test(fileName) || dir.global
const mode = (fileName.match(/(?<=\.)(client|server)(\.global)?$/)?.[1] || 'all') as 'client' | 'server' | 'all'
fileName = fileName.replace(/(\.(client|server))?(\.global)?$/, '')
if (fileName.toLowerCase() === 'index') {
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 componentNameParts: string[] = []
while (prefixParts.length &&
(prefixParts[0] || '').toLowerCase() !== (fileNameParts[0] || '').toLowerCase()
) {
componentNameParts.push(prefixParts.shift()!)
}
const componentName = pascalCase(componentNameParts) + pascalCase(fileNameParts)
const suffix = (mode !== 'all' ? `-${mode}` : '')
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,
prefetch: Boolean(dir.prefetch),
preload: Boolean(dir.preload),
// specific to the file
filePath,
pascalName,
kebabName,
chunkName,
shortPath,
export: 'default'
}
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)
}
// add server placeholder for .client components server side issue: #7085
components.forEach((component) => {
if (component.mode === 'client' && !components.some(c => c.pascalName === component.pascalName && c.mode === 'server')) {
components.push({
...component,
mode: 'server',
filePath: resolve(distDir, 'app/components/server-placeholder'),
chunkName: 'components/' + component.kebabName
})
}
})
return components
}