@@ -3,6 +3,40 @@ import { join } from 'node:path'
33import { addComponent , addComponentsDir , createResolver , defineNuxtModule } from '@nuxt/kit'
44import { parseSync } from 'oxc-parser'
55
6+ export interface ComponentDirConfig {
7+ path :string
8+ prefix ?:string
9+ }
10+
11+ export type ComponentDirInput = string | ComponentDirConfig
12+
13+ export type ComponentDirOption = ComponentDirInput | ComponentDirInput [ ]
14+
15+ interface NormalizedComponentDir {
16+ path :string
17+ prefix :string
18+ }
19+
20+ function isComponentDirConfig ( value :unknown ) :value isComponentDirConfig {
21+ return typeof value === 'object' && value !== null && 'path' in value
22+ }
23+
24+ function normalizeComponentDirs ( componentDir :ComponentDirOption , fallbackPrefix :string ) :NormalizedComponentDir [ ] {
25+ const dirs = Array . isArray ( componentDir ) ?componentDir :[ componentDir ]
26+
27+ return dirs
28+ . filter ( ( dir ) :dir isComponentDirInput => Boolean ( dir ) )
29+ . map ( ( dir ) => {
30+ if ( typeof dir === 'string' )
31+ return { path :dir , prefix :fallbackPrefix }
32+
33+ if ( isComponentDirConfig ( dir ) )
34+ return { path :dir . path , prefix :dir . prefix ?? fallbackPrefix }
35+
36+ throw new Error ( 'Invalid componentDir entry provided to shadcn module.' )
37+ } )
38+ }
39+
640// TODO: add test to make sure all registry is being parse correctly
741// Module options TypeScript interface definition
842export interface ModuleOptions {
@@ -17,7 +51,7 @@ export interface ModuleOptions {
1751 *@link https://nuxt.com/docs/api/nuxt-config#alias
1852 *@default "@/components/ui"
1953 */
20- componentDir ?:string
54+ componentDir ?:ComponentDirOption
2155}
2256
2357export default defineNuxtModule < ModuleOptions > ( {
@@ -30,63 +64,66 @@ export default defineNuxtModule<ModuleOptions>({
3064componentDir :'@/components/ui' ,
3165} ,
3266async setup ( { prefix, componentDir} , nuxt ) {
33- const COMPONENT_DIR_PATH = componentDir !
3467const ROOT_DIR_PATH = nuxt . options . rootDir
3568const { resolve, resolvePath} = createResolver ( ROOT_DIR_PATH )
3669
37- // Components Auto Imports
38- const componentsPath = await resolvePath ( COMPONENT_DIR_PATH )
39-
40- // Early return if directory doesn't exist
41- if ( ! existsSync ( componentsPath ) ) {
42- console . warn ( `Component directory does not exist:${ componentsPath } ` )
43- return
44- }
45-
46- // Tell Nuxt to not scan `componentsDir` for auto imports as we will do it manually
47- // See https://github.com/unovue/shadcn-vue/pull/528#discussion_r1590206268
48- addComponentsDir ( {
49- path :componentsPath ,
50- extensions :[ ] ,
51- ignore :[ '**/*' ] ,
52- } , {
53- prepend :true ,
54- } )
70+ const normalizedDirs = normalizeComponentDirs ( componentDir ?? '@/components/ui' , prefix ?? 'Ui' )
71+
72+ await Promise . all ( normalizedDirs . map ( async ( { path, prefix :dirPrefix } ) => {
73+ // Components Auto Imports
74+ const componentsPath = await resolvePath ( path )
75+
76+ // Early return if directory doesn't exist
77+ if ( ! existsSync ( componentsPath ) ) {
78+ console . warn ( `Component directory does not exist:${ componentsPath } ` )
79+ return
80+ }
81+
82+ // Tell Nuxt to not scan `componentsDir` for auto imports as we will do it manually
83+ // See https://github.com/unovue/shadcn-vue/pull/528#discussion_r1590206268
84+ addComponentsDir ( {
85+ path :componentsPath ,
86+ extensions :[ ] ,
87+ ignore :[ '**/*' ] ,
88+ } , {
89+ prepend :true ,
90+ } )
91+
92+ // Manually scan `componentsDir` for components and register them for auto imports
93+ try {
94+ await Promise . all ( readdirSync ( componentsPath ) . map ( async ( dir ) => {
95+ try {
96+ const filePath = await resolvePath ( join ( path , dir , 'index' ) , { extensions :[ '.ts' , '.js' ] } )
97+ const content = readFileSync ( filePath , { encoding :'utf8' } )
98+ const ast = parseSync ( filePath , content , {
99+ sourceType :'module' ,
100+ } )
101+
102+ const exportedKeys :string [ ] = ast . program . body
103+ . filter ( node => node . type === 'ExportNamedDeclaration' )
104+ //@ts -expect-error parse return any
105+ . flatMap ( node => node . specifiers ?. map ( specifier => specifier . exported ?. name ) || [ ] )
106+ . filter ( ( key :string ) => / ^ [ A - Z ] / . test ( key ) )
55107
56- // Manually scan `componentsDir` for components and register them for auto imports
57- try {
58- await Promise . all ( readdirSync ( componentsPath ) . map ( async ( dir ) => {
59- try {
60- const filePath = await resolvePath ( join ( COMPONENT_DIR_PATH , dir , 'index' ) , { extensions :[ '.ts' , '.js' ] } )
61- const content = readFileSync ( filePath , { encoding :'utf8' } )
62- const ast = parseSync ( filePath , content , {
63- sourceType :'module' ,
64- } )
65-
66- const exportedKeys :string [ ] = ast . program . body
67- . filter ( node => node . type === 'ExportNamedDeclaration' )
68- //@ts -expect-error parse return any
69- . flatMap ( node => node . specifiers ?. map ( specifier => specifier . exported ?. name ) || [ ] )
70- . filter ( ( key :string ) => / ^ [ A - Z ] / . test ( key ) )
71-
72- exportedKeys . forEach ( ( key ) => {
73- addComponent ( {
74- name :`${ prefix } ${ key } ` , // name of the component to be used in vue templates
75- export :key , // (optional) if the component is a named (rather than default) export
76- filePath :resolve ( filePath ) ,
77- priority :1 ,
108+ exportedKeys . forEach ( ( key ) => {
109+ addComponent ( {
110+ name :`${ dirPrefix } ${ key } ` , // name of the component to be used in vue templates
111+ export :key , // (optional) if the component is a named (rather than default) export
112+ filePath :resolve ( filePath ) ,
113+ priority :1 ,
114+ } )
78115} )
79- } )
80- }
81- catch ( err ) {
82- if ( err instanceof Error )
83- console . warn ( 'Module error: ' , err . message )
84- }
85- } ) )
86- }
87- catch ( err ) {
88- if ( err instanceof Error )
89- console . warn ( err . message )
90- }
116+ }
117+ catch ( err ) {
118+ if ( err instanceof Error )
119+ console . warn ( 'Module error: ' , err . message )
120+ }
121+ } ) )
122+ }
123+ catch ( err ) {
124+ if ( err instanceof Error )
125+ console . warn ( err . message )
126+ }
127+ } ) )
91128} ,
92129} )