Merge pull request #505 from tailwindcss/variant-plugins

Support creating new variants through plugins (WIP)
This commit is contained in:
Adam Wathan 2018-07-11 22:28:30 +09:30 committed by GitHub
commit 38c211a164
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 290 additions and 129 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

@ -1,7 +1,7 @@
import postcss from 'postcss'
import plugin from '../src/lib/substituteClassApplyAtRules'
function run(input, opts = () => {}) {
function run(input, opts = {}) {
return postcss([plugin(opts)]).process(input, { from: undefined })
}

View File

@ -2,7 +2,7 @@ import postcss from 'postcss'
import plugin from '../src/lib/evaluateTailwindFunctions'
function run(input, opts = {}) {
return postcss([plugin(() => opts)]).process(input, { from: undefined })
return postcss([plugin(opts)]).process(input, { from: undefined })
}
test('it looks up values in the config using dot notation', () => {

View File

@ -26,7 +26,7 @@ function processPluginsWithValidConfig(config) {
}
test('options are not required', () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [container()],
})
@ -48,7 +48,7 @@ test('options are not required', () => {
})
test('screens can be specified explicitly', () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
container({
screens: {
@ -71,7 +71,7 @@ test('screens can be specified explicitly', () => {
})
test('screens can be an array', () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
container({
screens: ['400px', '500px'],
@ -91,7 +91,7 @@ test('screens can be an array', () => {
})
test('the container can be centered by default', () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
container({
center: true,
@ -121,7 +121,7 @@ test('the container can be centered by default', () => {
})
test('horizontal padding can be included by default', () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
container({
padding: '2rem',
@ -151,7 +151,7 @@ test('horizontal padding can be included by default', () => {
})
test('setting all options at once', () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
container({
screens: {

View File

@ -19,7 +19,7 @@ function processPluginsWithValidConfig(config) {
}
test('plugins can create utilities with object syntax', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities({
@ -54,7 +54,7 @@ test('plugins can create utilities with object syntax', () => {
})
test('plugins can create utilities with arrays of objects', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities([
@ -95,7 +95,7 @@ test('plugins can create utilities with arrays of objects', () => {
})
test('plugins can create utilities with raw PostCSS nodes', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities([
@ -139,7 +139,7 @@ test('plugins can create utilities with raw PostCSS nodes', () => {
})
test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities([
@ -182,7 +182,7 @@ test('plugins can create utilities with mixed object styles and PostCSS nodes',
})
test('plugins can create utilities with variants', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities(
@ -220,7 +220,7 @@ test('plugins can create utilities with variants', () => {
})
test('plugins can create components with object syntax', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents({
@ -253,7 +253,7 @@ test('plugins can create components with object syntax', () => {
})
test('plugins can create components with raw PostCSS nodes', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents([
@ -301,7 +301,7 @@ test('plugins can create components with raw PostCSS nodes', () => {
})
test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents([
@ -348,7 +348,7 @@ test('plugins can create components with mixed object styles and raw PostCSS nod
})
test('plugins can create components with media queries with object syntax', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents({
@ -399,7 +399,7 @@ test('plugins can create components with media queries with object syntax', () =
})
test('media queries can be defined multiple times using objects-in-array syntax', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents([
@ -452,7 +452,7 @@ test('media queries can be defined multiple times using objects-in-array syntax'
})
test('plugins can create nested rules', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents({
@ -519,7 +519,7 @@ test('plugins can create rules with escaped selectors', () => {
],
}
const [components, utilities] = processPluginsWithValidConfig(config)
const { components, utilities } = processPluginsWithValidConfig(config)
expect(components.length).toBe(0)
expect(css(utilities)).toMatchCss(`
@ -532,7 +532,7 @@ test('plugins can create rules with escaped selectors', () => {
})
test('plugins can access the current config', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
screens: {
sm: '576px',
md: '768px',
@ -593,7 +593,7 @@ test('plugins can access the current config', () => {
})
test('plugins can provide fallbacks to keys missing from the config', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
borderRadius: {
'1': '1px',
'2': '2px',
@ -620,7 +620,7 @@ test('plugins can provide fallbacks to keys missing from the config', () => {
})
test('variants are optional when adding utilities', () => {
const [, utilities] = processPluginsWithValidConfig({
const { utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities({
@ -642,7 +642,7 @@ test('variants are optional when adding utilities', () => {
})
test('plugins can add multiple sets of utilities and components', () => {
const [components, utilities] = processPluginsWithValidConfig({
const { components, utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities, addComponents }) {
addComponents({
@ -699,7 +699,7 @@ test('plugins can add multiple sets of utilities and components', () => {
})
test('plugins respect prefix and important options by default when adding utilities', () => {
const [, utilities] = processPluginsWithValidConfig({
const { utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities({
@ -725,7 +725,7 @@ test('plugins respect prefix and important options by default when adding utilit
})
test("component declarations respect the 'prefix' option by default", () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents({
@ -748,7 +748,7 @@ test("component declarations respect the 'prefix' option by default", () => {
})
test("component declarations can optionally ignore 'prefix' option", () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents(
@ -774,7 +774,7 @@ test("component declarations can optionally ignore 'prefix' option", () => {
})
test("component declarations are not affected by the 'important' option", () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
function({ addComponents }) {
addComponents({
@ -797,7 +797,7 @@ test("component declarations are not affected by the 'important' option", () =>
})
test("plugins can apply the user's chosen prefix to components manually", () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
function({ addComponents, prefix }) {
addComponents(
@ -823,7 +823,7 @@ test("plugins can apply the user's chosen prefix to components manually", () =>
})
test('prefix can optionally be ignored for utilities', () => {
const [, utilities] = processPluginsWithValidConfig({
const { utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities(
@ -854,7 +854,7 @@ test('prefix can optionally be ignored for utilities', () => {
})
test('important can optionally be ignored for utilities', () => {
const [, utilities] = processPluginsWithValidConfig({
const { utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities(
@ -885,7 +885,7 @@ test('important can optionally be ignored for utilities', () => {
})
test('variants can still be specified when ignoring prefix and important options', () => {
const [, utilities] = processPluginsWithValidConfig({
const { utilities } = processPluginsWithValidConfig({
plugins: [
function({ addUtilities }) {
addUtilities(
@ -909,7 +909,7 @@ test('variants can still be specified when ignoring prefix and important options
})
expect(css(utilities)).toMatchCss(`
@variants responsive, hover, focus{
@variants responsive, hover, focus {
.rotate-90 {
transform: rotate(90deg)
}
@ -918,7 +918,7 @@ test('variants can still be specified when ignoring prefix and important options
})
test('prefix will prefix all classes in a selector', () => {
const [components] = processPluginsWithValidConfig({
const { components } = processPluginsWithValidConfig({
plugins: [
function({ addComponents, prefix }) {
addComponents(

View File

@ -2,7 +2,7 @@ import postcss from 'postcss'
import plugin from '../src/lib/substituteResponsiveAtRules'
import config from '../defaultConfig.stub.js'
function run(input, opts = () => config) {
function run(input, opts = config) {
return postcss([plugin(opts)]).process(input, { from: undefined })
}
@ -31,7 +31,7 @@ test('it can generate responsive variants', () => {
}
`
return run(input, () => ({
return run(input, {
screens: {
sm: '500px',
md: '750px',
@ -40,7 +40,7 @@ test('it can generate responsive variants', () => {
options: {
separator: ':',
},
})).then(result => {
}).then(result => {
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
@ -71,7 +71,7 @@ test('it can generate responsive variants with a custom separator', () => {
}
`
return run(input, () => ({
return run(input, {
screens: {
sm: '500px',
md: '750px',
@ -80,7 +80,7 @@ test('it can generate responsive variants with a custom separator', () => {
options: {
separator: '__',
},
})).then(result => {
}).then(result => {
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
@ -117,7 +117,7 @@ test('responsive variants are grouped', () => {
}
`
return run(input, () => ({
return run(input, {
screens: {
sm: '500px',
md: '750px',
@ -126,7 +126,7 @@ test('responsive variants are grouped', () => {
options: {
separator: ':',
},
})).then(result => {
}).then(result => {
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
@ -152,7 +152,7 @@ test('screen prefix is only applied to the last class in a selector', () => {
}
`
return run(input, () => ({
return run(input, {
screens: {
sm: '500px',
md: '750px',
@ -161,7 +161,7 @@ test('screen prefix is only applied to the last class in a selector', () => {
options: {
separator: ':',
},
})).then(result => {
}).then(result => {
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
@ -187,7 +187,7 @@ test('responsive variants are generated for all selectors in a rule', () => {
}
`
return run(input, () => ({
return run(input, {
screens: {
sm: '500px',
md: '750px',
@ -196,7 +196,7 @@ test('responsive variants are generated for all selectors in a rule', () => {
options: {
separator: ':',
},
})).then(result => {
}).then(result => {
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
@ -209,7 +209,7 @@ test('selectors with no classes cannot be made responsive', () => {
}
`
expect.assertions(1)
return run(input, () => ({
return run(input, {
screens: {
sm: '500px',
md: '750px',
@ -218,7 +218,7 @@ test('selectors with no classes cannot be made responsive', () => {
options: {
separator: ':',
},
})).catch(e => {
}).catch(e => {
expect(e).toMatchObject({ name: 'CssSyntaxError' })
})
})
@ -230,7 +230,7 @@ test('all selectors in a rule must contain classes', () => {
}
`
expect.assertions(1)
return run(input, () => ({
return run(input, {
screens: {
sm: '500px',
md: '750px',
@ -239,7 +239,7 @@ test('all selectors in a rule must contain classes', () => {
options: {
separator: ':',
},
})).catch(e => {
}).catch(e => {
expect(e).toMatchObject({ name: 'CssSyntaxError' })
})
})

View File

@ -1,9 +1,10 @@
import postcss from 'postcss'
import plugin from '../src/lib/substituteVariantsAtRules'
import config from '../defaultConfig.stub.js'
import processPlugins from '../src/util/processPlugins'
function run(input, opts = () => config) {
return postcss([plugin(opts)]).process(input, { from: undefined })
function run(input, opts = config) {
return postcss([plugin(opts, processPlugins(opts))]).process(input, { from: undefined })
}
test('it can generate hover variants', () => {
@ -92,7 +93,7 @@ test('it can generate group-hover variants', () => {
test('it can generate hover, active and focus variants', () => {
const input = `
@variants hover, active, group-hover, focus {
@variants group-hover, hover, focus, active {
.banana { color: yellow; }
.chocolate { color: brown; }
}
@ -141,3 +142,135 @@ test('it wraps the output in a responsive at-rule if responsive is included as a
expect(result.warnings().length).toBe(0)
})
})
test('variants are generated in the order specified', () => {
const input = `
@variants focus, active, hover {
.banana { color: yellow; }
.chocolate { color: brown; }
}
`
const output = `
.banana { color: yellow; }
.chocolate { color: brown; }
.focus\\:banana:focus { color: yellow; }
.focus\\:chocolate:focus { color: brown; }
.active\\:banana:active { color: yellow; }
.active\\:chocolate:active { color: brown; }
.hover\\:banana:hover { color: yellow; }
.hover\\:chocolate:hover { color: brown; }
`
return run(input).then(result => {
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
})
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; }
.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', ({ 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)}`
})
})
},
],
}).then(result => {
expect(result.css).toMatchCss(output)
expect(result.warnings().length).toBe(0)
})
})

View File

@ -5,13 +5,7 @@ import postcss from 'postcss'
import perfectionist from 'perfectionist'
import registerConfigAsDependency from './lib/registerConfigAsDependency'
import substituteTailwindAtRules from './lib/substituteTailwindAtRules'
import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
import substituteVariantsAtRules from './lib/substituteVariantsAtRules'
import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules'
import substituteScreenAtRules from './lib/substituteScreenAtRules'
import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules'
import processTailwindFeatures from './processTailwindFeatures'
import mergeConfigWithDefaults from './util/mergeConfigWithDefaults'
const plugin = postcss.plugin('tailwind', config => {
@ -36,26 +30,19 @@ const plugin = postcss.plugin('tailwind', config => {
)
}
return postcss(
return postcss([
...plugins,
...[
substituteTailwindAtRules(lazyConfig),
evaluateTailwindFunctions(lazyConfig),
substituteVariantsAtRules(lazyConfig),
substituteResponsiveAtRules(lazyConfig),
substituteScreenAtRules(lazyConfig),
substituteClassApplyAtRules(lazyConfig),
perfectionist({
cascade: true,
colorShorthand: true,
indentSize: 2,
maxSelectorLength: 1,
maxValueLength: false,
trimLeadingZero: true,
trimTrailingZeros: true,
}),
]
)
processTailwindFeatures(lazyConfig),
perfectionist({
cascade: true,
colorShorthand: true,
indentSize: 2,
maxSelectorLength: 1,
maxValueLength: false,
trimLeadingZero: true,
trimTrailingZeros: true,
}),
])
})
plugin.defaultConfig = function() {

View File

@ -5,7 +5,7 @@ export default function(config) {
return functions({
functions: {
config: (path, defaultValue) => {
return _.get(config(), _.trim(path, `'"`), defaultValue)
return _.get(config, _.trim(path, `'"`), defaultValue)
},
},
})

View File

@ -6,8 +6,8 @@ import buildSelectorVariant from '../util/buildSelectorVariant'
export default function(config) {
return function(css) {
const screens = config().screens
const separator = config().options.separator
const screens = config.screens
const separator = config.options.separator
const responsiveRules = []
let finalRules = []

View File

@ -3,17 +3,15 @@ import buildMediaQuery from '../util/buildMediaQuery'
export default function(config) {
return function(css) {
const options = config()
css.walkAtRules('screen', atRule => {
const screen = atRule.params
if (!_.has(options.screens, screen)) {
if (!_.has(config.screens, screen)) {
throw atRule.error(`No \`${screen}\` screen found.`)
}
atRule.name = 'media'
atRule.params = buildMediaQuery(options.screens[screen])
atRule.params = buildMediaQuery(config.screens[screen])
})
}
}

View File

@ -3,14 +3,9 @@ import postcss from 'postcss'
import utilityModules from '../utilityModules'
import prefixTree from '../util/prefixTree'
import generateModules from '../util/generateModules'
import processPlugins from '../util/processPlugins'
export default function(config) {
export default function(config, { components: pluginComponents, utilities: pluginUtilities }) {
return function(css) {
const unwrappedConfig = config()
const [pluginComponents, pluginUtilities] = processPlugins(unwrappedConfig)
css.walkAtRules('tailwind', atRule => {
if (atRule.params === 'preflight') {
const preflightTree = postcss.parse(
@ -35,9 +30,9 @@ export default function(config) {
}
if (atRule.params === 'utilities') {
const utilities = generateModules(utilityModules, unwrappedConfig.modules, unwrappedConfig)
const utilities = generateModules(utilityModules, config.modules, config)
if (unwrappedConfig.options.important) {
if (config.options.important) {
utilities.walkDecls(decl => (decl.important = true))
}
@ -49,7 +44,7 @@ export default function(config) {
nodes: pluginUtilities,
})
prefixTree(tailwindUtilityTree, unwrappedConfig.options.prefix)
prefixTree(tailwindUtilityTree, config.options.prefix)
tailwindUtilityTree.walk(node => (node.source = atRule.source))
pluginUtilityTree.walk(node => (node.source = atRule.source))

View File

@ -1,48 +1,35 @@
import _ from 'lodash'
import postcss from 'postcss'
import buildSelectorVariant from '../util/buildSelectorVariant'
function buildPseudoClassVariant(selector, pseudoClass, separator) {
return `${buildSelectorVariant(selector, pseudoClass, separator)}:${pseudoClass}`
}
import generateVariantFunction from '../util/generateVariantFunction'
function generatePseudoClassVariant(pseudoClass) {
return (container, config) => {
const cloned = container.clone()
cloned.walkRules(rule => {
rule.selector = buildPseudoClassVariant(rule.selector, pseudoClass, config.options.separator)
return generateVariantFunction(({ modifySelectors, separator }) => {
return modifySelectors(({ className }) => {
return `.${pseudoClass}${separator}${className}:${pseudoClass}`
})
container.before(cloned.nodes)
}
})
}
const variantGenerators = {
'group-hover': (container, { options: { separator } }) => {
const cloned = container.clone()
cloned.walkRules(rule => {
rule.selector = `.group:hover ${buildSelectorVariant(
rule.selector,
'group-hover',
separator
)}`
const defaultVariantGenerators = {
'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'),
}
export default function(config) {
export default function(config, { variantGenerators: pluginVariantGenerators }) {
return function(css) {
const unwrappedConfig = config()
const variantGenerators = {
...defaultVariantGenerators,
...pluginVariantGenerators,
}
css.walkAtRules('variants', atRule => {
const variants = postcss.list.comma(atRule.params)
const variants = postcss.list.comma(atRule.params).filter(variant => variant !== '')
if (variants.includes('responsive')) {
const responsiveParent = postcss.atRule({ name: 'responsive' })
@ -52,10 +39,8 @@ export default function(config) {
atRule.before(atRule.clone().nodes)
_.forEach(['group-hover', 'hover', 'focus', 'active'], variant => {
if (variants.includes(variant)) {
variantGenerators[variant](atRule, unwrappedConfig)
}
_.forEach(_.without(variants, 'responsive'), variant => {
variantGenerators[variant](atRule, config)
})
atRule.remove()

View File

@ -0,0 +1,23 @@
import postcss from 'postcss'
import substituteTailwindAtRules from './lib/substituteTailwindAtRules'
import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
import substituteVariantsAtRules from './lib/substituteVariantsAtRules'
import substituteResponsiveAtRules from './lib/substituteResponsiveAtRules'
import substituteScreenAtRules from './lib/substituteScreenAtRules'
import substituteClassApplyAtRules from './lib/substituteClassApplyAtRules'
import processPlugins from './util/processPlugins'
export default function(lazyConfig) {
const config = lazyConfig()
const plugins = processPlugins(config)
return postcss([
substituteTailwindAtRules(config, plugins),
evaluateTailwindFunctions(config),
substituteVariantsAtRules(config, plugins),
substituteResponsiveAtRules(config),
substituteScreenAtRules(config),
substituteClassApplyAtRules(config),
])
}

View File

@ -0,0 +1,28 @@
import _ from 'lodash'
import postcss from 'postcss'
import escapeClassName from './escapeClassName'
export default function generateVariantFunction(generator) {
return (container, config) => {
const cloned = postcss.root({ nodes: container.clone().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
)
}
}

View File

@ -2,6 +2,7 @@ import _ from 'lodash'
import postcss from 'postcss'
import Node from 'postcss/lib/node'
import escapeClassName from '../util/escapeClassName'
import generateVariantFunction from '../util/generateVariantFunction'
import parseObjectStyles from '../util/parseObjectStyles'
import prefixSelector from '../util/prefixSelector'
import wrapWithVariants from '../util/wrapWithVariants'
@ -17,6 +18,7 @@ function parseStyles(styles) {
export default function(config) {
const pluginComponents = []
const pluginUtilities = []
const pluginVariantGenerators = {}
config.plugins.forEach(plugin => {
plugin({
@ -59,8 +61,15 @@ export default function(config) {
pluginComponents.push(...styles.nodes)
},
addVariant: (name, generator) => {
pluginVariantGenerators[name] = generateVariantFunction(generator)
},
})
})
return [pluginComponents, pluginUtilities]
return {
components: pluginComponents,
utilities: pluginUtilities,
variantGenerators: pluginVariantGenerators,
}
}