Skip to content

Commit

Permalink
feat: support at-rule versions of import/export (#76)
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense authored Nov 19, 2020
1 parent 329fbaa commit f466a25
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 61 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ A symbol is a string of alphanumeric, `-` or `_` characters. A replacement can b
- In the value of a declaration, i.e. `color: my_symbol;` or `box-shadow: 0 0 blur spread shadow-color`
- In a media expression i.e. `@media small {}` or `@media screen and not-large {}`

## extractICSS(css, removeRules = true)
## extractICSS(css, removeRules = true, mode = 'auto')

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

```js
import postcss from "postcss";
Expand Down Expand Up @@ -58,7 +58,10 @@ extractICSS(css);
*/
```

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

## createICSSRules(icssImports, icssExports, mode = 'rule')

Converts icss imports and exports definitions to postcss ast

Expand All @@ -78,6 +81,10 @@ createICSSRules(
);
```

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

## License

ISC
Expand Down
42 changes: 27 additions & 15 deletions src/createICSSRules.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const createImports = (imports, postcss) => {
const createImports = (imports, postcss, mode = "rule") => {
return Object.keys(imports).map((path) => {
const aliases = imports[path];
const declarations = Object.keys(aliases).map((key) =>
Expand All @@ -11,10 +11,17 @@ const createImports = (imports, postcss) => {

const hasDeclarations = declarations.length > 0;

const rule = postcss.rule({
selector: `:import('${path}')`,
raws: { after: hasDeclarations ? "\n" : "" },
});
const rule =
mode === "rule"
? postcss.rule({
selector: `:import('${path}')`,
raws: { after: hasDeclarations ? "\n" : "" },
})
: postcss.atRule({
name: "icss-import",
params: `'${path}'`,
raws: { after: hasDeclarations ? "\n" : "" },
});

if (hasDeclarations) {
rule.append(declarations);
Expand All @@ -24,7 +31,7 @@ const createImports = (imports, postcss) => {
});
};

const createExports = (exports, postcss) => {
const createExports = (exports, postcss, mode = "rule") => {
const declarations = Object.keys(exports).map((key) =>
postcss.decl({
prop: key,
Expand All @@ -36,20 +43,25 @@ const createExports = (exports, postcss) => {
if (declarations.length === 0) {
return [];
}
const rule =
mode === "rule"
? postcss.rule({
selector: `:export`,
raws: { after: "\n" },
})
: postcss.atRule({
name: "icss-export",
raws: { after: "\n" },
});

const rule = postcss
.rule({
selector: `:export`,
raws: { after: "\n" },
})
.append(declarations);
rule.append(declarations);

return [rule];
};

const createICSSRules = (imports, exports, postcss) => [
...createImports(imports, postcss),
...createExports(exports, postcss),
const createICSSRules = (imports, exports, postcss, mode) => [
...createImports(imports, postcss, mode),
...createExports(exports, postcss, mode),
];

module.exports = createICSSRules;
57 changes: 41 additions & 16 deletions src/extractICSS.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const importPattern = /^:import\(("[^"]*"|'[^']*'|[^"']+)\)$/;
const balancedQuotes = /^("[^"]*"|'[^']*'|[^"']+)$/;

const getDeclsObject = (rule) => {
const object = {};
Expand All @@ -11,37 +12,61 @@ const getDeclsObject = (rule) => {

return object;
};

const extractICSS = (css, removeRules = true) => {
/**
*
* @param {string} css
* @param {boolean} removeRules
* @param {auto|rule|atrule} mode
*/
const extractICSS = (css, removeRules = true, mode = "auto") => {
const icssImports = {};
const icssExports = {};

function addImports(node, path) {
const unquoted = path.replace(/'|"/g, "");
icssImports[unquoted] = Object.assign(
icssImports[unquoted] || {},
getDeclsObject(node)
);

if (removeRules) {
node.remove();
}
}

function addExports(node) {
Object.assign(icssExports, getDeclsObject(node));
if (removeRules) {
node.remove();
}
}

css.each((node) => {
if (node.type === "rule") {
if (node.type === "rule" && mode !== "atrule") {
if (node.selector.slice(0, 7) === ":import") {
const matches = importPattern.exec(node.selector);

if (matches) {
const path = matches[1].replace(/'|"/g, "");

icssImports[path] = Object.assign(
icssImports[path] || {},
getDeclsObject(node)
);

if (removeRules) {
node.remove();
}
addImports(node, matches[1]);
}
}

if (node.selector === ":export") {
Object.assign(icssExports, getDeclsObject(node));
addExports(node);
}
}

if (removeRules) {
node.remove();
if (node.type === "atrule" && mode !== "rule") {
if (node.name === "icss-import") {
const matches = balancedQuotes.exec(node.params);

if (matches) {
addImports(node, matches[1]);
}
}
if (node.name === "icss-export") {
addExports(node);
}
}
});

Expand Down
59 changes: 57 additions & 2 deletions test/createICSSRules.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const postcss = require("postcss");
const { createICSSRules } = require("../src");

const run = (imports, exports) => {
const run = (imports, exports, mode) => {
return postcss
.root()
.append(createICSSRules(imports, exports, postcss))
.append(createICSSRules(imports, exports, postcss, mode))
.toString();
};

Expand Down Expand Up @@ -58,3 +58,58 @@ test("create :import and :export", () => {
)
).toEqual(":import('colors') {\n a: b\n}\n:export {\n c: d\n}");
});

test("create empty @icss-import statement", () => {
expect(
run(
{
"path/file": {},
},
{},
"atrule"
)
).toEqual("@icss-import 'path/file'");
});

test("create @icss-import statement", () => {
expect(
run(
{
"path/file": {
e: "f",
},
},
{},
"atrule"
)
).toEqual("@icss-import 'path/file' {\n e: f\n}");
});

test("create @icss-export statement", () => {
expect(
run(
{},
{
a: "b",
c: "d",
},
"atrule"
)
).toEqual("@icss-export {\n a: b;\n c: d\n}");
});

test("create @icss-import and @icss-export", () => {
expect(
run(
{
colors: {
a: "b",
},
},
{
c: "d",
},
"atrule"
)
).toEqual("@icss-import 'colors' {\n a: b\n}\n@icss-export {\n c: d\n}");
});
Loading

0 comments on commit f466a25

Please sign in to comment.