@@ -538,17 +538,24 @@ async function parseCss(
538538 node . context = { }
539539 }
540540
541- // Replace `@apply` rules with the actual utility classes.
542- features |= substituteAtApply ( ast , designSystem )
543-
544- features |= substituteFunctions ( ast , designSystem . resolveThemeValue )
545-
546- // Remove `@utility`, we couldn't replace it before yet because we had to
547- // handle the nested `@apply` at-rules first.
541+ // Post-process `@utility` nodes.
542+ //
543+ // We have to do this as a separate step because if we replace the `@utility`
544+ // nodes when we collect it in the system, then we can't substitute the
545+ // `@apply` at-rules anymore.
546+ //
547+ // We also can't substitute the `@apply` at-rules while collecting the
548+ // `@utility` rules, because if it relies on a utility that is defined later,
549+ // then we wouldn't be able to resolve the applied utility.
550+ //
551+ // Lastly, we can't rely on the single `substituteAtApply` call at the end
552+ // because this replaces `@apply`, but won't replace `@apply` if we injected
553+ // an `@apply`.
548554 walk ( ast , ( node , { replaceWith } ) => {
549555 if ( node . kind !== 'at-rule' ) return
550556
551557 if ( node . name === '@utility' ) {
558+ substituteAtApply ( node . nodes , designSystem )
552559 replaceWith ( [ ] )
553560 }
554561
@@ -557,6 +564,11 @@ async function parseCss(
557564 return WalkAction . Skip
558565 } )
559566
567+ // Replace `@apply` rules with the actual utility classes.
568+ features |= substituteAtApply ( ast , designSystem )
569+
570+ features |= substituteFunctions ( ast , designSystem . resolveThemeValue )
571+
560572 return {
561573 designSystem,
562574 ast,
0 commit comments