Skip to content

Commit c3ecb59

Browse files
authored
Transform global selectors (#119)
* Replace :root, body, html with the prefix * Add details about global selectors * Add option to not transform global selectors * Merge redundant descriptions
1 parent efa3b28 commit c3ecb59

File tree

7 files changed

+186
-24
lines changed

7 files changed

+186
-24
lines changed

README.md

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,32 @@ $ npm install postcss-prefix-selector
2727

2828
## Usage with PostCSS
2929

30+
A prefix is added before most selectors. Below is an example of how CSS will be transformed by adding a prefix called `.namespace`.
31+
3032
```js
3133
const prefixer = require('postcss-prefix-selector')
3234

3335
// css to be processed
3436
const css = fs.readFileSync("input.css", "utf8")
3537

3638
const out = postcss().use(prefixer({
37-
prefix: '.some-selector',
39+
prefix: '.namespace',
3840
exclude: ['.c'],
39-
40-
// Optional transform callback for case-by-case overrides
41-
transform: function (prefix, selector, prefixedSelector, filePath, rule) {
42-
if (selector === 'body') {
43-
return 'body' + prefix;
44-
} else {
45-
return prefixedSelector;
46-
}
47-
}
4841
})).process(css).css
4942
```
5043

51-
Using the options above and the CSS below...
52-
5344
```css
54-
body {
55-
background: red;
45+
/* Input */
46+
.a, .b {
47+
color: aqua;
5648
}
5749

58-
.a, .b {
50+
.c {
51+
color: coral;
52+
}
53+
54+
/* Output */
55+
.namespace .a, .namespace .b {
5956
color: aqua;
6057
}
6158

@@ -64,19 +61,45 @@ body {
6461
}
6562
```
6663

67-
You will get the following output
64+
Please note that global selectors (`html`, `body`, `:root`) cannot be prefixed so instead they will be replaced with the prefix. This behaviour can be disabled with the `skipGlobalSelectors` option.
6865

6966
```css
70-
body.some-selector {
71-
background: red;
72-
}
67+
/* Input */
68+
:root { --bs-blue:#0d6efd; }
69+
html { padding: 0; }
70+
body { margin: 0; }
71+
72+
/* Output */
73+
.namespace { --bs-blue:#0d6efd; }
74+
.namespace { padding: 0; }
75+
.namespace { margin: 0; }
76+
```
7377

74-
.some-selector .a, .some-selector .b {
75-
color: aqua;
78+
It's also possible to customize the way prefixing is done by defining a transform function:
79+
80+
```js
81+
const out = postcss().use(prefixer({
82+
prefix: '.namespace',
83+
// Optional transform callback for case-by-case overrides
84+
transform: function (prefix, selector, prefixedSelector, filePath, rule) {
85+
if (selector === 'body') {
86+
return 'body' + prefix;
87+
} else {
88+
return prefixedSelector;
89+
}
90+
}
91+
})).process(css).css
92+
```
93+
94+
```css
95+
/* Input */
96+
body {
97+
background: red;
7698
}
7799

78-
.c {
79-
color: coral;
100+
/* Output */
101+
body.namespace {
102+
background: red;
80103
}
81104
```
82105

@@ -180,6 +203,7 @@ export default defineConfig({
180203
| `transform` | `Function` | In cases where you may want to use the prefix differently for different selectors, it is possible to pass in a custom transform method |
181204
| `ignoreFiles` | `string[]` or `RegExp[]` | List of ignored files for processing |
182205
| `includeFiles` | `string[]` or `RegExp[]` | List of included files for processing |
206+
| `skipGlobalSelectors` | `boolean` | When enabled, global selectors (`html`, `body`, `root`) won't be modified by the prefixer. Default: `false`. |
183207
184208
## Maintainer
185209
@@ -193,7 +217,7 @@ This project uses Mocha. If you submit a PR, please add appropriate tests and ma
193217
194218
## License
195219
196-
[MIT](LICENSE) © 2015-2022 Jonathan Ong.
220+
[MIT](LICENSE) © 2015-2024 Jonathan Ong.
197221
198222
[gitpod-image]: https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod
199223
[gitpod-url]: https://gitpod.io/#https://github.com/RadValentin/postcss-prefix-selector

index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ const prefixPlugin = (options = {}) => {
4848
);
4949
}
5050

51+
// replace :root, body, html with the prefix
52+
if ([':root', 'body', 'html'].some(globalSel => selector.startsWith(globalSel))) {
53+
if (options.skipGlobalSelectors) {
54+
return selector;
55+
}
56+
57+
return selector.replace(/(html\s+body|:root\s+body|html|:root|body)/gm, prefix);
58+
}
59+
5160
return prefixWithSpace + selector;
5261
});
5362
}

