1
- import { readFile } from 'fs/promises'
2
- import { basename } from 'pathe'
3
- import { defineNuxtModule , resolveModule , createResolver , addServerHandler } from '@nuxt/kit'
4
- import { parseComponent } from './utils/parseComponent'
5
- import type { ComponentProp , ComponentSlot , HookData } from './types'
1
+ import type { MetaCheckerOptions } from 'vue-component-meta'
2
+ import {
3
+ addServerHandler ,
4
+ addTemplate ,
5
+ createResolver ,
6
+ defineNuxtModule ,
7
+ resolveModule
8
+ } from '@nuxt/kit'
6
9
7
- export interface ModuleOptions { }
10
+ import { createComponentMetaCheckerByJsonConfig } from 'vue-component-meta'
11
+ import type { HookData } from './types'
8
12
13
+ export interface ModuleOptions {
14
+ checkerOptions ?: MetaCheckerOptions
15
+ }
9
16
export interface ModuleHooks {
10
17
'component-meta:parsed' ( data : HookData ) : void
11
18
}
@@ -15,44 +22,153 @@ export default defineNuxtModule<ModuleOptions>({
15
22
name : 'nuxt-component-meta' ,
16
23
configKey : 'componentMeta'
17
24
} ,
18
- setup ( _options , nuxt ) {
25
+ defaults : ( ) => ( {
26
+ checkerOptions : {
27
+ forceUseTs : true ,
28
+ schema : { }
29
+ }
30
+ } ) ,
31
+ setup ( options , nuxt ) {
19
32
const resolver = createResolver ( import . meta. url )
20
33
21
- let componentMeta
34
+ let componentMeta : any = { }
35
+
36
+ // default to empty permisive object if no componentMeta is defined
37
+ const script = [ 'export const components = {}' , 'export default components' ]
38
+ const dts = [
39
+ "import type { NuxtComponentMeta } from 'nuxt-component-meta'" ,
40
+ 'export type { NuxtComponentMeta }' ,
41
+ 'export type NuxtComponentMetaNames = string' ,
42
+ 'declare const components: Record<NuxtComponentMetaNames, NuxtComponentMeta>' ,
43
+ 'export { components as default, components }'
44
+ ]
45
+
22
46
nuxt . hook ( 'components:extend' , async ( components ) => {
23
- componentMeta = await Promise . all (
24
- components . map ( async ( component ) => {
25
- const path = resolveModule ( ( component as any ) . filePath , { paths : nuxt . options . rootDir } )
26
- const source = await readFile ( path , { encoding : 'utf-8' } )
27
-
28
- const data : HookData = {
29
- meta : {
30
- name : ( component as any ) . pascalName ,
31
- global : Boolean ( component . global ) ,
32
- props : [ ] as ComponentProp [ ] ,
33
- slots : [ ] as ComponentSlot [ ]
34
- } ,
35
- path,
36
- source
37
- }
38
-
39
- const { props, slots } = parseComponent ( data . meta . name , source , { filename : basename ( path ) } )
40
- data . meta . props = props
47
+ const checker = createComponentMetaCheckerByJsonConfig (
48
+ nuxt . options . rootDir ,
49
+ {
50
+ extends : '../tsconfig.json' ,
51
+ include : [
52
+ '**/*'
53
+ ]
54
+ } ,
55
+ options . checkerOptions
56
+ )
57
+
58
+ function reducer ( acc : any , component : any ) {
59
+ if ( component . name ) {
60
+ acc [ component . name ] = component
61
+ }
62
+
63
+ return acc
64
+ }
65
+
66
+ async function mapper ( component : any ) : Promise < HookData [ 'meta' ] > {
67
+ const path = resolveModule ( component . filePath , {
68
+ paths : nuxt . options . rootDir
69
+ } )
70
+
71
+ const data = {
72
+ meta : {
73
+ name : component . pascalName ,
74
+ global : Boolean ( component . global ) ,
75
+ props : [ ] ,
76
+ slots : [ ] ,
77
+ events : [ ] ,
78
+ exposed : [ ]
79
+ } ,
80
+ path,
81
+ source : ''
82
+ } as HookData
83
+
84
+ if ( ! checker ) {
85
+ return data . meta
86
+ }
87
+
88
+ try {
89
+ const { props, slots, events, exposed } = checker ?. getComponentMeta ( path )
90
+
41
91
data . meta . slots = slots
92
+ data . meta . events = events
93
+ data . meta . exposed = exposed
94
+ data . meta . props = props
95
+ . filter ( prop => ! prop . global )
96
+ . sort ( ( a , b ) => {
97
+ // sort required properties first
98
+ if ( ! a . required && b . required ) {
99
+ return 1
100
+ }
101
+ if ( a . required && ! b . required ) {
102
+ return - 1
103
+ }
104
+ // then ensure boolean properties are sorted last
105
+ if ( a . type === 'boolean' && b . type !== 'boolean' ) {
106
+ return 1
107
+ }
108
+ if ( a . type !== 'boolean' && b . type === 'boolean' ) {
109
+ return - 1
110
+ }
111
+
112
+ return 0
113
+ } )
42
114
43
115
// @ts -ignore
44
116
await nuxt . callHook ( 'component-meta:parsed' , data )
117
+ } catch ( error : any ) {
118
+ console . error ( `Unable to parse component "${ path } ": ${ error } ` )
119
+ }
45
120
46
- return data . meta
47
- } )
121
+ return data . meta
122
+ }
123
+
124
+ componentMeta = ( await Promise . all ( components . map ( mapper ) ) ) . reduce (
125
+ reducer ,
126
+ { }
48
127
)
128
+
129
+ // generate virtual script
130
+ script . splice ( 0 , script . length )
131
+ script . push ( `export const components = ${ JSON . stringify ( componentMeta ) } ` )
132
+ script . push ( 'export default components' )
133
+
134
+ for ( const key in componentMeta ) {
135
+ script . push ( `export const meta${ key } = ${ JSON . stringify (
136
+ componentMeta [ key ]
137
+ ) } `)
138
+ }
139
+
140
+ // generate typescript definition file
141
+ const componentMetaKeys = Object . keys ( componentMeta )
142
+ const componentNameString = componentMetaKeys . map ( name => `"${ name } "` )
143
+ const exportNames = componentMetaKeys . map ( name => `meta${ name } ` )
144
+
145
+ dts . splice ( 2 , script . length ) // keep the two first lines (import type and export NuxtComponentMeta)
146
+ dts . push ( `export type NuxtComponentMetaNames = ${ componentNameString . join ( ' | ' ) } ` )
147
+ dts . push ( 'declare const components: Record<NuxtComponentMetaNames, NuxtComponentMeta>' )
148
+
149
+ for ( const exportName of exportNames ) {
150
+ dts . push ( `declare const ${ exportName } : NuxtComponentMeta` )
151
+ }
152
+
153
+ dts . push ( `export { components as default, components, ${ exportNames . join ( ', ' ) } }` )
154
+ } )
155
+
156
+ const template = addTemplate ( {
157
+ filename : 'nuxt-component-meta.mjs' ,
158
+ getContents : ( ) => script . join ( '\n' )
159
+ } )
160
+ addTemplate ( {
161
+ filename : 'nuxt-component-meta.d.ts' ,
162
+ getContents : ( ) => dts . join ( '\n' ) ,
163
+ write : true
49
164
} )
165
+ nuxt . options . alias [ '#nuxt-component-meta' ] = template . dst !
50
166
51
167
nuxt . hook ( 'nitro:config' , ( nitroConfig ) => {
52
168
nitroConfig . handlers = nitroConfig . handlers || [ ]
53
169
nitroConfig . virtual = nitroConfig . virtual || { }
54
170
55
- nitroConfig . virtual [ '#meta/virtual/meta' ] = ( ) => `export const components = ${ JSON . stringify ( componentMeta ) } `
171
+ nitroConfig . virtual [ '#meta/virtual/meta' ] = ( ) => script . join ( '\n' )
56
172
} )
57
173
58
174
addServerHandler ( {
0 commit comments