Skip to content

Commit b00a5cc

Browse files
authored
fix(core): honor rule's noMerge option in shortcut (#483)
1 parent ad0794a commit b00a5cc

File tree

3 files changed

+61
-10
lines changed

3 files changed

+61
-10
lines changed

packages/core/src/generator/index.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ export class UnoGenerator {
187187
const rules = sorted
188188
.reverse()
189189
.map(([selector, body, noMerge], idx) => {
190-
if (selector && this.config.mergeSelectors) {
190+
if (!noMerge && selector && this.config.mergeSelectors) {
191191
// search for rules that has exact same body, and merge them
192192
for (let i = idx + 1; i < size; i++) {
193193
const current = sorted[i]
194-
if (!noMerge && current && current[0] && current[1] === body) {
194+
if (current && !current[2] && current[0] && current[1] === body) {
195195
current[0].push(...selector)
196196
return null
197197
}
@@ -414,7 +414,7 @@ export class UnoGenerator {
414414
expanded: string[],
415415
meta: RuleMeta = { layer: this.config.shortcutsLayer },
416416
): Promise<StringifiedUtil[] | undefined> {
417-
const selectorMap = new TwoKeyMap<string, string | undefined, [CSSEntries[], number]>()
417+
const selectorMap = new TwoKeyMap<string, string | undefined, [[CSSEntries, boolean][], number]>()
418418

419419
const parsed = (
420420
await Promise.all(uniq(expanded)
@@ -437,22 +437,31 @@ export class UnoGenerator {
437437

438438
// find existing selector/mediaQuery pair and merge
439439
const mapItem = selectorMap.getFallback(selector, parent, [[], item[0]])
440-
// append entries
441-
mapItem[0].push(entries)
440+
// add entries
441+
mapItem[0].push([entries, !!item[3]?.noMerge])
442442
}
443443

444444
return selectorMap
445445
.map(([e, index], selector, mediaQuery) => {
446-
const split = e.filter(entries => entries.some(entry => entry[0] === CONTROL_SHORTCUT_NO_MERGE))
447-
const rest = e.filter(entries => entries.every(entry => entry[0] !== CONTROL_SHORTCUT_NO_MERGE))
448-
return [...split, rest.flat(1)].map((entries): StringifiedUtil | undefined => {
446+
const stringify = (noMerge: boolean) => (entries: CSSEntries): StringifiedUtil | undefined => {
449447
const body = entriesToCss(entries)
450448
if (body)
451-
return [index, selector, body, mediaQuery, meta]
449+
return [index, selector, body, mediaQuery, { ...meta, noMerge }]
452450
return undefined
451+
}
452+
453+
const merges = [
454+
[e.filter(([, noMerge]) => noMerge).map(([entries]) => entries), true],
455+
[e.filter(([, noMerge]) => !noMerge).map(([entries]) => entries), false],
456+
] as [CSSEntries[], boolean][]
457+
458+
return merges.map(([e, noMerge]) => {
459+
const splits = e.filter(entries => entries.some(entry => entry[0] === CONTROL_SHORTCUT_NO_MERGE))
460+
const rests = e.filter(entries => entries.every(entry => entry[0] !== CONTROL_SHORTCUT_NO_MERGE))
461+
return [...splits.map(stringify(noMerge)), ...[rests.flat(1)].map(stringify(noMerge))]
453462
})
454463
})
455-
.flat(1)
464+
.flat(2)
456465
.filter(Boolean) as StringifiedUtil[]
457466
}
458467

test/__snapshots__/selector-no-merge.test.ts.snap

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,26 @@ exports[`selector > rules split selector 1`] = `
77
.not-merged{merged:1;}"
88
`;
99

10+
exports[`variant > variant shortcuts early 1`] = `
11+
"/* layer: shortcuts */
12+
.m2-no-merge::non-breaking{merged:1;}
13+
.m2-no-merge{merged:1;}"
14+
`;
15+
16+
exports[`variant > variant shortcuts late 1`] = `
17+
"/* layer: shortcuts */
18+
.m3-no-merge-ordered{late:2;}
19+
.m3-no-merge-ordered::non-breaking{late:2;}"
20+
`;
21+
1022
exports[`variant > variant split selector 1`] = `
1123
"/* layer: default */
1224
.moz\\\\:no-merge::non-breaking{merged:1;}
1325
.webkit\\\\:no-merge::breaking{merged:1;}"
1426
`;
27+
28+
exports[`variant > variant split shortcuts 1`] = `
29+
"/* layer: shortcuts */
30+
.m1-no-merge::breaking{merged:1;}
31+
.m1-no-merge::non-breaking{merged:1;}"
32+
`;

test/selector-no-merge.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,41 @@ describe('selector', () => {
1919

2020
describe('variant', () => {
2121
const uno = createGenerator({
22+
shortcuts: [
23+
[/^m1-(.+)$/, ([, s]) => `moz:${s} webkit:${s}`],
24+
[/^m2-(.+)$/, ([, s]) => `moz:${s} merge-candidate`],
25+
[/^m3-(.+)$/, ([, s]) => `moz:${s} merge-candidate-early`],
26+
],
2227
variants: [
2328
variantMatcher('moz', s => `${s}::non-breaking`),
2429
variantMatcher('webkit', s => `${s}::breaking`),
2530
],
2631
rules: [
2732
[/^no-merge$/, () => ({ merged: 1 }), { noMerge: true }],
33+
['merge-candidate', { merged: 1 }],
34+
35+
['merge-candidate-early', { late: 2 }],
36+
[/^no-merge-ordered$/, () => ({ late: 2 }), { noMerge: true }],
2837
],
2938
})
3039

3140
test('variant split selector', async() => {
3241
const { css } = await uno.generate('moz:no-merge webkit:no-merge')
3342
expect(css).toMatchSnapshot()
3443
})
44+
45+
test('variant split shortcuts', async() => {
46+
const { css } = await uno.generate('m1-no-merge')
47+
expect(css).toMatchSnapshot()
48+
})
49+
50+
test('variant shortcuts early', async() => {
51+
const { css } = await uno.generate('m2-no-merge')
52+
expect(css).toMatchSnapshot()
53+
})
54+
55+
test('variant shortcuts late', async() => {
56+
const { css } = await uno.generate('m3-no-merge-ordered')
57+
expect(css).toMatchSnapshot()
58+
})
3559
})

0 commit comments

Comments
 (0)