test/fixtures/global-selectors.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
html {
2+
margin: 0;
3+
}
4+
5+
body {
6+
padding: 0;
7+
}
8+
9+
html body div {
10+
color: blue;
11+
}
12+
13+
:root {
14+
--bs-blue:#0d6efd;
15+
}
16+
17+
:root .a {
18+
--bs-green: #bada55;
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.prefix {
2+
margin: 0;
3+
}
4+
5+
.prefix {
6+
padding: 0;
7+
}
8+
9+
.prefix div {
10+
color: blue;
11+
}
12+
13+
.prefix {
14+
--bs-blue:#0d6efd;
15+
}
16+
17+
.prefix .a {
18+
--bs-green: #bada55;
19+
}

test/fixtures/pseudo-classes.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
:first-child {
2+
color: pink;
3+
}
4+
5+
:hover {
6+
color: blue;
7+
}
8+
9+
:active {
10+
color: red;
11+
}
12+
13+
* {
14+
background-color: red;
15+
}
16+
17+
::first-letter {
18+
color: pink;
19+
}
20+
21+
::before {
22+
content: '';
23+
width: 10px;
24+
height: 10px;
25+
background-color: blue;
26+
position: absolute;
27+
}
28+
29+
#wow {
30+
color: yellow;
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.prefix :first-child {
2+
color: pink;
3+
}
4+
5+
.prefix :hover {
6+
color: blue;
7+
}
8+
9+
.prefix :active {
10+
color: red;
11+
}
12+
13+
.prefix * {
14+
background-color: red;
15+
}
16+
17+
.prefix ::first-letter {
18+
color: pink;
19+
}
20+
21+
.prefix ::before {
22+
content: '';
23+
width: 10px;
24+
height: 10px;
25+
background-color: blue;
26+
position: absolute;
27+
}
28+
29+
.prefix #wow {
30+
color: yellow;
31+
}

test/test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,35 @@ it('should prefix postcss nested selectors', () => {
348348
assert.equal(out, expected);
349349
});
350350

351+
it('should prefix pseudo-classes', () => {
352+
const out = postcss()
353+
.use(prefixer({ prefix: '.prefix' }))
354+
.process(getFixtureContents('pseudo-classes.css')).css;
355+
356+
const expected = getFixtureContents('pseudo-classes.expected.css');
357+
assert.equal(out, expected);
358+
});
359+
360+
it('should replace :root, body and html with the prefix', () => {
361+
const out = postcss()
362+
.use(prefixer({ prefix: '.prefix' }))
363+
.process(getFixtureContents('global-selectors.css')).css;
364+
365+
const expected = getFixtureContents('global-selectors.expected.css');
366+
assert.equal(out, expected);
367+
});
368+
369+
it('should skip global selectors when option is enabled', () => {
370+
const out = postcss()
371+
.use(prefixer({
372+
prefix: '.prefix',
373+
skipGlobalSelectors: true
374+
})).process(getFixtureContents('global-selectors.css')).css;
375+
376+
const expected = getFixtureContents('global-selectors.css');
377+
assert.equal(out, expected);
378+
});
379+
351380
function getFixtureContents(name) {
352381
return fs.readFileSync(`test/fixtures/${name}`, 'utf8').trim();
353382
}

0 commit comments

Comments
 (0)