Skip to content

Commit

Permalink
Match apply rules against a lookup table instead of searching
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwathan committed Mar 1, 2018
1 parent 05fbe1a commit eacc463
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 15 deletions.
16 changes: 16 additions & 0 deletions __tests__/applyAtRule.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ test('it fails if the class does not exist', () => {
})
})

test('applying classes that are defined in a media query is not supported', () => {
const input = `
@media (min-width: 300px) {
.a { color: blue; }
}
.b {
@apply .a;
}
`
expect.assertions(1)
return run(input).catch(e => {
expect(e).toMatchObject({ name: 'CssSyntaxError' })
})
})

test('applying classes that are ever used in a media query is not supported', () => {
const input = `
.a {
Expand Down
41 changes: 26 additions & 15 deletions src/lib/substituteClassApplyAtRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@ import _ from 'lodash'
import postcss from 'postcss'
import escapeClassName from '../util/escapeClassName'

function normalizeClassName(className) {
return `.${escapeClassName(_.trimStart(className, '.'))}`
}

function findMixin(css, mixin, onError) {
const matches = []
function buildClassTable(css) {
const classTable = {}

css.walkRules(rule => {
if (rule.selectors.includes(mixin)) {
if (rule.parent.type !== 'root') {
// prettier-ignore
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is nested inside of an at-rule (@${rule.parent.name}).`)
}

matches.push(rule)
if (!_.has(classTable, rule.selector)) {
classTable[rule.selector] = []
}
classTable[rule.selector].push(rule)
})

return classTable
}

function normalizeClassName(className) {
return `.${escapeClassName(_.trimStart(className, '.'))}`
}

function findMixin(classTable, mixin, onError) {
const matches = _.get(classTable, mixin, [])

if (_.isEmpty(matches)) {
// prettier-ignore
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.`)
Expand All @@ -30,11 +32,20 @@ function findMixin(css, mixin, onError) {
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is included in multiple rulesets.`)
}

return _.flatten(matches.map(match => match.clone().nodes))
const [match] = matches

if (match.parent.type !== 'root') {
// prettier-ignore
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is nested inside of an at-rule (@${match.parent.name}).`)
}

return match.clone().nodes
}

export default function() {
return function(css) {
const classLookup = buildClassTable(css)

css.walkRules(rule => {
rule.walkAtRules('apply', atRule => {
const mixins = postcss.list.space(atRule.params)
Expand All @@ -53,7 +64,7 @@ export default function() {
const decls = _(classes)
.reject(mixin => mixin === '!important')
.flatMap(mixin => {
return findMixin(css, normalizeClassName(mixin), message => {
return findMixin(classLookup, normalizeClassName(mixin), message => {
throw atRule.error(message)
})
})
Expand Down

0 comments on commit eacc463

Please sign in to comment.