Add support for writing variant plugins with raw PostCSS API

This commit is contained in:
Adam Wathan 2018-06-26 10:31:06 -04:00
parent 9b22ff3513
commit f974b8df03
3 changed files with 108 additions and 27 deletions

View file

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

View file

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

View file

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