@csszyx/compiler 0.10.7 → 0.10.9

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/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('@csszyx/core');
4
- const transformCore = require('./shared/compiler.DIYC1vdY.cjs');
4
+ const transformCore = require('./shared/compiler.mibv6qPF.cjs');
5
5
  const oxcParser = require('oxc-parser');
6
6
  const t = require('@babel/types');
7
7
  const node_crypto = require('node:crypto');
@@ -245,6 +245,9 @@ function transformSourceCode(source, filename, options) {
245
245
  }
246
246
  const createMergedClassNameValue = (szExpr) => {
247
247
  if (!existingClassExpr) {
248
+ if (t__namespace.isStringLiteral(szExpr) && szExpr.value === "") {
249
+ return t__namespace.jsxExpressionContainer(t__namespace.identifier("undefined"));
250
+ }
248
251
  return t__namespace.isStringLiteral(szExpr) ? szExpr : t__namespace.jsxExpressionContainer(szExpr);
249
252
  }
250
253
  if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
@@ -387,6 +390,17 @@ function transformSourceCode(source, filename, options) {
387
390
  transformed = true;
388
391
  return;
389
392
  }
393
+ const hoistedNested = tryHoistNestedConditional(
394
+ flatExpression,
395
+ getBinding
396
+ );
397
+ if (hoistedNested !== null) {
398
+ path.node.name.name = "className";
399
+ path.node.value = createMergedClassNameValue(hoistedNested);
400
+ collectFromExpr(hoistedNested, collectedClasses);
401
+ transformed = true;
402
+ return;
403
+ }
390
404
  const partial = evaluatePartialObject$1(flatExpression);
391
405
  if (partial !== null && !partial.hasSpread && (partial.dynamicProps.size > 0 || partial.conditionalClasses.length > 0)) {
392
406
  const staticClasses = [];
@@ -826,6 +840,9 @@ function parseStyleStringToObjectExpr(styleStr) {
826
840
  }
827
841
  return t__namespace.objectExpression(objProps);
828
842
  }
843
+ function emptyClassToUndefined(node) {
844
+ return t__namespace.isStringLiteral(node) && node.value === "" ? t__namespace.identifier("undefined") : node;
845
+ }
829
846
  function tryStaticTransformNode(node, getBinding) {
830
847
  if (t__namespace.isTSAsExpression(node) || t__namespace.isTSSatisfiesExpression(node)) {
831
848
  return tryStaticTransformNode(node.expression, getBinding);
@@ -862,12 +879,111 @@ function tryStaticTransformNode(node, getBinding) {
862
879
  const consequent = tryStaticTransformNode(node.consequent, getBinding);
863
880
  const alternate = tryStaticTransformNode(node.alternate, getBinding);
864
881
  if (consequent !== null && alternate !== null) {
865
- return t__namespace.conditionalExpression(node.test, consequent, alternate);
882
+ return t__namespace.conditionalExpression(
883
+ node.test,
884
+ emptyClassToUndefined(consequent),
885
+ emptyClassToUndefined(alternate)
886
+ );
866
887
  }
867
888
  return null;
868
889
  }
869
890
  return null;
870
891
  }
892
+ function scanNestedConditionals(node) {
893
+ let topLevel = 0;
894
+ let nested = 0;
895
+ let test = null;
896
+ for (const prop of node.properties) {
897
+ if (!t__namespace.isObjectProperty(prop)) {
898
+ continue;
899
+ }
900
+ const value = prop.value;
901
+ if (t__namespace.isConditionalExpression(value)) {
902
+ topLevel++;
903
+ } else if (t__namespace.isObjectExpression(value)) {
904
+ nested += countAllConditionals(value);
905
+ test ??= firstConditionalTest(value);
906
+ }
907
+ }
908
+ return { topLevel, nested, test };
909
+ }
910
+ function countAllConditionals(node) {
911
+ let count = 0;
912
+ for (const prop of node.properties) {
913
+ if (!t__namespace.isObjectProperty(prop)) {
914
+ continue;
915
+ }
916
+ const value = prop.value;
917
+ if (t__namespace.isConditionalExpression(value)) {
918
+ count++;
919
+ } else if (t__namespace.isObjectExpression(value)) {
920
+ count += countAllConditionals(value);
921
+ }
922
+ }
923
+ return count;
924
+ }
925
+ function firstConditionalTest(node) {
926
+ for (const prop of node.properties) {
927
+ if (!t__namespace.isObjectProperty(prop)) {
928
+ continue;
929
+ }
930
+ const value = prop.value;
931
+ if (t__namespace.isConditionalExpression(value)) {
932
+ return value.test;
933
+ }
934
+ if (t__namespace.isObjectExpression(value)) {
935
+ const inner = firstConditionalTest(value);
936
+ if (inner) {
937
+ return inner;
938
+ }
939
+ }
940
+ }
941
+ return null;
942
+ }
943
+ function cloneObjectPickingBranch(node, pick) {
944
+ return t__namespace.objectExpression(
945
+ node.properties.map((prop) => {
946
+ if (!t__namespace.isObjectProperty(prop)) {
947
+ return t__namespace.cloneNode(prop);
948
+ }
949
+ const value = prop.value;
950
+ let nextValue = t__namespace.cloneNode(value);
951
+ if (t__namespace.isConditionalExpression(value)) {
952
+ nextValue = t__namespace.cloneNode(value[pick]);
953
+ } else if (t__namespace.isObjectExpression(value)) {
954
+ nextValue = cloneObjectPickingBranch(value, pick);
955
+ }
956
+ return t__namespace.objectProperty(
957
+ t__namespace.cloneNode(prop.key),
958
+ nextValue,
959
+ prop.computed,
960
+ prop.shorthand
961
+ );
962
+ })
963
+ );
964
+ }
965
+ function tryHoistNestedConditional(node, getBinding) {
966
+ const { topLevel, nested, test } = scanNestedConditionals(node);
967
+ if (topLevel !== 0 || nested !== 1 || test === null) {
968
+ return null;
969
+ }
970
+ const consequent = tryStaticTransformNode(
971
+ cloneObjectPickingBranch(node, "consequent"),
972
+ getBinding
973
+ );
974
+ const alternate = tryStaticTransformNode(
975
+ cloneObjectPickingBranch(node, "alternate"),
976
+ getBinding
977
+ );
978
+ if (!consequent || !alternate || !t__namespace.isStringLiteral(consequent) || !t__namespace.isStringLiteral(alternate)) {
979
+ return null;
980
+ }
981
+ return t__namespace.conditionalExpression(
982
+ test,
983
+ emptyClassToUndefined(consequent),
984
+ emptyClassToUndefined(alternate)
985
+ );
986
+ }
871
987
  function tryHoistConditionalSpread(node, getBinding) {
872
988
  let conditionalSpreadIdx = -1;
873
989
  let conditionalExpr = null;
@@ -899,7 +1015,11 @@ function tryHoistConditionalSpread(node, getBinding) {
899
1015
  if (!t__namespace.isStringLiteral(resolvedA) || !t__namespace.isStringLiteral(resolvedB)) {
900
1016
  return null;
901
1017
  }
902
- return t__namespace.conditionalExpression(conditionalExpr.test, resolvedA, resolvedB);
1018
+ return t__namespace.conditionalExpression(
1019
+ conditionalExpr.test,
1020
+ emptyClassToUndefined(resolvedA),
1021
+ emptyClassToUndefined(resolvedB)
1022
+ );
903
1023
  }
904
1024
  function readStaticConfigObject(configExpr, key, scope) {
905
1025
  for (const prop of configExpr.properties) {
@@ -1029,20 +1149,20 @@ function buildConditionalClassExpr(baseClasses, conditionalClasses) {
1029
1149
  if (conditionalClasses.length === 0) {
1030
1150
  return t__namespace.stringLiteral(baseClasses);
1031
1151
  }
1032
- const makeCondExpr = (cc) => t__namespace.conditionalExpression(
1152
+ const makeCondExpr = (cc, bare) => t__namespace.conditionalExpression(
1033
1153
  cc.test,
1034
- t__namespace.stringLiteral(cc.consequent),
1035
- t__namespace.stringLiteral(cc.alternate)
1154
+ bare && cc.consequent === "" ? t__namespace.identifier("undefined") : t__namespace.stringLiteral(cc.consequent),
1155
+ bare && cc.alternate === "" ? t__namespace.identifier("undefined") : t__namespace.stringLiteral(cc.alternate)
1036
1156
  );
1037
1157
  if (conditionalClasses.length === 1 && !baseClasses) {
1038
- return makeCondExpr(conditionalClasses[0]);
1158
+ return makeCondExpr(conditionalClasses[0], true);
1039
1159
  }
1040
1160
  const quasis = [];
1041
1161
  const exprs = [];
1042
1162
  for (let i = 0; i < conditionalClasses.length; i++) {
1043
1163
  const prefix = i === 0 ? baseClasses ? `${baseClasses} ` : "" : " ";
1044
1164
  quasis.push(t__namespace.templateElement({ raw: prefix, cooked: prefix }, false));
1045
- exprs.push(makeCondExpr(conditionalClasses[i]));
1165
+ exprs.push(makeCondExpr(conditionalClasses[i], false));
1046
1166
  }
1047
1167
  quasis.push(t__namespace.templateElement({ raw: "", cooked: "" }, true));
1048
1168
  return t__namespace.templateLiteral(quasis, exprs);
@@ -1522,6 +1642,10 @@ class CsszyxCompiler {
1522
1642
  }
1523
1643
  }
1524
1644
 
1645
+ function sortStrings(values) {
1646
+ return [...values].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1647
+ }
1648
+
1525
1649
  const CSS_VAR_REFERENCE_RE = /var\(\s*(--[\w-]+)/g;
1526
1650
  function scanGlobalVarUsages(source, filename = "file.tsx", options = {}) {
1527
1651
  if (!source.includes("--") && !source.includes("var(")) {
@@ -1731,7 +1855,7 @@ function extractVarReferences(value) {
1731
1855
  for (const match of value.matchAll(CSS_VAR_REFERENCE_RE)) {
1732
1856
  refs.add(match[1]);
1733
1857
  }
1734
- return [...refs].sort();
1858
+ return sortStrings(refs);
1735
1859
  }
1736
1860
  function shouldReportToken(name, tokenFilter) {
1737
1861
  return tokenFilter === null || tokenFilter.has(name);
@@ -2005,7 +2129,7 @@ class ManifestBuilder {
2005
2129
  * @returns {string} SHA-256 checksum
2006
2130
  */
2007
2131
  computeChecksum(tokens) {
2008
- const sortedKeys = Object.keys(tokens).sort();
2132
+ const sortedKeys = sortStrings(Object.keys(tokens));
2009
2133
  const sortedTokens = {};
2010
2134
  for (const key of sortedKeys) {
2011
2135
  sortedTokens[key] = tokens[key];
@@ -2631,6 +2755,29 @@ function transformOxc(source, filename, options) {
2631
2755
  transformed = true;
2632
2756
  return;
2633
2757
  }
2758
+ const nestedConditionalClassExpr = buildNestedConditionalClassExpression(
2759
+ expression,
2760
+ effectiveFilename,
2761
+ objectBindings,
2762
+ source,
2763
+ classes,
2764
+ globalVarAliases,
2765
+ cssVariableMap
2766
+ );
2767
+ if (nestedConditionalClassExpr) {
2768
+ if (classNameAttr || szAttrs.length > 1) {
2769
+ runtimeFallbackExpr = expression;
2770
+ runtimeFallbackAttr = szAttr;
2771
+ break;
2772
+ }
2773
+ edits.overwrite(
2774
+ szAttr.start,
2775
+ szAttr.end,
2776
+ `className={${nestedConditionalClassExpr}}`
2777
+ );
2778
+ transformed = true;
2779
+ return;
2780
+ }
2634
2781
  const partial = buildPartialObjectTransform(
2635
2782
  expression,
2636
2783
  effectiveFilename,
@@ -2769,7 +2916,7 @@ function transformOxc(source, filename, options) {
2769
2916
  ...existingRaw ? existingRaw.split(/\s+/).filter(Boolean) : [],
2770
2917
  ...szDerived
2771
2918
  ];
2772
- const mergedAttr = `className="${mergedClasses.join(" ")}"`;
2919
+ const mergedAttr = mergedClasses.length === 0 ? "className={undefined}" : `className="${mergedClasses.join(" ")}"`;
2773
2920
  if (classNameAttr) {
2774
2921
  edits.overwrite(classNameAttr.start, classNameAttr.end, mergedAttr);
2775
2922
  for (const szAttr of szAttrs) {
@@ -2867,7 +3014,7 @@ function extractElementName(nameNode) {
2867
3014
  }
2868
3015
  return "<unknown>";
2869
3016
  }
2870
- function astObjectToSzObject(node, filename, bindings) {
3017
+ function astObjectToSzObject(node, filename, bindings, branchPick) {
2871
3018
  const result = {};
2872
3019
  for (const propRaw of node.properties) {
2873
3020
  if (propRaw.type === "SpreadElement") {
@@ -2904,7 +3051,7 @@ function astObjectToSzObject(node, filename, bindings) {
2904
3051
  `unsupported key shape ${prop.key.type} at ${filename}:${prop.key.start}`
2905
3052
  );
2906
3053
  }
2907
- result[key] = astValueToSzValue(prop.value, filename, bindings);
3054
+ result[key] = astValueToSzValue(prop.value, filename, bindings, branchPick);
2908
3055
  }
2909
3056
  return result;
2910
3057
  }
@@ -3336,6 +3483,80 @@ function resolveObjectExpression(node, bindings) {
3336
3483
  }
3337
3484
  return null;
3338
3485
  }
3486
+ function countOxcConditionals(node) {
3487
+ let count = 0;
3488
+ for (const propRaw of node.properties) {
3489
+ if (propRaw.type !== "Property") {
3490
+ continue;
3491
+ }
3492
+ const value = propRaw.value;
3493
+ if (value.type === "ConditionalExpression") {
3494
+ count++;
3495
+ } else if (value.type === "ObjectExpression") {
3496
+ count += countOxcConditionals(value);
3497
+ }
3498
+ }
3499
+ return count;
3500
+ }
3501
+ function firstOxcConditional(node) {
3502
+ for (const propRaw of node.properties) {
3503
+ if (propRaw.type !== "Property") {
3504
+ continue;
3505
+ }
3506
+ const value = propRaw.value;
3507
+ if (value.type === "ConditionalExpression") {
3508
+ return value;
3509
+ }
3510
+ if (value.type === "ObjectExpression") {
3511
+ const found = firstOxcConditional(value);
3512
+ if (found) {
3513
+ return found;
3514
+ }
3515
+ }
3516
+ }
3517
+ return null;
3518
+ }
3519
+ function buildNestedConditionalClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3520
+ let topLevel = 0;
3521
+ for (const propRaw of node.properties) {
3522
+ if (propRaw.type === "Property" && propRaw.value.type === "ConditionalExpression") {
3523
+ topLevel++;
3524
+ }
3525
+ }
3526
+ const first = firstOxcConditional(node);
3527
+ if (topLevel !== 0 || countOxcConditionals(node) !== 1 || !first) {
3528
+ return null;
3529
+ }
3530
+ const compileBranch = (pick) => {
3531
+ try {
3532
+ return transformCore.transform(
3533
+ applyGlobalVarAliasesToSzObject(
3534
+ astObjectToSzObject(node, filename, bindings, pick),
3535
+ globalVarAliases,
3536
+ cssVariableMap
3537
+ )
3538
+ ).className;
3539
+ } catch (err) {
3540
+ if (err instanceof OxcNotImplementedError) {
3541
+ return null;
3542
+ }
3543
+ throw err;
3544
+ }
3545
+ };
3546
+ const consequent = compileBranch("consequent");
3547
+ const alternate = compileBranch("alternate");
3548
+ if (consequent === null || alternate === null) {
3549
+ return null;
3550
+ }
3551
+ for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
3552
+ if (cls) {
3553
+ classes.add(cls);
3554
+ }
3555
+ }
3556
+ const testSource = source.slice(first.test.start, first.test.end);
3557
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3558
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3559
+ }
3339
3560
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3340
3561
  let conditionalSpread = null;
3341
3562
  const otherProps = [];
@@ -3381,7 +3602,8 @@ function buildConditionalSpreadClassExpression(node, filename, bindings, source,
3381
3602
  }
3382
3603
  }
3383
3604
  const testSource = source.slice(conditionalSpread.test.start, conditionalSpread.test.end);
3384
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
3605
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3606
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3385
3607
  }
3386
3608
  function compileConditionalSpreadBranch(branch, otherProps, sourceNode, filename, bindings, globalVarAliases, cssVariableMap) {
3387
3609
  const branchObject = resolveObjectExpression(branch, bindings);
@@ -3444,7 +3666,11 @@ function buildPartialObjectTransform(node, filename, bindings, source, options,
3444
3666
  classParts.push(entry.consequent, entry.alternate);
3445
3667
  }
3446
3668
  const className = classParts.filter(Boolean).join(" ");
3447
- const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : `className="${className}"`;
3669
+ const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : className === "" ? (
3670
+ // An sz that lowers to zero classes emits `className={undefined}` so the
3671
+ // DOM has no `class` attribute, instead of the noisy `class=""`.
3672
+ "className={undefined}"
3673
+ ) : `className="${className}"`;
3448
3674
  const styleProps = [...partial.dynamicProps.entries()].filter(([id]) => !hoistedNames?.has(id)).map(
3449
3675
  ([, info]) => `${JSON.stringify(info.varName)}: ${generateStyleValueSource(info, source)}`
3450
3676
  );
@@ -4018,9 +4244,11 @@ function buildCSSVarClassName(info) {
4018
4244
  function buildConditionalClassSource(classParts, conditionals, source) {
4019
4245
  if (conditionals.length === 1) {
4020
4246
  const [entry] = conditionals;
4021
- const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${JSON.stringify(entry.consequent)} : ${JSON.stringify(entry.alternate)}`;
4022
4247
  const staticParts = classParts.slice(0, -2).filter(Boolean);
4023
- if (staticParts.length === 0) {
4248
+ const bare = staticParts.length === 0;
4249
+ const branch = (cls) => bare && cls === "" ? "undefined" : JSON.stringify(cls);
4250
+ const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${branch(entry.consequent)} : ${branch(entry.alternate)}`;
4251
+ if (bare) {
4024
4252
  return ternary;
4025
4253
  }
4026
4254
  return `\`${staticParts.join(" ")} \${${ternary}}\``;
@@ -4072,7 +4300,8 @@ function buildStaticConditionalClassExpression(node, filename, bindings, source,
4072
4300
  }
4073
4301
  }
4074
4302
  const testSource = source.slice(node.test.start, node.test.end);
4075
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
4303
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
4304
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
4076
4305
  }
4077
4306
  function resolveStaticClassString(node, filename, bindings, globalVarAliases, cssVariableMap) {
4078
4307
  const unwrapped = unwrapExpression(node);
@@ -4112,7 +4341,15 @@ function extractKeyName(key) {
4112
4341
  }
4113
4342
  return null;
4114
4343
  }
4115
- function astValueToSzValue(node, filename, bindings) {
4344
+ function astValueToSzValue(node, filename, bindings, branchPick) {
4345
+ if (branchPick && node.type === "ConditionalExpression") {
4346
+ return astValueToSzValue(
4347
+ node[branchPick],
4348
+ filename,
4349
+ bindings,
4350
+ branchPick
4351
+ );
4352
+ }
4116
4353
  if (node.type === "Literal") {
4117
4354
  const value = node.value;
4118
4355
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -4138,7 +4375,7 @@ function astValueToSzValue(node, filename, bindings) {
4138
4375
  );
4139
4376
  }
4140
4377
  if (node.type === "ObjectExpression") {
4141
- return astObjectToSzObject(node, filename, bindings);
4378
+ return astObjectToSzObject(node, filename, bindings, branchPick);
4142
4379
  }
4143
4380
  if (node.type === "Identifier" || node.type === "MemberExpression") {
4144
4381
  throw new OxcNotImplementedError(
@@ -4409,6 +4646,7 @@ exports.mergeOptions = mergeOptions;
4409
4646
  exports.parseManifest = parseManifest;
4410
4647
  exports.scanGlobalVarUsages = scanGlobalVarUsages;
4411
4648
  exports.serializeManifest = serializeManifest;
4649
+ exports.sortStrings = sortStrings;
4412
4650
  exports.transformOxc = transformOxc;
4413
4651
  exports.transformRust = transformRust;
4414
4652
  exports.transformRustBatch = transformRustBatch;
package/dist/index.d.cts CHANGED
@@ -581,6 +581,28 @@ declare const COLOR_PROPERTIES: Set<string>;
581
581
  */
582
582
  declare function getCSSVariableName(property: string, variantPrefix?: string): string;
583
583
 
584
+ /**
585
+ * Deterministic, locale-independent ascending sort for string collections.
586
+ *
587
+ * A bare `Array.prototype.sort()` coerces elements to strings, so it silently
588
+ * mis-orders numbers (`[10, 9]` → `[10, 9]`). Every ordered collection in this
589
+ * codebase is a set of identifiers — class names, mangle tokens, filenames,
590
+ * variant names, CSS-variable references — so the correct sort is lexicographic
591
+ * AND locale-independent (a locale-aware `localeCompare` would make build output
592
+ * order depend on the runner's locale and break reproducibility).
593
+ *
594
+ * This helper encodes exactly that, and its `T extends string` bound makes the
595
+ * intent compiler-checked: passing a `number[]` (or a `(string | number)[]`) is a
596
+ * type error, so a future numeric sort cannot slip through as a bare `.sort()`.
597
+ * The `no-restricted-syntax` lint rule forbids bare `.sort()` to route every sort
598
+ * through here or an explicit comparator.
599
+ *
600
+ * @template T - the (string) element type.
601
+ * @param values - the strings to sort (any iterable; not mutated).
602
+ * @returns a new array sorted ascending by UTF-16 code unit.
603
+ */
604
+ declare function sortStrings<T extends string>(values: Iterable<T>): T[];
605
+
584
606
  /**
585
607
  * Phase D production transform — `transformOxc()` is the oxc-parser +
586
608
  * magic-string replacement for `transformSourceCode()` (Babel). The
@@ -13639,5 +13661,5 @@ declare const DEFAULT_COMPILER_OPTIONS: Required<CompilerOptions>;
13639
13661
  */
13640
13662
  declare function mergeOptions(options?: Partial<CompilerOptions>): Required<CompilerOptions>;
13641
13663
 
13642
- export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_CATEGORY_MAP, PropertyCategory, SzObject, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
13664
+ export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_CATEGORY_MAP, PropertyCategory, SzObject, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, sortStrings, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
13643
13665
  export type { BackgroundProps, BorderProps, BorderRadiusValue, CSSVarUsage, ColorName, ColorObjectValue, ColorPropValue, ColorShade, ColorValue, CompilerOptions, ContainerSize, CssVariableMangleValue, CustomTheme, EffectsProps, FilterProps, FlexboxGridProps, FractionValue, GlobalVarAliasTableInput, GlobalVarUsageDiagnostic, GlobalVarUsageKind, GlobalVarUsageLocation, InteractivityProps, LayoutProps, NegativeSpacingValue, RecoveryManifest, RecoveryMode, RecoveryToken, ScanGlobalVarUsagesOptions, ShadowValue, SizingProps, SourceTransformResult, SpacingProps, SpacingScale, SpacingValue, SvgProps, SzPropValue, SzProps, SzPropsBase, TableProps, TokenData, TokenMetadata, TransformOxcResult, TransformProps, TransformRustFile, TransformSourceCodeOptions, TransitionAnimationProps, TypographyProps, VariantModifiers };
package/dist/index.d.mts CHANGED
@@ -581,6 +581,28 @@ declare const COLOR_PROPERTIES: Set<string>;
581
581
  */
582
582
  declare function getCSSVariableName(property: string, variantPrefix?: string): string;
583
583
 
584
+ /**
585
+ * Deterministic, locale-independent ascending sort for string collections.
586
+ *
587
+ * A bare `Array.prototype.sort()` coerces elements to strings, so it silently
588
+ * mis-orders numbers (`[10, 9]` → `[10, 9]`). Every ordered collection in this
589
+ * codebase is a set of identifiers — class names, mangle tokens, filenames,
590
+ * variant names, CSS-variable references — so the correct sort is lexicographic
591
+ * AND locale-independent (a locale-aware `localeCompare` would make build output
592
+ * order depend on the runner's locale and break reproducibility).
593
+ *
594
+ * This helper encodes exactly that, and its `T extends string` bound makes the
595
+ * intent compiler-checked: passing a `number[]` (or a `(string | number)[]`) is a
596
+ * type error, so a future numeric sort cannot slip through as a bare `.sort()`.
597
+ * The `no-restricted-syntax` lint rule forbids bare `.sort()` to route every sort
598
+ * through here or an explicit comparator.
599
+ *
600
+ * @template T - the (string) element type.
601
+ * @param values - the strings to sort (any iterable; not mutated).
602
+ * @returns a new array sorted ascending by UTF-16 code unit.
603
+ */
604
+ declare function sortStrings<T extends string>(values: Iterable<T>): T[];
605
+
584
606
  /**
585
607
  * Phase D production transform — `transformOxc()` is the oxc-parser +
586
608
  * magic-string replacement for `transformSourceCode()` (Babel). The
@@ -13639,5 +13661,5 @@ declare const DEFAULT_COMPILER_OPTIONS: Required<CompilerOptions>;
13639
13661
  */
13640
13662
  declare function mergeOptions(options?: Partial<CompilerOptions>): Required<CompilerOptions>;
13641
13663
 
13642
- export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_CATEGORY_MAP, PropertyCategory, SzObject, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
13664
+ export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_CATEGORY_MAP, PropertyCategory, SzObject, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, sortStrings, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
13643
13665
  export type { BackgroundProps, BorderProps, BorderRadiusValue, CSSVarUsage, ColorName, ColorObjectValue, ColorPropValue, ColorShade, ColorValue, CompilerOptions, ContainerSize, CssVariableMangleValue, CustomTheme, EffectsProps, FilterProps, FlexboxGridProps, FractionValue, GlobalVarAliasTableInput, GlobalVarUsageDiagnostic, GlobalVarUsageKind, GlobalVarUsageLocation, InteractivityProps, LayoutProps, NegativeSpacingValue, RecoveryManifest, RecoveryMode, RecoveryToken, ScanGlobalVarUsagesOptions, ShadowValue, SizingProps, SourceTransformResult, SpacingProps, SpacingScale, SpacingValue, SvgProps, SzPropValue, SzProps, SzPropsBase, TableProps, TokenData, TokenMetadata, TransformOxcResult, TransformProps, TransformRustFile, TransformSourceCodeOptions, TransitionAnimationProps, TypographyProps, VariantModifiers };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { init, version, transform_sz, encode } from '@csszyx/core';
2
- import { t as transform, s as setSzWarnLocation, f as formatSzWarnLocation, C as COLOR_PROPERTIES, P as PROPERTY_MAP, g as getCSSVariableName, a as PropertyCategory, K as KNOWN_VARIANTS, b as getVariantPrefix, c as getPropertyCategory, d as stripInvalidColorStrings } from './shared/compiler.DINxoUCF.mjs';
3
- export { B as BOOLEAN_SHORTHANDS, e as PROPERTY_CATEGORY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, i as isValidSzProp, n as normalizeClassName } from './shared/compiler.DINxoUCF.mjs';
2
+ import { t as transform, s as setSzWarnLocation, f as formatSzWarnLocation, C as COLOR_PROPERTIES, P as PROPERTY_MAP, g as getCSSVariableName, a as PropertyCategory, K as KNOWN_VARIANTS, b as getVariantPrefix, c as getPropertyCategory, d as stripInvalidColorStrings } from './shared/compiler.CghwJ6p5.mjs';
3
+ export { B as BOOLEAN_SHORTHANDS, e as PROPERTY_CATEGORY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, i as isValidSzProp, n as normalizeClassName } from './shared/compiler.CghwJ6p5.mjs';
4
4
  import { parseSync } from 'oxc-parser';
5
5
  import * as t from '@babel/types';
6
6
  import { createHash } from 'node:crypto';
@@ -226,6 +226,9 @@ function transformSourceCode(source, filename, options) {
226
226
  }
227
227
  const createMergedClassNameValue = (szExpr) => {
228
228
  if (!existingClassExpr) {
229
+ if (t.isStringLiteral(szExpr) && szExpr.value === "") {
230
+ return t.jsxExpressionContainer(t.identifier("undefined"));
231
+ }
229
232
  return t.isStringLiteral(szExpr) ? szExpr : t.jsxExpressionContainer(szExpr);
230
233
  }
231
234
  if (existingClassNameNode && path.parentPath?.isJSXOpeningElement()) {
@@ -368,6 +371,17 @@ function transformSourceCode(source, filename, options) {
368
371
  transformed = true;
369
372
  return;
370
373
  }
374
+ const hoistedNested = tryHoistNestedConditional(
375
+ flatExpression,
376
+ getBinding
377
+ );
378
+ if (hoistedNested !== null) {
379
+ path.node.name.name = "className";
380
+ path.node.value = createMergedClassNameValue(hoistedNested);
381
+ collectFromExpr(hoistedNested, collectedClasses);
382
+ transformed = true;
383
+ return;
384
+ }
371
385
  const partial = evaluatePartialObject$1(flatExpression);
372
386
  if (partial !== null && !partial.hasSpread && (partial.dynamicProps.size > 0 || partial.conditionalClasses.length > 0)) {
373
387
  const staticClasses = [];
@@ -807,6 +821,9 @@ function parseStyleStringToObjectExpr(styleStr) {
807
821
  }
808
822
  return t.objectExpression(objProps);
809
823
  }
824
+ function emptyClassToUndefined(node) {
825
+ return t.isStringLiteral(node) && node.value === "" ? t.identifier("undefined") : node;
826
+ }
810
827
  function tryStaticTransformNode(node, getBinding) {
811
828
  if (t.isTSAsExpression(node) || t.isTSSatisfiesExpression(node)) {
812
829
  return tryStaticTransformNode(node.expression, getBinding);
@@ -843,12 +860,111 @@ function tryStaticTransformNode(node, getBinding) {
843
860
  const consequent = tryStaticTransformNode(node.consequent, getBinding);
844
861
  const alternate = tryStaticTransformNode(node.alternate, getBinding);
845
862
  if (consequent !== null && alternate !== null) {
846
- return t.conditionalExpression(node.test, consequent, alternate);
863
+ return t.conditionalExpression(
864
+ node.test,
865
+ emptyClassToUndefined(consequent),
866
+ emptyClassToUndefined(alternate)
867
+ );
847
868
  }
848
869
  return null;
849
870
  }
850
871
  return null;
851
872
  }
873
+ function scanNestedConditionals(node) {
874
+ let topLevel = 0;
875
+ let nested = 0;
876
+ let test = null;
877
+ for (const prop of node.properties) {
878
+ if (!t.isObjectProperty(prop)) {
879
+ continue;
880
+ }
881
+ const value = prop.value;
882
+ if (t.isConditionalExpression(value)) {
883
+ topLevel++;
884
+ } else if (t.isObjectExpression(value)) {
885
+ nested += countAllConditionals(value);
886
+ test ??= firstConditionalTest(value);
887
+ }
888
+ }
889
+ return { topLevel, nested, test };
890
+ }
891
+ function countAllConditionals(node) {
892
+ let count = 0;
893
+ for (const prop of node.properties) {
894
+ if (!t.isObjectProperty(prop)) {
895
+ continue;
896
+ }
897
+ const value = prop.value;
898
+ if (t.isConditionalExpression(value)) {
899
+ count++;
900
+ } else if (t.isObjectExpression(value)) {
901
+ count += countAllConditionals(value);
902
+ }
903
+ }
904
+ return count;
905
+ }
906
+ function firstConditionalTest(node) {
907
+ for (const prop of node.properties) {
908
+ if (!t.isObjectProperty(prop)) {
909
+ continue;
910
+ }
911
+ const value = prop.value;
912
+ if (t.isConditionalExpression(value)) {
913
+ return value.test;
914
+ }
915
+ if (t.isObjectExpression(value)) {
916
+ const inner = firstConditionalTest(value);
917
+ if (inner) {
918
+ return inner;
919
+ }
920
+ }
921
+ }
922
+ return null;
923
+ }
924
+ function cloneObjectPickingBranch(node, pick) {
925
+ return t.objectExpression(
926
+ node.properties.map((prop) => {
927
+ if (!t.isObjectProperty(prop)) {
928
+ return t.cloneNode(prop);
929
+ }
930
+ const value = prop.value;
931
+ let nextValue = t.cloneNode(value);
932
+ if (t.isConditionalExpression(value)) {
933
+ nextValue = t.cloneNode(value[pick]);
934
+ } else if (t.isObjectExpression(value)) {
935
+ nextValue = cloneObjectPickingBranch(value, pick);
936
+ }
937
+ return t.objectProperty(
938
+ t.cloneNode(prop.key),
939
+ nextValue,
940
+ prop.computed,
941
+ prop.shorthand
942
+ );
943
+ })
944
+ );
945
+ }
946
+ function tryHoistNestedConditional(node, getBinding) {
947
+ const { topLevel, nested, test } = scanNestedConditionals(node);
948
+ if (topLevel !== 0 || nested !== 1 || test === null) {
949
+ return null;
950
+ }
951
+ const consequent = tryStaticTransformNode(
952
+ cloneObjectPickingBranch(node, "consequent"),
953
+ getBinding
954
+ );
955
+ const alternate = tryStaticTransformNode(
956
+ cloneObjectPickingBranch(node, "alternate"),
957
+ getBinding
958
+ );
959
+ if (!consequent || !alternate || !t.isStringLiteral(consequent) || !t.isStringLiteral(alternate)) {
960
+ return null;
961
+ }
962
+ return t.conditionalExpression(
963
+ test,
964
+ emptyClassToUndefined(consequent),
965
+ emptyClassToUndefined(alternate)
966
+ );
967
+ }
852
968
  function tryHoistConditionalSpread(node, getBinding) {
853
969
  let conditionalSpreadIdx = -1;
854
970
  let conditionalExpr = null;
@@ -880,7 +996,11 @@ function tryHoistConditionalSpread(node, getBinding) {
880
996
  if (!t.isStringLiteral(resolvedA) || !t.isStringLiteral(resolvedB)) {
881
997
  return null;
882
998
  }
883
- return t.conditionalExpression(conditionalExpr.test, resolvedA, resolvedB);
999
+ return t.conditionalExpression(
1000
+ conditionalExpr.test,
1001
+ emptyClassToUndefined(resolvedA),
1002
+ emptyClassToUndefined(resolvedB)
1003
+ );
884
1004
  }
885
1005
  function readStaticConfigObject(configExpr, key, scope) {
886
1006
  for (const prop of configExpr.properties) {
@@ -1010,20 +1130,20 @@ function buildConditionalClassExpr(baseClasses, conditionalClasses) {
1010
1130
  if (conditionalClasses.length === 0) {
1011
1131
  return t.stringLiteral(baseClasses);
1012
1132
  }
1013
- const makeCondExpr = (cc) => t.conditionalExpression(
1133
+ const makeCondExpr = (cc, bare) => t.conditionalExpression(
1014
1134
  cc.test,
1015
- t.stringLiteral(cc.consequent),
1016
- t.stringLiteral(cc.alternate)
1135
+ bare && cc.consequent === "" ? t.identifier("undefined") : t.stringLiteral(cc.consequent),
1136
+ bare && cc.alternate === "" ? t.identifier("undefined") : t.stringLiteral(cc.alternate)
1017
1137
  );
1018
1138
  if (conditionalClasses.length === 1 && !baseClasses) {
1019
- return makeCondExpr(conditionalClasses[0]);
1139
+ return makeCondExpr(conditionalClasses[0], true);
1020
1140
  }
1021
1141
  const quasis = [];
1022
1142
  const exprs = [];
1023
1143
  for (let i = 0; i < conditionalClasses.length; i++) {
1024
1144
  const prefix = i === 0 ? baseClasses ? `${baseClasses} ` : "" : " ";
1025
1145
  quasis.push(t.templateElement({ raw: prefix, cooked: prefix }, false));
1026
- exprs.push(makeCondExpr(conditionalClasses[i]));
1146
+ exprs.push(makeCondExpr(conditionalClasses[i], false));
1027
1147
  }
1028
1148
  quasis.push(t.templateElement({ raw: "", cooked: "" }, true));
1029
1149
  return t.templateLiteral(quasis, exprs);
@@ -1503,6 +1623,10 @@ class CsszyxCompiler {
1503
1623
  }
1504
1624
  }
1505
1625
 
1626
+ function sortStrings(values) {
1627
+ return [...values].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1628
+ }
1629
+
1506
1630
  const CSS_VAR_REFERENCE_RE = /var\(\s*(--[\w-]+)/g;
1507
1631
  function scanGlobalVarUsages(source, filename = "file.tsx", options = {}) {
1508
1632
  if (!source.includes("--") && !source.includes("var(")) {
@@ -1712,7 +1836,7 @@ function extractVarReferences(value) {
1712
1836
  for (const match of value.matchAll(CSS_VAR_REFERENCE_RE)) {
1713
1837
  refs.add(match[1]);
1714
1838
  }
1715
- return [...refs].sort();
1839
+ return sortStrings(refs);
1716
1840
  }
1717
1841
  function shouldReportToken(name, tokenFilter) {
1718
1842
  return tokenFilter === null || tokenFilter.has(name);
@@ -1986,7 +2110,7 @@ class ManifestBuilder {
1986
2110
  * @returns {string} SHA-256 checksum
1987
2111
  */
1988
2112
  computeChecksum(tokens) {
1989
- const sortedKeys = Object.keys(tokens).sort();
2113
+ const sortedKeys = sortStrings(Object.keys(tokens));
1990
2114
  const sortedTokens = {};
1991
2115
  for (const key of sortedKeys) {
1992
2116
  sortedTokens[key] = tokens[key];
@@ -2612,6 +2736,29 @@ function transformOxc(source, filename, options) {
2612
2736
  transformed = true;
2613
2737
  return;
2614
2738
  }
2739
+ const nestedConditionalClassExpr = buildNestedConditionalClassExpression(
2740
+ expression,
2741
+ effectiveFilename,
2742
+ objectBindings,
2743
+ source,
2744
+ classes,
2745
+ globalVarAliases,
2746
+ cssVariableMap
2747
+ );
2748
+ if (nestedConditionalClassExpr) {
2749
+ if (classNameAttr || szAttrs.length > 1) {
2750
+ runtimeFallbackExpr = expression;
2751
+ runtimeFallbackAttr = szAttr;
2752
+ break;
2753
+ }
2754
+ edits.overwrite(
2755
+ szAttr.start,
2756
+ szAttr.end,
2757
+ `className={${nestedConditionalClassExpr}}`
2758
+ );
2759
+ transformed = true;
2760
+ return;
2761
+ }
2615
2762
  const partial = buildPartialObjectTransform(
2616
2763
  expression,
2617
2764
  effectiveFilename,
@@ -2750,7 +2897,7 @@ function transformOxc(source, filename, options) {
2750
2897
  ...existingRaw ? existingRaw.split(/\s+/).filter(Boolean) : [],
2751
2898
  ...szDerived
2752
2899
  ];
2753
- const mergedAttr = `className="${mergedClasses.join(" ")}"`;
2900
+ const mergedAttr = mergedClasses.length === 0 ? "className={undefined}" : `className="${mergedClasses.join(" ")}"`;
2754
2901
  if (classNameAttr) {
2755
2902
  edits.overwrite(classNameAttr.start, classNameAttr.end, mergedAttr);
2756
2903
  for (const szAttr of szAttrs) {
@@ -2848,7 +2995,7 @@ function extractElementName(nameNode) {
2848
2995
  }
2849
2996
  return "<unknown>";
2850
2997
  }
2851
- function astObjectToSzObject(node, filename, bindings) {
2998
+ function astObjectToSzObject(node, filename, bindings, branchPick) {
2852
2999
  const result = {};
2853
3000
  for (const propRaw of node.properties) {
2854
3001
  if (propRaw.type === "SpreadElement") {
@@ -2885,7 +3032,7 @@ function astObjectToSzObject(node, filename, bindings) {
2885
3032
  `unsupported key shape ${prop.key.type} at ${filename}:${prop.key.start}`
2886
3033
  );
2887
3034
  }
2888
- result[key] = astValueToSzValue(prop.value, filename, bindings);
3035
+ result[key] = astValueToSzValue(prop.value, filename, bindings, branchPick);
2889
3036
  }
2890
3037
  return result;
2891
3038
  }
@@ -3317,6 +3464,80 @@ function resolveObjectExpression(node, bindings) {
3317
3464
  }
3318
3465
  return null;
3319
3466
  }
3467
+ function countOxcConditionals(node) {
3468
+ let count = 0;
3469
+ for (const propRaw of node.properties) {
3470
+ if (propRaw.type !== "Property") {
3471
+ continue;
3472
+ }
3473
+ const value = propRaw.value;
3474
+ if (value.type === "ConditionalExpression") {
3475
+ count++;
3476
+ } else if (value.type === "ObjectExpression") {
3477
+ count += countOxcConditionals(value);
3478
+ }
3479
+ }
3480
+ return count;
3481
+ }
3482
+ function firstOxcConditional(node) {
3483
+ for (const propRaw of node.properties) {
3484
+ if (propRaw.type !== "Property") {
3485
+ continue;
3486
+ }
3487
+ const value = propRaw.value;
3488
+ if (value.type === "ConditionalExpression") {
3489
+ return value;
3490
+ }
3491
+ if (value.type === "ObjectExpression") {
3492
+ const found = firstOxcConditional(value);
3493
+ if (found) {
3494
+ return found;
3495
+ }
3496
+ }
3497
+ }
3498
+ return null;
3499
+ }
3500
+ function buildNestedConditionalClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3501
+ let topLevel = 0;
3502
+ for (const propRaw of node.properties) {
3503
+ if (propRaw.type === "Property" && propRaw.value.type === "ConditionalExpression") {
3504
+ topLevel++;
3505
+ }
3506
+ }
3507
+ const first = firstOxcConditional(node);
3508
+ if (topLevel !== 0 || countOxcConditionals(node) !== 1 || !first) {
3509
+ return null;
3510
+ }
3511
+ const compileBranch = (pick) => {
3512
+ try {
3513
+ return transform(
3514
+ applyGlobalVarAliasesToSzObject(
3515
+ astObjectToSzObject(node, filename, bindings, pick),
3516
+ globalVarAliases,
3517
+ cssVariableMap
3518
+ )
3519
+ ).className;
3520
+ } catch (err) {
3521
+ if (err instanceof OxcNotImplementedError) {
3522
+ return null;
3523
+ }
3524
+ throw err;
3525
+ }
3526
+ };
3527
+ const consequent = compileBranch("consequent");
3528
+ const alternate = compileBranch("alternate");
3529
+ if (consequent === null || alternate === null) {
3530
+ return null;
3531
+ }
3532
+ for (const cls of `${consequent} ${alternate}`.split(/\s+/)) {
3533
+ if (cls) {
3534
+ classes.add(cls);
3535
+ }
3536
+ }
3537
+ const testSource = source.slice(first.test.start, first.test.end);
3538
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3539
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3540
+ }
3320
3541
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3321
3542
  let conditionalSpread = null;
3322
3543
  const otherProps = [];
@@ -3362,7 +3583,8 @@ function buildConditionalSpreadClassExpression(node, filename, bindings, source,
3362
3583
  }
3363
3584
  }
3364
3585
  const testSource = source.slice(conditionalSpread.test.start, conditionalSpread.test.end);
3365
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
3586
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
3587
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
3366
3588
  }
3367
3589
  function compileConditionalSpreadBranch(branch, otherProps, sourceNode, filename, bindings, globalVarAliases, cssVariableMap) {
3368
3590
  const branchObject = resolveObjectExpression(branch, bindings);
@@ -3425,7 +3647,11 @@ function buildPartialObjectTransform(node, filename, bindings, source, options,
3425
3647
  classParts.push(entry.consequent, entry.alternate);
3426
3648
  }
3427
3649
  const className = classParts.filter(Boolean).join(" ");
3428
- const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : `className="${className}"`;
3650
+ const classNameAttr = partial.conditionalClasses.length > 0 ? `className={${buildConditionalClassSource(classParts, partial.conditionalClasses, source)}}` : className === "" ? (
3651
+ // An sz that lowers to zero classes emits `className={undefined}` so the
3652
+ // DOM has no `class` attribute, instead of the noisy `class=""`.
3653
+ "className={undefined}"
3654
+ ) : `className="${className}"`;
3429
3655
  const styleProps = [...partial.dynamicProps.entries()].filter(([id]) => !hoistedNames?.has(id)).map(
3430
3656
  ([, info]) => `${JSON.stringify(info.varName)}: ${generateStyleValueSource(info, source)}`
3431
3657
  );
@@ -3999,9 +4225,11 @@ function buildCSSVarClassName(info) {
3999
4225
  function buildConditionalClassSource(classParts, conditionals, source) {
4000
4226
  if (conditionals.length === 1) {
4001
4227
  const [entry] = conditionals;
4002
- const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${JSON.stringify(entry.consequent)} : ${JSON.stringify(entry.alternate)}`;
4003
4228
  const staticParts = classParts.slice(0, -2).filter(Boolean);
4004
- if (staticParts.length === 0) {
4229
+ const bare = staticParts.length === 0;
4230
+ const branch = (cls) => bare && cls === "" ? "undefined" : JSON.stringify(cls);
4231
+ const ternary = `${source.slice(entry.test.start, entry.test.end)} ? ${branch(entry.consequent)} : ${branch(entry.alternate)}`;
4232
+ if (bare) {
4005
4233
  return ternary;
4006
4234
  }
4007
4235
  return `\`${staticParts.join(" ")} \${${ternary}}\``;
@@ -4053,7 +4281,8 @@ function buildStaticConditionalClassExpression(node, filename, bindings, source,
4053
4281
  }
4054
4282
  }
4055
4283
  const testSource = source.slice(node.test.start, node.test.end);
4056
- return `${testSource} ? ${JSON.stringify(consequent)} : ${JSON.stringify(alternate)}`;
4284
+ const branch = (cls) => cls === "" ? "undefined" : JSON.stringify(cls);
4285
+ return `${testSource} ? ${branch(consequent)} : ${branch(alternate)}`;
4057
4286
  }
4058
4287
  function resolveStaticClassString(node, filename, bindings, globalVarAliases, cssVariableMap) {
4059
4288
  const unwrapped = unwrapExpression(node);
@@ -4093,7 +4322,15 @@ function extractKeyName(key) {
4093
4322
  }
4094
4323
  return null;
4095
4324
  }
4096
- function astValueToSzValue(node, filename, bindings) {
4325
+ function astValueToSzValue(node, filename, bindings, branchPick) {
4326
+ if (branchPick && node.type === "ConditionalExpression") {
4327
+ return astValueToSzValue(
4328
+ node[branchPick],
4329
+ filename,
4330
+ bindings,
4331
+ branchPick
4332
+ );
4333
+ }
4097
4334
  if (node.type === "Literal") {
4098
4335
  const value = node.value;
4099
4336
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -4119,7 +4356,7 @@ function astValueToSzValue(node, filename, bindings) {
4119
4356
  );
4120
4357
  }
4121
4358
  if (node.type === "ObjectExpression") {
4122
- return astObjectToSzObject(node, filename, bindings);
4359
+ return astObjectToSzObject(node, filename, bindings, branchPick);
4123
4360
  }
4124
4361
  if (node.type === "Identifier" || node.type === "MemberExpression") {
4125
4362
  throw new OxcNotImplementedError(
@@ -4358,4 +4595,4 @@ function mergeOptions(options = {}) {
4358
4595
  };
4359
4596
  }
4360
4597
 
4361
- export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, KNOWN_VARIANTS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_MAP, PropertyCategory, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, transform, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
4598
+ export { COLOR_PROPERTIES, CsszyxCompiler, DEFAULT_COMPILER_OPTIONS, KNOWN_VARIANTS, ManifestBuilder, OxcNotImplementedError, OxcRustNotImplementedError, PROPERTY_MAP, PropertyCategory, VERSION, buildParentMap, createRecoveryToken, ensureRustTransformAvailable, generateRecoveryToken, getCSSVariableName, getPropertyCategory, hoistCSSVariables, injectRecoveryToken, isRustTransformAvailable, isValidRecoveryMode, mergeOptions, parseManifest, scanGlobalVarUsages, serializeManifest, sortStrings, transform, transformOxc, transformRust, transformRustBatch, transformSourceCode, validateManifest, validateSzRecover };
@@ -1496,6 +1496,16 @@ function handleSupports(supportsObj, prefix) {
1496
1496
  }
1497
1497
  let szTransformDepth = 0;
1498
1498
  let szWarnLocation;
1499
+ let szHintedProjectScan = false;
1500
+ function hintProjectScanOnce(location) {
1501
+ if (szHintedProjectScan || !location || process.env.CSSZYX_NO_PROJECT_SCAN_HINT === "1") {
1502
+ return;
1503
+ }
1504
+ szHintedProjectScan = true;
1505
+ console.warn(
1506
+ "[csszyx] Tip: run `npx @csszyx/cli check` to scan every file for sz key issues at once (dev warnings only surface files as you open them)."
1507
+ );
1508
+ }
1499
1509
  function setSzWarnLocation(location) {
1500
1510
  szWarnLocation = location;
1501
1511
  }
@@ -2370,6 +2380,7 @@ function transformImpl(szProp, prefix, mangleMap) {
2370
2380
  `[csszyx] Unknown property "${rawKey}" in sz prop${at}. This will be ignored. Check for typos.`
2371
2381
  );
2372
2382
  }
2383
+ hintProjectScanOnce(szWarnLocation);
2373
2384
  }
2374
2385
  }
2375
2386
  if (value === true) {
@@ -1498,6 +1498,16 @@ function handleSupports(supportsObj, prefix) {
1498
1498
  }
1499
1499
  let szTransformDepth = 0;
1500
1500
  let szWarnLocation;
1501
+ let szHintedProjectScan = false;
1502
+ function hintProjectScanOnce(location) {
1503
+ if (szHintedProjectScan || !location || process.env.CSSZYX_NO_PROJECT_SCAN_HINT === "1") {
1504
+ return;
1505
+ }
1506
+ szHintedProjectScan = true;
1507
+ console.warn(
1508
+ "[csszyx] Tip: run `npx @csszyx/cli check` to scan every file for sz key issues at once (dev warnings only surface files as you open them)."
1509
+ );
1510
+ }
1501
1511
  function setSzWarnLocation(location) {
1502
1512
  szWarnLocation = location;
1503
1513
  }
@@ -2372,6 +2382,7 @@ function transformImpl(szProp, prefix, mangleMap) {
2372
2382
  `[csszyx] Unknown property "${rawKey}" in sz prop${at}. This will be ignored. Check for typos.`
2373
2383
  );
2374
2384
  }
2385
+ hintProjectScanOnce(szWarnLocation);
2375
2386
  }
2376
2387
  }
2377
2388
  if (value === true) {
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const transformCore = require('./shared/compiler.DIYC1vdY.cjs');
3
+ const transformCore = require('./shared/compiler.mibv6qPF.cjs');
4
4
 
5
5
 
6
6
 
@@ -1 +1 @@
1
- export { B as BOOLEAN_SHORTHANDS, K as KNOWN_VARIANTS, M as MAX_SZ_DEPTH, P as PROPERTY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, j as SzDepthError, V as VARIANT_MAP, f as formatSzWarnLocation, b as getVariantPrefix, k as isForbiddenSzKey, i as isValidSzProp, l as normalizeArbitraryValue, m as normalizeArbitraryVariant, n as normalizeClassName, s as setSzWarnLocation, t as transform } from './shared/compiler.DINxoUCF.mjs';
1
+ export { B as BOOLEAN_SHORTHANDS, K as KNOWN_VARIANTS, M as MAX_SZ_DEPTH, P as PROPERTY_MAP, R as REMOVED_BOOLEAN_SUGAR, S as SPECIAL_VARIANTS, h as SUGGESTION_MAP, j as SzDepthError, V as VARIANT_MAP, f as formatSzWarnLocation, b as getVariantPrefix, k as isForbiddenSzKey, i as isValidSzProp, l as normalizeArbitraryValue, m as normalizeArbitraryVariant, n as normalizeClassName, s as setSzWarnLocation, t as transform } from './shared/compiler.CghwJ6p5.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/compiler",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
4
4
  "description": "Core compiler and transformation logic for csszyx",
5
5
  "keywords": [
6
6
  "csszyx",
@@ -65,7 +65,7 @@
65
65
  "@babel/types": "^7.23.6",
66
66
  "magic-string": "0.30.21",
67
67
  "oxc-parser": "0.131.0",
68
- "@csszyx/core": "0.10.7"
68
+ "@csszyx/core": "0.10.9"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__core": "^7.20.5",
@@ -74,7 +74,7 @@
74
74
  "csstype": "^3.0.0",
75
75
  "typescript": "^6.0.3",
76
76
  "unbuild": "^3.6.1",
77
- "vitest": "^4.1.6"
77
+ "vitest": "^4.1.9"
78
78
  },
79
79
  "sideEffects": false,
80
80
  "engines": {