Skip to content

Commit 03eee75

Browse files
committed
Update to use new :local(.identifier) syntax
1 parent ee32ac5 commit 03eee75

File tree

3 files changed

+49
-28
lines changed

3 files changed

+49
-28
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
[PostCSS] plugin to transform global selectors into the [local scope] format of [Webpack]'s [css-loader].
66

7-
**WARNING: This project depends on [css-loader]'s [local scope] feature, which is marked as experimental and is very likely to change in the future.**
7+
**WARNING: This project depends on [css-loader]'s [local scope] feature, which is marked as experimental and is very likely to change in the future. Always ensure you're using the latest version of both [css-loader] and postcss-local-scope together.**
88

99
## Why?
1010

1111
Everyone agrees that dumping JavaScript in the global scope is a terrible idea. Why is CSS any different?
1212

1313
Imagine if we could import the CSS that a component needs without leaking selectors into the global scope. We wouldn't need naming conventions like [BEM] to avoid naming collisions, and we could prevent accidental coupling between components by ensuring our CSS follows the same scoping rules as any JavaScript module.
1414

15-
Webpack allows [local scope] in CSS with [css-loader], but it's opt-in via a special `.local[identifier]` syntax.
15+
Webpack allows [local scope] in CSS with [css-loader], but it's opt-in via a special `:local(.identifier)` syntax.
1616

1717
This plugin transforms standard class selectors into local identifiers so that [local scope] is the default and global styles are the exception, just like any sane module system.
1818

@@ -75,25 +75,25 @@ Classes are dynamically generated at build time by [css-loader], so components a
7575
## Transformation examples
7676

7777
```css
78-
.foo { ... } /* => */ .local[foo] { ... }
78+
.foo { ... } /* => */ :local(.foo) { ... }
7979

80-
.foo .bar { ... } /* => */ .local[foo] .local[bar] { ... }
80+
.foo .bar { ... } /* => */ :local(.foo) :local(.bar) { ... }
8181

8282
/* Shorthand global selector */
8383

8484
:global .foo .bar { ... } /* => */ .foo .bar { ... }
8585

86-
.foo :global .bar { ... } /* => */ .local[foo] .bar { ... }
86+
.foo :global .bar { ... } /* => */ :local(.foo) .bar { ... }
8787

8888
/* Targeted global selector */
8989

90-
:global(.foo) .bar { ... } /* => */ .foo .local[bar] { ... }
90+
:global(.foo) .bar { ... } /* => */ .foo :local(.bar) { ... }
9191

92-
.foo:global(.bar) { ... } /* => */ .local[foo].bar { ... }
92+
.foo:global(.bar) { ... } /* => */ :local(.foo).bar { ... }
9393

94-
.foo :global(.bar) .baz { ... } /* => */ .local[foo] .bar .local[baz] { ... }
94+
.foo :global(.bar) .baz { ... } /* => */ :local(.foo) .bar :local(.baz) { ... }
9595

96-
.foo:global(.bar) .baz { ... } /* => */ .local[foo].bar .local[baz] { ... }
96+
.foo:global(.bar) .baz { ... } /* => */ :local(.foo).bar :local(.baz) { ... }
9797
```
9898

9999
[PostCSS]: https://github.com/postcss/postcss

