diff --git a/.eslintrc b/.eslintrc index ff4ccca48..87712fea0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,10 @@ }, "parserOptions": { "ecmaVersion": 6, - "sourceType": "module" + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } }, "extends": ["eslint-config-postcss", "prettier"], "plugins": ["prettier"], diff --git a/__tests__/variantsAtRule.test.js b/__tests__/variantsAtRule.test.js index 43f9899e6..3ef28a210 100644 --- a/__tests__/variantsAtRule.test.js +++ b/__tests__/variantsAtRule.test.js @@ -166,3 +166,34 @@ test('variants are generated in the order specified', () => { expect(result.warnings().length).toBe(0) }) }) + +test('plugin variants work', () => { + const input = ` + @variants first-child { + .banana { color: yellow; } + .chocolate { color: brown; } + } + ` + + const output = ` + .banana { color: yellow; } + .chocolate { color: brown; } + .first-child\\:banana:first-child { color: yellow; } + .first-child\\:chocolate:first-child { color: brown; } + ` + + return run(input, () => ({ + ...config, + plugins: [ + ...config.plugins, + function({ addVariant }) { + addVariant('first-child', ({ className, separator }) => { + return `.first-child${separator}${className}:first-child` + }) + }, + ], + })).then(result => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) diff --git a/src/lib/substituteVariantsAtRules.js b/src/lib/substituteVariantsAtRules.js index 67e1e4595..33f1f944b 100644 --- a/src/lib/substituteVariantsAtRules.js +++ b/src/lib/substituteVariantsAtRules.js @@ -1,6 +1,7 @@ import _ from 'lodash' import postcss from 'postcss' import buildSelectorVariant from '../util/buildSelectorVariant' +import processPlugins from '../util/processPlugins' function buildPseudoClassVariant(selector, pseudoClass, separator) { return `${buildSelectorVariant(selector, pseudoClass, separator)}:${pseudoClass}` @@ -18,7 +19,7 @@ function generatePseudoClassVariant(pseudoClass) { } } -const variantGenerators = { +const defaultVariantGenerators = { 'group-hover': (container, { options: { separator } }) => { const cloned = container.clone() @@ -40,6 +41,10 @@ const variantGenerators = { export default function(config) { return function(css) { const unwrappedConfig = config() + const variantGenerators = { + ...defaultVariantGenerators, + ...processPlugins(unwrappedConfig).variantGenerators, + } css.walkAtRules('variants', atRule => { const variants = postcss.list.comma(atRule.params).filter(variant => variant !== '') diff --git a/src/util/processPlugins.js b/src/util/processPlugins.js index 320dc5c8e..c8665f8c9 100644 --- a/src/util/processPlugins.js +++ b/src/util/processPlugins.js @@ -14,6 +14,21 @@ function parseStyles(styles) { return _.flatMap(styles, style => (style instanceof Node ? style : parseObjectStyles(style))) } +function generateVariantFunction(generator) { + return (container, config) => { + const cloned = container.clone() + + cloned.walkRules(rule => { + rule.selector = generator({ + className: rule.selector.slice(1), + separator: escapeClassName(config.options.separator), + }) + }) + + container.before(cloned.nodes) + } +} + export default function(config) { const pluginComponents = [] const pluginUtilities = [] @@ -60,6 +75,9 @@ export default function(config) { pluginComponents.push(...styles.nodes) }, + addVariant: (name, generator) => { + pluginVariantGenerators[name] = generateVariantFunction(generator) + }, }) })