forgecss 0.2.1 → 0.4.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.d.ts +2 -4
- package/index.js +3 -7
- package/lib/generator.js +6 -1
- package/lib/helpers.js +19 -0
- package/lib/inventory.d.ts +1 -0
- package/lib/inventory.js +29 -0
- package/lib/transformers/arbitrary.js +32 -0
- package/lib/transformers/mediaQuery.js +11 -24
- 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.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
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 = {
|
|
8
8
|
inventoryFiles: ["css", "less", "scss"],
|
|
9
9
|
usageFiles: ["html", "jsx", "tsx"],
|
|
10
10
|
usageAttributes: ["class", "className"],
|
|
11
|
-
|
|
12
|
-
queries: {}
|
|
13
|
-
}
|
|
11
|
+
breakpoints: {}
|
|
14
12
|
};
|
|
15
13
|
|
|
16
14
|
export default function ForgeCSS(options) {
|
|
17
15
|
const config = { ...DEFAULT_OPTIONS };
|
|
18
16
|
|
|
19
|
-
config.
|
|
20
|
-
queries: Object.assign({}, DEFAULT_OPTIONS.mapping.queries, options?.mapping?.queries ?? {})
|
|
21
|
-
};
|
|
17
|
+
config.breakpoints = Object.assign({}, DEFAULT_OPTIONS.breakpoints, options?.breakpoints ?? {});
|
|
22
18
|
|
|
23
19
|
async function result(output) {
|
|
24
20
|
try {
|
package/lib/generator.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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";
|
|
5
|
+
import { resolveApplys } from "./inventory.js";
|
|
4
6
|
|
|
5
7
|
export async function generateOutputCSS(config) {
|
|
6
8
|
const bucket = {};
|
|
@@ -12,6 +14,8 @@ export async function generateOutputCSS(config) {
|
|
|
12
14
|
return;
|
|
13
15
|
} else if (pseudoClassTransformer(label, usages[file][label], bucket)) {
|
|
14
16
|
return;
|
|
17
|
+
} else if (arbitraryTransformer(label, usages[file][label], bucket)) {
|
|
18
|
+
return;
|
|
15
19
|
}
|
|
16
20
|
} catch (err) {
|
|
17
21
|
console.error(
|
|
@@ -24,6 +28,7 @@ export async function generateOutputCSS(config) {
|
|
|
24
28
|
}
|
|
25
29
|
});
|
|
26
30
|
});
|
|
31
|
+
resolveApplys(bucket);
|
|
27
32
|
return Object.keys(bucket)
|
|
28
33
|
.map((key) => {
|
|
29
34
|
if (bucket[key].rules) {
|
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
|
+
}
|
package/lib/inventory.d.ts
CHANGED
package/lib/inventory.js
CHANGED
|
@@ -19,8 +19,37 @@ export function getStylesByClassName(selector) {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
|
+
if (decls.length === 0) {
|
|
23
|
+
console.warn(`forgecss: Warning - no styles found for class "${selector}".`);
|
|
24
|
+
}
|
|
22
25
|
return decls;
|
|
23
26
|
}
|
|
24
27
|
export function invalidateInvetory() {
|
|
25
28
|
INVENTORY = {};
|
|
29
|
+
}
|
|
30
|
+
export function resolveApplys(bucket) {
|
|
31
|
+
Object.keys(INVENTORY).forEach((filePath) => {
|
|
32
|
+
INVENTORY[filePath].walkRules((rule) => {
|
|
33
|
+
rule.walkDecls((d) => {
|
|
34
|
+
if (d.prop === '--apply') {
|
|
35
|
+
const classesToApply = d.value.split(' ').map(c => c.trim()).filter(Boolean);
|
|
36
|
+
const newRule = postcss.rule({ selector: rule.selector });
|
|
37
|
+
classesToApply.forEach((className) => {
|
|
38
|
+
const styles = getStylesByClassName(className);
|
|
39
|
+
styles.forEach((style) => {
|
|
40
|
+
newRule.append({
|
|
41
|
+
prop: style.prop,
|
|
42
|
+
value: style.value,
|
|
43
|
+
important: style.important
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
if (!bucket['_APPLY_']) {
|
|
48
|
+
bucket["_APPLY_"] = postcss.root();
|
|
49
|
+
}
|
|
50
|
+
bucket["_APPLY_"].append(newRule);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
26
55
|
}
|
|
@@ -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,43 +1,30 @@
|
|
|
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
|
-
if (!config?.
|
|
6
|
+
export default function mediaQueryTransformer(config, label, classes, bucket) {
|
|
7
|
+
if (!config?.breakpoints[label]) {
|
|
7
8
|
return false;
|
|
8
9
|
}
|
|
9
10
|
if (!bucket[label]) {
|
|
10
11
|
bucket[label] = {
|
|
11
12
|
rules: postcss.atRule({
|
|
12
13
|
name: "media",
|
|
13
|
-
params: `all and (${config.
|
|
14
|
+
params: `all and (${config.breakpoints[label]})`
|
|
14
15
|
}),
|
|
15
16
|
classes: {}
|
|
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
|