2017-08-26 14:36:40 -05:00
import _ from 'lodash'
2017-11-04 01:32:25 -05:00
import postcss from 'postcss'
2017-10-06 15:40:40 -04:00
import escapeClassName from '../util/escapeClassName'
2018-09-14 16:22:49 -04:00
import prefixSelector from '../util/prefixSelector'
2019-07-11 17:07:24 +12:00
import increaseSpecificity from '../util/increaseSpecificity'
2017-10-06 15:40:40 -04:00
2018-02-28 20:00:25 -05:00
function buildClassTable ( css ) {
const classTable = { }
2017-11-10 12:18:20 -05:00
css . walkRules ( rule => {
2018-02-28 20:00:25 -05:00
if ( ! _ . has ( classTable , rule . selector ) ) {
classTable [ rule . selector ] = [ ]
2017-11-10 12:18:20 -05:00
}
2018-02-28 20:00:25 -05:00
classTable [ rule . selector ] . push ( rule )
2017-11-10 12:18:20 -05:00
} )
2018-02-28 20:00:25 -05:00
return classTable
}
2018-07-11 11:45:45 -04:00
function buildShadowTable ( generatedUtilities ) {
const utilities = postcss . root ( )
2018-07-11 19:14:33 -04:00
postcss . root ( { nodes : generatedUtilities } ) . walkAtRules ( 'variants' , atRule => {
2018-07-11 11:45:45 -04:00
utilities . append ( atRule . clone ( ) . nodes )
} )
return buildClassTable ( utilities )
}
2018-02-28 20:00:25 -05:00
function normalizeClassName ( className ) {
return ` . ${ escapeClassName ( _ . trimStart ( className , '.' ) ) } `
}
2018-09-15 08:24:42 -04:00
function findClass ( classToApply , classTable , onError ) {
const matches = _ . get ( classTable , classToApply , [ ] )
2018-02-28 20:00:25 -05:00
2017-11-16 07:51:56 -05:00
if ( _ . isEmpty ( matches ) ) {
2018-09-15 08:24:42 -04:00
return [ ]
2017-11-10 12:18:20 -05:00
}
2017-11-16 07:54:40 -05:00
if ( matches . length > 1 ) {
2017-11-16 08:16:59 -05:00
// prettier-ignore
2018-07-11 11:45:45 -04:00
throw onError ( ` \` @apply \` cannot be used with ${ classToApply } because ${ classToApply } is included in multiple rulesets. ` )
2017-11-16 07:54:40 -05:00
}
2018-02-28 20:00:25 -05:00
const [ match ] = matches
if ( match . parent . type !== 'root' ) {
// prettier-ignore
2018-07-11 11:45:45 -04:00
throw onError ( ` \` @apply \` cannot be used with ${ classToApply } because ${ classToApply } is nested inside of an at-rule (@ ${ match . parent . name } ). ` )
2018-02-28 20:00:25 -05:00
}
return match . clone ( ) . nodes
2017-11-10 12:18:20 -05:00
}
2018-07-11 11:45:45 -04:00
export default function ( config , generatedUtilities ) {
2017-11-04 00:35:59 -05:00
return function ( css ) {
2018-02-28 20:00:25 -05:00
const classLookup = buildClassTable ( css )
2018-09-21 14:50:40 -04:00
const shadowLookup = buildShadowTable ( generatedUtilities )
2018-02-28 20:00:25 -05:00
2017-11-04 01:32:25 -05:00
css . walkRules ( rule => {
2017-08-26 11:26:38 -04:00
rule . walkAtRules ( 'apply' , atRule => {
2018-07-11 11:45:45 -04:00
const classesAndProperties = postcss . list . space ( atRule . params )
2017-08-25 16:02:12 -05:00
2017-10-06 15:40:40 -04:00
/ *
* Don ' t wreck CSSNext - style @ apply rules :
* http : //cssnext.io/features/#custom-properties-set-apply
*
* These are deprecated in CSSNext but still playing it safe for now .
* We might consider renaming this at - rule .
* /
2018-07-11 11:45:45 -04:00
const [ customProperties , classes ] = _ . partition ( classesAndProperties , classOrProperty => {
return _ . startsWith ( classOrProperty , '--' )
2017-08-26 11:26:38 -04:00
} )
2017-08-25 16:02:12 -05:00
2017-12-12 07:30:09 -05:00
const decls = _ ( classes )
2018-07-11 11:45:45 -04:00
. reject ( cssClass => cssClass === '!important' )
. flatMap ( cssClass => {
2018-09-15 08:24:42 -04:00
const classToApply = normalizeClassName ( cssClass )
const onError = message => {
return atRule . error ( message )
}
return _ . reduce (
[
2019-08-06 08:23:47 -04:00
// Find exact class match in user's CSS
( ) => {
return findClass ( classToApply , classLookup , onError )
} ,
// Find exact class match in shadow lookup
( ) => {
return findClass ( classToApply , shadowLookup , onError )
} ,
// Find prefixed version of class in shadow lookup
( ) => {
return findClass (
prefixSelector ( config . prefix , classToApply ) ,
shadowLookup ,
onError
)
} ,
// Find important-scoped version of class in shadow lookup
( ) => {
return findClass (
increaseSpecificity ( config . important , classToApply ) ,
shadowLookup ,
onError
)
} ,
// Find important-scoped and prefixed version of class in shadow lookup
( ) => {
return findClass (
increaseSpecificity (
config . important ,
prefixSelector ( config . prefix , classToApply )
) ,
shadowLookup ,
onError
)
} ,
2018-09-15 08:24:42 -04:00
( ) => {
// prettier-ignore
2018-10-17 11:47:14 +02:00
throw onError ( ` \` @apply \` cannot be used with \` ${ classToApply } \` because \` ${ classToApply } \` either cannot be found, or its actual definition includes a pseudo-selector like :hover, :active, etc. If you're sure that \` ${ classToApply } \` exists, make sure that any \` @import \` statements are being properly processed *before* Tailwind CSS sees your CSS, as \` @apply \` can only be used for classes in the same CSS tree. ` )
2018-09-15 08:24:42 -04:00
} ,
] ,
( classDecls , candidate ) => ( ! _ . isEmpty ( classDecls ) ? classDecls : candidate ( ) ) ,
[ ]
2018-09-14 17:04:54 +02:00
)
2017-08-26 11:26:38 -04:00
} )
2017-12-12 07:30:09 -05:00
. value ( )
2017-08-25 16:02:12 -05:00
2018-07-11 11:45:45 -04:00
_ . tap ( _ . last ( classesAndProperties ) === '!important' , important => {
2017-12-12 07:30:09 -05:00
decls . forEach ( decl => ( decl . important = important ) )
2017-12-08 16:05:54 -05:00
} )
2017-08-28 11:26:17 -04:00
atRule . before ( decls )
2017-08-25 16:02:12 -05:00
2017-08-26 11:26:38 -04:00
atRule . params = customProperties . join ( ' ' )
2017-08-25 16:02:12 -05:00
2017-08-26 11:26:38 -04:00
if ( _ . isEmpty ( customProperties ) ) {
atRule . remove ( )
}
} )
} )
}
2017-11-03 09:54:15 -04:00
}