Skip to content
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ _Note: only for auto preprocessing_

### Global style

Add a `global` attribute to your `style` tag and instead of scoping the css, all of its content will be interpreted as global style.
Add a `global` attribute to your `style` tag and instead of scoping the CSS, all of its content will be interpreted as global style.

```html
<style global>
Expand All @@ -89,10 +89,38 @@ Add a `global` attribute to your `style` tag and instead of scoping the css, all
</style>
```

_Note<sup>1</sup>: needs postcss to be installed_
_Note<sup>1</sup>: needs PostCSS to be installed._

_Note<sup>2</sup>: if you're using it as a standalone processor, it works best if added to the end of the processors array._

_Note<sup>3</sup>: if you need to have some styles be scoped inside a global style tag, use `:local` in the same way you'd use `:global`._

### Global rule

Use a `:global` rule to only expose parts of the stylesheet:

```html
<style lang="scss">
.scoped-style {}

:global {
@import 'global-stylesheet.scss';

.global-style {
.global-child-style {}
}
}
</style>
```

Works best with nesting-enabled CSS preprocessors, but regular CSS selectors like `div :global .global1 .global2` are also supported.

_Note<sup>1</sup>: needs PostCSS to be installed._

_Note<sup>2</sup>: if you're using it as a standalone processor, it works best if added to the end of the processors array._

_Note<sup>3</sup>: wrapping `@keyframes` inside `:global {}` blocks is not supported. Use the [`-global-` name prefix for global keyframes](https://svelte.dev/docs#style)._

### Preprocessors

Current supported out-of-the-box preprocessors are `SCSS`, `Stylus`, `Less`, `Coffeescript`, `TypeScript`, `Pug`, `PostCSS`, `Babel`.
Expand Down Expand Up @@ -135,7 +163,9 @@ Current supported out-of-the-box preprocessors are `SCSS`, `Stylus`, `Less`, `Co
<!-- Or -->

<style type="text/stylus">
$color=reddivcolor: $color;
$color = red
div
color: $color
</style>
```

Expand Down
11 changes: 11 additions & 0 deletions src/autoProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface Transformers {
coffeescript?: TransformerOptions<Options.Coffeescript>;
pug?: TransformerOptions<Options.Pug>;
globalStyle?: TransformerOptions<Options.Typescript>;
globalRule?: TransformerOptions<Options.Typescript>;
replace?: Options.Replace;
[languageName: string]: TransformerOptions<any>;
}
Expand Down Expand Up @@ -55,6 +56,7 @@ type AutoPreprocessOptions = {
coffeescript?: TransformerOptions<Options.Coffeescript>;
pug?: TransformerOptions<Options.Pug>;
globalStyle?: TransformerOptions<Options.Typescript>;
globalRule?: TransformerOptions<Options.Typescript>;
// workaround while we don't have this
// https://github.com/microsoft/TypeScript/issues/17867
[languageName: string]:
Expand Down Expand Up @@ -270,6 +272,15 @@ export function autoPreprocess(
map = transformed.map;
}

const transformed = await runTransformer('globalRule', null, {
content: code,
map,
filename,
});

code = transformed.code;
map = transformed.map;

return { code, map, dependencies };
},
};
Expand Down
13 changes: 13 additions & 0 deletions src/processors/globalRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { PreprocessorGroup } from '../types';

export default (): PreprocessorGroup => {
return {
async style({ content, filename }) {
const { default: transformer } = await import(
'../transformers/globalRule'
);

return transformer({ content, filename });
},
};
};
24 changes: 24 additions & 0 deletions src/transformers/globalRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import postcss from 'postcss';

import { Transformer } from '../types';
import { wrapSelectorInGlobal } from './globalStyle';

const globalifyRulePlugin = (root: any) => {
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();
});
};

const transformer: Transformer<never> = async ({ content, filename }) => {
const { css, map: newMap } = await postcss()
.use(globalifyRulePlugin)
.process(content, { from: filename, map: true });

return { code: css, map: newMap };
};

export default transformer;
31 changes: 17 additions & 14 deletions src/transformers/globalStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ import postcss from 'postcss';

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

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-')) {
Expand All @@ -14,20 +30,7 @@ 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
80 changes: 80 additions & 0 deletions test/transformers/globalRule.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import autoProcess from '../../src';
import { preprocess } from '../utils';

describe('transformer - globalRule', () => {
it('wraps selector in :global(...) modifier', async () => {
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(...) 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(.test){}:global(.foo){}`,
);
});

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()).toMatch(
// either be :global(div .cls){}
// or :global(div) :global(.cls){}
/(:global\(div .cls\)\{\}|:global\(div\) :global\(\.cls\)\{\})/,
);
});

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()).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\)\{\})/,
);
});
});
2 changes: 1 addition & 1 deletion test/transformers/scss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const implementation: Options.Sass['implementation'] = {
};

describe('transformer - scss', () => {
it('should prepend scss content via `data` option property - via defaul async render', async () => {
it('should prepend scss content via `data` option property - via default async render', async () => {
const template = `<style lang="scss"></style>`;
const opts = getAutoPreprocess({
scss: {
Expand Down