1
- import { join , relative } from 'path'
2
1
import { existsSync } from 'fs'
3
- import { defuArrayFn } from 'defu'
2
+ import { join , relative } from 'pathe'
3
+ import defu , { defuArrayFn } from 'defu'
4
4
import chalk from 'chalk'
5
5
import consola from 'consola'
6
6
import {
7
7
defineNuxtModule ,
8
8
installModule ,
9
9
addTemplate ,
10
10
addDevServerHandler ,
11
- requireModule ,
12
11
isNuxt2 ,
13
12
createResolver ,
14
13
resolvePath ,
15
- addVitePlugin
14
+ addVitePlugin ,
15
+ tryRequireModule
16
16
} from '@nuxt/kit'
17
17
import { name , version } from '../package.json'
18
18
import vitePlugin from './hmr'
19
19
import defaultTailwindConfig from './tailwind.config'
20
20
21
21
const logger = consola . withScope ( 'nuxt:tailwindcss' )
22
22
23
+ const layerPaths = ( srcDir : string ) => ( [
24
+ `${ srcDir } /components/**/*.{vue,js,ts}` ,
25
+ `${ srcDir } /layouts/**/*.vue` ,
26
+ `${ srcDir } /pages/**/*.vue` ,
27
+ `${ srcDir } /composables/**/*.{js,ts}` ,
28
+ `${ srcDir } /plugins/**/*.{js,ts}` ,
29
+ `${ srcDir } /App.{js,ts,vue}` ,
30
+ `${ srcDir } /app.{js,ts,vue}` ,
31
+ `${ srcDir } /Error.{js,ts,vue}` ,
32
+ `${ srcDir } /error.{js,ts,vue}`
33
+ ] )
34
+
23
35
export interface ModuleHooks {
24
36
'tailwindcss:config' : ( tailwindConfig : any ) => void
25
37
}
@@ -33,46 +45,76 @@ export default defineNuxtModule({
33
45
defaults : nuxt => ( {
34
46
configPath : 'tailwind.config' ,
35
47
cssPath : join ( nuxt . options . dir . assets , 'css/tailwind.css' ) ,
36
- config : defaultTailwindConfig ( nuxt . options ) ,
48
+ config : defaultTailwindConfig ( ) ,
37
49
viewer : true ,
38
50
exposeConfig : false ,
39
51
injectPosition : 0 ,
40
52
disableHmrHotfix : false
41
53
} ) ,
42
54
async setup ( moduleOptions , nuxt ) {
43
- const configPath = await resolvePath ( moduleOptions . configPath )
44
- const cssPath = await resolvePath ( moduleOptions . cssPath , { extensions : [ '.css' , '.sass' , '.scss' , '.less' , '.styl' ] } )
45
- const injectPosition = ~ ~ Math . min ( moduleOptions . injectPosition , ( nuxt . options . css || [ ] ) . length + 1 )
55
+ /**
56
+ * Config file handling
57
+ */
46
58
47
- // Include CSS file in project css
48
- let resolvedCss : string
49
- if ( typeof cssPath === 'string' ) {
50
- if ( existsSync ( cssPath ) ) {
51
- logger . info ( `Using Tailwind CSS from ~/${ relative ( nuxt . options . srcDir , cssPath ) } ` )
52
- resolvedCss = cssPath
53
- } else {
54
- logger . info ( 'Using default Tailwind CSS file from runtime/tailwind.css' )
55
- resolvedCss = createResolver ( import . meta. url ) . resolve ( 'runtime/tailwind.css' )
59
+ const configPaths = [ ]
60
+ const contentPaths = [ ]
61
+
62
+ /**
63
+ * Push config paths into `configPaths` without extension.
64
+ * Allows next steps of processing to try both .js / .ts when resolving the config.
65
+ */
66
+ const addConfigPath = async ( path : string | string [ ] ) => {
67
+ if ( typeof path === 'string' ) {
68
+ path = ( await resolvePath ( path ) ) . split ( '.' ) . slice ( 0 , - 1 ) . join ( '.' )
69
+ configPaths . push ( path )
70
+ return
71
+ }
72
+
73
+ if ( Array . isArray ( path ) ) {
74
+ for ( let _path of path ) {
75
+ _path = ( await resolvePath ( _path ) ) . split ( '.' ) . slice ( 0 , - 1 ) . join ( '.' )
76
+ configPaths . push ( )
77
+ }
56
78
}
57
79
}
58
80
59
- // Inject only if this file isn't listed already by user (e.g. user may put custom path both here and in css):
60
- const resolvedNuxtCss = await Promise . all ( nuxt . options . css . map ( p => resolvePath ( p ) ) )
61
- if ( ! resolvedNuxtCss . includes ( resolvedCss ) ) {
62
- nuxt . options . css . splice ( injectPosition , 0 , resolvedCss )
81
+ // Support `extends` directories
82
+ if ( nuxt . options . _layers && nuxt . options . _layers . length > 1 ) {
83
+ interface NuxtLayer {
84
+ config : any
85
+ configFile : string
86
+ cwd : string
87
+ }
88
+
89
+ for ( const layer of ( nuxt . options . _layers as NuxtLayer [ ] ) ) {
90
+ await addConfigPath ( layer ?. config ?. tailwindcss ?. configPath || join ( layer . cwd , 'tailwind.config' ) )
91
+ contentPaths . push ( ...layerPaths ( layer . cwd ) )
92
+ }
93
+ } else {
94
+ await addConfigPath ( moduleOptions . configPath )
95
+ contentPaths . push ( ...layerPaths ( nuxt . options . srcDir ) )
63
96
}
64
97
65
- // Extend the Tailwind config
98
+ // Watch the Tailwind config file to restart the server
99
+ if ( nuxt . options . dev ) {
100
+ configPaths . forEach ( path => nuxt . options . watch . push ( path ) )
101
+ }
102
+
103
+ // Recursively resolve each config and merge tailwind configs together.
66
104
let tailwindConfig : any = { }
67
- if ( existsSync ( configPath ) ) {
68
- tailwindConfig = requireModule ( configPath , { clearCache : true } )
69
- logger . info ( `Merging Tailwind config from ~/ ${ relative ( nuxt . options . srcDir , configPath ) } ` )
105
+ for ( const configPath of configPaths ) {
106
+ const _tailwindConfig = tryRequireModule ( configPath , { clearCache : true } )
107
+
70
108
// Transform purge option from Array to object with { content }
71
- if ( Array . isArray ( tailwindConfig . purge ) ) {
72
- tailwindConfig . content = tailwindConfig . purge
109
+ if ( _tailwindConfig && Array . isArray ( _tailwindConfig . purge ) ) {
110
+ _tailwindConfig . content = _tailwindConfig . purge
73
111
}
112
+
113
+ tailwindConfig = defu ( _tailwindConfig || { } , tailwindConfig )
74
114
}
75
115
116
+ tailwindConfig . content = [ ...( tailwindConfig . content || [ ] ) , ...contentPaths ]
117
+
76
118
// Merge with our default purgecss default
77
119
tailwindConfig = defuArrayFn ( tailwindConfig , moduleOptions . config )
78
120
@@ -93,18 +135,43 @@ export default defineNuxtModule({
93
135
nuxt . options . alias [ '#tailwind-config' ] = template . dst
94
136
}
95
137
96
- // Watch the Tailwind config file to restart the server
97
- if ( nuxt . options . dev ) {
98
- nuxt . options . watch . push ( configPath )
99
- }
100
-
101
138
// Allow extending tailwindcss config by other modules
102
139
// @ts -ignore
103
140
await nuxt . callHook ( 'tailwindcss:config' , tailwindConfig )
104
141
105
142
// Compute tailwindConfig hash
106
143
tailwindConfig . _hash = String ( Date . now ( ) )
107
144
145
+ /**
146
+ * CSS file handling
147
+ */
148
+
149
+ const cssPath = await resolvePath ( moduleOptions . cssPath , { extensions : [ '.css' , '.sass' , '.scss' , '.less' , '.styl' ] } )
150
+ const injectPosition = ~ ~ Math . min ( moduleOptions . injectPosition , ( nuxt . options . css || [ ] ) . length + 1 )
151
+
152
+ // Include CSS file in project css
153
+ let resolvedCss : string
154
+ if ( typeof cssPath === 'string' ) {
155
+ if ( existsSync ( cssPath ) ) {
156
+ logger . info ( `Using Tailwind CSS from ~/${ relative ( nuxt . options . srcDir , cssPath ) } ` )
157
+ resolvedCss = cssPath
158
+ } else {
159
+ logger . info ( 'Using default Tailwind CSS file from runtime/tailwind.css' )
160
+ // @ts -ignore
161
+ resolvedCss = createResolver ( import . meta. url ) . resolve ( 'runtime/tailwind.css' )
162
+ }
163
+ }
164
+
165
+ // Inject only if this file isn't listed already by user (e.g. user may put custom path both here and in css):
166
+ const resolvedNuxtCss = await Promise . all ( nuxt . options . css . map ( p => resolvePath ( p ) ) )
167
+ if ( ! resolvedNuxtCss . includes ( resolvedCss ) ) {
168
+ nuxt . options . css . splice ( injectPosition , 0 , resolvedCss )
169
+ }
170
+
171
+ /**
172
+ * PostCSS 8 support for Nuxt 2
173
+ */
174
+
108
175
// Setup postcss plugins
109
176
// https://tailwindcss.com/docs/using-with-preprocessors#future-css-features
110
177
const postcssOptions =
@@ -120,11 +187,19 @@ export default defineNuxtModule({
120
187
await installModule ( '@nuxt/postcss8' )
121
188
}
122
189
190
+ /**
191
+ * Vite HMR support
192
+ */
193
+
123
194
if ( nuxt . options . dev && ! moduleOptions . disableHmrHotfix ) {
124
195
// Insert Vite plugin to work around HMR issue
125
196
addVitePlugin ( vitePlugin ( tailwindConfig , nuxt . options . rootDir , resolvedCss ) )
126
197
}
127
198
199
+ /**
200
+ * Viewer
201
+ */
202
+
128
203
// Add _tailwind config viewer endpoint
129
204
if ( nuxt . options . dev && moduleOptions . viewer ) {
130
205
const route = '/_tailwind'
0 commit comments