@@ -76,16 +76,17 @@ async function parseCss(
7676
7777 await substituteAtImports ( ast , base , loadStylesheet )
7878
79- // Find all `@theme` declarations
8079 let important : boolean | null = null
8180 let theme = new Theme ( )
8281 let customVariants : ( ( designSystem : DesignSystem ) => void ) [ ] = [ ]
8382 let customUtilities : ( ( designSystem : DesignSystem ) => void ) [ ] = [ ]
8483 let firstThemeRule : Rule | null = null
8584 let globs : { base : string ; pattern : string } [ ] = [ ]
8685
86+ // Handle at-rules
8787 walk ( ast , ( node , { parent, replaceWith, context } ) => {
8888 if ( node . kind !== 'rule' ) return
89+ if ( node . selector [ 0 ] !== '@' ) return
8990
9091 // Collect custom `@utility` at-rules
9192 if ( node . selector . startsWith ( '@utility ' ) ) {
@@ -196,124 +197,117 @@ async function parseCss(
196197 }
197198 }
198199
199- // Drop instances of `@media theme(…)`
200- //
201- // We support `@import "tailwindcss/theme" theme(reference)` as a way to
202- // import an external theme file as a reference, which becomes `@media
203- // theme(reference) { … }` when the `@import` is processed.
204- if ( node . selector . startsWith ( '@media theme(' ) ) {
205- let themeParams = node . selector . slice ( 13 , - 1 )
200+ if ( node . selector . startsWith ( '@media ' ) ) {
201+ let params = segment ( node . selector . slice ( 7 ) , ' ' )
202+ let unknownParams : string [ ] = [ ]
206203
207- walk ( node . nodes , ( child ) => {
208- if ( child . kind !== 'rule' ) {
209- throw new Error (
210- 'Files imported with `@import "…" theme(…)` must only contain `@theme` blocks.' ,
211- )
212- }
213- if ( child . selector === '@theme' || child . selector . startsWith ( '@theme ' ) ) {
214- child . selector += ' ' + themeParams
215- return WalkAction . Skip
204+ for ( let param of params ) {
205+ // Handle `@media theme(…)`
206+ //
207+ // We support `@import "tailwindcss/theme" theme(reference)` as a way to
208+ // import an external theme file as a reference, which becomes `@media
209+ // theme(reference) { … }` when the `@import` is processed.
210+ if ( param . startsWith ( 'theme(' ) ) {
211+ let themeParams = param . slice ( 6 , - 1 )
212+
213+ walk ( node . nodes , ( child ) => {
214+ if ( child . kind !== 'rule' ) {
215+ throw new Error (
216+ 'Files imported with `@import "…" theme(…)` must only contain `@theme` blocks.' ,
217+ )
218+ }
219+ if ( child . selector === '@theme' || child . selector . startsWith ( '@theme ' ) ) {
220+ child . selector += ' ' + themeParams
221+ return WalkAction . Skip
222+ }
223+ } )
216224 }
217- } )
218- replaceWith ( node . nodes )
219- return WalkAction . Skip
220- }
221225
222- // Drop instances of `@media prefix(…)`
223- //
224- // We support `@import "tailwindcss" prefix(ident)` as a way to
225- // configure a theme prefix for variables and utilities.
226- if ( node . selector . startsWith ( '@media prefix(' ) ) {
227- let themeParams = node . selector . slice ( 7 )
228-
229- walk ( node . nodes , ( child ) => {
230- if ( child . kind !== 'rule' ) return
231- if ( child . selector === '@theme' || child . selector . startsWith ( '@theme ' ) ) {
232- child . selector += ' ' + themeParams
233- return WalkAction . Skip
226+ // Handle `@media prefix(…)`
227+ //
228+ // We support `@import "tailwindcss" prefix(ident)` as a way to
229+ // configure a theme prefix for variables and utilities.
230+ else if ( param . startsWith ( 'prefix(' ) ) {
231+ let prefix = param . slice ( 7 , - 1 )
232+
233+ walk ( node . nodes , ( child ) => {
234+ if ( child . kind !== 'rule' ) return
235+ if ( child . selector === '@theme' || child . selector . startsWith ( '@theme ' ) ) {
236+ child . selector += ` prefix(${ prefix } )`
237+ return WalkAction . Skip
238+ }
239+ } )
234240 }
235- } )
236- replaceWith ( node . nodes )
237- return WalkAction . Skip
238- }
239-
240- if ( node . selector . startsWith ( '@media' ) ) {
241- let features = segment ( node . selector . slice ( 6 ) , ' ' )
242- let shouldReplace = true
243241
244- for ( let i = 0 ; i < features . length ; i ++ ) {
245- let part = features [ i ]
242+ // Handle important
243+ else if ( param === 'important' ) {
244+ important = true
245+ }
246246
247- // Drop instances of `@media important`
248247 //
249- // We support `@import "tailwindcss" important` to mark all declarations
250- // in generated utilities as `!important`.
251- if ( part === 'important' ) {
252- important = true
253- shouldReplace = true
254- features [ i ] = ''
248+ else {
249+ unknownParams . push ( param )
255250 }
256251 }
257252
258- let remaining = features . filter ( Boolean ) . join ( ' ' )
259-
260- node . selector = `@media ${ remaining } `
261-
262- if ( remaining . trim ( ) === '' && shouldReplace ) {
253+ if ( unknownParams . length > 0 ) {
254+ node . selector = `@media ${ unknownParams . join ( ' ' ) } `
255+ } else if ( params . length > 0 ) {
263256 replaceWith ( node . nodes )
264257 }
265258
266259 return WalkAction . Skip
267260 }
268261
269- if ( node . selector !== '@theme' && ! node . selector . startsWith ( '@theme ' ) ) return
262+ // Handle `@theme`
263+ if ( node . selector === '@theme' || node . selector . startsWith ( '@theme ' ) ) {
264+ let [ themeOptions , themePrefix ] = parseThemeOptions ( node . selector )
270265
271- let [ themeOptions , themePrefix ] = parseThemeOptions ( node . selector )
266+ if ( themePrefix ) {
267+ if ( ! IS_VALID_PREFIX . test ( themePrefix ) ) {
268+ throw new Error (
269+ `The prefix "${ themePrefix } " is invalid. Prefixes must be lowercase ASCII letters (a-z) only.` ,
270+ )
271+ }
272272
273- if ( themePrefix ) {
274- if ( ! IS_VALID_PREFIX . test ( themePrefix ) ) {
275- throw new Error (
276- `The prefix "${ themePrefix } " is invalid. Prefixes must be lowercase ASCII letters (a-z) only.` ,
277- )
273+ theme . prefix = themePrefix
278274 }
279275
280- theme . prefix = themePrefix
281- }
282-
283- // Record all custom properties in the `@theme` declaration
284- walk ( node . nodes , ( child , { replaceWith } ) => {
285- // Collect `@keyframes` rules to re-insert with theme variables later,
286- // since the `@theme` rule itself will be removed.
287- if ( child . kind === 'rule' && child . selector . startsWith ( '@keyframes ' ) ) {
288- theme . addKeyframes ( child )
289- replaceWith ( [ ] )
290- return WalkAction . Skip
291- }
276+ // Record all custom properties in the `@theme` declaration
277+ walk ( node . nodes , ( child , { replaceWith } ) => {
278+ // Collect `@keyframes` rules to re-insert with theme variables later,
279+ // since the `@theme` rule itself will be removed.
280+ if ( child . kind === 'rule' && child . selector . startsWith ( '@keyframes ' ) ) {
281+ theme . addKeyframes ( child )
282+ replaceWith ( [ ] )
283+ return WalkAction . Skip
284+ }
292285
293- if ( child . kind === 'comment' ) return
294- if ( child . kind === 'declaration' && child . property . startsWith ( '--' ) ) {
295- theme . add ( child . property , child . value ?? '' , themeOptions )
296- return
297- }
286+ if ( child . kind === 'comment' ) return
287+ if ( child . kind === 'declaration' && child . property . startsWith ( '--' ) ) {
288+ theme . add ( child . property , child . value ?? '' , themeOptions )
289+ return
290+ }
298291
299- let snippet = toCss ( [ rule ( node . selector , [ child ] ) ] )
300- . split ( '\n' )
301- . map ( ( line , idx , all ) => `${ idx === 0 || idx >= all . length - 2 ? ' ' : '>' } ${ line } ` )
302- . join ( '\n' )
292+ let snippet = toCss ( [ rule ( node . selector , [ child ] ) ] )
293+ . split ( '\n' )
294+ . map ( ( line , idx , all ) => `${ idx === 0 || idx >= all . length - 2 ? ' ' : '>' } ${ line } ` )
295+ . join ( '\n' )
303296
304- throw new Error (
305- `\`@theme\` blocks must only contain custom properties or \`@keyframes\`.\n\n${ snippet } ` ,
306- )
307- } )
297+ throw new Error (
298+ `\`@theme\` blocks must only contain custom properties or \`@keyframes\`.\n\n${ snippet } ` ,
299+ )
300+ } )
308301
309- // Keep a reference to the first `@theme` rule to update with the full theme
310- // later, and delete any other `@theme` rules.
311- if ( ! firstThemeRule && ! ( themeOptions & ThemeOptions . REFERENCE ) ) {
312- firstThemeRule = node
313- } else {
314- replaceWith ( [ ] )
302+ // Keep a reference to the first `@theme` rule to update with the full
303+ // theme later, and delete any other `@theme` rules.
304+ if ( ! firstThemeRule && ! ( themeOptions & ThemeOptions . REFERENCE ) ) {
305+ firstThemeRule = node
306+ } else {
307+ replaceWith ( [ ] )
308+ }
309+ return WalkAction . Skip
315310 }
316- return WalkAction . Skip
317311 } )
318312
319313 let designSystem = buildDesignSystem ( theme )
0 commit comments