Support for basic variant generator plugins

Allows you to write a plugin that registers a new variant but only
allows you to modify the selector (like what our built-in generators
do.)

Next steps are to support variants that wrap rules with at-rules
(like @supports for example), variants that can modify properties
(as opposed to just selectors), and to give variant plugin authors
control over how responsive variants interact with their own variants.
This commit is contained in:
Adam Wathan 2018-06-22 12:30:30 -04:00
parent 9eca69ad83
commit d77bc055ee
4 changed files with 59 additions and 2 deletions

View File

@ -4,7 +4,10 @@
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"extends": ["eslint-config-postcss", "prettier"],
"plugins": ["prettier"],

View File

@ -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)
})
})

View File

@ -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 !== '')

View File

@ -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)
},
})
})