Skip to content

Commit eacc463

Browse files
committed
Match apply rules against a lookup table instead of searching
1 parent 05fbe1a commit eacc463

File tree

2 files changed

+42
-15
lines changed

2 files changed

+42
-15
lines changed

__tests__/applyAtRule.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ test('it fails if the class does not exist', () => {
8080
})
8181
})
8282

83+
test('applying classes that are defined in a media query is not supported', () => {
84+
const input = `
85+
@media (min-width: 300px) {
86+
.a { color: blue; }
87+
}
88+
89+
.b {
90+
@apply .a;
91+
}
92+
`
93+
expect.assertions(1)
94+
return run(input).catch(e => {
95+
expect(e).toMatchObject({ name: 'CssSyntaxError' })
96+
})
97+
})
98+
8399
test('applying classes that are ever used in a media query is not supported', () => {
84100
const input = `
85101
.a {

src/lib/substituteClassApplyAtRules.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,26 @@ import _ from 'lodash'
22
import postcss from 'postcss'
33
import escapeClassName from '../util/escapeClassName'
44

5-
function normalizeClassName(className) {
6-
return `.${escapeClassName(_.trimStart(className, '.'))}`
7-
}
8-
9-
function findMixin(css, mixin, onError) {
10-
const matches = []
5+
function buildClassTable(css) {
6+
const classTable = {}
117

128
css.walkRules(rule => {
13-
if (rule.selectors.includes(mixin)) {
14-
if (rule.parent.type !== 'root') {
15-
// prettier-ignore
16-
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is nested inside of an at-rule (@${rule.parent.name}).`)
17-
}
18-
19-
matches.push(rule)
9+
if (!_.has(classTable, rule.selector)) {
10+
classTable[rule.selector] = []
2011
}
12+
classTable[rule.selector].push(rule)
2113
})
2214

15+
return classTable
16+
}
17+
18+
function normalizeClassName(className) {
19+
return `.${escapeClassName(_.trimStart(className, '.'))}`
20+
}
21+
22+
function findMixin(classTable, mixin, onError) {
23+
const matches = _.get(classTable, mixin, [])
24+
2325
if (_.isEmpty(matches)) {
2426
// prettier-ignore
2527
onError(`\`@apply\` cannot be used with \`${mixin}\` because \`${mixin}\` either cannot be found, or it's actual definition includes a pseudo-selector like :hover, :active, etc. If you're sure that \`${mixin}\` 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.`)
@@ -30,11 +32,20 @@ function findMixin(css, mixin, onError) {
3032
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is included in multiple rulesets.`)
3133
}
3234

33-
return _.flatten(matches.map(match => match.clone().nodes))
35+
const [match] = matches
36+
37+
if (match.parent.type !== 'root') {
38+
// prettier-ignore
39+
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is nested inside of an at-rule (@${match.parent.name}).`)
40+
}
41+
42+
return match.clone().nodes
3443
}
3544

3645
export default function() {
3746
return function(css) {
47+
const classLookup = buildClassTable(css)
48+
3849
css.walkRules(rule => {
3950
rule.walkAtRules('apply', atRule => {
4051
const mixins = postcss.list.space(atRule.params)
@@ -53,7 +64,7 @@ export default function() {
5364
const decls = _(classes)
5465
.reject(mixin => mixin === '!important')
5566
.flatMap(mixin => {
56-
return findMixin(css, normalizeClassName(mixin), message => {
67+
return findMixin(classLookup, normalizeClassName(mixin), message => {
5768
throw atRule.error(message)
5869
})
5970
})

0 commit comments

Comments
 (0)