Fallback to shadow table

This commit is contained in:
Adam Wathan 2018-07-11 09:56:11 -04:00
parent 38c211a164
commit cdbea36564
3 changed files with 92 additions and 7 deletions

View file

@ -1,7 +1,8 @@
import postcss from 'postcss'
import plugin from '../src/lib/substituteClassApplyAtRules'
import defaultConfig from '../defaultCOnfig.stub.js'
function run(input, opts = {}) {
function run(input, opts = defaultConfig) {
return postcss([plugin(opts)]).process(input, { from: undefined })
}
@ -168,3 +169,38 @@ test('it does not match classes that have multiple rules', () => {
expect(e).toMatchObject({ name: 'CssSyntaxError' })
})
})
test('it does not match classes that have multiple rules', () => {
const input = `
.a {
color: red;
}
.b {
@apply .a;
}
.a {
color: blue;
}
`
expect.assertions(1)
return run(input).catch(e => {
expect(e).toMatchObject({ name: 'CssSyntaxError' })
})
})
test('you can apply utility classes that do not actually exist as long as they would exist if utilities were being generated', () => {
const input = `
.foo { @apply .mt-4; }
`
const expected = `
.foo { margin-top: 1rem; }
`
return run(input, defaultConfig).then(result => {
expect(result.css).toEqual(expected)
expect(result.warnings().length).toBe(0)
})
})

View file

@ -1,6 +1,47 @@
import _ from 'lodash'
import postcss from 'postcss'
import escapeClassName from '../util/escapeClassName'
import utilityModules from '../utilityModules'
import prefixTree from '../util/prefixTree'
import generateModules from '../util/generateModules'
function buildShadowTable(config, pluginUtilities) {
const utilities = postcss.root()
const generatedUtilities = generateModules(utilityModules, _.fromPairs(Object.keys(config.modules).map((k) => [k, []])), config)
generatedUtilities.walkAtRules('variants', atRule => {
utilities.append(atRule.clone().nodes)
})
const tailwindUtilityTree = postcss.root({
nodes: utilities.nodes,
})
const pluginUtilityTree = postcss.root({
nodes: pluginUtilities,
})
prefixTree(tailwindUtilityTree, config.options.prefix)
const shadowTable = {}
tailwindUtilityTree.walkRules(rule => {
if (!_.has(shadowTable, rule.selector)) {
shadowTable[rule.selector] = []
}
shadowTable[rule.selector].push(rule)
})
pluginUtilityTree.walkRules(rule => {
if (!_.has(shadowTable, rule.selector)) {
shadowTable[rule.selector] = []
}
shadowTable[rule.selector].push(rule)
})
return shadowTable
}
function buildClassTable(css) {
const classTable = {}
@ -19,17 +60,23 @@ function normalizeClassName(className) {
return `.${escapeClassName(_.trimStart(className, '.'))}`
}
function findMixin(classTable, mixin, onError) {
function findMixin(classTable, shadowLookup, 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.`)
if (_.isEmpty(shadowLookup)) {
// 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.`)
return
}
return findMixin(shadowLookup, {}, mixin, onError)
}
if (matches.length > 1) {
// prettier-ignore
onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is included in multiple rulesets.`)
return
}
const [match] = matches
@ -37,14 +84,16 @@ function findMixin(classTable, mixin, onError) {
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
}
return match.clone().nodes
}
export default function() {
export default function(config, { components: pluginComponents = {}, utilities: pluginUtilities = {} } = {}) {
return function(css) {
const classLookup = buildClassTable(css)
const shadowLookup = buildShadowTable(config, pluginUtilities)
css.walkRules(rule => {
rule.walkAtRules('apply', atRule => {
@ -64,7 +113,7 @@ export default function() {
const decls = _(classes)
.reject(mixin => mixin === '!important')
.flatMap(mixin => {
return findMixin(classLookup, normalizeClassName(mixin), message => {
return findMixin(classLookup, shadowLookup, normalizeClassName(mixin), message => {
throw atRule.error(message)
})
})

View file

@ -18,6 +18,6 @@ export default function(lazyConfig) {
substituteVariantsAtRules(config, plugins),
substituteResponsiveAtRules(config),
substituteScreenAtRules(config),
substituteClassApplyAtRules(config),
substituteClassApplyAtRules(config, plugins),
])
}