forgecss 0.2.1 → 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/client/fx.js +66 -8
- package/dist/forgecss.min.js +1 -1
- package/index.js +1 -1
- 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/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.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 = {
|
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
|