Skip to content

Commit f1cccee

Browse files
committed
feat: allow sort files
1 parent e59fea4 commit f1cccee

File tree

4 files changed

+125
-21
lines changed

4 files changed

+125
-21
lines changed

src/core/context.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -215,27 +215,27 @@ export class Context {
215215
private updateComponentNameMap() {
216216
this._componentNameMap = {}
217217

218-
Array
219-
.from(this._componentPaths)
220-
.forEach((path) => {
221-
const fileName = getNameFromFilePath(path, this.options)
222-
const name = this.options.prefix
223-
? `${pascalCase(this.options.prefix)}${pascalCase(fileName)}`
224-
: pascalCase(fileName)
225-
if (isExclude(name, this.options.excludeNames)) {
226-
debug.components('exclude', name)
227-
return
228-
}
229-
if (this._componentNameMap[name] && !this.options.allowOverrides) {
230-
console.warn(`[unplugin-vue-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`)
231-
return
232-
}
218+
const files = Array.from(this._componentPaths)
219+
220+
;(this.options.sort?.(this.options.root, files) || files).forEach((path) => {
221+
const fileName = getNameFromFilePath(path, this.options)
222+
const name = this.options.prefix
223+
? `${pascalCase(this.options.prefix)}${pascalCase(fileName)}`
224+
: pascalCase(fileName)
225+
if (isExclude(name, this.options.excludeNames)) {
226+
debug.components('exclude', name)
227+
return
228+
}
229+
if (this._componentNameMap[name] && !this.options.allowOverrides) {
230+
console.warn(`[unplugin-vue-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`)
231+
return
232+
}
233233

234-
this._componentNameMap[name] = {
235-
as: name,
236-
from: path,
237-
}
238-
})
234+
this._componentNameMap[name] = {
235+
as: name,
236+
from: path,
237+
}
238+
})
239239
}
240240

241241
async findComponent(name: string, type: 'component' | 'directive', excludePaths: string[] = []): Promise<ComponentInfo | undefined> {

src/core/options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getPackageInfoSync, isPackageExists } from 'local-pkg'
55
import { detectTypeImports } from './type-imports/detect'
66
import { escapeSpecialChars } from './utils'
77

8-
export const defaultOptions: Omit<Required<Options>, 'include' | 'exclude' | 'excludeNames' | 'transformer' | 'globs' | 'globsExclude' | 'directives' | 'types' | 'version'> = {
8+
export const defaultOptions: Omit<Required<Options>, 'include' | 'exclude' | 'excludeNames' | 'transformer' | 'globs' | 'globsExclude' | 'directives' | 'types' | 'version' | 'sort'> = {
99
dirs: 'src/components',
1010
extensions: 'vue',
1111
deep: true,

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ export interface Options {
230230
* @default 'default'
231231
*/
232232
syncMode?: 'default' | 'append' | 'overwrite'
233+
234+
sort?: (root: string, files: string[]) => string[]
233235
}
234236

235237
export type ResolvedOptions = Omit<

test/sort.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { relative, resolve } from 'pathe'
2+
import { describe, expect, it } from 'vitest'
3+
import { Context } from '../src/core/context'
4+
5+
const root = resolve(__dirname, '../examples/vite-vue3')
6+
7+
function cleanup(data: any) {
8+
return Object.values(data).map((e: any) => {
9+
delete e.absolute
10+
e.from = relative(root, e.from).replace(/\\/g, '/')
11+
return e
12+
})
13+
}
14+
15+
describe('sort', () => {
16+
it('sort ascending works', async () => {
17+
const ctx = new Context({
18+
dirs: ['src/components/ui'],
19+
sort(_, files) {
20+
return files.toSorted((a, b) => a.localeCompare(b))
21+
},
22+
})
23+
ctx.setRoot(root)
24+
ctx.searchGlob()
25+
26+
expect(cleanup(ctx.componentNameMap)).toMatchInlineSnapshot(`
27+
[
28+
{
29+
"as": "Button",
30+
"from": "src/components/ui/button.vue",
31+
},
32+
{
33+
"as": "Checkbox",
34+
"from": "src/components/ui/nested/checkbox.vue",
35+
},
36+
]
37+
`)
38+
39+
// simulate the watcher adding a components
40+
ctx.addComponents(resolve(root, 'src/components/book/index.vue').replace(/\\/g, '/'))
41+
ctx.searchGlob()
42+
43+
expect(cleanup(ctx.componentNameMap)).toMatchInlineSnapshot(`
44+
[
45+
{
46+
"as": "Book",
47+
"from": "src/components/book/index.vue",
48+
},
49+
{
50+
"as": "Button",
51+
"from": "src/components/ui/button.vue",
52+
},
53+
{
54+
"as": "Checkbox",
55+
"from": "src/components/ui/nested/checkbox.vue",
56+
},
57+
]
58+
`)
59+
})
60+
61+
it('sort descending works', async () => {
62+
const ctx = new Context({
63+
dirs: ['src/components/book'],
64+
sort(_, files) {
65+
return files.toSorted((a, b) => b.localeCompare(a))
66+
},
67+
})
68+
ctx.setRoot(root)
69+
ctx.searchGlob()
70+
71+
expect(cleanup(ctx.componentNameMap)).toMatchInlineSnapshot(`
72+
[
73+
{
74+
"as": "Book",
75+
"from": "src/components/book/index.vue",
76+
},
77+
]
78+
`)
79+
80+
// simulate the watcher adding a components
81+
ctx.addComponents(resolve(root, 'src/components/ui/button.vue').replace(/\\/g, '/'))
82+
ctx.addComponents(resolve(root, 'src/components/ui/nested/checkbox.vue').replace(/\\/g, '/'))
83+
ctx.searchGlob()
84+
85+
expect(cleanup(ctx.componentNameMap)).toMatchInlineSnapshot(`
86+
[
87+
{
88+
"as": "Checkbox",
89+
"from": "src/components/ui/nested/checkbox.vue",
90+
},
91+
{
92+
"as": "Button",
93+
"from": "src/components/ui/button.vue",
94+
},
95+
{
96+
"as": "Book",
97+
"from": "src/components/book/index.vue",
98+
},
99+
]
100+
`)
101+
})
102+
})

0 commit comments

Comments
 (0)