forgecss 0.1.5 → 0.1.7
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 +12 -15
- package/index.d.ts +7 -5
- package/index.js +22 -21
- package/lib/generator.js +49 -7
- package/lib/inventory.js +23 -0
- package/lib/{processFile.js → processor.js} +22 -21
- package/package.json +1 -1
- package/lib/styles.js +0 -61
package/cli.js
CHANGED
|
@@ -7,9 +7,9 @@ import { program } from "commander";
|
|
|
7
7
|
import ForgeCSS from './index.js';
|
|
8
8
|
import chokidar from "chokidar";
|
|
9
9
|
|
|
10
|
-
program.option("--config", "Path to forgecss config file", process.cwd() + "/forgecss.config.js");
|
|
11
|
-
program.option("--watch", "Enable watch mode", false);
|
|
12
|
-
program.option("--verbose", "Enable watch mode", false);
|
|
10
|
+
program.option("-c, --config <string>,", "Path to forgecss config file", process.cwd() + "/forgecss.config.js");
|
|
11
|
+
program.option("-w, --watch", "Enable watch mode", false);
|
|
12
|
+
program.option("-v, --verbose", "Enable watch mode", false);
|
|
13
13
|
program.parse();
|
|
14
14
|
|
|
15
15
|
const options = program.opts();
|
|
@@ -24,22 +24,20 @@ async function loadConfig(configPath) {
|
|
|
24
24
|
const fileUrl = pathToFileURL(abs).href;
|
|
25
25
|
|
|
26
26
|
const mod = await import(fileUrl);
|
|
27
|
-
return mod.default ?? mod;
|
|
27
|
+
return mod.default ?? mod;
|
|
28
28
|
}
|
|
29
29
|
async function runForgeCSS(lookAtPath = null) {
|
|
30
30
|
if (!config) {
|
|
31
31
|
// The very first run
|
|
32
32
|
config = await loadConfig(options.config);
|
|
33
33
|
if (options.watch) {
|
|
34
|
+
if (!config.source) {
|
|
35
|
+
throw new Error('forgecss: missing "source" in configuration.');
|
|
36
|
+
}
|
|
34
37
|
const watcher = chokidar.watch(config.source, {
|
|
35
38
|
persistent: true,
|
|
36
39
|
ignoreInitial: true,
|
|
37
|
-
ignored: (p, stats) =>
|
|
38
|
-
if (path.resolve(p) === path.resolve(config.output)) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
40
|
+
ignored: (p, stats) => path.resolve(p) === path.resolve(config.output)
|
|
43
41
|
});
|
|
44
42
|
watcher.on("change", async (filePath) => {
|
|
45
43
|
if (options.verbose) {
|
|
@@ -52,11 +50,10 @@ async function runForgeCSS(lookAtPath = null) {
|
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
|
-
ForgeCSS(config).parse(lookAtPath)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
53
|
+
await ForgeCSS(config).parse(lookAtPath);
|
|
54
|
+
if (options.verbose) {
|
|
55
|
+
console.log(`forgecss: CSS generation at ${config.output} completed.`);
|
|
56
|
+
}
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
runForgeCSS();
|
package/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type ForgeCSSOptions = {
|
|
2
2
|
source: string;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
inventoryFiles?: string[];
|
|
4
|
+
usageFiles?: string[];
|
|
5
|
+
usageAttributes?: string[];
|
|
6
6
|
mapping: {
|
|
7
7
|
queries: {
|
|
8
8
|
[key: string]: {
|
|
@@ -13,6 +13,8 @@ export type ForgeCSSOptions = {
|
|
|
13
13
|
output: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export
|
|
16
|
+
export type ForgeInstance = {
|
|
17
17
|
parse: (filePathToSpecificFile?: string) => Promise<void>;
|
|
18
|
-
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function forgecss(options?: ForgeCSSOptions): ForgeInstance;
|
package/index.js
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
import getAllFiles from "./lib/getAllFiles.js";
|
|
2
|
-
import { extractStyles } from "./lib/
|
|
3
|
-
import {
|
|
2
|
+
import { extractStyles } from "./lib/inventory.js";
|
|
3
|
+
import { invalidateUsageCache, findUsages } from "./lib/processor.js";
|
|
4
4
|
import { generateOutputCSS } from "./lib/generator.js";
|
|
5
5
|
|
|
6
6
|
const DEFAULT_OPTIONS = {
|
|
7
7
|
source: null,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
inventoryFiles: ["css", "less", "scss"],
|
|
9
|
+
usageFiles: ["html", "jsx", "tsx"],
|
|
10
|
+
usageAttributes: ["class", "className"],
|
|
11
11
|
mapping: {
|
|
12
12
|
queries: {}
|
|
13
13
|
},
|
|
14
14
|
output: null
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export default function
|
|
17
|
+
export default function ForgeCSS(options = { source: null, output: null, mapping: {} }) {
|
|
18
18
|
const config = { ...DEFAULT_OPTIONS };
|
|
19
19
|
|
|
20
|
-
config.
|
|
21
|
-
config.
|
|
20
|
+
config.source = options.source ?? DEFAULT_OPTIONS.source;
|
|
21
|
+
config.mapping = Object.assign({}, DEFAULT_OPTIONS.mapping, options.mapping ?? {});
|
|
22
|
+
config.output = options.output ?? DEFAULT_OPTIONS.output;
|
|
22
23
|
|
|
23
24
|
if (!config.source) {
|
|
24
|
-
throw new Error('forgecss: "source"
|
|
25
|
+
throw new Error('forgecss: missing "source" in configuration.');
|
|
25
26
|
}
|
|
26
27
|
if (!config.output) {
|
|
27
|
-
throw new Error('forgecss: "output"
|
|
28
|
+
throw new Error('forgecss: missing "output" in configuration.');
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
return {
|
|
31
32
|
async parse(lookAtPath = null) {
|
|
32
|
-
//
|
|
33
|
+
// filling the inventory
|
|
33
34
|
try {
|
|
34
35
|
if (lookAtPath) {
|
|
35
|
-
if (config.
|
|
36
|
+
if (config.inventoryFiles.includes(lookAtPath.split(".").pop().toLowerCase())) {
|
|
36
37
|
await extractStyles(lookAtPath);
|
|
37
38
|
}
|
|
38
39
|
} else {
|
|
39
|
-
let files = await getAllFiles(config.source, config.
|
|
40
|
+
let files = await getAllFiles(config.source, config.inventoryFiles);
|
|
40
41
|
for (let file of files) {
|
|
41
42
|
await extractStyles(file);
|
|
42
43
|
}
|
|
@@ -44,26 +45,26 @@ export default function forgecss(options = { styles: {}, ui: {}, mapping: {}, ou
|
|
|
44
45
|
} catch (err) {
|
|
45
46
|
console.error(`forgecss: error extracting styles: ${err}`);
|
|
46
47
|
}
|
|
47
|
-
//
|
|
48
|
+
// finding the usages
|
|
48
49
|
try {
|
|
49
50
|
if (lookAtPath) {
|
|
50
|
-
if (config.
|
|
51
|
-
|
|
52
|
-
await
|
|
51
|
+
if (config.usageFiles.includes(lookAtPath.split(".").pop().toLowerCase())) {
|
|
52
|
+
invalidateUsageCache(lookAtPath);
|
|
53
|
+
await findUsages(lookAtPath);
|
|
53
54
|
}
|
|
54
55
|
} else {
|
|
55
|
-
let files = await getAllFiles(config.source, config.
|
|
56
|
+
let files = await getAllFiles(config.source, config.usageFiles);
|
|
56
57
|
for (let file of files) {
|
|
57
|
-
await
|
|
58
|
+
await findUsages(file);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
} catch(err) {
|
|
61
|
+
} catch (err) {
|
|
61
62
|
console.error(`forgecss: error extracting declarations: ${err}`);
|
|
62
63
|
}
|
|
63
64
|
// generating the output CSS
|
|
64
65
|
try {
|
|
65
66
|
await generateOutputCSS(config);
|
|
66
|
-
} catch(err) {
|
|
67
|
+
} catch (err) {
|
|
67
68
|
console.error(`forgecss: error generating output CSS: ${err}`);
|
|
68
69
|
}
|
|
69
70
|
}
|
package/lib/generator.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import postcss from "postcss";
|
|
1
2
|
import { writeFile } from "fs/promises";
|
|
2
|
-
|
|
3
|
-
import {
|
|
3
|
+
|
|
4
|
+
import { getUsages } from "./processor.js";
|
|
5
|
+
import { getStylesByClassName } from "./inventory.js";
|
|
4
6
|
|
|
5
7
|
export async function generateOutputCSS(config) {
|
|
6
8
|
const cache = {};
|
|
7
|
-
const
|
|
8
|
-
Object.keys(
|
|
9
|
-
Object.keys(
|
|
9
|
+
const usages = getUsages();
|
|
10
|
+
Object.keys(usages).map((file) => {
|
|
11
|
+
Object.keys(usages[file]).forEach(async (label) => {
|
|
10
12
|
try {
|
|
11
|
-
createMediaStyle(config, label,
|
|
13
|
+
createMediaStyle(config, label, usages[file][label], cache);
|
|
12
14
|
} catch (err) {
|
|
13
|
-
console.error(`Error generating media query for label ${label} in file ${file}: ${err}`);
|
|
15
|
+
console.error(`Error generating media query for label ${label} (found in file ${file}): ${err}`);
|
|
14
16
|
}
|
|
15
17
|
});
|
|
16
18
|
});
|
|
@@ -22,4 +24,44 @@ export async function generateOutputCSS(config) {
|
|
|
22
24
|
`/* ForgeCSS autogenerated file */\n${result}`,
|
|
23
25
|
"utf-8"
|
|
24
26
|
);
|
|
27
|
+
}
|
|
28
|
+
export function createMediaStyle(config, label, selectors, cache) {
|
|
29
|
+
if (!config.mapping.queries[label]) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Unknown media query label: ${label}. Check app-fe/wwwroot/scripts/lib/generateMediaQueries.js for available mappings.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (!cache[label]) {
|
|
35
|
+
cache[label] = {
|
|
36
|
+
mq: postcss.atRule({
|
|
37
|
+
name: "media",
|
|
38
|
+
params: `all and (${config.mapping.queries[label].query})`
|
|
39
|
+
}),
|
|
40
|
+
classes: {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const mq = cache[label].mq;
|
|
44
|
+
selectors.forEach((selector) => {
|
|
45
|
+
const prefixedSelector = `.${label}_${selector}`;
|
|
46
|
+
if (cache[label].classes[prefixedSelector]) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
cache[label].classes[prefixedSelector] = true;
|
|
50
|
+
const rule = postcss.rule({ selector: prefixedSelector });
|
|
51
|
+
const decls = getStylesByClassName(selector);
|
|
52
|
+
if (decls.length === 0) {
|
|
53
|
+
console.warn(`Warning: No styles found for class .${selector} used in media query ${label}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
decls.forEach((d) => {
|
|
57
|
+
rule.append(
|
|
58
|
+
postcss.decl({
|
|
59
|
+
prop: d.prop,
|
|
60
|
+
value: d.value,
|
|
61
|
+
important: d.important
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
mq.append(rule);
|
|
66
|
+
});
|
|
25
67
|
}
|
package/lib/inventory.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import postcss from "postcss";
|
|
3
|
+
import safeParser from "postcss-safe-parser";
|
|
4
|
+
|
|
5
|
+
const INVENTORY = {};
|
|
6
|
+
|
|
7
|
+
export async function extractStyles(filePath) {
|
|
8
|
+
const content = await readFile(filePath, 'utf-8');
|
|
9
|
+
INVENTORY[filePath] = postcss.parse(content, { parser: safeParser });
|
|
10
|
+
}
|
|
11
|
+
export function getStylesByClassName(selector) {
|
|
12
|
+
const decls = [];
|
|
13
|
+
Object.keys(INVENTORY).forEach((filePath) => {
|
|
14
|
+
INVENTORY[filePath].walkRules((rule) => {
|
|
15
|
+
if (rule.selectors && rule.selectors.includes(`.${selector}`)) {
|
|
16
|
+
rule.walkDecls((d) => {
|
|
17
|
+
decls.push({ prop: d.prop, value: d.value, important: d.important });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
return decls;
|
|
23
|
+
}
|
|
@@ -4,18 +4,19 @@ import { fromHtml } from "hast-util-from-html";
|
|
|
4
4
|
import { visit } from "unist-util-visit";
|
|
5
5
|
|
|
6
6
|
const FUNC_NAME = 'fx';
|
|
7
|
-
const
|
|
7
|
+
const USAGES = {};
|
|
8
8
|
|
|
9
9
|
const { parse } = swc;
|
|
10
10
|
|
|
11
|
-
export async function
|
|
12
|
-
const extension = filePath.split('.').pop().toLowerCase();
|
|
11
|
+
export async function findUsages(filePath) {
|
|
13
12
|
try {
|
|
14
|
-
if (
|
|
13
|
+
if (USAGES[filePath]) {
|
|
14
|
+
// already processed
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
USAGES[filePath] = {};
|
|
18
18
|
const content = await readFile(filePath, "utf-8");
|
|
19
|
+
const extension = filePath.split('.').pop().toLowerCase();
|
|
19
20
|
|
|
20
21
|
// HTML
|
|
21
22
|
if (extension === "html") {
|
|
@@ -34,7 +35,7 @@ export async function extractDeclarations(filePath) {
|
|
|
34
35
|
tsx: true,
|
|
35
36
|
decorators: false
|
|
36
37
|
});
|
|
37
|
-
|
|
38
|
+
traverseASTNode(ast, {
|
|
38
39
|
JSXExpressionContainer(node) {
|
|
39
40
|
if (node?.expression?.callee?.value === FUNC_NAME && node?.expression?.arguments) {
|
|
40
41
|
if (node?.expression?.arguments[0]) {
|
|
@@ -55,9 +56,9 @@ export async function extractDeclarations(filePath) {
|
|
|
55
56
|
console.error(`forgecss: error processing file ${filePath}: ${err}`);
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
|
-
export function
|
|
59
|
-
if (
|
|
60
|
-
delete
|
|
59
|
+
export function invalidateUsageCache(filePath) {
|
|
60
|
+
if (USAGES[filePath]) {
|
|
61
|
+
delete USAGES[filePath];
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
function pushToDeclarations(filePath, classesString = "") {
|
|
@@ -67,16 +68,16 @@ function pushToDeclarations(filePath, classesString = "") {
|
|
|
67
68
|
let [label, classes] = part.split(":");
|
|
68
69
|
classes = classes.split(",");
|
|
69
70
|
classes.forEach((cls) => {
|
|
70
|
-
if (!
|
|
71
|
-
|
|
71
|
+
if (!USAGES[filePath][label]) {
|
|
72
|
+
USAGES[filePath][label] = [];
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
+
USAGES[filePath][label].push(cls);
|
|
74
75
|
});
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
function
|
|
80
|
+
function traverseASTNode(node, visitors, stack = []) {
|
|
80
81
|
if (!node || typeof node.type !== "string") {
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
@@ -95,23 +96,23 @@ function traverseNode(node, visitors, stack = []) {
|
|
|
95
96
|
child.forEach((c) => {
|
|
96
97
|
if (c) {
|
|
97
98
|
if (typeof c.type === "string") {
|
|
98
|
-
|
|
99
|
+
traverseASTNode(c, visitors, [node].concat(stack));
|
|
99
100
|
} else if (c?.expression && typeof c.expression.type === "string") {
|
|
100
|
-
|
|
101
|
+
traverseASTNode(c.expression, visitors, [node].concat(stack));
|
|
101
102
|
} else if (c?.callee && typeof c.callee.type === "string") {
|
|
102
|
-
|
|
103
|
+
traverseASTNode(c.callee, visitors, [node].concat(stack));
|
|
103
104
|
} else if (c?.left && typeof c.left.type === "string") {
|
|
104
|
-
|
|
105
|
+
traverseASTNode(c.left, visitors, [node].concat(stack));
|
|
105
106
|
} else if (c?.right && typeof c.right.type === "string") {
|
|
106
|
-
|
|
107
|
+
traverseASTNode(c.right, visitors, [node].concat(stack));
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
});
|
|
110
111
|
} else if (child && typeof child.type === "string") {
|
|
111
|
-
|
|
112
|
+
traverseASTNode(child, visitors, [node].concat(stack));
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
|
-
export function
|
|
116
|
-
return
|
|
116
|
+
export function getUsages() {
|
|
117
|
+
return USAGES;
|
|
117
118
|
}
|
package/package.json
CHANGED
package/lib/styles.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { readFile } from "fs/promises";
|
|
2
|
-
import postcss from "postcss";
|
|
3
|
-
import safeParser from "postcss-safe-parser";
|
|
4
|
-
|
|
5
|
-
const STYLES = {};
|
|
6
|
-
|
|
7
|
-
export async function extractStyles(filePath) {
|
|
8
|
-
const content = await readFile(filePath, 'utf-8');
|
|
9
|
-
STYLES[filePath] = postcss.parse(content, { parser: safeParser });
|
|
10
|
-
}
|
|
11
|
-
export function getStylesByClassName(selector) {
|
|
12
|
-
const decls = [];
|
|
13
|
-
Object.keys(STYLES).forEach((filePath) => {
|
|
14
|
-
STYLES[filePath].walkRules((rule) => {
|
|
15
|
-
if (rule.selectors && rule.selectors.includes(`.${selector}`)) {
|
|
16
|
-
rule.walkDecls((d) => {
|
|
17
|
-
decls.push({ prop: d.prop, value: d.value, important: d.important });
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
return decls;
|
|
23
|
-
}
|
|
24
|
-
export function createMediaStyle(config, label, selectors, cache) {
|
|
25
|
-
if (!config.mapping.queries[label]) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`Unknown media query label: ${label}. Check app-fe/wwwroot/scripts/lib/generateMediaQueries.js for available mappings.`
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
if (!cache[label]) {
|
|
31
|
-
cache[label] = {
|
|
32
|
-
mq: postcss.atRule({
|
|
33
|
-
name: "media",
|
|
34
|
-
params: `all and (${config.mapping.queries[label].query})`
|
|
35
|
-
}),
|
|
36
|
-
classes: {}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
const mq = cache[label].mq;
|
|
40
|
-
selectors.forEach((selector) => {
|
|
41
|
-
const prefixedSelector = `.${label}_${selector}`;
|
|
42
|
-
if (cache[label].classes[prefixedSelector]) { return; }
|
|
43
|
-
cache[label].classes[prefixedSelector] = true;
|
|
44
|
-
const rule = postcss.rule({ selector: prefixedSelector });
|
|
45
|
-
const decls = getStylesByClassName(selector);
|
|
46
|
-
if (decls.length === 0) {
|
|
47
|
-
console.warn(`Warning: No styles found for class .${selector} used in media query ${label}`);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
decls.forEach((d) => {
|
|
51
|
-
rule.append(
|
|
52
|
-
postcss.decl({
|
|
53
|
-
prop: d.prop,
|
|
54
|
-
value: d.value,
|
|
55
|
-
important: d.important
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
});
|
|
59
|
-
mq.append(rule);
|
|
60
|
-
});
|
|
61
|
-
}
|