Skip to content

Commit f466a25

Browse files
authored
feat: support at-rule versions of import/export (#76)
1 parent 329fbaa commit f466a25

File tree

5 files changed

+244
-61
lines changed

5 files changed

+244
-61
lines changed

README.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ A symbol is a string of alphanumeric, `-` or `_` characters. A replacement can b
2626
- In the value of a declaration, i.e. `color: my_symbol;` or `box-shadow: 0 0 blur spread shadow-color`
2727
- In a media expression i.e. `@media small {}` or `@media screen and not-large {}`
2828

29-
## extractICSS(css, removeRules = true)
29+
## extractICSS(css, removeRules = true, mode = 'auto')
3030

31-
Extracts and remove (if removeRules is equal true) from PostCSS tree `:import` and `:export` statements.
31+
Extracts and remove (if removeRules is equal true) from PostCSS tree `:import`, `@icss-import`, `:export` and `@icss-export` statements.
3232

3333
```js
3434
import postcss from "postcss";
@@ -58,7 +58,10 @@ extractICSS(css);
5858
*/
5959
```
6060

61-
## createICSSRules(icssImports, icssExports)
61+
By default both the pseudo and at-rule form of the import and export statements
62+
will be removed. Pass the `mode` option to limit to only one type.
63+
64+
## createICSSRules(icssImports, icssExports, mode = 'rule')
6265

6366
Converts icss imports and exports definitions to postcss ast
6467

@@ -78,6 +81,10 @@ createICSSRules(
7881
);
7982
```
8083

84+
By default it will create pseudo selector rules (`:import` and `:export`). Pass
85+
`atrule` for `mode` to instead generate `@icss-import` and `@icss-export`, which
86+
may be more resilient to post processing by other tools.
87+
8188
## License
8289

8390
ISC

src/createICSSRules.js

+27-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const createImports = (imports, postcss) => {
1+
const createImports = (imports, postcss, mode = "rule") => {
22
return Object.keys(imports).map((path) => {
33
const aliases = imports[path];
44
const declarations = Object.keys(aliases).map((key) =>
@@ -11,10 +11,17 @@ const createImports = (imports, postcss) => {
1111

1212
const hasDeclarations = declarations.length > 0;
1313

14-
const rule = postcss.rule({
15-
selector: `:import('${path}')`,
16-
raws: { after: hasDeclarations ? "\n" : "" },
17-
});
14+
const rule =
15+
mode === "rule"
16+
? postcss.rule({
17+
selector: `:import('${path}')`,
18+
raws: { after: hasDeclarations ? "\n" : "" },
19+
})
20+
: postcss.atRule({
21+
name: "icss-import",
22+
params: `'${path}'`,
23+
raws: { after: hasDeclarations ? "\n" : "" },
24+
});
1825

1926
if (hasDeclarations) {
2027
rule.append(declarations);
@@ -24,7 +31,7 @@ const createImports = (imports, postcss) => {
2431
});
2532
};
2633

27-
const createExports = (exports, postcss) => {
34+
const createExports = (exports, postcss, mode = "rule") => {
2835
const declarations = Object.keys(exports).map((key) =>
2936
postcss.decl({
3037
prop: key,
@@ -36,20 +43,25 @@ const createExports = (exports, postcss) => {
3643
if (declarations.length === 0) {
3744
return [];
3845
}
46+
const rule =
47+
mode === "rule"
48+
? postcss.rule({
49+
selector: `:export`,
50+
raws: { after: "\n" },
51+
})
52+
: postcss.atRule({
53+
name: "icss-export",
54+
raws: { after: "\n" },
55+
});
3956

40-
const rule = postcss
41-
.rule({
42-
selector: `:export`,
43-
raws: { after: "\n" },
44-
})
45-
.append(declarations);
57+
rule.append(declarations);
4658

4759
return [rule];
4860
};
4961

50-
const createICSSRules = (imports, exports, postcss) => [
51-
...createImports(imports, postcss),
52-
...createExports(exports, postcss),
62+
const createICSSRules = (imports, exports, postcss, mode) => [
63+
...createImports(imports, postcss, mode),
64+
...createExports(exports, postcss, mode),
5365
];
5466

5567
module.exports = createICSSRules;

src/extractICSS.js

+41-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const importPattern = /^:import\(("[^"]*"|'[^']*'|[^"']+)\)$/;
2+
const balancedQuotes = /^("[^"]*"|'[^']*'|[^"']+)$/;
23

34
const getDeclsObject = (rule) => {
45
const object = {};
@@ -11,37 +12,61 @@ const getDeclsObject = (rule) => {
1112

1213
return object;
1314
};
14-
15-
const extractICSS = (css, removeRules = true) => {
15+
/**
16+
*
17+
* @param {string} css
18+
* @param {boolean} removeRules
19+
* @param {auto|rule|atrule} mode
20+
*/
21+
const extractICSS = (css, removeRules = true, mode = "auto") => {
1622
const icssImports = {};
1723
const icssExports = {};
1824

25+
function addImports(node, path) {
26+
const unquoted = path.replace(/'|"/g, "");
27+
icssImports[unquoted] = Object.assign(
28+
icssImports[unquoted] || {},
29+
getDeclsObject(node)
30+
);
31+
32+
if (removeRules) {
33+
node.remove();
34+
}
35+
}
36+
37+
function addExports(node) {
38+
Object.assign(icssExports, getDeclsObject(node));
39+
if (removeRules) {
40+
node.remove();
41+
}
42+
}
43+
1944
css.each((node) => {
20-
if (node.type === "rule") {
45+
if (node.type === "rule" && mode !== "atrule") {
2146
if (node.selector.slice(0, 7) === ":import") {
2247
const matches = importPattern.exec(node.selector);
2348

2449
if (matches) {
25-
const path = matches[1].replace(/'|"/g, "");
26-
27-
icssImports[path] = Object.assign(
28-
icssImports[path] || {},
29-
getDeclsObject(node)
30-
);
31-
32-
if (removeRules) {
33-
node.remove();
34-
}
50+
addImports(node, matches[1]);
3551
}
3652
}
3753

3854
if (node.selector === ":export") {
39-
Object.assign(icssExports, getDeclsObject(node));
55+
addExports(node);
56+
}
57+
}
4058

41-
if (removeRules) {
42-
node.remove();
59+
if (node.type === "atrule" && mode !== "rule") {
60+
if (node.name === "icss-import") {
61+
const matches = balancedQuotes.exec(node.params);
62+
63+
if (matches) {
64+
addImports(node, matches[1]);
4365
}
4466
}
67+
if (node.name === "icss-export") {
68+
addExports(node);
69+
}
4570
}
4671
});
4772

test/createICSSRules.test.js

+57-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const postcss = require("postcss");
22
const { createICSSRules } = require("../src");
33

4-
const run = (imports, exports) => {
4+
const run = (imports, exports, mode) => {
55
return postcss
66
.root()
7-
.append(createICSSRules(imports, exports, postcss))
7+
.append(createICSSRules(imports, exports, postcss, mode))
88
.toString();
99
};
1010

@@ -58,3 +58,58 @@ test("create :import and :export", () => {
5858
)
5959
).toEqual(":import('colors') {\n a: b\n}\n:export {\n c: d\n}");
6060
});
61+
62+
test("create empty @icss-import statement", () => {
63+
expect(
64+
run(
65+
{
66+
"path/file": {},
67+
},
68+
{},
69+
"atrule"
70+
)
71+
).toEqual("@icss-import 'path/file'");
72+
});
73+
74+
test("create @icss-import statement", () => {
75+
expect(
76+
run(
77+
{
78+
"path/file": {
79+
e: "f",
80+
},
81+
},
82+
{},
83+
"atrule"
84+
)
85+
).toEqual("@icss-import 'path/file' {\n e: f\n}");
86+
});
87+
88+
test("create @icss-export statement", () => {
89+
expect(
90+
run(
91+
{},
92+
{
93+
a: "b",
94+
c: "d",
95+
},
96+
"atrule"
97+
)
98+
).toEqual("@icss-export {\n a: b;\n c: d\n}");
99+
});
100+
101+
test("create @icss-import and @icss-export", () => {
102+
expect(
103+
run(
104+
{
105+
colors: {
106+
a: "b",
107+
},
108+
},
109+
{
110+
c: "d",
111+
},
112+
"atrule"
113+
)
114+
).toEqual("@icss-import 'colors' {\n a: b\n}\n@icss-export {\n c: d\n}");
115+
});

0 commit comments

Comments
 (0)