forgecss 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ast.json +1148 -0
- package/cli.js +2 -2
- package/client/fx.js +66 -8
- package/dist/forgecss.min.js +1 -1
- package/index.d.ts +3 -3
- package/index.js +12 -8
- package/lib/generator.js +4 -1
- package/lib/helpers.js +19 -0
- package/lib/transformers/arbitrary.js +32 -0
- package/lib/transformers/mediaQuery.js +9 -22
- package/lib/transformers/pseudo.js +19 -30
- package/lib/{processor.js → usages.js} +23 -18
- package/package.json +1 -1
- /package/lib/{processor.d.ts → usages.d.ts} +0 -0
package/cli.js
CHANGED
|
@@ -55,9 +55,9 @@ async function runForgeCSS(lookAtPath = null) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
if (lookAtPath) {
|
|
58
|
-
instance.parseFile(lookAtPath, config.output);
|
|
58
|
+
instance.parseFile({ file: lookAtPath, output: config.output });
|
|
59
59
|
} else {
|
|
60
|
-
instance.parseDirectory(config.dir, config.output);
|
|
60
|
+
instance.parseDirectory({ dir: config.dir, output: config.output });
|
|
61
61
|
}
|
|
62
62
|
if (options.verbose) {
|
|
63
63
|
console.log(`forgecss: ${config.output} generated successfully.`);
|
package/client/fx.js
CHANGED
|
@@ -1,16 +1,74 @@
|
|
|
1
1
|
export default function fx(classes) {
|
|
2
|
-
return classes
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
return parseClass(classes).map((className) => {
|
|
3
|
+
let [label, rest] = splitClassName(className);
|
|
4
|
+
if (!label || label === "[true]") return rest;
|
|
5
|
+
if (label === "[false]") return false;
|
|
6
|
+
label = normalizeLabel(label);
|
|
7
7
|
return rest
|
|
8
8
|
.split(",")
|
|
9
|
-
.map((cls) => `${label}
|
|
10
|
-
.filter(Boolean)
|
|
9
|
+
.map((cls) => `${label}--${cls}`)
|
|
11
10
|
.join(" ");
|
|
12
11
|
})
|
|
13
12
|
.filter(Boolean)
|
|
14
13
|
.join(" ");
|
|
15
|
-
}
|
|
14
|
+
}
|
|
15
|
+
export function splitClassName(label) {
|
|
16
|
+
const lastColonIndex = label.lastIndexOf(":");
|
|
17
|
+
if (lastColonIndex === -1) {
|
|
18
|
+
return [null, label];
|
|
19
|
+
}
|
|
20
|
+
const prefix = label.slice(0, lastColonIndex);
|
|
21
|
+
const rest = label.slice(lastColonIndex + 1);
|
|
22
|
+
return [prefix, rest];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function normalizeLabel(label) {
|
|
26
|
+
let normalized = label.trim();
|
|
27
|
+
normalized = normalized.replace(/[&]/g, "I");
|
|
28
|
+
normalized = normalized.replace(/[:| =]/g, "-");
|
|
29
|
+
normalized = normalized.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function parseClass(str) {
|
|
34
|
+
const out = [];
|
|
35
|
+
let buf = "";
|
|
36
|
+
|
|
37
|
+
let depth = 0;
|
|
38
|
+
let quote = null;
|
|
39
|
+
for (let i = 0; i < str.length; i++) {
|
|
40
|
+
const ch = str[i];
|
|
41
|
+
if (depth > 0) {
|
|
42
|
+
if (quote) {
|
|
43
|
+
buf += ch;
|
|
44
|
+
if (ch === quote && str[i - 1] !== "\\") quote = null;
|
|
45
|
+
continue;
|
|
46
|
+
} else if (ch === "'" || ch === '"') {
|
|
47
|
+
quote = ch;
|
|
48
|
+
buf += ch;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (ch === "[") {
|
|
53
|
+
depth++;
|
|
54
|
+
buf += ch;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === "]" && depth > 0) {
|
|
58
|
+
depth--;
|
|
59
|
+
buf += ch;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (depth === 0 && /\s/.test(ch)) {
|
|
63
|
+
if (buf) out.push(buf);
|
|
64
|
+
buf = "";
|
|
65
|
+
while (i + 1 < str.length && /\s/.test(str[i + 1])) i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
buf += ch;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (buf) out.push(buf);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
16
74
|
|
package/dist/forgecss.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{function
|
|
1
|
+
(()=>{function s(n){return a(n).map(t=>{let[e,i]=f(t);return!e||e==="[true]"?i:e==="[false]"?!1:(e=c(e),i.split(",").map(l=>`${e}--${l}`).join(" "))}).filter(Boolean).join(" ")}function f(n){let t=n.lastIndexOf(":");if(t===-1)return[null,n];let e=n.slice(0,t),i=n.slice(t+1);return[e,i]}function c(n){let t=n.trim();return t=t.replace(/[&]/g,"I"),t=t.replace(/[:| =]/g,"-"),t=t.replace(/[^a-zA-Z0-9_-]/g,""),t}function a(n){let t=[],e="",i=0,l=null;for(let r=0;r<n.length;r++){let o=n[r];if(i>0){if(l){e+=o,o===l&&n[r-1]!=="\\"&&(l=null);continue}else if(o==="'"||o==='"'){l=o,e+=o;continue}}if(o==="["){i++,e+=o;continue}if(o==="]"&&i>0){i--,e+=o;continue}if(i===0&&/\s/.test(o)){for(e&&t.push(e),e="";r+1<n.length&&/\s/.test(n[r+1]);)r++;continue}e+=o}return e&&t.push(e),t}function u(n){for(var t=n||document,e=t.querySelectorAll("[class]"),i=0;i<e.length;i++){var l=e[i],r=l.getAttribute("class");if(r){var o=s(r);typeof o=="string"&&o!==r&&l.setAttribute("class",o)}}}window.fx=s;window.forgecss=u;document.readyState!=="loading"?u():document.addEventListener("DOMContentLoaded",function(){u()});window.addEventListener("load",function(){u()});})();
|
package/index.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export type ForgeCSSOptions = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export type ForgeInstance = {
|
|
13
|
-
parseDirectory: (
|
|
14
|
-
parseFile: (
|
|
15
|
-
parse: (css: string
|
|
13
|
+
parseDirectory: (options: { dir: string; output?: string }) => Promise<string>;
|
|
14
|
+
parseFile: (options: { file: string; output?: string }) => Promise<string>;
|
|
15
|
+
parse: (options: { css: string; html?: string; jsx?: string; output?: string }) => Promise<string>;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
declare function ForgeCSS(options?: ForgeCSSOptions): ForgeInstance;
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { writeFile } from "fs/promises";
|
|
2
2
|
import getAllFiles from "./lib/getAllFiles.js";
|
|
3
3
|
import { extractStyles, invalidateInvetory } from "./lib/inventory.js";
|
|
4
|
-
import { invalidateUsageCache, findUsages } from "./lib/
|
|
4
|
+
import { invalidateUsageCache, findUsages } from "./lib/usages.js";
|
|
5
5
|
import { generateOutputCSS } from "./lib/generator.js";
|
|
6
6
|
|
|
7
7
|
const DEFAULT_OPTIONS = {
|
|
@@ -34,7 +34,7 @@ export default function ForgeCSS(options) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
return {
|
|
37
|
-
async parseDirectory(dir, output = null) {
|
|
37
|
+
async parseDirectory({ dir, output = null }) {
|
|
38
38
|
if (!dir) {
|
|
39
39
|
throw new Error('forgecss: parseDirectory requires "dir" as an argument.');
|
|
40
40
|
}
|
|
@@ -59,7 +59,7 @@ export default function ForgeCSS(options) {
|
|
|
59
59
|
// generating the output CSS
|
|
60
60
|
return result(output);
|
|
61
61
|
},
|
|
62
|
-
async parseFile(file, output = null) {
|
|
62
|
+
async parseFile({ file, output = null }) {
|
|
63
63
|
if (!file) {
|
|
64
64
|
throw new Error('forgecss: parseFile requires "file" as an argument.');
|
|
65
65
|
}
|
|
@@ -84,12 +84,12 @@ export default function ForgeCSS(options) {
|
|
|
84
84
|
// generating the output CSS
|
|
85
85
|
return result(output);
|
|
86
86
|
},
|
|
87
|
-
async parse(css, html, output = null) {
|
|
87
|
+
async parse({ css, html, jsx, output = null }) {
|
|
88
88
|
if (!css) {
|
|
89
|
-
throw new Error('forgecss: parse requires "css"
|
|
89
|
+
throw new Error('forgecss: parse requires "css".');
|
|
90
90
|
}
|
|
91
|
-
if (!html) {
|
|
92
|
-
throw new Error('forgecss: parse requires "html"
|
|
91
|
+
if (!html && !jsx) {
|
|
92
|
+
throw new Error('forgecss: parse requires "html" or "jsx".');
|
|
93
93
|
}
|
|
94
94
|
invalidateInvetory();
|
|
95
95
|
invalidateUsageCache();
|
|
@@ -101,7 +101,11 @@ export default function ForgeCSS(options) {
|
|
|
101
101
|
}
|
|
102
102
|
// finding the usages
|
|
103
103
|
try {
|
|
104
|
-
|
|
104
|
+
if (html) {
|
|
105
|
+
await findUsages("usage.html", html);
|
|
106
|
+
} else if (jsx) {
|
|
107
|
+
await findUsages("usage.jsx", jsx);
|
|
108
|
+
}
|
|
105
109
|
} catch (err) {
|
|
106
110
|
console.error(`forgecss: error extracting usages.`, err);
|
|
107
111
|
}
|
package/lib/generator.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getUsages } from "./
|
|
1
|
+
import { getUsages } from "./usages.js";
|
|
2
|
+
import arbitraryTransformer from "./transformers/arbitrary.js";
|
|
2
3
|
import mediaQueryTransformer from "./transformers/mediaQuery.js";
|
|
3
4
|
import pseudoClassTransformer from "./transformers/pseudo.js";
|
|
4
5
|
|
|
@@ -12,6 +13,8 @@ export async function generateOutputCSS(config) {
|
|
|
12
13
|
return;
|
|
13
14
|
} else if (pseudoClassTransformer(label, usages[file][label], bucket)) {
|
|
14
15
|
return;
|
|
16
|
+
} else if (arbitraryTransformer(label, usages[file][label], bucket)) {
|
|
17
|
+
return;
|
|
15
18
|
}
|
|
16
19
|
} catch (err) {
|
|
17
20
|
console.error(
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import postcss from "postcss";
|
|
2
|
+
import { getStylesByClassName } from "./inventory.js";
|
|
3
|
+
|
|
4
|
+
export function setDeclarations(selector, rule) {
|
|
5
|
+
const decls = getStylesByClassName(selector);
|
|
6
|
+
if (decls.length === 0) {
|
|
7
|
+
console.warn(`forgecss: no class ".${selector}" found`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
decls.forEach((d) => {
|
|
11
|
+
rule.append(
|
|
12
|
+
postcss.decl({
|
|
13
|
+
prop: d.prop,
|
|
14
|
+
value: d.value,
|
|
15
|
+
important: d.important
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import postcss from "postcss";
|
|
2
|
+
|
|
3
|
+
import { normalizeLabel } from "../../client/fx.js";
|
|
4
|
+
import { setDeclarations } from "../helpers.js";
|
|
5
|
+
|
|
6
|
+
export default function arbitraryTransformer(label, classes, bucket) {
|
|
7
|
+
if (label.startsWith("[") && label.endsWith("]")) {
|
|
8
|
+
let arbitrarySelector = label.slice(1, -1).trim();
|
|
9
|
+
if (['', 'true'].includes(arbitrarySelector)) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
classes.forEach((cls) => {
|
|
13
|
+
const I = normalizeLabel(label) + "--" + cls;
|
|
14
|
+
const selector = evaluateArbitrary(arbitrarySelector, I);
|
|
15
|
+
const root = postcss.root();
|
|
16
|
+
if (bucket[I]) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const rule = postcss.rule({ selector });
|
|
20
|
+
setDeclarations(cls, rule);
|
|
21
|
+
root.append(rule);
|
|
22
|
+
bucket[I] = root;
|
|
23
|
+
});
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function evaluateArbitrary(label, I) {
|
|
30
|
+
label = label.replace(/[&]/g, `.${I}`);
|
|
31
|
+
return label;
|
|
32
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import postcss from "postcss";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { setDeclarations } from "../helpers.js";
|
|
4
|
+
import {normalizeLabel} from "../../client/fx.js";
|
|
4
5
|
|
|
5
|
-
export default function mediaQueryTransformer(config, label,
|
|
6
|
+
export default function mediaQueryTransformer(config, label, classes, bucket) {
|
|
6
7
|
if (!config?.mapping?.queries[label]) {
|
|
7
8
|
return false;
|
|
8
9
|
}
|
|
@@ -16,28 +17,14 @@ export default function mediaQueryTransformer(config, label, selectors, bucket)
|
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
const rules = bucket[label].rules;
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
if (bucket[label].classes[
|
|
20
|
+
classes.forEach((cls) => {
|
|
21
|
+
const selector = `.${normalizeLabel(label)}--${cls}`;
|
|
22
|
+
if (bucket[label].classes[selector]) {
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
|
-
bucket[label].classes[
|
|
25
|
-
const rule = postcss.rule({ selector
|
|
26
|
-
|
|
27
|
-
if (decls.length === 0) {
|
|
28
|
-
console.warn(`forgecss: no styles found for class ".${selector}" used in media query "${label}"`);
|
|
29
|
-
delete bucket[label];
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
decls.forEach((d) => {
|
|
33
|
-
rule.append(
|
|
34
|
-
postcss.decl({
|
|
35
|
-
prop: d.prop,
|
|
36
|
-
value: d.value,
|
|
37
|
-
important: d.important
|
|
38
|
-
})
|
|
39
|
-
);
|
|
40
|
-
});
|
|
25
|
+
bucket[label].classes[selector] = true; // caching
|
|
26
|
+
const rule = postcss.rule({ selector });
|
|
27
|
+
setDeclarations(cls, rule);
|
|
41
28
|
rules.append(rule);
|
|
42
29
|
});
|
|
43
30
|
return true;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import postcss from "postcss";
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import {setDeclarations} from "../helpers.js";
|
|
4
|
+
import {normalizeLabel} from "../../client/fx.js";
|
|
3
5
|
|
|
4
6
|
const ALLOWED_PSEUDO_CLASSES = [
|
|
5
7
|
"hover",
|
|
@@ -24,34 +26,21 @@ const ALLOWED_PSEUDO_CLASSES = [
|
|
|
24
26
|
"user-invalid"
|
|
25
27
|
];
|
|
26
28
|
|
|
27
|
-
export default function pseudoClassTransformer(label,
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (decls.length === 0) {
|
|
41
|
-
console.warn(`forgecss: no styles found for class ".${selector}" used in pseudo class "${label}"`);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
decls.forEach((d) => {
|
|
45
|
-
rule.append(
|
|
46
|
-
postcss.decl({
|
|
47
|
-
prop: d.prop,
|
|
48
|
-
value: d.value,
|
|
49
|
-
important: d.important
|
|
50
|
-
})
|
|
51
|
-
);
|
|
29
|
+
export default function pseudoClassTransformer(label, classes, bucket) {
|
|
30
|
+
if (ALLOWED_PSEUDO_CLASSES.includes(label)) {
|
|
31
|
+
classes.forEach((cls) => {
|
|
32
|
+
const selector = `.${normalizeLabel(label)}--${cls}:${label}`;
|
|
33
|
+
const root = postcss.root();
|
|
34
|
+
if (bucket[selector]) {
|
|
35
|
+
// already have that
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const rule = postcss.rule({ selector });
|
|
39
|
+
setDeclarations(cls, rule);
|
|
40
|
+
root.append(rule);
|
|
41
|
+
bucket[selector] = root;
|
|
52
42
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return true;
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
57
46
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import swc from "@swc/core";
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
2
|
+
import { readFile, writeFile } from "fs/promises";
|
|
3
3
|
import { fromHtml } from "hast-util-from-html";
|
|
4
4
|
import { visit } from "unist-util-visit";
|
|
5
|
+
import { parseClass } from "../client/fx.js";
|
|
5
6
|
|
|
6
7
|
const FUNC_NAME = 'fx';
|
|
7
8
|
let USAGES = {};
|
|
@@ -35,6 +36,7 @@ export async function findUsages(filePath, fileContent = null) {
|
|
|
35
36
|
tsx: true,
|
|
36
37
|
decorators: false
|
|
37
38
|
});
|
|
39
|
+
// writeFile(process.cwd() + '/ast.json', JSON.stringify(ast, null, 2), 'utf-8').catch(() => {});
|
|
38
40
|
traverseASTNode(ast, {
|
|
39
41
|
JSXExpressionContainer(node) {
|
|
40
42
|
if (node?.expression?.callee?.value === FUNC_NAME && node?.expression?.arguments) {
|
|
@@ -42,10 +44,8 @@ export async function findUsages(filePath, fileContent = null) {
|
|
|
42
44
|
const arg = node.expression.arguments[0];
|
|
43
45
|
let value = arg?.expression.value;
|
|
44
46
|
if (arg.expression.type === "TemplateLiteral") {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
value += elem?.cooked || "";
|
|
48
|
-
});
|
|
47
|
+
let quasis = arg.expression.quasis.map((elem) => elem?.cooked || "");
|
|
48
|
+
value = quasis.join("");
|
|
49
49
|
}
|
|
50
50
|
storeUsage(filePath, value);
|
|
51
51
|
}
|
|
@@ -69,20 +69,25 @@ export function getUsages() {
|
|
|
69
69
|
return USAGES;
|
|
70
70
|
}
|
|
71
71
|
function storeUsage(filePath, classesString = "") {
|
|
72
|
-
if (classesString)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
if (!classesString) return;
|
|
73
|
+
|
|
74
|
+
parseClass(classesString).forEach((part) => {
|
|
75
|
+
if (part.includes(":")) {
|
|
76
|
+
const lastColonIndex = part.lastIndexOf(":");
|
|
77
|
+
const label = part.slice(0, lastColonIndex); // "desktop" or "[&:hover]"
|
|
78
|
+
const clsPart = part.slice(lastColonIndex + 1); // e.g. "mt1"
|
|
79
|
+
const classes = clsPart.split(",");
|
|
80
|
+
|
|
81
|
+
if (label === "[]") return;
|
|
82
|
+
|
|
83
|
+
if (!USAGES[filePath][label]) {
|
|
84
|
+
USAGES[filePath][label] = [];
|
|
83
85
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
classes.forEach((cls) => {
|
|
87
|
+
USAGES[filePath][label].push(cls);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
86
91
|
}
|
|
87
92
|
function traverseASTNode(node, visitors, stack = []) {
|
|
88
93
|
if (!node || typeof node.type !== "string") {
|
package/package.json
CHANGED
|
File without changes
|