@@ -20,6 +20,10 @@ export function compile(css: string): {
2020 invalidCandidates . add ( candidate )
2121 }
2222
23+ // Track `@apply` information
24+ let isApplyUsed = css . includes ( '@apply' )
25+ let applyables = new Map < string , AstNode [ ] > ( )
26+
2327 // Find all `@theme` declarations
2428 let theme = new Theme ( )
2529 let firstThemeRule : Rule | null = null
@@ -28,6 +32,14 @@ export function compile(css: string): {
2832 walk ( ast , ( node , { replaceWith } ) => {
2933 if ( node . kind !== 'rule' ) return
3034
35+ // Track all user-defined classes for `@apply` support
36+ if ( isApplyUsed && node . selector [ 0 ] === '.' && ! node . selector . includes ( ' ' ) ) {
37+ let candidate = node . selector . slice ( 1 )
38+ let existing = applyables . get ( candidate ) ?? [ ]
39+ existing . push ( node )
40+ applyables . set ( candidate , existing )
41+ }
42+
3143 // Drop instances of `@media reference`
3244 //
3345 // We support `@import "tailwindcss/theme" reference` as a way to import an external theme file
@@ -143,7 +155,7 @@ export function compile(css: string): {
143155 } )
144156
145157 // Replace `@apply` rules with the actual utility classes.
146- if ( css . includes ( '@apply' ) ) {
158+ if ( isApplyUsed ) {
147159 walk ( ast , ( node , { replaceWith } ) => {
148160 if ( node . kind === 'rule' && node . selector [ 0 ] === '@' && node . selector . startsWith ( '@apply' ) ) {
149161 let candidates = node . selector
@@ -153,6 +165,25 @@ export function compile(css: string): {
153165
154166 // Replace the `@apply` rule with the actual utility classes
155167 {
168+ let newNodes : AstNode [ ] = [ ]
169+ for ( let candidate of candidates . splice ( 0 ) ) {
170+ if ( applyables . has ( candidate ) ) {
171+ for ( let candidateNode of applyables . get ( candidate ) ?? [ ] ) {
172+ candidateNode = structuredClone ( candidateNode )
173+
174+ if ( candidateNode . kind === 'rule' && candidateNode . selector [ 0 ] !== '@' ) {
175+ for ( let child of candidateNode . nodes ) {
176+ newNodes . push ( child )
177+ }
178+ } else {
179+ newNodes . push ( candidateNode )
180+ }
181+ }
182+ } else {
183+ candidates . push ( candidate )
184+ }
185+ }
186+
156187 // Parse the candidates to an AST that we can replace the `@apply` rule with.
157188 let candidateAst = compileCandidates ( candidates , designSystem , {
158189 onInvalidCandidate : ( candidate ) => {
@@ -163,7 +194,6 @@ export function compile(css: string): {
163194 // Collect the nodes to insert in place of the `@apply` rule. When a
164195 // rule was used, we want to insert its children instead of the rule
165196 // because we don't want the wrapping selector.
166- let newNodes : AstNode [ ] = [ ]
167197 for ( let candidateNode of candidateAst ) {
168198 if ( candidateNode . kind === 'rule' && candidateNode . selector [ 0 ] !== '@' ) {
169199 for ( let child of candidateNode . nodes ) {
0 commit comments