mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Merge pull request #505 from tailwindcss/variant-plugins
Support creating new variants through plugins (WIP)
This commit is contained in:
commit
38c211a164
@ -4,7 +4,10 @@
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true
|
||||
}
|
||||
},
|
||||
"extends": ["eslint-config-postcss", "prettier"],
|
||||
"plugins": ["prettier"],
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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' })
|
||||
})
|
||||
})
|
||||
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
39
src/index.js
39
src/index.js
@ -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() {
|
||||
|
||||
@ -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)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -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 = []
|
||||
|
||||
|
||||
@ -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])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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()
|
||||
|
||||
23
src/processTailwindFeatures.js
Normal file
23
src/processTailwindFeatures.js
Normal 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),
|
||||
])
|
||||
}
|
||||
28
src/util/generateVariantFunction.js
Normal file
28
src/util/generateVariantFunction.js
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user