Skip to content

Commit

Permalink
Merge pull request #15 from CanopyTax/v2.1.0
Browse files Browse the repository at this point in the history
fix tests, fix logic, update readme, bump version
  • Loading branch information
geoctrl authored Jan 6, 2022
2 parents 1e37be0 + a9222d0 commit 5f61a0d
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 35 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
node_modules
dev-env
.idea
.idea
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,40 @@ export default class Foo extends Component {
}
}
```

## Opt-out of scoping

### A quick look at `kremling` scoping
`kremling` css by default is **global**, and it requires you to opt-in to scoping by
prepending an ampersand (&) symbol at the beginning of each rule:

```js
const css = `
.global {
background-color: blue;
}
& .scoped {
background-color: red;
}
`;
```

### `kremling-loader` scoping

Funny enough, `kremling-loader` is the exact opposite 🥴. Since the loader
intelligently adds scoping without ampersands, by default, the css is always
**scoped**. You have to opt-out of scoping by prepending a `:global` pseudo
class at the beginning of your rule:

```scss
// scoped
.scoped {
background-color: blue;
}

// global
:global .global {
background-color: red;
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kremling-loader",
"version": "2.0.0",
"version": "2.1.0",
"main": "index.js",
"author": "@geoctrl",
"license": "MIT",
Expand Down
54 changes: 33 additions & 21 deletions src/postcss-kremling-plugin.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
const parser = require('postcss-selector-parser');

module.exports = ({ id, namespace }) => {
function parseSelectors(ruleSelectors, left) {
let scope = true;
const output = parser(selectors => {
if (left) {
const s = selectors.first.first;
if (s.type === 'pseudo') {
if (s.value === ':global') {
s.remove();
}
scope = false;
return;
}
}
selectors.each(selector => {
const attr = parser.attribute({ attribute: `${namespace}="${id}"` });
const combinator = parser.combinator({ value: ' ' });
if (selector.at(0).type === 'class' || selector.at(0).type === 'id' || left) {
selector.insertBefore(selector.at(0), attr);
if (left) selector.insertAfter(selector.at(0), combinator);
} else {
selector.insertAfter(selector.at(0), attr);
}
return selector;
});
}).processSync(ruleSelectors, { lossless: false });
return { output, scope };
}
return {
postcssPlugin: 'postcss-kremling-plugin',
Root(root) {
root.walkRules(function (rule) {
rule.selector = parser(selectors => {
selectors.each(selector => {
// omit pseudo classes
if (selector.first.type === 'pseudo') {
if (selector.first.value === ':global') {
selector.first.remove();
}
return selector;
}
const { output: left, scope } = parseSelectors(rule.selector, true);
let right = '';
if (scope) {
right = parseSelectors(rule.selector, false).output;
}

if (selector.first.type === 'class' || selector.first.type === 'id') {
const attr = parser.attribute({ attribute: `${namespace}="${id}"` });
const newSelector = selector.clone();
newSelector.insertAfter(selector.at(0), parser.combinator({ value: ' ' }));
newSelector.insertAfter(selector.at(1), attr);
selector.insertBefore(selector.first, newSelector);
selector.insertAfter(selector.at(0), parser.combinator({ value: ', ' }));
selector.insertAfter(selector.at(1), attr);
}
return selector;
});
}).processSync(rule.selector, { lossless: false });
rule.selector = `${left.trim()}${right ? `,${right.trim()}` : ''}`;
return rule;
});
},
Expand Down
2 changes: 1 addition & 1 deletion test/css-test-files/add-selector.css
Original file line number Diff line number Diff line change
@@ -1 +1 @@
.a{}
.a{}
2 changes: 1 addition & 1 deletion test/css-test-files/config-options.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.okay {
transform: scale(2);
}
}
3 changes: 3 additions & 0 deletions test/css-test-files/de-scope.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:global .test {
background-color: red;
}
2 changes: 1 addition & 1 deletion test/css-test-files/double-single-quotes.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.okay {
content: '';
content: "";
}
}
2 changes: 1 addition & 1 deletion test/css-test-files/multiple-selectors.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.okay,
input {
background-color: red;
}
}
2 changes: 1 addition & 1 deletion test/css-test-files/namespace.css
Original file line number Diff line number Diff line change
@@ -1 +1 @@
.okay {background-color: red;}
.okay {background-color: red;}
3 changes: 3 additions & 0 deletions test/css-test-files/pseudo-classes.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:root {
background-color: red;
}
2 changes: 1 addition & 1 deletion test/css-test-files/reverse-order-selectors.css
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[title]{}
[title]{}
20 changes: 15 additions & 5 deletions test/kremling-loader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,35 @@ describe('kremling-loader', () => {
});

test('should prepend all selectors with kremling selector', async () => {
const result = '[kremling="p1"] .a,[kremling="p1"].a{}';
const result = '[kremling="p1"] .a,[kremling="p1"].a{} ';
expect((await run('add-selector.css')).styles).toBe(result);
});

test('should reverse order of the first selector if the selector is not a class or id', async () => {
const result = '[kremling="p2"] [title],[title][kremling="p2"]{}';
const result = '[kremling="p2"] [title],[title][kremling="p2"]{} ';
expect((await run('reverse-order-selectors.css')).styles).toBe(result);
})

test('should build kremling selectors from multiple css selectors', async () => {
const result = '[kremling="p3"] .okay,[kremling="p3"] input,[kremling="p3"].okay,input[kremling="p3"] { background-color: red; }';
const result = '[kremling="p3"] .okay,[kremling="p3"] input,[kremling="p3"].okay,input[kremling="p3"] { background-color: red; } ';
expect((await run('multiple-selectors.css')).styles).toBe(result);
});

test('should use namespace from webpack options', async () => {
const result = `[pizza="p4"] .okay,[pizza="p4"].okay {background-color: red;}`;
const result = `[pizza="p4"] .okay,[pizza="p4"].okay {background-color: red;} `;
expect((await run('namespace.css', { namespace: 'pizza' })).styles).toBe(result);
});

test('should not scope pseudo classes', async () => {
const result = ':root { background-color: red; } ';
expect((await run('pseudo-classes.css')).styles).toBe(result);
});

test('should de-scope with :global syntax', async () => {
const result = '.test { background-color: red; } ';
expect((await run('de-scope.css')).styles).toBe(result);
});

test('should allow double and single quotes without blowing up', async () => {
expect(!!await run('double-single-quotes.css')).toBe(true);
});
Expand All @@ -49,4 +59,4 @@ function parseEsModuleString(str) {
async function run(file, opts = {}) {
const stats = await compiler(file, opts);
return parseEsModuleString(stats.toJson({ source: true }).modules[0].source);
}
}

0 comments on commit 5f61a0d

Please sign in to comment.