Allow returning parallel variants from addVariant or matchVariant 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
This commit is contained in:
parent
2dffc87447
commit
d2fdf9eb09
6 changed files with 108 additions and 2 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue