forgecss 0.1.6 → 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 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; // support both default and named export
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).then(() => {
56
- if (options.verbose) {
57
- console.log(`forgecss: CSS generation at ${config.output} completed.`);
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
- stylesMatch?: string[];
4
- declarationsMatch?: string[];
5
- declarationsMatchAttributes?: string[];
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 default function forgecss(options?: ForgeCSSOptions): {
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,43 +1,43 @@
1
1
  import getAllFiles from "./lib/getAllFiles.js";
2
- import { extractStyles } from "./lib/styles.js";
3
- import { deleteDeclarations, extractDeclarations } from "./lib/processFile.js";
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
- stylesMatch: ['css', 'less', 'scss'],
9
- declarationsMatch: ['html', 'jsx', 'tsx'],
10
- declarationsMatchAttributes: ['class', 'className'],
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 forgecss(options = { source: null, output: null, mapping: {} }) {
17
+ export default function ForgeCSS(options = { source: null, output: null, mapping: {} }) {
18
18
  const config = { ...DEFAULT_OPTIONS };
19
19
 
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;
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;
23
23
 
24
24
  if (!config.source) {
25
- throw new Error('forgecss: "source" option is required.');
25
+ throw new Error('forgecss: missing "source" in configuration.');
26
26
  }
27
27
  if (!config.output) {
28
- throw new Error('forgecss: "output" option is required.');
28
+ throw new Error('forgecss: missing "output" in configuration.');
29
29
  }
30
30
 
31
31
  return {
32
32
  async parse(lookAtPath = null) {
33
- // fetching the styles
33
+ // filling the inventory
34
34
  try {
35
35
  if (lookAtPath) {
36
- if (config.stylesMatch.includes(lookAtPath.split(".").pop().toLowerCase())) {
36
+ if (config.inventoryFiles.includes(lookAtPath.split(".").pop().toLowerCase())) {
37
37
  await extractStyles(lookAtPath);
38
38
  }
39
39
  } else {
40
- let files = await getAllFiles(config.source, config.stylesMatch);
40
+ let files = await getAllFiles(config.source, config.inventoryFiles);
41
41
  for (let file of files) {
42
42
  await extractStyles(file);
43
43
  }
@@ -45,17 +45,17 @@ export default function forgecss(options = { source: null, output: null, mapping
45
45
  } catch (err) {
46
46
  console.error(`forgecss: error extracting styles: ${err}`);
47
47
  }
48
- // fetching the declarations
48
+ // finding the usages
49
49
  try {
50
50
  if (lookAtPath) {
51
- if (config.declarationsMatch.includes(lookAtPath.split(".").pop().toLowerCase())) {
52
- deleteDeclarations(lookAtPath);
53
- await extractDeclarations(lookAtPath);
51
+ if (config.usageFiles.includes(lookAtPath.split(".").pop().toLowerCase())) {
52
+ invalidateUsageCache(lookAtPath);
53
+ await findUsages(lookAtPath);
54
54
  }
55
55
  } else {
56
- let files = await getAllFiles(config.source, config.declarationsMatch);
56
+ let files = await getAllFiles(config.source, config.usageFiles);
57
57
  for (let file of files) {
58
- await extractDeclarations(file);
58
+ await findUsages(file);
59
59
  }
60
60
  }
61
61
  } catch (err) {
package/lib/generator.js CHANGED
@@ -1,16 +1,18 @@
1
+ import postcss from "postcss";
1
2
  import { writeFile } from "fs/promises";
2
- import { getDeclarations } from "./processFile.js";
3
- import { createMediaStyle } from "./styles.js";
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 declarations = getDeclarations();
8
- Object.keys(declarations).map((file) => {
9
- Object.keys(declarations[file]).forEach(async (label) => {
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, declarations[file][label], cache);
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
  }
@@ -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 DECLARATIONS = {};
7
+ const USAGES = {};
8
8
 
9
9
  const { parse } = swc;
10
10
 
11
- export async function extractDeclarations(filePath) {
12
- const extension = filePath.split('.').pop().toLowerCase();
11
+ export async function findUsages(filePath) {
13
12
  try {
14
- if (DECLARATIONS[filePath]) {
13
+ if (USAGES[filePath]) {
14
+ // already processed
15
15
  return;
16
16
  }
17
- DECLARATIONS[filePath] = {};
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
- traverseNode(ast, {
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 deleteDeclarations(filePath) {
59
- if (DECLARATIONS[filePath]) {
60
- delete DECLARATIONS[filePath];
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 (!DECLARATIONS[filePath][label]) {
71
- DECLARATIONS[filePath][label] = [];
71
+ if (!USAGES[filePath][label]) {
72
+ USAGES[filePath][label] = [];
72
73
  }
73
- DECLARATIONS[filePath][label].push(cls);
74
+ USAGES[filePath][label].push(cls);
74
75
  });
75
76
  }
76
77
  });
77
78
  }
78
79
  }
79
- function traverseNode(node, visitors, stack = []) {
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
- traverseNode(c, visitors, [node].concat(stack));
99
+ traverseASTNode(c, visitors, [node].concat(stack));
99
100
  } else if (c?.expression && typeof c.expression.type === "string") {
100
- traverseNode(c.expression, visitors, [node].concat(stack));
101
+ traverseASTNode(c.expression, visitors, [node].concat(stack));
101
102
  } else if (c?.callee && typeof c.callee.type === "string") {
102
- traverseNode(c.callee, visitors, [node].concat(stack));
103
+ traverseASTNode(c.callee, visitors, [node].concat(stack));
103
104
  } else if (c?.left && typeof c.left.type === "string") {
104
- traverseNode(c.left, visitors, [node].concat(stack));
105
+ traverseASTNode(c.left, visitors, [node].concat(stack));
105
106
  } else if (c?.right && typeof c.right.type === "string") {
106
- traverseNode(c.right, visitors, [node].concat(stack));
107
+ traverseASTNode(c.right, visitors, [node].concat(stack));
107
108
  }
108
109
  }
109
110
  });
110
111
  } else if (child && typeof child.type === "string") {
111
- traverseNode(child, visitors, [node].concat(stack));
112
+ traverseASTNode(child, visitors, [node].concat(stack));
112
113
  }
113
114
  }
114
115
  }
115
- export function getDeclarations() {
116
- return DECLARATIONS;
116
+ export function getUsages() {
117
+ return USAGES;
117
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgecss",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "ForgeCSS turns strings into fully generated responsive CSS using a custom DSL.",
6
6
  "author": "Krasimir Tsonev",
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
- }