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/fx.js CHANGED
@@ -1,3 +1,3 @@
1
- import forgeCSSExpressionTransformer from "./client/fx.js";
1
+ import forgeCSSExpressionTransformer from "./lib/fx.js";
2
2
 
3
3
  export default forgeCSSExpressionTransformer;
package/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export type ForgeCSSOptions = {
6
6
  [key: string]: string;
7
7
  };
8
8
  verbose?: boolean;
9
+ minify?: boolean;
9
10
  };
10
11
 
11
12
  export type ForgeInstance = {
package/index.js CHANGED
@@ -1,17 +1,20 @@
1
1
  import path from 'path';
2
2
  import { writeFile } from "fs/promises";
3
- import getAllFiles from "./lib/getAllFiles.js";
4
- import { extractStyles, invalidateInvetory } from "./lib/inventory.js";
5
- import { invalidateUsageCache, findUsages } from "./lib/usages.js";
6
- import { generateOutputCSS } from "./lib/generator.js";
7
3
  import chokidar from "chokidar";
8
4
 
5
+ import { extractStyles, getStylesByClassName, invalidateInventory, resolveApplys } from "./lib/inventory.js";
6
+ import { invalidateUsageCache, findUsages, getUsages } from "./lib/usages.js";
7
+ import { astToRules, rulesToCSS } from './lib/forge-lang/Compiler.js';
8
+ import { toAST } from './lib/forge-lang/Parser.js';
9
+ import { getAllFiles } from './lib/helpers.js';
10
+
9
11
  const DEFAULT_OPTIONS = {
10
12
  inventoryFiles: ["css", "less", "scss"],
11
13
  usageFiles: ["html", "jsx", "tsx"],
12
14
  usageAttributes: ["class", "className"],
13
15
  breakpoints: {},
14
- verbose: true
16
+ verbose: true,
17
+ minify: true
15
18
  };
16
19
 
17
20
  export default function ForgeCSS(options) {
@@ -22,14 +25,30 @@ export default function ForgeCSS(options) {
22
25
  config.usageFiles = options?.usageFiles ?? DEFAULT_OPTIONS.usageFiles;
23
26
  config.usageAttributes = options?.usageAttributes ?? DEFAULT_OPTIONS.usageAttributes;
24
27
  config.verbose = options?.verbose ?? DEFAULT_OPTIONS.verbose;
28
+ config.minify = options?.minify ?? DEFAULT_OPTIONS.minify;
25
29
 
26
30
  async function result(output) {
27
31
  try {
28
- const css = await generateOutputCSS(config);
32
+ const cache = {};
33
+ const usages = getUsages();
34
+ const ast = toAST(
35
+ Object.values(usages).reduce((acc, i) => {
36
+ return acc.concat(i);
37
+ }, [])
38
+ );
39
+ let rules = astToRules(ast, {
40
+ getStylesByClassName,
41
+ cache,
42
+ config
43
+ });
44
+ rules.push(resolveApplys());
45
+ const css = rulesToCSS(rules.filter(Boolean), config);
29
46
  if (output) {
30
- await writeFile(output, `/* ForgeCSS autogenerated file */\n${css}`, "utf-8");
47
+ await writeFile(output, `/* ForgeCSS auto-generated file */\n${css}`, "utf-8");
48
+ }
49
+ if (config.verbose) {
50
+ console.log("forgecss: Output CSS generated successfully.");
31
51
  }
32
- console.log("forgecss: Output CSS generated successfully.");
33
52
  return css;
34
53
  } catch (err) {
35
54
  console.error(`forgecss: error generating output CSS: ${err}`);
@@ -44,6 +63,8 @@ export default function ForgeCSS(options) {
44
63
  });
45
64
  watcher.on("change", async (filePath) => {
46
65
  if (config.verbose) {
66
+ invalidateUsageCache(filePath)
67
+ invalidateInventory(filePath);
47
68
  console.log(`forgecss: Detected change in ${filePath}`);
48
69
  }
49
70
  callback();
@@ -117,7 +138,7 @@ export default function ForgeCSS(options) {
117
138
  if (!html && !jsx) {
118
139
  throw new Error('forgecss: parse requires "html" or "jsx".');
119
140
  }
120
- invalidateInvetory();
141
+ invalidateInventory();
121
142
  invalidateUsageCache();
122
143
  // filling the inventory
123
144
  try {
@@ -0,0 +1,122 @@
1
+ import postcss from "postcss";
2
+ import { NODE_TYPE, ALLOWED_PSEUDO_CLASSES } from "./constants.js";
3
+ import { minifyCSS } from './utils.js'
4
+ import { normalizeLabel } from "../fx.js";
5
+
6
+ export function astToRules(ast, options) {
7
+ let rules = [];
8
+ const { getStylesByClassName, cache = {}, config } = options
9
+ // console.log(
10
+ // "\n====================================================================== ^\n",
11
+ // JSON.stringify(ast, null, 2),
12
+ // "\n====================================================================== $\n"
13
+ // );
14
+
15
+ for(let node of ast) {
16
+ switch (node.type) {
17
+ case NODE_TYPE.TOKEN:
18
+ // ignoring ... just tokens
19
+ break;
20
+ case NODE_TYPE.VARIANT:
21
+ let variantSelector = node.selector;
22
+ let classes = (node?.payload?.value ?? "").split(",").map((c) => c.trim()).filter(Boolean);
23
+ let childRules;
24
+ if (!node.payload.value && typeof node.payload === 'object') {
25
+ childRules = astToRules([node.payload], options);
26
+ }
27
+
28
+ // -------------------------------------------------------- pseudo
29
+ if (ALLOWED_PSEUDO_CLASSES.includes(variantSelector)) {
30
+ classes.forEach(cls => {
31
+ let selector = `.${variantSelector}_${cls}`;
32
+ const rule = createRule(`${selector}:${variantSelector}`, cls, cache);
33
+ if (rule) {
34
+ rules.push(rule);
35
+ }
36
+ });
37
+ // -------------------------------------------------------- media queries
38
+ } else if (config.breakpoints[variantSelector]) {
39
+ let mediaRule;
40
+ if (cache[config.breakpoints[variantSelector]]) {
41
+ mediaRule = cache[config.breakpoints[variantSelector]];
42
+ } else {
43
+ mediaRule = cache[config.breakpoints[variantSelector]] = postcss.atRule({
44
+ name: "media",
45
+ params: config.breakpoints[variantSelector]
46
+ });
47
+ rules.push(mediaRule);
48
+ }
49
+ if (childRules) {
50
+ childRules.forEach(r => {
51
+ mediaRule.append(r);
52
+ })
53
+ } else {
54
+ classes.forEach((cls) => {
55
+ let selector = `.${variantSelector}_${cls}`;
56
+ const rule = createRule(selector, cls, cache);
57
+ if (rule) {
58
+ mediaRule.append(rule);
59
+ }
60
+ });
61
+ }
62
+ } else if (node.payload?.type === NODE_TYPE.TOKEN && node.simple === true) {
63
+ console.warn(`forgecss: there is no breakpoint defined for label "${variantSelector}".`);
64
+ // -------------------------------------------------------- arbitrary
65
+ } else {
66
+ classes.forEach(cls => {
67
+ if (Array.isArray(variantSelector)) {
68
+ variantSelector = variantSelector
69
+ .map(({ type, value, selector, payload }) => {
70
+ if (type === "token") {
71
+ return value;
72
+ }
73
+ })
74
+ .filter(Boolean)
75
+ .join(" ");
76
+ }
77
+ if (["", "true"].includes(variantSelector)) {
78
+ return;
79
+ }
80
+ const I = normalizeLabel(variantSelector) + "_" + cls;
81
+ const selector = evaluateArbitrary(variantSelector, I);
82
+ const rule = createRule(selector, cls, cache);
83
+ if (rule) {
84
+ rules.push(rule);
85
+ }
86
+ })
87
+ }
88
+ break;
89
+ }
90
+ }
91
+
92
+ function createRule(selector, pickStylesFrom, cache = {}) {
93
+ if (cache[selector]) {
94
+ return;
95
+ }
96
+ const newRule = cache[selector] = postcss.rule({ selector });
97
+ const decls = getStylesByClassName(pickStylesFrom);
98
+ if (decls.length === 0) {
99
+ return;
100
+ }
101
+ decls.forEach((d) => {
102
+ newRule.append(
103
+ postcss.decl({
104
+ prop: d.prop,
105
+ value: d.value,
106
+ important: d.important
107
+ })
108
+ );
109
+ });
110
+ return newRule;
111
+ }
112
+ function evaluateArbitrary(variant, I) {
113
+ variant = variant.replace(/[&]/g, `.${I}`);
114
+ return variant;
115
+ }
116
+
117
+ return rules;
118
+ }
119
+
120
+ export function rulesToCSS(rules, { minify } = { minify: true }) {
121
+ return minify ? minifyCSS(rules.map((r) => r.toString()).join("")) : rules.map((r) => r.toString()).join("\n");
122
+ }
@@ -0,0 +1,208 @@
1
+ export function toAST(input, cache = {}) {
2
+ if (cache[input]) return cache[input];
3
+
4
+ if (Array.isArray(input)) {
5
+ const optimized = [];
6
+ input.forEach((str) => {
7
+ str
8
+ .trim()
9
+ .split(" ")
10
+ .forEach((part) => {
11
+ if (!optimized.includes(part)) optimized.push(part);
12
+ });
13
+ });
14
+ input = optimized.join(" ");
15
+ }
16
+
17
+ const s = String(input ?? "").trim();
18
+ let i = 0;
19
+
20
+ const isWS = (ch) => ch === " " || ch === "\n" || ch === "\t" || ch === "\r";
21
+
22
+ function skipWS() {
23
+ while (i < s.length && isWS(s[i])) i++;
24
+ }
25
+
26
+ function parseSequence(stopChar) {
27
+ const nodes = [];
28
+ while (i < s.length) {
29
+ skipWS();
30
+ if (stopChar && s[i] === stopChar) break;
31
+ if (i >= s.length) break;
32
+ nodes.push(parseItem());
33
+ }
34
+ return nodes;
35
+ }
36
+
37
+ function readIdentUntilDelimiter() {
38
+ let out = "";
39
+ while (i < s.length) {
40
+ const ch = s[i];
41
+ // stop at whitespace, "(", ")", ":" (variant separator)
42
+ if (isWS(ch) || ch === "(" || ch === ")" || ch === ":") break;
43
+ // IMPORTANT: DO NOT consume "[" here; it may be:
44
+ // - leading bracket variant (handled in parseItem when ch === "[")
45
+ // - attribute selector suffix (handled in parseItem after reading head)
46
+ if (ch === "[") break;
47
+
48
+ out += ch;
49
+ i++;
50
+ }
51
+ return out.trim();
52
+ }
53
+
54
+ function isVariantLabel(str) {
55
+ return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(str);
56
+ }
57
+
58
+ function isCallName(str) {
59
+ return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(str);
60
+ }
61
+
62
+ function parseItem() {
63
+ skipWS();
64
+ const ch = s[i];
65
+
66
+ // Bracket variant: [selector]:payload
67
+ if (ch === "[") {
68
+ const selectorRaw = parseBracketContent(); // returns content WITHOUT outer []
69
+ const selectorAst = toAST(selectorRaw, cache);
70
+
71
+ if (s[i] === ":") {
72
+ i++;
73
+ const payload = parseItem();
74
+ return { type: "variant", selector: selectorAst, payload };
75
+ }
76
+
77
+ // If it's just a standalone bracket chunk (not a variant),
78
+ // keep it as a token string. (You can change this if you prefer AST here.)
79
+ return { type: "token", value: `[${selectorRaw}]` };
80
+ }
81
+
82
+ // Read label/name/token
83
+ let head = readIdentUntilDelimiter();
84
+
85
+ // NEW: absorb attribute selector suffixes: foo[...][...]
86
+ // This handles &\[type=...\] and similar.
87
+ while (s[i] === "[") {
88
+ const inner = parseBracketContent(); // consumes the bracket block
89
+ head += `[${inner}]`;
90
+ }
91
+
92
+ // Label variant: hover:..., desktop:..., focus:...
93
+ if (s[i] === ":" && isVariantLabel(head)) {
94
+ i++; // consume ":"
95
+ const payload = parseItem();
96
+ return { type: "variant", selector: head, payload, simple: true };
97
+ }
98
+
99
+ // Call: name(...)
100
+ if (s[i] === "(" && isCallName(head)) {
101
+ i++; // consume "("
102
+ const args = [];
103
+ while (i < s.length) {
104
+ skipWS();
105
+ if (s[i] === ")") {
106
+ i++;
107
+ break;
108
+ }
109
+ args.push(parseItem());
110
+ skipWS();
111
+ if (s[i] === ",") i++;
112
+ }
113
+ return { type: "call", name: head, args };
114
+ }
115
+
116
+ if (s[i] === ":") {
117
+ head += ":";
118
+ i++; // consume ":"
119
+
120
+ // absorb following identifier / call / selector chunk
121
+ while (i < s.length) {
122
+ const ch = s[i];
123
+ if (isWS(ch) || ch === ")" || ch === ",") break;
124
+
125
+ if (ch === "[") {
126
+ const inner = parseBracketContent();
127
+ head += `[${inner}]`;
128
+ continue;
129
+ }
130
+
131
+ if (ch === "(") {
132
+ head += "(";
133
+ i++;
134
+ let depth = 1;
135
+ while (i < s.length && depth > 0) {
136
+ if (s[i] === "(") depth++;
137
+ if (s[i] === ")") depth--;
138
+ head += s[i++];
139
+ }
140
+ continue;
141
+ }
142
+
143
+ head += ch;
144
+ i++;
145
+ }
146
+ }
147
+
148
+ return { type: "token", value: head };
149
+ }
150
+
151
+ function parseBracketContent() {
152
+ // assumes s[i] === "["
153
+ i++; // consume "["
154
+ let out = "";
155
+ let bracket = 1;
156
+ let quote = null;
157
+
158
+ while (i < s.length) {
159
+ const ch = s[i];
160
+
161
+ if (quote) {
162
+ out += ch;
163
+ if (ch === "\\" && i + 1 < s.length) {
164
+ i++;
165
+ out += s[i];
166
+ } else if (ch === quote) {
167
+ quote = null;
168
+ }
169
+ i++;
170
+ continue;
171
+ }
172
+
173
+ if (ch === "'" || ch === '"') {
174
+ quote = ch;
175
+ out += ch;
176
+ i++;
177
+ continue;
178
+ }
179
+
180
+ if (ch === "[") {
181
+ bracket++;
182
+ out += ch;
183
+ i++;
184
+ continue;
185
+ }
186
+
187
+ if (ch === "]") {
188
+ bracket--;
189
+ if (bracket === 0) {
190
+ i++; // consume final "]"
191
+ break;
192
+ }
193
+ out += ch;
194
+ i++;
195
+ continue;
196
+ }
197
+
198
+ out += ch;
199
+ i++;
200
+ }
201
+
202
+ return out;
203
+ }
204
+
205
+ const ast = parseSequence(null);
206
+ cache[input] = ast;
207
+ return ast;
208
+ }
@@ -0,0 +1,27 @@
1
+ export const NODE_TYPE = {
2
+ VARIANT: "variant",
3
+ CALL: "call",
4
+ TOKEN: "token"
5
+ };
6
+ export 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
+ ];
@@ -0,0 +1,18 @@
1
+ export function minifyCSS(css) {
2
+ return (
3
+ css
4
+ // remove comments
5
+ .replace(/\/\*[\s\S]*?\*\//g, "")
6
+ // remove whitespace around symbols
7
+ .replace(/\s*([{}:;,])\s*/g, "$1")
8
+ // remove trailing semicolons
9
+ .replace(/;}/g, "}")
10
+ // collapse multiple spaces
11
+ .replace(/\s+/g, " ")
12
+ // remove spaces before/after braces
13
+ .replace(/\s*{\s*/g, "{")
14
+ .replace(/\s*}\s*/g, "}")
15
+ // trim
16
+ .trim()
17
+ );
18
+ }
@@ -1,18 +1,4 @@
1
- export default function fx(classes) {
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
- return rest
8
- .split(",")
9
- .map((cls) => `${label}--${cls}`)
10
- .join(" ");
11
- })
12
- .filter(Boolean)
13
- .join(" ");
14
- }
15
- export function splitClassName(label) {
1
+ function splitClassName(label) {
16
2
  const lastColonIndex = label.lastIndexOf(":");
17
3
  if (lastColonIndex === -1) {
18
4
  return [null, label];
@@ -21,16 +7,7 @@ export function splitClassName(label) {
21
7
  const rest = label.slice(lastColonIndex + 1);
22
8
  return [prefix, rest];
23
9
  }
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) {
10
+ function parseClass(str) {
34
11
  const out = [];
35
12
  let buf = "";
36
13
 
@@ -71,4 +48,25 @@ export function parseClass(str) {
71
48
  if (buf) out.push(buf);
72
49
  return out;
73
50
  }
74
-
51
+ export default function fx(classes) {
52
+ return parseClass(classes)
53
+ .map((className) => {
54
+ let [label, rest] = splitClassName(className);
55
+ if (!label || label === "[true]") return rest;
56
+ if (label === "[false]") return false;
57
+ label = normalizeLabel(label);
58
+ return rest
59
+ .split(",")
60
+ .map((cls) => `${label}_${cls}`)
61
+ .join(" ");
62
+ })
63
+ .filter(Boolean)
64
+ .join(" ");
65
+ }
66
+ export function normalizeLabel(label) {
67
+ let normalized = label.trim();
68
+ normalized = normalized.replace(/[&]/g, "I");
69
+ normalized = normalized.replace(/[:| =]/g, "-");
70
+ normalized = normalized.replace(/[^a-zA-Z0-9_-]/g, "");
71
+ return normalized;
72
+ }
package/lib/helpers.js CHANGED
@@ -1,19 +1,30 @@
1
- import postcss from "postcss";
2
- import { getStylesByClassName } from "./inventory.js";
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
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;
4
+ export 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
+ }
9
27
  }
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
- });
28
+
29
+ return result;
19
30
  }
package/lib/usages.js CHANGED
@@ -2,7 +2,6 @@ import swc from "@swc/core";
2
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";
6
5
 
7
6
  const FUNC_NAME = 'fx';
8
7
  let USAGES = {};
@@ -15,7 +14,7 @@ export async function findUsages(filePath, fileContent = null) {
15
14
  // already processed
16
15
  return;
17
16
  }
18
- USAGES[filePath] = {};
17
+ USAGES[filePath] = [];
19
18
  const content = fileContent ? fileContent : await readFile(filePath, "utf-8");
20
19
  const extension = filePath.split('.').pop().toLowerCase();
21
20
 
@@ -24,7 +23,7 @@ export async function findUsages(filePath, fileContent = null) {
24
23
  const ast = fromHtml(content);
25
24
  visit(ast, "element", (node) => {
26
25
  if (node.properties.className) {
27
- storeUsage(filePath, node.properties.className.join(' '));
26
+ USAGES[filePath].push(node.properties.className.join(" "));
28
27
  }
29
28
  });
30
29
  return;
@@ -47,7 +46,7 @@ export async function findUsages(filePath, fileContent = null) {
47
46
  let quasis = arg.expression.quasis.map((elem) => elem?.cooked || "");
48
47
  value = quasis.join("");
49
48
  }
50
- storeUsage(filePath, value);
49
+ USAGES[filePath].push(value);
51
50
  }
52
51
  }
53
52
  }
@@ -68,27 +67,6 @@ export function invalidateUsageCache(filePath) {
68
67
  export function getUsages() {
69
68
  return USAGES;
70
69
  }
71
- function storeUsage(filePath, classesString = "") {
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] = [];
85
- }
86
- classes.forEach((cls) => {
87
- USAGES[filePath][label].push(cls);
88
- });
89
- }
90
- });
91
- }
92
70
  function traverseASTNode(node, visitors, stack = []) {
93
71
  if (!node || typeof node.type !== "string") {
94
72
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgecss",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",
package/scripts/build.js CHANGED
@@ -11,15 +11,21 @@ const minify = true;
11
11
 
12
12
  (async function () {
13
13
  await esbuild.build({
14
- entryPoints: [path.join(__dirname, "..", "client", "index.js")],
14
+ entryPoints: [path.join(__dirname, "..", "standalone", "client.js")],
15
+ bundle: true,
16
+ minify,
17
+ outfile: path.join(__dirname, "..", "dist", "client.min.js"),
18
+ platform: "browser",
19
+ sourcemap: false,
20
+ plugins: []
21
+ });
22
+ await esbuild.build({
23
+ entryPoints: [path.join(__dirname, "..", "standalone", "forgecss.js")],
15
24
  bundle: true,
16
25
  minify,
17
26
  outfile: path.join(__dirname, "..", "dist", "forgecss.min.js"),
18
27
  platform: "browser",
19
28
  sourcemap: false,
20
- plugins: [],
21
- define: {
22
- __VERSION__: JSON.stringify(pkg.version)
23
- }
29
+ plugins: []
24
30
  });
25
31
  })();
@@ -1,4 +1,4 @@
1
- import fxFn from "./fx.js";
1
+ import fxFn from "../lib/fx.js";
2
2
 
3
3
  function forgecss(root) {
4
4
  var rootNode = root || document;