diff --git a/src/transformers/globalRule.ts b/src/transformers/globalRule.ts index 27af4377..3a5b689b 100644 --- a/src/transformers/globalRule.ts +++ b/src/transformers/globalRule.ts @@ -1,19 +1,15 @@ import postcss from 'postcss'; import { Transformer } from '../types'; -import { globalifyPlugin } from './globalStyle'; +import { wrapSelectorInGlobal } from './globalStyle'; const globalifyRulePlugin = (root: any) => { - root.walkAtRules(/^global$/, (atrule: any) => { - globalifyPlugin(atrule); - let after = atrule; - - atrule.each(function (child: any) { - after.after(child); - after = child; - }); - - atrule.remove(); + root.walkRules(/:global(?!\()/, (rule: any) => { + const [beginning, ...rest] = rule.selector.split(/:global(?!\()/); + rule.selector = ( + beginning.trim() + ' ' + + rest.filter((x: string) => !!x).map(wrapSelectorInGlobal).join(' ') + ).trim(); }); }; diff --git a/src/transformers/globalStyle.ts b/src/transformers/globalStyle.ts index 9d03aa2b..fce02330 100644 --- a/src/transformers/globalStyle.ts +++ b/src/transformers/globalStyle.ts @@ -2,7 +2,23 @@ import postcss from 'postcss'; import { Transformer } from '../types'; -export const globalifyPlugin = (root: any) => { +export const wrapSelectorInGlobal = (selector: string) => { + return selector + .trim() + .split(' ') + .map((selectorPart) => { + if (selectorPart.startsWith(':local')) { + return selectorPart.replace(/:local\((.+?)\)/g, '$1'); + } + if (selectorPart.startsWith(':global')) { + return selectorPart; + } + return `:global(${selectorPart})`; + }) + .join(' '); +}; + +const globalifyPlugin = (root: any) => { root.walkAtRules(/keyframes$/, (atrule: any) => { if (!atrule.params.startsWith('-global-')) { atrule.params = '-global-' + atrule.params; @@ -14,20 +30,7 @@ export const globalifyPlugin = (root: any) => { return; } - rule.selectors = rule.selectors.map((selector: string) => { - return selector - .split(' ') - .map((selectorPart) => { - if (selectorPart.startsWith(':local')) { - return selectorPart.replace(/:local\((.+?)\)/g, '$1'); - } - if (selectorPart.startsWith(':global')) { - return selectorPart; - } - return `:global(${selectorPart})`; - }) - .join(' '); - }); + rule.selectors = rule.selectors.map(wrapSelectorInGlobal); }); }; diff --git a/test/transformers/globalRule.test.ts b/test/transformers/globalRule.test.ts index aa21fe5b..e0f63ae4 100644 --- a/test/transformers/globalRule.test.ts +++ b/test/transformers/globalRule.test.ts @@ -3,7 +3,7 @@ import { preprocess } from '../utils'; describe('transformer - globalRule', () => { it('wraps selector in :global(...) modifier', async () => { - const template = ``; + const template = ``; const opts = autoProcess(); const preprocessed = await preprocess(template, opts); expect(preprocessed.toString()).toContain( @@ -11,32 +11,70 @@ describe('transformer - globalRule', () => { ); }); - it('wraps selector in :global(...) modifier only inside the rule', async () => { - const template = ``; + it('wraps selector in :global(...) only if needed', async () => { + const template = ``; const opts = autoProcess(); const preprocessed = await preprocess(template, opts); expect(preprocessed.toString()).toContain( - `:global(div){color:red}.test{}`, + `:global(.test){}:global(.foo){}`, ); }); - it('wraps selector in :global(...) only if needed', async () => { - const template = ``; + it('wraps selector in :global(...) on multiple levels', async () => { + const template = ''; const opts = autoProcess(); const preprocessed = await preprocess(template, opts); - expect(preprocessed.toString()).toContain( - `:global(.test){}:global(.foo){}`, + expect(preprocessed.toString()).toMatch( + // either be :global(div .cls){} + // or :global(div) :global(.cls){} + /(:global\(div .cls\)\{\}|:global\(div\) :global\(\.cls\)\{\})/, ); }); - it("prefixes @keyframes names with '-global-' only if needed", async () => { - const template = ``; + it('wraps selector in :global(...) on multiple levels when in the middle', async () => { + const template = ''; const opts = autoProcess(); const preprocessed = await preprocess(template, opts); - expect(preprocessed.toString()).toContain( - `@keyframes -global-a {from{} to{}}@keyframes -global-b {from{} to{}}`, + expect(preprocessed.toString()).toMatch( + // either be div :global(span .cls) {} + // or div :global(span) :global(.cls) {} + /div (:global\(span .cls\)\{\}|:global\(span\) :global\(\.cls\)\{\})/, + ); + }); + + it('does not break when at the end', async () => { + const template = ''; + const opts = autoProcess(); + const preprocessed = await preprocess(template, opts); + expect(preprocessed.toString()).toContain('span{}'); + }); + + it('works with collapsed nesting several times', async () => { + const template = ''; + const opts = autoProcess(); + const preprocessed = await preprocess(template, opts); + expect(preprocessed.toString()).toMatch( + // either be div :global(span .cls) {} + // or div :global(span) :global(.cls) {} + /div (:global\(span .cls\)\{\}|:global\(span\) :global\(\.cls\)\{\})/, + ); + }); + + it('does not interfere with the :global(...) syntax', async () => { + const template = ''; + const opts = autoProcess(); + const preprocessed = await preprocess(template, opts); + expect(preprocessed.toString()).toContain('div :global(span){}'); + }); + + it('allows mixing with the :global(...) syntax', async () => { + const template = ''; + const opts = autoProcess(); + const preprocessed = await preprocess(template, opts); + expect(preprocessed.toString()).toMatch( + // either be div :global(span .cls) {} + // or div :global(span) :global(.cls) {} + /div (:global\(span .cls\)\{\}|:global\(span\) :global\(\.cls\)\{\})/, ); }); });