diff --git a/__tests__/applyComplexClasses.test.js b/__tests__/applyComplexClasses.test.js index a211f6f62..1793ffd84 100644 --- a/__tests__/applyComplexClasses.test.js +++ b/__tests__/applyComplexClasses.test.js @@ -655,7 +655,7 @@ describe('using apply with the prefix option', () => { }, ]) - return run(input, config, () => processPlugins(corePlugins(config), config)).then(result => { + return run(input, config).then(result => { expect(result.css).toMatchCss(expected) expect(result.warnings().length).toBe(0) }) @@ -679,7 +679,7 @@ describe('using apply with the prefix option', () => { }, ]) - return run(input, config, () => processPlugins(corePlugins(config), config)).then(result => { + return run(input, config).then(result => { expect(result.css).toMatchCss(expected) expect(result.warnings().length).toBe(0) }) @@ -697,7 +697,7 @@ describe('using apply with the prefix option', () => { }, ]) - return run(input, config, () => processPlugins(corePlugins(config), config)).catch(e => { + return run(input, config).catch(e => { expect(e).toMatchObject({ name: 'CssSyntaxError' }) }) }) @@ -720,7 +720,7 @@ describe('using apply with the prefix option', () => { }, ]) - return run(input, config, () => processPlugins(corePlugins(config), config)).then(result => { + return run(input, config).then(result => { expect(result.css).toMatchCss(expected) expect(result.warnings().length).toBe(0) }) @@ -744,7 +744,7 @@ describe('using apply with the prefix option', () => { }, ]) - return run(input, config, () => processPlugins(corePlugins(config), config)).then(result => { + return run(input, config).then(result => { expect(result.css).toMatchCss(expected) expect(result.warnings().length).toBe(0) }) @@ -764,16 +764,62 @@ describe('using apply with the prefix option', () => { expect.assertions(1) - return run(input, config, () => processPlugins(corePlugins(config), config)).catch(e => { + return run(input, config).catch(e => { expect(e).toMatchObject({ name: 'CssSyntaxError', reason: 'The `mt-4` class does not exist, but `tw-mt-4` does. Did you forget the prefix?', }) }) }) + + test('you can apply classes with important and a prefix enabled', () => { + const input = ` + .foo { @apply tw-mt-4; } + ` + + const expected = ` + .foo { margin-top: 1rem; } + ` + + const config = resolveConfig([ + { + ...defaultConfig, + prefix: 'tw-', + important: true, + }, + ]) + + return run(input, config).then(result => { + expect(result.css).toMatchCss(expected) + expect(result.warnings().length).toBe(0) + }) + }) + + test('you can apply classes with an important selector and a prefix enabled', () => { + const input = ` + .foo { @apply tw-mt-4; } + ` + + const expected = ` + .foo { margin-top: 1rem; } + ` + + const config = resolveConfig([ + { + ...defaultConfig, + prefix: 'tw-', + important: '#app', + }, + ]) + + return run(input, config).then(result => { + expect(result.css).toMatchCss(expected) + expect(result.warnings().length).toBe(0) + }) + }) }) -test.skip('you can apply utility classes when a selector is used for the important option', () => { +test('you can apply utility classes when a selector is used for the important option', () => { const input = ` .foo { @apply mt-8 mb-8; @@ -794,30 +840,7 @@ test.skip('you can apply utility classes when a selector is used for the importa }, ]) - return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => { - expect(result.css).toMatchCss(expected) - expect(result.warnings().length).toBe(0) - }) -}) - -test.skip('you can apply utility classes without using the given prefix even if important (selector) is used', () => { - const input = ` - .foo { @apply .tw-mt-4 .mb-4; } - ` - - const expected = ` - .foo { margin-top: 1rem; margin-bottom: 1rem; } - ` - - const config = resolveConfig([ - { - ...defaultConfig, - prefix: 'tw-', - important: '#app', - }, - ]) - - return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => { + return run(input, config).then(result => { expect(result.css).toMatchCss(expected) expect(result.warnings().length).toBe(0) }) diff --git a/src/featureFlags.js b/src/featureFlags.js index 3c881e6a1..9e8895045 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -70,8 +70,7 @@ export function issueFlagNotices(config) { .map(s => chalk.cyan(s)) .join(', ') - console.log() - log.info(`You have opted-in to future-facing breaking changes: ${changes}`) + log.info(`\nYou have opted-in to future-facing breaking changes: ${changes}`) log.info( 'These changes are stable and will be the default behavior in the next major version of Tailwind.' ) @@ -82,8 +81,7 @@ export function issueFlagNotices(config) { .map(s => chalk.yellow(s)) .join(', ') - console.log() - log.warn(`You have enabled experimental features: ${changes}`) + log.warn(`\nYou have enabled experimental features: ${changes}`) log.warn( 'Experimental features are not covered by semver, may introduce breaking changes, and can change at any time.' ) @@ -94,8 +92,7 @@ export function issueFlagNotices(config) { .map(s => chalk.magenta(s)) .join(', ') - console.log() - log.risk(`There are upcoming breaking changes: ${changes}`) + log.risk(`\nThere are upcoming breaking changes: ${changes}`) log.risk( 'We highly recommend opting-in to these changes now to simplify upgrading Tailwind in the future.' ) diff --git a/src/flagged/applyComplexClasses.js b/src/flagged/applyComplexClasses.js index 3cf4395b3..f47649884 100644 --- a/src/flagged/applyComplexClasses.js +++ b/src/flagged/applyComplexClasses.js @@ -210,11 +210,11 @@ function processApplyAtRules(css, lookupTree, config) { afterRule, ] - const root = _.tap(postcss.root({ nodes: rulesToInsert }), root => + const { nodes } = _.tap(postcss.root({ nodes: rulesToInsert }), root => root.walkDecls(d => (d.important = important)) ) - const mergedRules = mergeAdjacentRules(rule, root.nodes) + const mergedRules = mergeAdjacentRules(rule, nodes) inject.remove() rule.after(mergedRules) diff --git a/src/lib/applyImportantConfiguration.js b/src/lib/applyImportantConfiguration.js new file mode 100644 index 000000000..7a2cb2d2c --- /dev/null +++ b/src/lib/applyImportantConfiguration.js @@ -0,0 +1,19 @@ +export default function applyImportantConfiguration(_config) { + return function(css) { + css.walkRules(rule => { + const important = rule.__tailwind ? rule.__tailwind.important : false + + if (!important) { + return + } + + if (typeof important === 'string') { + rule.selectors = rule.selectors.map(selector => { + return `${rule.__tailwind.important} ${selector}` + }) + } else { + rule.walkDecls(decl => (decl.important = true)) + } + }) + } +} diff --git a/src/lib/purgeUnusedStyles.js b/src/lib/purgeUnusedStyles.js index e496b14e5..296715b48 100644 --- a/src/lib/purgeUnusedStyles.js +++ b/src/lib/purgeUnusedStyles.js @@ -5,7 +5,8 @@ import chalk from 'chalk' import { log } from '../cli/utils' import * as emoji from '../cli/emoji' -function removeTailwindComments(css) { +function removeTailwindMarkers(css) { + css.walkAtRules('tailwind', rule => rule.remove()) css.walkComments(comment => { switch (comment.text.trim()) { case 'tailwind start components': @@ -28,7 +29,7 @@ export default function purgeUnusedUtilities(config) { ) if (!purgeEnabled) { - return removeTailwindComments + return removeTailwindMarkers } // Skip if `purge: []` since that's part of the default config @@ -48,7 +49,7 @@ export default function purgeUnusedUtilities(config) { log( chalk.white('\n https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css') ) - return removeTailwindComments + return removeTailwindMarkers } return postcss([ @@ -73,7 +74,7 @@ export default function purgeUnusedUtilities(config) { }) } }, - removeTailwindComments, + removeTailwindMarkers, purgecss({ content: Array.isArray(config.purge) ? config.purge : config.purge.content, defaultExtractor: content => { diff --git a/src/processTailwindFeatures.js b/src/processTailwindFeatures.js index 7ea80ee8f..91ea35fd2 100644 --- a/src/processTailwindFeatures.js +++ b/src/processTailwindFeatures.js @@ -8,6 +8,7 @@ import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules' import convertLayerAtRulesToControlComments from './lib/convertLayerAtRulesToControlComments' import substituteScreenAtRules from './lib/substituteScreenAtRules' import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules' +import applyImportantConfiguration from './lib/applyImportantConfiguration' import purgeUnusedStyles from './lib/purgeUnusedStyles' import corePlugins from './corePlugins' @@ -40,9 +41,7 @@ export default function(getConfig) { convertLayerAtRulesToControlComments(config), substituteScreenAtRules(config), substituteClassApplyAtRules(config, getProcessedPlugins), - function(css) { - css.walkAtRules('tailwind', rule => rule.remove()) - }, + applyImportantConfiguration(config), purgeUnusedStyles(config), ]).process(css, { from: _.get(css, 'source.input.file') }) } diff --git a/src/util/processPlugins.js b/src/util/processPlugins.js index d147d9a4d..e6effae3b 100644 --- a/src/util/processPlugins.js +++ b/src/util/processPlugins.js @@ -9,8 +9,6 @@ import parseObjectStyles from '../util/parseObjectStyles' import prefixSelector from '../util/prefixSelector' import wrapWithVariants from '../util/wrapWithVariants' import cloneNodes from '../util/cloneNodes' -import increaseSpecificity from '../util/increaseSpecificity' -import selectorParser from 'postcss-selector-parser' function parseStyles(styles) { if (!Array.isArray(styles)) { @@ -20,14 +18,6 @@ function parseStyles(styles) { return _.flatMap(styles, style => (style instanceof Node ? style : parseObjectStyles(style))) } -function containsClass(value) { - return selectorParser(selectors => { - let classFound = false - selectors.walkClasses(() => (classFound = true)) - return classFound - }).transformSync(value) -} - function wrapWithLayer(rules, layer) { return postcss .atRule({ @@ -102,19 +92,10 @@ export default function(plugins, config) { rule.selector = applyConfiguredPrefix(rule.selector) } - if (options.respectImportant && _.get(config, 'important')) { - if (config.important === true) { - rule.walkDecls(decl => (decl.important = true)) - } else if (typeof config.important === 'string') { - if (containsClass(config.important)) { - throw rule.error( - `Classes are not allowed when using the \`important\` option with a string argument. Please use an ID instead.` - ) - } - - rule.selectors = rule.selectors.map(selector => { - return increaseSpecificity(config.important, selector) - }) + if (options.respectImportant && config.important) { + rule.__tailwind = { + ...rule.__tailwind, + important: config.important, } } })