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/client/fx.js CHANGED
@@ -1,16 +1,74 @@
1
1
  export default function fx(classes) {
2
- return classes
3
- .split(" ")
4
- .map((className) => {
5
- const [label, rest] = className.split(":");
6
- if (!rest) return label;
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}_${cls}`)
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
 
@@ -1 +1 @@
1
- (()=>{function o(i){return i.split(" ").map(s=>{let[e,t]=s.split(":");return t?t.split(",").map(n=>`${e}_${n}`).filter(Boolean).join(" "):e}).filter(Boolean).join(" ")}function r(i){for(var s=i||document,e=s.querySelectorAll("[class]"),t=0;t<e.length;t++){var n=e[t],a=n.getAttribute("class");if(a){var f=o(a);typeof f=="string"&&f!==a&&n.setAttribute("class",f)}}}window.fx=o;window.forgecss=r;document.readyState!=="loading"?r():document.addEventListener("DOMContentLoaded",function(){r()});window.addEventListener("load",function(){r()});})();
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/processor.js";
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 "./processor.js";
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 { getStylesByClassName } from "../inventory.js";
3
+ import { setDeclarations } from "../helpers.js";
4
+ import {normalizeLabel} from "../../client/fx.js";
4
5
 
5
- export default function mediaQueryTransformer(config, label, selectors, bucket) {
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
- selectors.forEach((selector) => {
20
- const prefixedSelector = `.${label}_${selector}`;
21
- if (bucket[label].classes[prefixedSelector]) {
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[prefixedSelector] = true; // caching
25
- const rule = postcss.rule({ selector: prefixedSelector });
26
- const decls = getStylesByClassName(selector);
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
- import { getStylesByClassName } from "../inventory.js";
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, selectors, bucket) {
28
- if (!ALLOWED_PSEUDO_CLASSES.includes(label)) {
29
- return false;
30
- }
31
- const root = postcss.root();
32
- selectors.forEach((selector) => {
33
- const key = `${label}_${selector}`;
34
- if (bucket[key]) {
35
- // already have that
36
- return;
37
- }
38
- const rule = postcss.rule({ selector: `.${key}:${label}` });
39
- const decls = getStylesByClassName(selector);
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
- root.append(rule);
54
- bucket[key] = root;
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
- value = "";
46
- arg.expression.quasis.forEach((elem) => {
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
- classesString.split(" ").forEach((part) => {
74
- if (part.indexOf(":") > -1) {
75
- let [label, classes] = part.split(":");
76
- classes = classes.split(",");
77
- classes.forEach((cls) => {
78
- if (!USAGES[filePath][label]) {
79
- USAGES[filePath][label] = [];
80
- }
81
- USAGES[filePath][label].push(cls);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgecss",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "ForgeCSS turns strings into fully generated responsive CSS using a custom DSL.",
6
6
  "author": "Krasimir Tsonev",
File without changes