index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = postcss.plugin('postcss-local-scope', function () {
1616
function transformSelector(rule, selector) {
1717
// Syntax warnings
1818
var trimmedSelector = selector.trim();
19-
if (!/^\./.test(trimmedSelector)) {
19+
if (!/^\./.test(trimmedSelector) && !/^\:local/.test(trimmedSelector)) {
2020
if (!/^\:global/.test(trimmedSelector)) {
2121
throw rule.error('Global selector detected in local context. Does this selector really need to be global? If so, you need to explicitly export it into the global scope with ":global", e.g. ":global '+trimmedSelector+'"', { plugin: 'postcss-local-scope' });
2222
}
@@ -26,7 +26,8 @@ function transformSelector(rule, selector) {
2626
.replace(/\:global\((.*?)\)/g, escapeDots)
2727
.replace(/\:global (.*)/g, escapeDots)
2828
.replace(/\.local\[(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)\]/g, '.$1') // source: http://stackoverflow.com/questions/448981/what-characters-are-valid-in-css-class-names-selectors
29-
.replace(/\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)/g, '.local[$1]')
29+
.replace(/\:local\(\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)\)/g, '.$1')
30+
.replace(/\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)/g, ':local(.$1)')
3031
.replace(new RegExp(ESCAPED_DOT, 'g'), '.');
3132
}
3233

test.js

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,27 @@ var tests = [
77
{
88
should: 'scope selectors',
99
input: '.foobar {}',
10-
expected: '.local[foobar] {}'
10+
expected: ':local(.foobar) {}'
1111
},
1212
{
1313
should: 'scope multiple selectors',
1414
input: '.foo, .baz {}',
15-
expected: '.local[foo], .local[baz] {}'
15+
expected: ':local(.foo), :local(.baz) {}'
1616
},
1717
{
1818
should: 'scope sibling selectors',
1919
input: '.foo ~ .baz {}',
20-
expected: '.local[foo] ~ .local[baz] {}'
20+
expected: ':local(.foo) ~ :local(.baz) {}'
2121
},
2222
{
2323
should: 'scope psuedo elements',
2424
input: '.foo:after {}',
25-
expected: '.local[foo]:after {}'
25+
expected: ':local(.foo):after {}'
2626
},
2727
{
2828
should: 'scope media queries',
2929
input: '@media only screen { .foo {} }',
30-
expected: '@media only screen { .local[foo] {} }'
30+
expected: '@media only screen { :local(.foo) {} }'
3131
},
3232
{
3333
should: 'allow narrow global selectors',
@@ -52,42 +52,62 @@ var tests = [
5252
{
5353
should: 'allow narrow global selectors nested inside local styles',
5454
input: '.foo :global(.foo .bar) {}',
55-
expected: '.local[foo] .foo .bar {}'
55+
expected: ':local(.foo) .foo .bar {}'
5656
},
5757
{
5858
should: 'allow broad global selectors nested inside local styles',
5959
input: '.foo :global .foo .bar {}',
60-
expected: '.local[foo] .foo .bar {}'
60+
expected: ':local(.foo) .foo .bar {}'
6161
},
6262
{
6363
should: 'allow narrow global selectors appended to local styles',
6464
input: '.foo:global(.foo.bar) {}',
65-
expected: '.local[foo].foo.bar {}'
65+
expected: ':local(.foo).foo.bar {}'
6666
},
6767
{
6868
should: 'ignore selectors that are already local',
69-
input: '.local[foobar] {}',
70-
expected: '.local[foobar] {}'
69+
input: ':local(.foobar) {}',
70+
expected: ':local(.foobar) {}'
7171
},
7272
{
7373
should: 'ignore nested selectors that are already local',
74-
input: '.local[foo] .local[bar] {}',
75-
expected: '.local[foo] .local[bar] {}'
74+
input: ':local(.foo) :local(.bar) {}',
75+
expected: ':local(.foo) :local(.bar) {}'
7676
},
7777
{
7878
should: 'ignore multiple selectors that are already local',
79-
input: '.local[foo], .local[bar] {}',
80-
expected: '.local[foo], .local[bar] {}'
79+
input: ':local(.foo), :local(.bar) {}',
80+
expected: ':local(.foo), :local(.bar) {}'
8181
},
8282
{
8383
should: 'ignore sibling selectors that are already local',
84-
input: '.local[foo] ~ .local[bar] {}',
85-
expected: '.local[foo] ~ .local[bar] {}'
84+
input: ':local(.foo) ~ :local(.bar) {}',
85+
expected: ':local(.foo) ~ :local(.bar) {}'
8686
},
8787
{
8888
should: 'ignore psuedo elements that are already local',
89+
input: ':local(.foo):after {}',
90+
expected: ':local(.foo):after {}'
91+
},
92+
{
93+
should: 'convert nested selectors that are already local using the old syntax into the new local syntax',
94+
input: '.local[foo] .local[bar] {}',
95+
expected: ':local(.foo) :local(.bar) {}'
96+
},
97+
{
98+
should: 'convert multiple selectors that are already local using the old syntax into the new local syntax',
99+
input: '.local[foo], .local[bar] {}',
100+
expected: ':local(.foo), :local(.bar) {}'
101+
},
102+
{
103+
should: 'convert sibling selectors that are already local using the old syntax into the new local syntax',
104+
input: '.local[foo] ~ .local[bar] {}',
105+
expected: ':local(.foo) ~ :local(.bar) {}'
106+
},
107+
{
108+
should: 'convert psuedo elements that are already local using the old syntax into the new local syntax',
89109
input: '.local[foo]:after {}',
90-
expected: '.local[foo]:after {}'
110+
expected: ':local(.foo):after {}'
91111
}
92112
];
93113

0 commit comments

Comments
 (0)