Skip to content

Commit

Permalink
feat: replace the @global for :global for CSS modules compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
illright authored and kaisermann committed Jun 5, 2020
1 parent a8b1b46 commit cca29fb
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 40 deletions.
18 changes: 7 additions & 11 deletions src/transformers/globalRule.ts
Original file line number Diff line number Diff line change
@@ -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();
});
};

Expand Down
33 changes: 18 additions & 15 deletions src/transformers/globalStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
});
};

Expand Down
66 changes: 52 additions & 14 deletions test/transformers/globalRule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,78 @@ import { preprocess } from '../utils';

describe('transformer - globalRule', () => {
it('wraps selector in :global(...) modifier', async () => {
const template = `<style>@global{div{color:red}.test{}}</style>`;
const template = `<style>:global div{color:red}:global .test{}</style>`;
const opts = autoProcess();
const preprocessed = await preprocess(template, opts);
expect(preprocessed.toString()).toContain(
`:global(div){color:red}:global(.test){}`,
);
});

it('wraps selector in :global(...) modifier only inside the rule', async () => {
const template = `<style>@global{div{color:red}}.test{}</style>`;
it('wraps selector in :global(...) only if needed', async () => {
const template = `<style>:global .test{}:global :global(.foo){}</style>`;
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 = `<style>@global{.test{}:global(.foo){}}</style>`;
it('wraps selector in :global(...) on multiple levels', async () => {
const template = '<style>:global div .cls{}</style>';
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 = `<style>
@global{@keyframes a {from{} to{}}@keyframes -global-b {from{} to{}}}
</style>`;
it('wraps selector in :global(...) on multiple levels when in the middle', async () => {
const template = '<style>div :global span .cls{}</style>';
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 = '<style>span :global{}</style>';
const opts = autoProcess();
const preprocessed = await preprocess(template, opts);
expect(preprocessed.toString()).toContain('span{}');
});

it('works with collapsed nesting several times', async () => {
const template = '<style>div :global span :global .cls{}</style>';
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 = '<style>div :global(span){}</style>';
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 = '<style>div :global(span) :global .cls{}</style>';
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\)\{\})/,
);
});
});

0 comments on commit cca29fb

Please sign in to comment.