clava 0.4.0 → 0.4.2
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/CHANGELOG.md +25 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +120 -22
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/src/index.ts +179 -87
- package/src/refine-warning.ts +161 -0
- package/src/types.ts +2 -2
- package/src/utils.ts +31 -10
- package/tests/_utils.ts +6 -2
- package/tests/build.test.ts +82 -7
- package/tests/extend.test.ts +7 -2
- package/tests/refine-warning.test.ts +28 -0
- package/tests/refine.test.ts +326 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# clava
|
|
2
2
|
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
- Improved [`refine`](https://clava.style/docs/reference/refine) iteration warnings to show the latest changing variant values with a shorter component creation stack.
|
|
6
|
+
- Fixed [`setDefaultVariants`](https://clava.style/docs/reference/refine#setdefaultvariants) calls with stable values to avoid extra [`refine`](https://clava.style/docs/reference/refine) passes.
|
|
7
|
+
|
|
8
|
+
## 0.4.1
|
|
9
|
+
|
|
10
|
+
### Improved refine iteration warning with debugging context
|
|
11
|
+
|
|
12
|
+
When the [`refine`](https://clava.style/docs/reference/refine) iteration cap is hit, the development warning now lists the variant keys that did not stabilize and includes the stack trace of the [`cv`](https://clava.style/docs/reference/cv) call that defined the component. This makes it easier to find the offending component without searching through the codebase.
|
|
13
|
+
|
|
14
|
+
```txt
|
|
15
|
+
Clava: Maximum refine iterations exceeded. This can happen when a refine callback calls setVariants or setDefaultVariants, but one of the variants changes on every run.
|
|
16
|
+
Variant(s) that did not stabilize: size.
|
|
17
|
+
Component created at:
|
|
18
|
+
at toolbarButton (src/components/toolbar.ts:42:18)
|
|
19
|
+
...
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The frame is captured at component creation but formatted lazily, so component creation stays cheap unless the warning actually fires, and the capture is skipped entirely for components that cannot enter the refine loop. The whole warning is wrapped in a `process.env.NODE_ENV !== "production"` guard, so bundlers that inline that constant strip the warning machinery from production builds.
|
|
23
|
+
|
|
24
|
+
### Other updates
|
|
25
|
+
|
|
26
|
+
- Removed refine warning-only code from production bundles when bundlers statically replace `process.env.NODE_ENV`.
|
|
27
|
+
|
|
3
28
|
## 0.4.0
|
|
4
29
|
|
|
5
30
|
### Removed `computedVariants` in favor of function values in `variants`
|
package/dist/index.d.ts
CHANGED
|
@@ -30,11 +30,11 @@ type ComponentProps<V = {}> = VariantValues<V> & NullableComponentResult;
|
|
|
30
30
|
type GetVariants<V> = (variants?: VariantValues<V>) => VariantValues<V>;
|
|
31
31
|
type ComponentPropKey<R extends ComponentResult> = keyof R | (R extends StyleClassProps ? "className" : never);
|
|
32
32
|
type KeySourceArray = readonly string[];
|
|
33
|
-
|
|
33
|
+
interface KeySourceComponent {
|
|
34
34
|
propKeys: readonly string[];
|
|
35
35
|
variantKeys: readonly string[];
|
|
36
36
|
getVariants: () => Record<string, unknown>;
|
|
37
|
-
}
|
|
37
|
+
}
|
|
38
38
|
type KeySource = KeySourceArray | KeySourceComponent;
|
|
39
39
|
type IsComponent<S> = S extends {
|
|
40
40
|
getVariants: () => unknown;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,92 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
|
+
//#region src/refine-warning.ts
|
|
3
|
+
function captureCreationFrame(skipFn) {
|
|
4
|
+
if (process.env.NODE_ENV === "production") return void 0;
|
|
5
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
6
|
+
const holder = {};
|
|
7
|
+
Error.captureStackTrace(holder, skipFn);
|
|
8
|
+
return holder;
|
|
9
|
+
}
|
|
10
|
+
return /* @__PURE__ */ new Error();
|
|
11
|
+
}
|
|
12
|
+
function formatCreationStack(frame) {
|
|
13
|
+
let stack = frame.stack;
|
|
14
|
+
if (!stack) return void 0;
|
|
15
|
+
const newlineIdx = stack.indexOf("\n");
|
|
16
|
+
if (newlineIdx > 0) {
|
|
17
|
+
const firstLine = stack.slice(0, newlineIdx);
|
|
18
|
+
if (firstLine === "Error" || firstLine.startsWith("Error:")) stack = stack.slice(newlineIdx + 1);
|
|
19
|
+
}
|
|
20
|
+
const frames = stack.split("\n");
|
|
21
|
+
for (let i = 0; i < frames.length; i++) {
|
|
22
|
+
const line = frames[i]?.trim();
|
|
23
|
+
if (!line) continue;
|
|
24
|
+
if (isInternalCreationFrame(line)) continue;
|
|
25
|
+
if (line.includes("/node_modules/")) continue;
|
|
26
|
+
if (line.includes("\\node_modules\\")) continue;
|
|
27
|
+
if (line.includes("node:internal")) continue;
|
|
28
|
+
return ` ${line}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isInternalCreationFrame(line) {
|
|
32
|
+
if (line.includes("captureCreationFrame")) return true;
|
|
33
|
+
if (line.startsWith("at cv ")) return true;
|
|
34
|
+
if (line.startsWith("at cv(")) return true;
|
|
35
|
+
if (line.startsWith("cv@")) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function formatVariantValue(value) {
|
|
39
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
40
|
+
if (typeof value === "number") {
|
|
41
|
+
if (Number.isNaN(value)) return "NaN";
|
|
42
|
+
return String(value);
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
45
|
+
if (value === void 0) return "undefined";
|
|
46
|
+
if (value === null) return "null";
|
|
47
|
+
if (typeof value === "boolean") return String(value);
|
|
48
|
+
if (typeof value === "symbol") return String(value);
|
|
49
|
+
if (typeof value === "function") return "[function]";
|
|
50
|
+
return "[object]";
|
|
51
|
+
}
|
|
52
|
+
function setVariantChange(into, key, from, to) {
|
|
53
|
+
into.set(key, {
|
|
54
|
+
from,
|
|
55
|
+
to
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function accumulateUnstableVariantChanges(into, prev, next) {
|
|
59
|
+
for (const key in next) {
|
|
60
|
+
if (!Object.hasOwn(next, key)) continue;
|
|
61
|
+
if (!Object.is(prev[key], next[key])) setVariantChange(into, key, prev[key], next[key]);
|
|
62
|
+
}
|
|
63
|
+
for (const key in prev) {
|
|
64
|
+
if (!Object.hasOwn(prev, key)) continue;
|
|
65
|
+
if (Object.hasOwn(next, key)) continue;
|
|
66
|
+
setVariantChange(into, key, prev[key], void 0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function formatVariantChanges(changes) {
|
|
70
|
+
return Array.from(changes).map(([key, { from, to }]) => {
|
|
71
|
+
return `${key}: ${formatVariantValue(from)} -> ${formatVariantValue(to)}`;
|
|
72
|
+
}).join(", ");
|
|
73
|
+
}
|
|
74
|
+
function warnRefineLimit({ runState, creationFrame, unstableChanges }) {
|
|
75
|
+
if (process.env.NODE_ENV === "production") return;
|
|
76
|
+
if (runState.warned) return;
|
|
77
|
+
runState.warned = true;
|
|
78
|
+
let message = "Clava: Maximum refine iterations exceeded. This can happen when a refine callback calls setVariants or setDefaultVariants, but one of the variants changes on every run.";
|
|
79
|
+
if (unstableChanges && unstableChanges.size > 0) {
|
|
80
|
+
message += `\nVariant(s) that did not stabilize: ${Array.from(unstableChanges.keys()).join(", ")}.`;
|
|
81
|
+
message += `\nLatest variant changes before warning: ${formatVariantChanges(unstableChanges)}.`;
|
|
82
|
+
}
|
|
83
|
+
if (creationFrame) {
|
|
84
|
+
const creationStack = formatCreationStack(creationFrame);
|
|
85
|
+
if (creationStack) message += `\nComponent created at:\n${creationStack}`;
|
|
86
|
+
}
|
|
87
|
+
console.warn(message);
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
2
90
|
//#region src/utils.ts
|
|
3
91
|
const hasOwn = Object.prototype.hasOwnProperty;
|
|
4
92
|
function isAsciiLetter(code) {
|
|
@@ -235,11 +323,6 @@ function areVariantsEqual(a, b) {
|
|
|
235
323
|
}
|
|
236
324
|
return true;
|
|
237
325
|
}
|
|
238
|
-
function warnRefineLimit(runState) {
|
|
239
|
-
if (runState.warned) return;
|
|
240
|
-
runState.warned = true;
|
|
241
|
-
if (process.env.NODE_ENV !== "production") console.warn("Clava: Maximum refine iterations exceeded. This can happen when a refine callback calls setVariants or setDefaultVariants, but one of the variants changes on every run.");
|
|
242
|
-
}
|
|
243
326
|
function getExtUserVariantProps(userVariantProps, protectedVariants, changedVariants) {
|
|
244
327
|
const extUserVariantProps = {};
|
|
245
328
|
Object.assign(extUserVariantProps, userVariantProps);
|
|
@@ -578,6 +661,7 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
578
661
|
}
|
|
579
662
|
const extMetasWithRefineCount = extMetasWithRefine.length;
|
|
580
663
|
const shouldCollectChangedVariants = extMetasWithRefineCount > 0;
|
|
664
|
+
const creationFrame = !!refine || extMetasWithRefineCount > 0 ? captureCreationFrame(cv) : void 0;
|
|
581
665
|
const functionVariantKeys = /* @__PURE__ */ new Set();
|
|
582
666
|
for (let i = 0; i < extCount; i++) {
|
|
583
667
|
const fnKeys = extMetas[i].functionVariantKeys;
|
|
@@ -736,7 +820,7 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
736
820
|
if (!Object.hasOwn(newVariants, key)) continue;
|
|
737
821
|
const value = newVariants[key];
|
|
738
822
|
setChangedVariant(key, value, true);
|
|
739
|
-
if (getCurrentVariantValue(key)
|
|
823
|
+
if (Object.is(getCurrentVariantValue(key), value)) continue;
|
|
740
824
|
ensureUpdated()[key] = value;
|
|
741
825
|
}
|
|
742
826
|
return;
|
|
@@ -750,7 +834,7 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
750
834
|
if (valueKey != null && disabledVariantValues[key]?.has(valueKey)) continue;
|
|
751
835
|
}
|
|
752
836
|
setChangedVariant(key, value, true);
|
|
753
|
-
if (getCurrentVariantValue(key)
|
|
837
|
+
if (Object.is(getCurrentVariantValue(key), value)) continue;
|
|
754
838
|
ensureUpdated()[key] = value;
|
|
755
839
|
}
|
|
756
840
|
},
|
|
@@ -765,9 +849,9 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
765
849
|
const valueKey = getVariantValueKey(value);
|
|
766
850
|
if (valueKey != null && disabledVariantValues[key]?.has(valueKey)) continue;
|
|
767
851
|
}
|
|
852
|
+
if (Object.is(getCurrentVariantValue(key), value)) continue;
|
|
768
853
|
setChangedVariant(key, value);
|
|
769
854
|
if (pendingProtectedVariants) pendingProtectedVariants[key] = value;
|
|
770
|
-
if (getCurrentVariantValue(key) === value) continue;
|
|
771
855
|
ensureUpdated()[key] = value;
|
|
772
856
|
}
|
|
773
857
|
},
|
|
@@ -898,16 +982,12 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
898
982
|
if (cStyle) Object.assign(styleOut, cStyle);
|
|
899
983
|
return workingResolved;
|
|
900
984
|
};
|
|
901
|
-
const compute = !refine && extMetasWithRefineCount === 0 ? (resolved, userVariantProps, skipKeys, skipValues, classesOut, styleOut, runState, protectedVariants, pendingProtectedVariants, protectedVariantKeys) => {
|
|
902
|
-
|
|
903
|
-
} : (resolved, userVariantProps, skipKeys, skipValues, classesOut, styleOut, runState, protectedVariants, pendingProtectedVariants, protectedVariantKeys) => {
|
|
904
|
-
runState ??= {
|
|
905
|
-
remaining: MAX_REFINE_RUNS,
|
|
906
|
-
warned: false
|
|
907
|
-
};
|
|
985
|
+
const compute = !refine && extMetasWithRefineCount === 0 ? computeOnce : (resolved, userVariantProps, skipKeys, skipValues, classesOut, styleOut, runState, protectedVariants, pendingProtectedVariants, protectedVariantKeys) => {
|
|
986
|
+
runState ??= { remaining: MAX_REFINE_RUNS };
|
|
908
987
|
protectedVariants ??= {};
|
|
909
988
|
protectedVariantKeys ??= /* @__PURE__ */ new Set();
|
|
910
989
|
let workingResolved = resolved;
|
|
990
|
+
let unstableChanges = null;
|
|
911
991
|
let lastClasses = [];
|
|
912
992
|
let lastStyle = {};
|
|
913
993
|
let isFirstRun = true;
|
|
@@ -935,8 +1015,16 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
935
1015
|
}
|
|
936
1016
|
return nextResolved;
|
|
937
1017
|
}
|
|
1018
|
+
if (process.env.NODE_ENV !== "production" && runState.remaining < 10) {
|
|
1019
|
+
if (!unstableChanges) unstableChanges = /* @__PURE__ */ new Map();
|
|
1020
|
+
accumulateUnstableVariantChanges(unstableChanges, workingResolved, nextResolved);
|
|
1021
|
+
}
|
|
938
1022
|
if (useDirectOutput && runState.remaining === 0) {
|
|
939
|
-
warnRefineLimit(
|
|
1023
|
+
warnRefineLimit({
|
|
1024
|
+
runState,
|
|
1025
|
+
creationFrame,
|
|
1026
|
+
unstableChanges
|
|
1027
|
+
});
|
|
940
1028
|
return nextResolved;
|
|
941
1029
|
}
|
|
942
1030
|
if (useDirectOutput) {
|
|
@@ -949,7 +1037,11 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
949
1037
|
workingResolved = nextResolved;
|
|
950
1038
|
isFirstRun = false;
|
|
951
1039
|
}
|
|
952
|
-
warnRefineLimit(
|
|
1040
|
+
warnRefineLimit({
|
|
1041
|
+
runState,
|
|
1042
|
+
creationFrame,
|
|
1043
|
+
unstableChanges
|
|
1044
|
+
});
|
|
953
1045
|
for (let i = 0; i < lastClasses.length; i++) classesOut.push(lastClasses[i]);
|
|
954
1046
|
Object.assign(styleOut, lastStyle);
|
|
955
1047
|
return workingResolved;
|
|
@@ -974,13 +1066,11 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
974
1066
|
return workingResolved;
|
|
975
1067
|
};
|
|
976
1068
|
const resolveRefine = refine || extMetasWithRefineCount > 0 ? (resolved, userVariantProps, filterOwnVariants = true, runState, protectedVariants, pendingProtectedVariants, protectedVariantKeys) => {
|
|
977
|
-
runState ??= {
|
|
978
|
-
remaining: MAX_REFINE_RUNS,
|
|
979
|
-
warned: false
|
|
980
|
-
};
|
|
1069
|
+
runState ??= { remaining: MAX_REFINE_RUNS };
|
|
981
1070
|
protectedVariants ??= {};
|
|
982
1071
|
protectedVariantKeys ??= /* @__PURE__ */ new Set();
|
|
983
1072
|
let workingResolved = resolved;
|
|
1073
|
+
let unstableChanges = null;
|
|
984
1074
|
let reachedLimit = true;
|
|
985
1075
|
while (runState.remaining > 0) {
|
|
986
1076
|
runState.remaining -= 1;
|
|
@@ -994,9 +1084,17 @@ function create({ transformClass = (className) => className } = {}) {
|
|
|
994
1084
|
reachedLimit = false;
|
|
995
1085
|
break;
|
|
996
1086
|
}
|
|
1087
|
+
if (process.env.NODE_ENV !== "production" && runState.remaining < 10) {
|
|
1088
|
+
if (!unstableChanges) unstableChanges = /* @__PURE__ */ new Map();
|
|
1089
|
+
accumulateUnstableVariantChanges(unstableChanges, workingResolved, nextResolved);
|
|
1090
|
+
}
|
|
997
1091
|
workingResolved = nextResolved;
|
|
998
1092
|
}
|
|
999
|
-
if (reachedLimit) warnRefineLimit(
|
|
1093
|
+
if (reachedLimit) warnRefineLimit({
|
|
1094
|
+
runState,
|
|
1095
|
+
creationFrame,
|
|
1096
|
+
unstableChanges
|
|
1097
|
+
});
|
|
1000
1098
|
return workingResolved;
|
|
1001
1099
|
} : null;
|
|
1002
1100
|
const computeResult = (props = EMPTY_DEFAULTS) => {
|