Skip to content

Commit

Permalink
Allow returning parallel variants from addVariant or matchVariant
Browse files Browse the repository at this point in the history
… callback functions (#8455)

* allow to return an array of format strings from matchVariant or
addVariant

* add parallel variant with function test

* upgrade test to use a function call

* allow to return parallel variants from variant function

Caveat: this now belongs to the same plugin and is not registered as
separate variants which means that sort order can differ.

* prevent crash if `.toMatchFormattedCss()` receives undefined

* update changelog

* ensure that we use a local list of variant functions

Now that a variant function can return a list of variant functions from
within the plugin, we have to make sure to executed and register those
functions as well.

However, we need to make sure that this list is local for the variant
and not "globally" registered otherwise we keep add a dynamic function
to the global list which results in duplicate output becaus multiple
duplicate variants will be registered.

* add little warning regarding potential clashes

* Update CHANGELOG.md
  • Loading branch information
RobinMalfait authored May 31, 2022
1 parent 2dffc87 commit d2fdf9e
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Create tailwind.config.cjs file in ESM package when running init ([#8363](https://github.com/tailwindlabs/tailwindcss/pull/8363))
- Fix `matchVariants` that use at-rules and placeholders ([#8392](https://github.com/tailwindlabs/tailwindcss/pull/8392))
- Improve types of the `tailwindcss/plugin` ([#8400](https://github.com/tailwindlabs/tailwindcss/pull/8400))
- Allow returning parallel variants from `addVariant` or `matchVariant` callback functions ([#8455](https://github.com/tailwindlabs/tailwindcss/pull/8455))

### Changed

Expand Down
2 changes: 1 addition & 1 deletion jest/customMatchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ expect.extend({
expect.extend({
// Compare two CSS strings with all whitespace removed
// This is probably naive but it's fast and works well enough.
toMatchFormattedCss(received, argument) {
toMatchFormattedCss(received = '', argument = '') {
function format(input) {
return prettier.format(input.replace(/\n/g, ''), {
parser: 'css',
Expand Down
22 changes: 21 additions & 1 deletion src/lib/generateRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function applyVariant(variant, matches, context) {
}

if (context.variantMap.has(variant)) {
let variantFunctionTuples = context.variantMap.get(variant)
let variantFunctionTuples = context.variantMap.get(variant).slice()
let result = []

for (let [meta, rule] of matches) {
Expand Down Expand Up @@ -216,6 +216,26 @@ function applyVariant(variant, matches, context) {
args,
})

// It can happen that a list of format strings is returned from within the function. In that
// case, we have to process them as well. We can use the existing `variantSort`.
if (Array.isArray(ruleWithVariant)) {
for (let [idx, variantFunction] of ruleWithVariant.entries()) {
// This is a little bit scary since we are pushing to an array of items that we are
// currently looping over. However, you can also think of it like a processing queue
// where you keep handling jobs until everything is done and each job can queue more
// jobs if needed.
variantFunctionTuples.push([
// TODO: This could have potential bugs if we shift the sort order from variant A far
// enough into the sort space of variant B. The chances are low, but if this happens
// then this might be the place too look at. One potential solution to this problem is
// reserving additional X places for these 'unknown' variants in between.
variantSort | BigInt(idx << ruleWithVariant.length),
variantFunction,
])
}
continue
}

if (typeof ruleWithVariant === 'string') {
collectedFormats.push(ruleWithVariant)
}
Expand Down
4 changes: 4 additions & 0 deletions src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,10 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
)
}

if (Array.isArray(result)) {
return result.map((variant) => parseVariant(variant))
}

// result may be undefined with legacy variants that use APIs like `modifySelectors`
return result && parseVariant(result)(api)
}
Expand Down
38 changes: 38 additions & 0 deletions tests/match-variants.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,41 @@ test('matched variant values maintain the sort order they are registered in', ()
`)
})
})

test('matchVariant can return an array of format strings from the function', () => {
let config = {
content: [
{
raw: html`<div class="test-[a,b,c]:underline"></div>`,
},
],
corePlugins: { preflight: false },
plugins: [
({ matchVariant }) => {
matchVariant({
test: (selector) => selector.split(',').map((selector) => `&.${selector} > *`),
})
},
],
}

let input = css`
@tailwind utilities;
`

return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.test-\[a\2c b\2c c\]\:underline.a > * {
text-decoration-line: underline;
}
.test-\[a\2c b\2c c\]\:underline.b > * {
text-decoration-line: underline;
}
.test-\[a\2c b\2c c\]\:underline.c > * {
text-decoration-line: underline;
}
`)
})
})
43 changes: 43 additions & 0 deletions tests/parallel-variants.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,46 @@ test('basic parallel variants', async () => {
`)
})
})

test('parallel variants can be generated using a function that returns parallel variants', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', () => ['& *::test', '&::test'])
},
],
}

return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold *::test {
font-weight: 700;
}
.test\:font-medium *::test {
font-weight: 500;
}
.test\:font-bold::test {
font-weight: 700;
}
.test\:font-medium::test {
font-weight: 500;
}
.hover\:test\:font-black *:hover::test {
font-weight: 900;
}
.hover\:test\:font-black:hover::test {
font-weight: 900;
}
`)
})
})

0 comments on commit d2fdf9e

Please sign in to comment.