forgecss 0.5.0 → 0.7.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/cli.js +2 -1
- package/dist/client.min.js +1 -0
- package/dist/forgecss.min.js +31 -1
- package/fx.js +1 -1
- package/index.d.ts +1 -0
- package/index.js +30 -9
- package/lib/forge-lang/Compiler.js +122 -0
- package/lib/forge-lang/Parser.js +208 -0
- package/lib/forge-lang/constants.js +27 -0
- package/lib/forge-lang/utils.js +18 -0
- package/{client → lib}/fx.js +24 -26
- package/lib/helpers.js +27 -16
- package/lib/usages.js +3 -25
- package/package.json +1 -1
- package/scripts/build.js +11 -5
- package/{client/index.js → standalone/client.js} +1 -1
- package/standalone/forgecss.js +76 -0
- package/{lib → standalone/lib}/inventory.js +17 -10
- package/standalone/lib/usages.js +36 -0
- package/lib/generator.js +0 -42
- package/lib/getAllFiles.js +0 -30
- package/lib/inventory.d.ts +0 -4
- package/lib/transformers/arbitrary.js +0 -32
- package/lib/transformers/mediaQuery.js +0 -31
- package/lib/transformers/pseudo.js +0 -46
- package/lib/usages.d.ts +0 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { extractStyles, getStylesByClassName, invalidateInventory, resolveApplys } from "./lib/inventory.js";
|
|
2
|
+
import { invalidateUsageCache, findUsages, getUsages } from "./lib/usages.js";
|
|
3
|
+
import { astToRules, rulesToCSS } from "../lib/forge-lang/Compiler.js";
|
|
4
|
+
import { toAST } from "../lib/forge-lang/Parser.js";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_OPTIONS = {
|
|
7
|
+
usageAttributes: ["class", "className"],
|
|
8
|
+
breakpoints: {},
|
|
9
|
+
verbose: true,
|
|
10
|
+
minify: true
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default function ForgeCSS(options) {
|
|
14
|
+
const config = { ...DEFAULT_OPTIONS };
|
|
15
|
+
|
|
16
|
+
config.breakpoints = Object.assign({}, DEFAULT_OPTIONS.breakpoints, options?.breakpoints ?? {});
|
|
17
|
+
config.usageAttributes = options?.usageAttributes ?? DEFAULT_OPTIONS.usageAttributes;
|
|
18
|
+
config.verbose = options?.verbose ?? DEFAULT_OPTIONS.verbose;
|
|
19
|
+
config.minify = options?.minify ?? DEFAULT_OPTIONS.minify;
|
|
20
|
+
|
|
21
|
+
async function result() {
|
|
22
|
+
try {
|
|
23
|
+
const cache = {};
|
|
24
|
+
const usages = getUsages();
|
|
25
|
+
const ast = toAST(
|
|
26
|
+
Object.values(usages).reduce((acc, i) => {
|
|
27
|
+
return acc.concat(i);
|
|
28
|
+
}, [])
|
|
29
|
+
);
|
|
30
|
+
let rules = astToRules(ast, {
|
|
31
|
+
getStylesByClassName,
|
|
32
|
+
cache,
|
|
33
|
+
config
|
|
34
|
+
});
|
|
35
|
+
rules.push(resolveApplys());
|
|
36
|
+
const css = rulesToCSS(rules.filter(Boolean), config);
|
|
37
|
+
if (config.verbose) {
|
|
38
|
+
console.log("forgecss: Output CSS generated successfully.");
|
|
39
|
+
}
|
|
40
|
+
return css;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error(`forgecss: error generating output CSS: ${err}`);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
async parse({ css, html, jsx }) {
|
|
49
|
+
if (!css) {
|
|
50
|
+
throw new Error('forgecss: parse requires "css".');
|
|
51
|
+
}
|
|
52
|
+
if (!html && !jsx) {
|
|
53
|
+
throw new Error('forgecss: parse requires "html" or "jsx".');
|
|
54
|
+
}
|
|
55
|
+
invalidateInventory();
|
|
56
|
+
invalidateUsageCache();
|
|
57
|
+
// filling the inventory
|
|
58
|
+
try {
|
|
59
|
+
await extractStyles(css);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`forgecss: error extracting styles.`, err);
|
|
62
|
+
}
|
|
63
|
+
// finding the usages
|
|
64
|
+
try {
|
|
65
|
+
if (html) {
|
|
66
|
+
await findUsages("usage.html", html);
|
|
67
|
+
} else if (jsx) {
|
|
68
|
+
await findUsages("usage.jsx", jsx);
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error(`forgecss: error extracting usages.`, err);
|
|
72
|
+
}
|
|
73
|
+
return result();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { readFile } from "fs/promises";
|
|
2
1
|
import postcss from "postcss";
|
|
3
2
|
import safeParser from "postcss-safe-parser";
|
|
4
3
|
|
|
5
4
|
let INVENTORY = {};
|
|
6
5
|
|
|
7
|
-
export async function extractStyles(
|
|
8
|
-
const content = css
|
|
6
|
+
export async function extractStyles(css = null) {
|
|
7
|
+
const content = css;
|
|
9
8
|
INVENTORY[filePath] = postcss.parse(content, { parser: safeParser });
|
|
10
9
|
}
|
|
11
10
|
export function getStylesByClassName(selector) {
|
|
@@ -20,14 +19,21 @@ export function getStylesByClassName(selector) {
|
|
|
20
19
|
});
|
|
21
20
|
});
|
|
22
21
|
if (decls.length === 0) {
|
|
23
|
-
console.warn(`forgecss:
|
|
22
|
+
console.warn(`forgecss: no styles found for class "${selector}".`);
|
|
24
23
|
}
|
|
25
24
|
return decls;
|
|
26
25
|
}
|
|
27
|
-
export function
|
|
28
|
-
|
|
26
|
+
export function invalidateInventory(filePath) {
|
|
27
|
+
if (!filePath) {
|
|
28
|
+
INVENTORY = {};
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (INVENTORY[filePath]) {
|
|
32
|
+
delete INVENTORY[filePath];
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
|
-
export function resolveApplys(
|
|
35
|
+
export function resolveApplys() {
|
|
36
|
+
let resolvedApplies;
|
|
31
37
|
Object.keys(INVENTORY).forEach((filePath) => {
|
|
32
38
|
INVENTORY[filePath].walkRules((rule) => {
|
|
33
39
|
rule.walkDecls((d) => {
|
|
@@ -44,12 +50,13 @@ export function resolveApplys(bucket) {
|
|
|
44
50
|
});
|
|
45
51
|
});
|
|
46
52
|
});
|
|
47
|
-
if (!
|
|
48
|
-
|
|
53
|
+
if (!resolvedApplies) {
|
|
54
|
+
resolvedApplies = postcss.root();
|
|
49
55
|
}
|
|
50
|
-
|
|
56
|
+
resolvedApplies.append(newRule);
|
|
51
57
|
}
|
|
52
58
|
});
|
|
53
59
|
});
|
|
54
60
|
});
|
|
61
|
+
return resolvedApplies;
|
|
55
62
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fromHtml } from "hast-util-from-html";
|
|
2
|
+
import { visit } from "unist-util-visit";
|
|
3
|
+
|
|
4
|
+
let USAGES = {};
|
|
5
|
+
|
|
6
|
+
export async function findUsages(fileContent = null) {
|
|
7
|
+
try {
|
|
8
|
+
if (USAGES[filePath]) {
|
|
9
|
+
// already processed
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
USAGES[filePath] = [];
|
|
13
|
+
const content = fileContent
|
|
14
|
+
|
|
15
|
+
const ast = fromHtml(content);
|
|
16
|
+
visit(ast, "element", (node) => {
|
|
17
|
+
if (node.properties.className) {
|
|
18
|
+
USAGES[filePath].push(node.properties.className.join(" "));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(`forgecss: error processing file ${filePath.replace(process.cwd(), '')}`, err);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function invalidateUsageCache(filePath) {
|
|
26
|
+
if (!filePath) {
|
|
27
|
+
USAGES = {};
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (USAGES[filePath]) {
|
|
31
|
+
delete USAGES[filePath];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function getUsages() {
|
|
35
|
+
return USAGES;
|
|
36
|
+
}
|
package/lib/generator.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { getUsages } from "./usages.js";
|
|
2
|
-
import arbitraryTransformer from "./transformers/arbitrary.js";
|
|
3
|
-
import mediaQueryTransformer from "./transformers/mediaQuery.js";
|
|
4
|
-
import pseudoClassTransformer from "./transformers/pseudo.js";
|
|
5
|
-
import { resolveApplys } from "./inventory.js";
|
|
6
|
-
|
|
7
|
-
export async function generateOutputCSS(config) {
|
|
8
|
-
const bucket = {};
|
|
9
|
-
const usages = getUsages();
|
|
10
|
-
Object.keys(usages).map((file) => {
|
|
11
|
-
Object.keys(usages[file]).forEach(async (label) => {
|
|
12
|
-
try {
|
|
13
|
-
if (mediaQueryTransformer(config, label, usages[file][label], bucket)) {
|
|
14
|
-
return;
|
|
15
|
-
} else if (pseudoClassTransformer(label, usages[file][label], bucket)) {
|
|
16
|
-
return;
|
|
17
|
-
} else if (arbitraryTransformer(label, usages[file][label], bucket)) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
} catch (err) {
|
|
21
|
-
console.error(
|
|
22
|
-
`forgecss: Error generating media query for label "${label}" (found in file ${file.replace(
|
|
23
|
-
process.cwd(),
|
|
24
|
-
""
|
|
25
|
-
)})`,
|
|
26
|
-
err
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
resolveApplys(bucket);
|
|
32
|
-
return Object.keys(bucket)
|
|
33
|
-
.map((key) => {
|
|
34
|
-
if (bucket[key].rules) {
|
|
35
|
-
return bucket[key].rules.toString();
|
|
36
|
-
}
|
|
37
|
-
return bucket[key].toString();
|
|
38
|
-
})
|
|
39
|
-
.filter(Boolean)
|
|
40
|
-
.join("\n");
|
|
41
|
-
}
|
|
42
|
-
|
package/lib/getAllFiles.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import fs from "fs/promises";
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
export default async function getAllFiles(dir, matchFiles) {
|
|
5
|
-
const result = [];
|
|
6
|
-
const stack = [dir];
|
|
7
|
-
|
|
8
|
-
while (stack.length > 0) {
|
|
9
|
-
const currentDir = stack.pop();
|
|
10
|
-
|
|
11
|
-
let dirHandle;
|
|
12
|
-
try {
|
|
13
|
-
dirHandle = await fs.opendir(currentDir);
|
|
14
|
-
} catch (err) {
|
|
15
|
-
throw err;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
for await (const entry of dirHandle) {
|
|
19
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
20
|
-
|
|
21
|
-
if (entry.isDirectory()) {
|
|
22
|
-
stack.push(fullPath);
|
|
23
|
-
} else if (matchFiles.includes(fullPath.split(".").pop()?.toLowerCase())) {
|
|
24
|
-
result.push(fullPath);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return result;
|
|
30
|
-
}
|
package/lib/inventory.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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,31 +0,0 @@
|
|
|
1
|
-
import postcss from "postcss";
|
|
2
|
-
|
|
3
|
-
import { setDeclarations } from "../helpers.js";
|
|
4
|
-
import {normalizeLabel} from "../../client/fx.js";
|
|
5
|
-
|
|
6
|
-
export default function mediaQueryTransformer(config, label, classes, bucket) {
|
|
7
|
-
if (!config?.breakpoints[label]) {
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
if (!bucket[label]) {
|
|
11
|
-
bucket[label] = {
|
|
12
|
-
rules: postcss.atRule({
|
|
13
|
-
name: "media",
|
|
14
|
-
params: `all and (${config.breakpoints[label]})`
|
|
15
|
-
}),
|
|
16
|
-
classes: {}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
const rules = bucket[label].rules;
|
|
20
|
-
classes.forEach((cls) => {
|
|
21
|
-
const selector = `.${normalizeLabel(label)}--${cls}`;
|
|
22
|
-
if (bucket[label].classes[selector]) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
bucket[label].classes[selector] = true; // caching
|
|
26
|
-
const rule = postcss.rule({ selector });
|
|
27
|
-
setDeclarations(cls, rule);
|
|
28
|
-
rules.append(rule);
|
|
29
|
-
});
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import postcss from "postcss";
|
|
2
|
-
|
|
3
|
-
import {setDeclarations} from "../helpers.js";
|
|
4
|
-
import {normalizeLabel} from "../../client/fx.js";
|
|
5
|
-
|
|
6
|
-
const ALLOWED_PSEUDO_CLASSES = [
|
|
7
|
-
"hover",
|
|
8
|
-
"active",
|
|
9
|
-
"focus",
|
|
10
|
-
"focus-visible",
|
|
11
|
-
"focus-within",
|
|
12
|
-
"disabled",
|
|
13
|
-
"enabled",
|
|
14
|
-
"read-only",
|
|
15
|
-
"read-write",
|
|
16
|
-
"checked",
|
|
17
|
-
"indeterminate",
|
|
18
|
-
"valid",
|
|
19
|
-
"invalid",
|
|
20
|
-
"required",
|
|
21
|
-
"optional",
|
|
22
|
-
"in-range",
|
|
23
|
-
"out-of-range",
|
|
24
|
-
"placeholder-shown",
|
|
25
|
-
"autofill",
|
|
26
|
-
"user-invalid"
|
|
27
|
-
];
|
|
28
|
-
|
|
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;
|
|
42
|
-
});
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
}
|
package/lib/usages.d.ts
DELETED