Skip to content

Commit 5e9b3ba

Browse files
Subwaytimeantfu
andauthored
feat: directory namespaces support (#8)
Co-authored-by: Anthony Fu <[email protected]>
1 parent 5b64de8 commit 5e9b3ba

File tree

13 files changed

+149
-20
lines changed

13 files changed

+149
-20
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
</a>
1717
</p>
1818

19-
2019
<br>
2120

2221
## Usage
@@ -36,11 +35,11 @@ import ViteComponents from 'vite-plugin-components'
3635
export default {
3736
plugins: [
3837
ViteComponents()
39-
]
40-
}
38+
],
39+
};
4140
```
4241

43-
That's all.
42+
That's all.
4443

4544
Use components in templates as you would usually do but NO `import` and `component registration` required anymore! It will import components on demand, code splitting is also possible.
4645

@@ -89,11 +88,18 @@ The following show the default values of the configuration
8988
ViteComponents({
9089
// relative paths to the directory to search for components.
9190
dirs: ['src/components'],
91+
9292
// valid file extensions for components.
9393
extensions: ['vue'],
9494
// search for subdirectories
9595
deep: true,
9696

97+
// Allow subdirectories as namespace prefix for components.
98+
directoryAsNamespace: false,
99+
// Subdirectory paths for ignoring namespace prefixes
100+
// works when `directoryAsNamespace: true`
101+
globalNamespaces: [],
102+
97103
// vite config
98104
// currently, vite does not provide an API for plugins to get the config https://github.com/vitejs/vite/issues/738
99105
// you will need to pass `alias` and `root` if you set them in vite config

src/context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class Context {
2626
throw new Error('[vite-plugin-components] extensions are required to search for components')
2727

2828
const extsGlob = exts.length === 1 ? exts[0] : `{${exts.join(',')}}`
29+
2930
this.globs = toArray(dirs).map(i =>
3031
deep
3132
? `${i}/**/*.${extsGlob}`
@@ -73,7 +74,7 @@ export class Context {
7374
Array
7475
.from(this._componentPaths)
7576
.forEach((path) => {
76-
const name = normalize(getNameFromFilePath(path))
77+
const name = normalize(getNameFromFilePath(path, this.options))
7778
if (this._componentNameMap[name]) {
7879
console.warn(`[vite-plugin-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`)
7980
return

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ const defaultOptions: Options = {
1010
dirs: 'src/components',
1111
extensions: 'vue',
1212
deep: true,
13+
14+
directoryAsNamespace: false,
15+
globalNamespaces: [],
16+
1317
alias: {},
1418
root: process.cwd(),
1519
}

src/types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,38 @@ export interface Options {
77
* @default 'src/components'
88
*/
99
dirs: string | string[]
10+
1011
/**
1112
* Valid file extensions for components.
1213
* @default ['vue']
1314
*/
1415
extensions: string | string[]
16+
1517
/**
1618
* Search for subdirectories
1719
* @default true
1820
*/
1921
deep: boolean
22+
23+
/**
24+
* Allow subdirectories as namespace prefix for components
25+
* @default false
26+
*/
27+
directoryAsNamespace: boolean
28+
29+
/**
30+
* Subdirectory paths for ignoring namespace prefixes
31+
* works when `directoryAsNamespace: true`
32+
* @default "[]"
33+
*/
34+
globalNamespaces: string[]
35+
2036
/**
2137
* Path alias, same as what you passed to vite root config
2238
* @default {}
2339
*/
2440
alias: Record<string, string>
41+
2542
/**
2643
* Root path of Vite project.
2744
* @default 'process.cwd()'

src/utils.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import path from 'path'
2-
// @ts-ignore
32
import minimatch from 'minimatch'
3+
import { Options } from './types'
4+
5+
export interface ResolveComponent {
6+
filename: string
7+
namespace?: string
8+
}
49

510
export function normalize(str: string) {
611
return capitalize(camelize(str))
@@ -20,6 +25,14 @@ export function toArray<T>(arr: T | T[]): T[] {
2025
return [arr]
2126
}
2227

28+
export function isEmpty(value: any) {
29+
if (!value || value === null || value === undefined || (Array.isArray(value) && Object.keys(value).length <= 0))
30+
return true
31+
32+
else
33+
return false
34+
}
35+
2336
export function matchGlobs(filepath: string, globs: string[]) {
2437
for (const glob of globs) {
2538
if (minimatch(filepath, glob))
@@ -28,15 +41,48 @@ export function matchGlobs(filepath: string, globs: string[]) {
2841
return false
2942
}
3043

31-
export function getNameFromFilePath(filePath: string): string {
44+
export function getNameFromFilePath(filePath: string, options: Options): string {
45+
const { dirs, directoryAsNamespace, globalNamespaces } = options
46+
3247
const parsedFilePath = path.parse(filePath)
33-
if (parsedFilePath.name === 'index') {
34-
const filePathSegments = filePath.split(path.sep)
35-
const parentDirName = filePathSegments[filePathSegments.length - 2]
36-
if (parentDirName)
37-
return parentDirName
48+
49+
let strippedPath = ''
50+
51+
// remove include directories from filepath
52+
for (const dir of toArray(dirs)) {
53+
if (parsedFilePath.dir.startsWith(dir)) {
54+
strippedPath = parsedFilePath.dir.slice(dir.length)
55+
break
56+
}
3857
}
39-
return parsedFilePath.name
58+
59+
let folders = strippedPath.slice(1).split('/').filter(Boolean)
60+
let filename = parsedFilePath.name
61+
62+
// set parent directory as filename if it is index
63+
if (filename === 'index' && !directoryAsNamespace) {
64+
filename = `${folders.slice(-1)[0]}`
65+
return filename
66+
}
67+
68+
if (directoryAsNamespace) {
69+
// remove namesspaces from folder names
70+
if (globalNamespaces.some((name: string) => folders.includes(name)))
71+
folders = folders.filter(f => !globalNamespaces.includes(f))
72+
73+
if (filename.toLowerCase() === 'index')
74+
filename = ''
75+
76+
if (!isEmpty(folders)) {
77+
// add folders to filename
78+
filename = [...folders, filename].filter(Boolean).join('-')
79+
}
80+
81+
console.log('!!!', filename)
82+
return filename
83+
}
84+
85+
return filename
4086
}
4187

4288
export function resolveAlias(filepath: string, alias: Record<string, string>) {

test/fixture/src/App.vue

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
11
<template>
2-
<ComponentA msg="a" />
3-
<component-b msg="b" />
4-
<ComponentC msg="c" />
5-
<recursive :data="tree" />
2+
<div class="block">
3+
<h1>Basic (4)</h1>
4+
<ComponentA msg="a" />
5+
<component-b msg="b" />
6+
<ComponentC msg="c" />
7+
<h3>Recursive Components</h3>
8+
<recursive :data="tree" />
9+
</div>
10+
11+
<div class="block">
12+
<h1>Namespaced (4)</h1>
13+
<!-- Index -->
14+
<Book />
15+
<UiButton />
16+
<UiNestedCheckbox />
17+
<!-- Global -->
18+
<Avatar />
19+
</div>
620
</template>
721

822
<script setup lang='ts'>
@@ -16,3 +30,12 @@ export const tree = ref({
1630
],
1731
})
1832
</script>
33+
34+
<style scoped>
35+
.block {
36+
padding: 0px 20px 10px 20px;
37+
margin: 20px 20px;
38+
border: 1px solid #888;
39+
border-radius: 5px;
40+
}
41+
</style>

test/fixture/src/components/Recursive.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div>
33
<div>{{ data.label }}</div>
44
<div class="child">
5-
<recursive v-for="item in data.children" :data="item" />
5+
<recursive v-for="(item, idx) in data.children" :key="idx" :data="item" />
66
</div>
77
</div>
88
</template>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<h3>Index Component: <code>book/index.vue</code></h3>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'Book',
8+
}
9+
</script>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<h3>Global Namespaced Component: <code>global/avatar.vue</code></h3>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'Avatar',
8+
}
9+
</script>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<h3>Namespaced Component: <code>iu/button.vue</code></h3>
3+
</template>
4+
5+
<script>
6+
export default {
7+
name: 'UiButton',
8+
}
9+
</script>

0 commit comments

Comments
 (0)