diff --git a/__tests__/applyAtRule.test.js b/__tests__/applyAtRule.test.js index 4d7933ca0..18c739314 100644 --- a/__tests__/applyAtRule.test.js +++ b/__tests__/applyAtRule.test.js @@ -1,9 +1,12 @@ import postcss from 'postcss' import plugin from '../src/lib/substituteClassApplyAtRules' -import defaultConfig from '../defaultCOnfig.stub.js' +import generateUtilities from '../src/util/generateUtilities' +import defaultConfig from '../defaultConfig.stub.js' -function run(input, opts = defaultConfig) { - return postcss([plugin(opts)]).process(input, { from: undefined }) +const defaultUtilities = generateUtilities(defaultConfig, []) + +function run(input, config = defaultConfig, utilities = defaultUtilities) { + return postcss([plugin(config, utilities)]).process(input, { from: undefined }) } test("it copies a class's declarations into itself", () => { @@ -199,7 +202,7 @@ test('you can apply utility classes that do not actually exist as long as they w .foo { margin-top: 1rem; } ` - return run(input, defaultConfig).then(result => { + return run(input).then(result => { expect(result.css).toEqual(expected) expect(result.warnings().length).toBe(0) }) diff --git a/src/lib/substituteClassApplyAtRules.js b/src/lib/substituteClassApplyAtRules.js index 3abb63381..2848f231e 100644 --- a/src/lib/substituteClassApplyAtRules.js +++ b/src/lib/substituteClassApplyAtRules.js @@ -1,47 +1,6 @@ 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 = {} @@ -56,48 +15,55 @@ function buildClassTable(css) { return classTable } +function buildShadowTable(generatedUtilities) { + const utilities = postcss.root() + + generatedUtilities.walkAtRules('variants', atRule => { + utilities.append(atRule.clone().nodes) + }) + + return buildClassTable(utilities) +} + function normalizeClassName(className) { return `.${escapeClassName(_.trimStart(className, '.'))}` } -function findMixin(classTable, shadowLookup, mixin, onError) { - const matches = _.get(classTable, mixin, []) +function findClass(classToApply, classTable, shadowLookup, onError) { + const matches = _.get(classTable, classToApply, []) if (_.isEmpty(matches)) { 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 + throw onError(`\`@apply\` cannot be used with \`${classToApply}\` because \`${classToApply}\` either cannot be found, or it's 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.`) } - return findMixin(shadowLookup, {}, mixin, onError) + return findClass(classToApply, shadowLookup, {}, onError) } if (matches.length > 1) { // prettier-ignore - onError(`\`@apply\` cannot be used with ${mixin} because ${mixin} is included in multiple rulesets.`) - return + throw onError(`\`@apply\` cannot be used with ${classToApply} because ${classToApply} is included in multiple rulesets.`) } 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 + throw onError(`\`@apply\` cannot be used with ${classToApply} because ${classToApply} is nested inside of an at-rule (@${match.parent.name}).`) } return match.clone().nodes } -export default function(config, { components: pluginComponents = {}, utilities: pluginUtilities = {} } = {}) { +export default function(config, generatedUtilities) { return function(css) { const classLookup = buildClassTable(css) - const shadowLookup = buildShadowTable(config, pluginUtilities) + const shadowLookup = buildShadowTable(generatedUtilities) css.walkRules(rule => { rule.walkAtRules('apply', atRule => { - const mixins = postcss.list.space(atRule.params) + const classesAndProperties = postcss.list.space(atRule.params) /* * Don't wreck CSSNext-style @apply rules: @@ -106,20 +72,20 @@ export default function(config, { components: pluginComponents = {}, utilities: * These are deprecated in CSSNext but still playing it safe for now. * We might consider renaming this at-rule. */ - const [customProperties, classes] = _.partition(mixins, mixin => { - return _.startsWith(mixin, '--') + const [customProperties, classes] = _.partition(classesAndProperties, classOrProperty => { + return _.startsWith(classOrProperty, '--') }) const decls = _(classes) - .reject(mixin => mixin === '!important') - .flatMap(mixin => { - return findMixin(classLookup, shadowLookup, normalizeClassName(mixin), message => { - throw atRule.error(message) + .reject(cssClass => cssClass === '!important') + .flatMap(cssClass => { + return findClass(normalizeClassName(cssClass), classLookup, shadowLookup, message => { + return atRule.error(message) }) }) .value() - _.tap(_.last(mixins) === '!important', important => { + _.tap(_.last(classesAndProperties) === '!important', important => { decls.forEach(decl => (decl.important = important)) }) diff --git a/src/lib/substituteTailwindAtRules.js b/src/lib/substituteTailwindAtRules.js index 0d7365426..05b7c938c 100644 --- a/src/lib/substituteTailwindAtRules.js +++ b/src/lib/substituteTailwindAtRules.js @@ -1,10 +1,7 @@ import fs from 'fs' import postcss from 'postcss' -import utilityModules from '../utilityModules' -import prefixTree from '../util/prefixTree' -import generateModules from '../util/generateModules' -export default function(config, { components: pluginComponents, utilities: pluginUtilities }) { +export default function(config, { components: pluginComponents }, generatedUtilities) { return function(css) { css.walkAtRules('tailwind', atRule => { if (atRule.params === 'preflight') { @@ -30,27 +27,8 @@ export default function(config, { components: pluginComponents, utilities: plugi } if (atRule.params === 'utilities') { - const utilities = generateModules(utilityModules, config.modules, config) - - if (config.options.important) { - utilities.walkDecls(decl => (decl.important = true)) - } - - const tailwindUtilityTree = postcss.root({ - nodes: utilities.nodes, - }) - - const pluginUtilityTree = postcss.root({ - nodes: pluginUtilities, - }) - - prefixTree(tailwindUtilityTree, config.options.prefix) - - tailwindUtilityTree.walk(node => (node.source = atRule.source)) - pluginUtilityTree.walk(node => (node.source = atRule.source)) - - atRule.before(tailwindUtilityTree) - atRule.before(pluginUtilityTree) + generatedUtilities.walk(node => (node.source = atRule.source)) + atRule.before(generatedUtilities) atRule.remove() } }) diff --git a/src/processTailwindFeatures.js b/src/processTailwindFeatures.js index fb8f30e20..f67e60603 100644 --- a/src/processTailwindFeatures.js +++ b/src/processTailwindFeatures.js @@ -6,18 +6,21 @@ import substituteVariantsAtRules from './lib/substituteVariantsAtRules' import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules' import substituteScreenAtRules from './lib/substituteScreenAtRules' import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules' + +import generateUtilities from './util/generateUtilities' import processPlugins from './util/processPlugins' export default function(lazyConfig) { const config = lazyConfig() - const plugins = processPlugins(config) + const processedPlugins = processPlugins(config) + const utilities = generateUtilities(config, processedPlugins.utilities) return postcss([ - substituteTailwindAtRules(config, plugins), + substituteTailwindAtRules(config, processedPlugins, utilities.clone()), evaluateTailwindFunctions(config), - substituteVariantsAtRules(config, plugins), + substituteVariantsAtRules(config, processedPlugins), substituteResponsiveAtRules(config), substituteScreenAtRules(config), - substituteClassApplyAtRules(config, plugins), + substituteClassApplyAtRules(config, utilities.clone()), ]) } diff --git a/src/util/generateUtilities.js b/src/util/generateUtilities.js new file mode 100644 index 000000000..b2843ddc6 --- /dev/null +++ b/src/util/generateUtilities.js @@ -0,0 +1,24 @@ +import _ from 'lodash' +import postcss from 'postcss' +import utilityModules from '../utilityModules' +import prefixTree from '../util/prefixTree' +import generateModules from '../util/generateModules' + +export default function(config, pluginUtilities) { + const utilities = generateModules(utilityModules, config.modules, config) + + if (config.options.important) { + utilities.walkDecls(decl => (decl.important = true)) + } + + const tailwindUtilityTree = postcss.root({ + nodes: utilities.nodes, + }) + + prefixTree(tailwindUtilityTree, config.options.prefix) + + return _.tap(postcss.root(), root => { + root.append(tailwindUtilityTree.nodes) + root.append(pluginUtilities) + }) +}