diff --git a/packages/core/src/generator/index.ts b/packages/core/src/generator/index.ts index 74643e7a6c..559ccc578f 100644 --- a/packages/core/src/generator/index.ts +++ b/packages/core/src/generator/index.ts @@ -225,12 +225,12 @@ export class UnoGenerator { const sorted: PreparedRule[] = items .filter(i => (i[4]?.layer || LAYER_DEFAULT) === layer) .sort((a, b) => a[0] - b[0] || (a[4]?.sort || 0) - (b[4]?.sort || 0) || a[1]?.localeCompare(b[1] || '') || a[2]?.localeCompare(b[2] || '') || 0) - .map(([, selector, body,, meta]) => { + .map(([, selector, body,, meta,, variantNoMerge]) => { const scopedSelector = selector ? applyScope(selector, scope) : selector return [ [[scopedSelector ?? '', meta?.sort ?? 0]], body, - !!meta?.noMerge, + !!(variantNoMerge ?? meta?.noMerge), ] }) if (!sorted.length) @@ -384,6 +384,7 @@ export class UnoGenerator { parent, layer: variantContextResult.layer, sort: variantContextResult.sort, + noMerge: variantContextResult.noMerge, } for (const p of this.config.postprocess) @@ -478,9 +479,9 @@ export class UnoGenerator { if (!parsed) return if (isRawUtil(parsed)) - return [parsed[0], undefined, parsed[1], undefined, parsed[2], this.config.details ? context : undefined] + return [parsed[0], undefined, parsed[1], undefined, parsed[2], this.config.details ? context : undefined, undefined] - const { selector, entries, parent, layer: variantLayer, sort: variantSort } = this.applyVariants(parsed) + const { selector, entries, parent, layer: variantLayer, sort: variantSort, noMerge } = this.applyVariants(parsed) const body = entriesToCss(entries) if (!body) @@ -492,7 +493,7 @@ export class UnoGenerator { layer: variantLayer ?? metaLayer, sort: variantSort ?? metaSort, } - return [parsed[0], selector, body, parent, ruleMeta, this.config.details ? context : undefined] + return [parsed[0], selector, body, parent, ruleMeta, this.config.details ? context : undefined, noMerge] } expandShortcut(input: string, context: RuleContext, depth = 5): [ShortcutValue[], RuleMeta | undefined] | undefined { @@ -585,15 +586,15 @@ export class UnoGenerator { const rawStringfieldUtil: StringifiedUtil[] = [] for (const item of parsed) { if (isRawUtil(item)) { - rawStringfieldUtil.push([item[0], undefined, item[1], undefined, item[2], context]) + rawStringfieldUtil.push([item[0], undefined, item[1], undefined, item[2], context, undefined]) continue } - const { selector, entries, parent, sort } = this.applyVariants(item, [...item[4], ...parentVariants], raw) + const { selector, entries, parent, sort, noMerge } = this.applyVariants(item, [...item[4], ...parentVariants], raw) // find existing selector/mediaQuery pair and merge const mapItem = selectorMap.getFallback(selector, parent, [[], item[0]]) // add entries - mapItem[0].push([entries, !!item[3]?.noMerge, sort ?? 0]) + mapItem[0].push([entries, !!(noMerge ?? item[3]?.noMerge), sort ?? 0]) } return rawStringfieldUtil.concat(selectorMap .map(([e, index], selector, joinedParents) => { @@ -603,7 +604,7 @@ export class UnoGenerator { return (flatten ? [entriesList.flat(1)] : entriesList).map((entries: CSSEntries): StringifiedUtil | undefined => { const body = entriesToCss(entries) if (body) - return [index, selector, body, joinedParents, { ...meta, noMerge, sort: maxSort }, context] + return [index, selector, body, joinedParents, { ...meta, noMerge, sort: maxSort }, context, undefined] return undefined }) } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index db282577c0..82f71a9bad 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -228,6 +228,11 @@ export interface VariantHandlerContext { * Order in which the variant is sorted within single rule. */ sort?: number + /** + * Option to not merge the resulting entries even if the body are the same. + * @default false + */ + noMerge?: boolean } export interface VariantHandler { @@ -655,6 +660,7 @@ export type StringifiedUtil = readonly [ parent: string | undefined, meta: RuleMeta | undefined, context: RuleContext | undefined, + noMerge: boolean | undefined, ] export type PreparedRule = readonly [ @@ -669,6 +675,7 @@ export interface UtilObject { parent: string | undefined layer: string | undefined sort: number | undefined + noMerge: boolean | undefined } export interface GenerateOptions { diff --git a/test/__snapshots__/variant-handler.test.ts.snap b/test/__snapshots__/variant-handler.test.ts.snap index c9008e957f..40e688eb67 100644 --- a/test/__snapshots__/variant-handler.test.ts.snap +++ b/test/__snapshots__/variant-handler.test.ts.snap @@ -1,5 +1,20 @@ // Vitest Snapshot v1 +exports[`variants > noMerge on variant 1`] = ` +"/* layer: default */ +.foo, +.var-a\\\\:foo::a, +.var-b\\\\:foo::b{name:bar;} +.var-c\\\\:foo::c{name:bar;}" +`; + +exports[`variants > noMerge variant with shortcut 1`] = ` +"/* layer: shortcuts */ +.bar::a, +.bar::b{name:bar;} +.bar::c{name:bar;}" +`; + exports[`variants > selector section is merged in order 1`] = ` "/* layer: default */ .prefix .pre\\\\:back\\\\:foo::pseudo, diff --git a/test/variant-handler.test.ts b/test/variant-handler.test.ts index 4f1dac7e58..206eb6847e 100644 --- a/test/variant-handler.test.ts +++ b/test/variant-handler.test.ts @@ -114,4 +114,72 @@ describe('variants', () => { expect(css).toMatchSnapshot() }) + + test('noMerge on variant', async () => { + const uno = createGenerator({ + rules: [ + ['foo', { name: 'bar' }], + ], + variants: [ + { + match(input) { + const match = input.match(/^(var-([abc])):/) + if (match) { + return { + matcher: input.slice(match[0].length), + handle: (input, next) => next({ + ...input, + pseudo: `${input.pseudo}::${match[2]}`, + noMerge: match[2] === 'c', + }), + } + } + }, + }, + ], + }) + + const { css } = await uno.generate([ + 'foo', + 'var-a:foo', + 'var-b:foo', + 'var-c:foo', + ].join(' '), { preflights: false }) + + expect(css).toMatchSnapshot() + }) + + test('noMerge variant with shortcut', async () => { + const uno = createGenerator({ + rules: [ + ['foo', { name: 'bar' }], + ], + shortcuts: [ + ['bar', 'var-a:foo var-b:foo var-c:foo'], + ], + variants: [ + { + match(input) { + const match = input.match(/^(var-([abc])):/) + if (match) { + return { + matcher: input.slice(match[0].length), + handle: (input, next) => next({ + ...input, + pseudo: `${input.pseudo}::${match[2]}`, + noMerge: match[2] === 'c', + }), + } + } + }, + }, + ], + }) + + const { css } = await uno.generate([ + 'bar', + ].join(' '), { preflights: false }) + + expect(css).toMatchSnapshot() + }) })