Skip to content

Commit cca29fb

Browse files
illrightkaisermann
authored andcommitted
feat: replace the @global for :global for CSS modules compliance
1 parent a8b1b46 commit cca29fb

File tree

3 files changed

+77
-40
lines changed

3 files changed

+77
-40
lines changed

Diff for: src/transformers/globalRule.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import postcss from 'postcss';
22

33
import { Transformer } from '../types';
4-
import { globalifyPlugin } from './globalStyle';
4+
import { wrapSelectorInGlobal } from './globalStyle';
55

66
const globalifyRulePlugin = (root: any) => {
7-
root.walkAtRules(/^global$/, (atrule: any) => {
8-
globalifyPlugin(atrule);
9-
let after = atrule;
10-
11-
atrule.each(function (child: any) {
12-
after.after(child);
13-
after = child;
14-
});
15-
16-
atrule.remove();
7+
root.walkRules(/:global(?!\()/, (rule: any) => {
8+
const [beginning, ...rest] = rule.selector.split(/:global(?!\()/);
9+
rule.selector = (
10+
beginning.trim() + ' '
11+
+ rest.filter((x: string) => !!x).map(wrapSelectorInGlobal).join(' ')
12+
).trim();
1713
});
1814
};
1915

Diff for: src/transformers/globalStyle.ts

+18-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,23 @@ import postcss from 'postcss';
22

33
import { Transformer } from '../types';
44

5-
export const globalifyPlugin = (root: any) => {
5+
export const wrapSelectorInGlobal = (selector: string) => {
6+
return selector
7+
.trim()
8+
.split(' ')
9+
.map((selectorPart) => {
10+
if (selectorPart.startsWith(':local')) {
11+
return selectorPart.replace(/:local\((.+?)\)/g, '$1');
12+
}
13+
if (selectorPart.startsWith(':global')) {
14+
return selectorPart;
15+
}
16+
return `:global(${selectorPart})`;
17+
})
18+
.join(' ');
19+
};
20+
21+
const globalifyPlugin = (root: any) => {
622
root.walkAtRules(/keyframes$/, (atrule: any) => {
723
if (!atrule.params.startsWith('-global-')) {
824
atrule.params = '-global-' + atrule.params;
@@ -14,20 +30,7 @@ export const globalifyPlugin = (root: any) => {
1430
return;
1531
}
1632

17-
rule.selectors = rule.selectors.map((selector: string) => {
18-
return selector
19-
.split(' ')
20-
.map((selectorPart) => {
21-
if (selectorPart.startsWith(':local')) {
22-
return selectorPart.replace(/:local\((.+?)\)/g, '$1');
23-
}
24-
if (selectorPart.startsWith(':global')) {
25-
return selectorPart;
26-
}
27-
return `:global(${selectorPart})`;
28-
})
29-
.join(' ');
30-
});
33+
rule.selectors = rule.selectors.map(wrapSelectorInGlobal);
3134
});
3235
};
3336

Diff for: test/transformers/globalRule.test.ts

+52-14
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,78 @@ import { preprocess } from '../utils';
33

44
describe('transformer - globalRule', () => {
55
it('wraps selector in :global(...) modifier', async () => {
6-
const template = `<style>@global{div{color:red}.test{}}</style>`;
6+
const template = `<style>:global div{color:red}:global .test{}</style>`;
77
const opts = autoProcess();
88
const preprocessed = await preprocess(template, opts);
99
expect(preprocessed.toString()).toContain(
1010
`:global(div){color:red}:global(.test){}`,
1111
);
1212
});
1313

14-
it('wraps selector in :global(...) modifier only inside the rule', async () => {
15-
const template = `<style>@global{div{color:red}}.test{}</style>`;
14+
it('wraps selector in :global(...) only if needed', async () => {
15+
const template = `<style>:global .test{}:global :global(.foo){}</style>`;
1616
const opts = autoProcess();
1717
const preprocessed = await preprocess(template, opts);
1818
expect(preprocessed.toString()).toContain(
19-
`:global(div){color:red}.test{}`,
19+
`:global(.test){}:global(.foo){}`,
2020
);
2121
});
2222

23-
it('wraps selector in :global(...) only if needed', async () => {
24-
const template = `<style>@global{.test{}:global(.foo){}}</style>`;
23+
it('wraps selector in :global(...) on multiple levels', async () => {
24+
const template = '<style>:global div .cls{}</style>';
2525
const opts = autoProcess();
2626
const preprocessed = await preprocess(template, opts);
27-
expect(preprocessed.toString()).toContain(
28-
`:global(.test){}:global(.foo){}`,
27+
expect(preprocessed.toString()).toMatch(
28+
// either be :global(div .cls){}
29+
// or :global(div) :global(.cls){}
30+
/(:global\(div .cls\)\{\}|:global\(div\) :global\(\.cls\)\{\})/,
2931
);
3032
});
3133

32-
it("prefixes @keyframes names with '-global-' only if needed", async () => {
33-
const template = `<style>
34-
@global{@keyframes a {from{} to{}}@keyframes -global-b {from{} to{}}}
35-
</style>`;
34+
it('wraps selector in :global(...) on multiple levels when in the middle', async () => {
35+
const template = '<style>div :global span .cls{}</style>';
3636
const opts = autoProcess();
3737
const preprocessed = await preprocess(template, opts);
38-
expect(preprocessed.toString()).toContain(
39-
`@keyframes -global-a {from{} to{}}@keyframes -global-b {from{} to{}}`,
38+
expect(preprocessed.toString()).toMatch(
39+
// either be div :global(span .cls) {}
40+
// or div :global(span) :global(.cls) {}
41+
/div (:global\(span .cls\)\{\}|:global\(span\) :global\(\.cls\)\{\})/,
42+
);
43+
});
44+
45+
it('does not break when at the end', async () => {
46+
const template = '<style>span :global{}</style>';
47+
const opts = autoProcess();
48+
const preprocessed = await preprocess(template, opts);
49+
expect(preprocessed.toString()).toContain('span{}');
50+
});
51+
52+
it('works with collapsed nesting several times', async () => {
53+
const template = '<style>div :global span :global .cls{}</style>';
54+
const opts = autoProcess();
55+
const preprocessed = await preprocess(template, opts);
56+
expect(preprocessed.toString()).toMatch(
57+
// either be div :global(span .cls) {}
58+
// or div :global(span) :global(.cls) {}
59+
/div (:global\(span .cls\)\{\}|:global\(span\) :global\(\.cls\)\{\})/,
60+
);
61+
});
62+
63+
it('does not interfere with the :global(...) syntax', async () => {
64+
const template = '<style>div :global(span){}</style>';
65+
const opts = autoProcess();
66+
const preprocessed = await preprocess(template, opts);
67+
expect(preprocessed.toString()).toContain('div :global(span){}');
68+
});
69+
70+
it('allows mixing with the :global(...) syntax', async () => {
71+
const template = '<style>div :global(span) :global .cls{}</style>';
72+
const opts = autoProcess();
73+
const preprocessed = await preprocess(template, opts);
74+
expect(preprocessed.toString()).toMatch(
75+
// either be div :global(span .cls) {}
76+
// or div :global(span) :global(.cls) {}
77+
/div (:global\(span .cls\)\{\}|:global\(span\) :global\(\.cls\)\{\})/,
4078
);
4179
});
4280
});

0 commit comments

Comments
 (0)