From f974b8df03c97ddaae0163d8e920df64867522b7 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Tue, 26 Jun 2018 10:31:06 -0400 Subject: [PATCH] Add support for writing variant plugins with raw PostCSS API --- __tests__/variantsAtRule.test.js | 82 +++++++++++++++++++++++++++- src/lib/substituteVariantsAtRules.js | 23 +++----- src/util/generateVariantFunction.js | 30 +++++++--- 3 files changed, 108 insertions(+), 27 deletions(-) diff --git a/__tests__/variantsAtRule.test.js b/__tests__/variantsAtRule.test.js index edb1a8083..cdb7d394c 100644 --- a/__tests__/variantsAtRule.test.js +++ b/__tests__/variantsAtRule.test.js @@ -168,7 +168,43 @@ test('variants are generated in the order specified', () => { }) }) -test('plugin variants work', () => { +test('plugin variants can modify rules using the raw PostCSS API', () => { + const input = ` + @variants important { + .banana { color: yellow; } + .chocolate { color: brown; } + } + ` + + const output = ` + .banana { color: yellow; } + .chocolate { color: brown; } + .\\!banana { color: yellow !important; } + .\\!chocolate { color: brown !important; } + ` + + return run(input, { + ...config, + plugins: [ + ...config.plugins, + function({ addVariant }) { + addVariant('important', ({ container }) => { + container.walkRules(rule => { + rule.selector = `.\\!${rule.selector.slice(1)}` + rule.walkDecls(decl => { + decl.important = true + }) + }) + }) + }, + ], + }).then(result => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('plugin variants can modify selectors with a simplified API', () => { const input = ` @variants first-child { .banana { color: yellow; } @@ -188,8 +224,48 @@ test('plugin variants work', () => { plugins: [ ...config.plugins, function({ addVariant }) { - addVariant('first-child', ({ className, separator }) => { - return `.first-child${separator}${className}:first-child` + addVariant('first-child', ({ modifySelectors, separator }) => { + modifySelectors(({ className }) => { + return `.first-child${separator}${className}:first-child` + }) + }) + }, + ], + }).then(result => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('plugin variants can wrap rules in another at-rule using the raw PostCSS API', () => { + const input = ` + @variants supports-grid { + .banana { color: yellow; } + .chocolate { color: brown; } + } + ` + + const output = ` + .banana { color: yellow; } + .chocolate { color: brown; } + @supports (display: grid) { + .supports-grid\\:banana { color: yellow; } + .supports-grid\\:chocolate { color: brown; } + } + ` + + return run(input, { + ...config, + plugins: [ + ...config.plugins, + function({ addVariant }) { + addVariant('supports-grid', ({ container, separator }) => { + const supportsRule = postcss.atRule({ name: 'supports', params: '(display: grid)' }) + supportsRule.nodes = container.nodes + container.nodes = [supportsRule] + supportsRule.walkRules(rule => { + rule.selector = `.supports-grid${separator}${rule.selector.slice(1)}` + }) }) }, ], diff --git a/src/lib/substituteVariantsAtRules.js b/src/lib/substituteVariantsAtRules.js index 9e3f5fc60..adae4cbc5 100644 --- a/src/lib/substituteVariantsAtRules.js +++ b/src/lib/substituteVariantsAtRules.js @@ -1,28 +1,21 @@ import _ from 'lodash' import postcss from 'postcss' -import buildSelectorVariant from '../util/buildSelectorVariant' import generateVariantFunction from '../util/generateVariantFunction' function generatePseudoClassVariant(pseudoClass) { - return generateVariantFunction(({ className, separator }) => { - return `.${pseudoClass}${separator}${className}:${pseudoClass}` + return generateVariantFunction(({ modifySelectors, separator }) => { + return modifySelectors(({ className }) => { + return `.${pseudoClass}${separator}${className}:${pseudoClass}` + }) }) } const defaultVariantGenerators = { - 'group-hover': (container, { options: { separator } }) => { - const cloned = container.clone() - - cloned.walkRules(rule => { - rule.selector = `.group:hover ${buildSelectorVariant( - rule.selector, - 'group-hover', - separator - )}` + 'group-hover': generateVariantFunction(({ modifySelectors, separator }) => { + return modifySelectors(({ className }) => { + return `.group:hover .group-hover${separator}${className}` }) - - container.before(cloned.nodes) - }, + }), hover: generatePseudoClassVariant('hover'), focus: generatePseudoClassVariant('focus'), active: generatePseudoClassVariant('active'), diff --git a/src/util/generateVariantFunction.js b/src/util/generateVariantFunction.js index dff63acfe..4046ca737 100644 --- a/src/util/generateVariantFunction.js +++ b/src/util/generateVariantFunction.js @@ -1,16 +1,28 @@ +import _ from 'lodash' +import postcss from 'postcss' import escapeClassName from './escapeClassName' export default function generateVariantFunction(generator) { return (container, config) => { - const cloned = container.clone() + const cloned = postcss.root({ nodes: container.clone().nodes }) - cloned.walkRules(rule => { - rule.selector = generator({ - className: rule.selector.slice(1), - separator: escapeClassName(config.options.separator), - }) - }) - - container.before(cloned.nodes) + container.before( + _.defaultTo( + generator({ + container: cloned, + separator: escapeClassName(config.options.separator), + modifySelectors: modifierFunction => { + cloned.walkRules(rule => { + rule.selector = modifierFunction({ + className: rule.selector.slice(1), + selector: rule.selector, + }) + }) + return cloned + }, + }), + cloned + ).nodes + ) } }