@csszyx/compiler 0.10.8 → 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
@@ -390,6 +390,17 @@ function transformSourceCode(source, filename, options) {
390
390
  transformed = true;
391
391
  return;
392
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
+ }
393
404
  const partial = evaluatePartialObject$1(flatExpression);
394
405
  if (partial !== null && !partial.hasSpread && (partial.dynamicProps.size > 0 || partial.conditionalClasses.length > 0)) {
395
406
  const staticClasses = [];
@@ -878,6 +889,101 @@ function tryStaticTransformNode(node, getBinding) {
878
889
  }
879
890
  return null;
880
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
+ }
881
987
  function tryHoistConditionalSpread(node, getBinding) {
882
988
  let conditionalSpreadIdx = -1;
883
989
  let conditionalExpr = null;
@@ -1536,6 +1642,10 @@ class CsszyxCompiler {
1536
1642
  }
1537
1643
  }
1538
1644
 
1645
+ function sortStrings(values) {
1646
+ return [...values].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1647
+ }
1648
+
1539
1649
  const CSS_VAR_REFERENCE_RE = /var\(\s*(--[\w-]+)/g;
1540
1650
  function scanGlobalVarUsages(source, filename = "file.tsx", options = {}) {
1541
1651
  if (!source.includes("--") && !source.includes("var(")) {
@@ -1745,7 +1855,7 @@ function extractVarReferences(value) {
1745
1855
  for (const match of value.matchAll(CSS_VAR_REFERENCE_RE)) {
1746
1856
  refs.add(match[1]);
1747
1857
  }
1748
- return [...refs].sort();
1858
+ return sortStrings(refs);
1749
1859
  }
1750
1860
  function shouldReportToken(name, tokenFilter) {
1751
1861
  return tokenFilter === null || tokenFilter.has(name);
@@ -2019,7 +2129,7 @@ class ManifestBuilder {
2019
2129
  * @returns {string} SHA-256 checksum
2020
2130
  */
2021
2131
  computeChecksum(tokens) {
2022
- const sortedKeys = Object.keys(tokens).sort();
2132
+ const sortedKeys = sortStrings(Object.keys(tokens));
2023
2133
  const sortedTokens = {};
2024
2134
  for (const key of sortedKeys) {
2025
2135
  sortedTokens[key] = tokens[key];
@@ -2645,6 +2755,29 @@ function transformOxc(source, filename, options) {
2645
2755
  transformed = true;
2646
2756
  return;
2647
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
+ }
2648
2781
  const partial = buildPartialObjectTransform(
2649
2782
  expression,
2650
2783
  effectiveFilename,
@@ -2881,7 +3014,7 @@ function extractElementName(nameNode) {
2881
3014
  }
2882
3015
  return "<unknown>";
2883
3016
  }
2884
- function astObjectToSzObject(node, filename, bindings) {
3017
+ function astObjectToSzObject(node, filename, bindings, branchPick) {
2885
3018
  const result = {};
2886
3019
  for (const propRaw of node.properties) {
2887
3020
  if (propRaw.type === "SpreadElement") {
@@ -2918,7 +3051,7 @@ function astObjectToSzObject(node, filename, bindings) {
2918
3051
  `unsupported key shape ${prop.key.type} at ${filename}:${prop.key.start}`
2919
3052
  );
2920
3053
  }
2921
- result[key] = astValueToSzValue(prop.value, filename, bindings);
3054
+ result[key] = astValueToSzValue(prop.value, filename, bindings, branchPick);
2922
3055
  }
2923
3056
  return result;
2924
3057
  }
@@ -3350,6 +3483,80 @@ function resolveObjectExpression(node, bindings) {
3350
3483
  }
3351
3484
  return null;
3352
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
+ }
3353
3560
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3354
3561
  let conditionalSpread = null;
3355
3562
  const otherProps = [];
@@ -4134,7 +4341,15 @@ function extractKeyName(key) {
4134
4341
  }
4135
4342
  return null;
4136
4343
  }
4137
- 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
+ }
4138
4353
  if (node.type === "Literal") {
4139
4354
  const value = node.value;
4140
4355
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -4160,7 +4375,7 @@ function astValueToSzValue(node, filename, bindings) {
4160
4375
  );
4161
4376
  }
4162
4377
  if (node.type === "ObjectExpression") {
4163
- return astObjectToSzObject(node, filename, bindings);
4378
+ return astObjectToSzObject(node, filename, bindings, branchPick);
4164
4379
  }
4165
4380
  if (node.type === "Identifier" || node.type === "MemberExpression") {
4166
4381
  throw new OxcNotImplementedError(
@@ -4431,6 +4646,7 @@ exports.mergeOptions = mergeOptions;
4431
4646
  exports.parseManifest = parseManifest;
4432
4647
  exports.scanGlobalVarUsages = scanGlobalVarUsages;
4433
4648
  exports.serializeManifest = serializeManifest;
4649
+ exports.sortStrings = sortStrings;
4434
4650
  exports.transformOxc = transformOxc;
4435
4651
  exports.transformRust = transformRust;
4436
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
@@ -371,6 +371,17 @@ function transformSourceCode(source, filename, options) {
371
371
  transformed = true;
372
372
  return;
373
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
+ }
374
385
  const partial = evaluatePartialObject$1(flatExpression);
375
386
  if (partial !== null && !partial.hasSpread && (partial.dynamicProps.size > 0 || partial.conditionalClasses.length > 0)) {
376
387
  const staticClasses = [];
@@ -859,6 +870,101 @@ function tryStaticTransformNode(node, getBinding) {
859
870
  }
860
871
  return null;
861
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
+ }
862
968
  function tryHoistConditionalSpread(node, getBinding) {
863
969
  let conditionalSpreadIdx = -1;
864
970
  let conditionalExpr = null;
@@ -1517,6 +1623,10 @@ class CsszyxCompiler {
1517
1623
  }
1518
1624
  }
1519
1625
 
1626
+ function sortStrings(values) {
1627
+ return [...values].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1628
+ }
1629
+
1520
1630
  const CSS_VAR_REFERENCE_RE = /var\(\s*(--[\w-]+)/g;
1521
1631
  function scanGlobalVarUsages(source, filename = "file.tsx", options = {}) {
1522
1632
  if (!source.includes("--") && !source.includes("var(")) {
@@ -1726,7 +1836,7 @@ function extractVarReferences(value) {
1726
1836
  for (const match of value.matchAll(CSS_VAR_REFERENCE_RE)) {
1727
1837
  refs.add(match[1]);
1728
1838
  }
1729
- return [...refs].sort();
1839
+ return sortStrings(refs);
1730
1840
  }
1731
1841
  function shouldReportToken(name, tokenFilter) {
1732
1842
  return tokenFilter === null || tokenFilter.has(name);
@@ -2000,7 +2110,7 @@ class ManifestBuilder {
2000
2110
  * @returns {string} SHA-256 checksum
2001
2111
  */
2002
2112
  computeChecksum(tokens) {
2003
- const sortedKeys = Object.keys(tokens).sort();
2113
+ const sortedKeys = sortStrings(Object.keys(tokens));
2004
2114
  const sortedTokens = {};
2005
2115
  for (const key of sortedKeys) {
2006
2116
  sortedTokens[key] = tokens[key];
@@ -2626,6 +2736,29 @@ function transformOxc(source, filename, options) {
2626
2736
  transformed = true;
2627
2737
  return;
2628
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
+ }
2629
2762
  const partial = buildPartialObjectTransform(
2630
2763
  expression,
2631
2764
  effectiveFilename,
@@ -2862,7 +2995,7 @@ function extractElementName(nameNode) {
2862
2995
  }
2863
2996
  return "<unknown>";
2864
2997
  }
2865
- function astObjectToSzObject(node, filename, bindings) {
2998
+ function astObjectToSzObject(node, filename, bindings, branchPick) {
2866
2999
  const result = {};
2867
3000
  for (const propRaw of node.properties) {
2868
3001
  if (propRaw.type === "SpreadElement") {
@@ -2899,7 +3032,7 @@ function astObjectToSzObject(node, filename, bindings) {
2899
3032
  `unsupported key shape ${prop.key.type} at ${filename}:${prop.key.start}`
2900
3033
  );
2901
3034
  }
2902
- result[key] = astValueToSzValue(prop.value, filename, bindings);
3035
+ result[key] = astValueToSzValue(prop.value, filename, bindings, branchPick);
2903
3036
  }
2904
3037
  return result;
2905
3038
  }
@@ -3331,6 +3464,80 @@ function resolveObjectExpression(node, bindings) {
3331
3464
  }
3332
3465
  return null;
3333
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
+ }
3334
3541
  function buildConditionalSpreadClassExpression(node, filename, bindings, source, classes, globalVarAliases, cssVariableMap) {
3335
3542
  let conditionalSpread = null;
3336
3543
  const otherProps = [];
@@ -4115,7 +4322,15 @@ function extractKeyName(key) {
4115
4322
  }
4116
4323
  return null;
4117
4324
  }
4118
- 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
+ }
4119
4334
  if (node.type === "Literal") {
4120
4335
  const value = node.value;
4121
4336
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -4141,7 +4356,7 @@ function astValueToSzValue(node, filename, bindings) {
4141
4356
  );
4142
4357
  }
4143
4358
  if (node.type === "ObjectExpression") {
4144
- return astObjectToSzObject(node, filename, bindings);
4359
+ return astObjectToSzObject(node, filename, bindings, branchPick);
4145
4360
  }
4146
4361
  if (node.type === "Identifier" || node.type === "MemberExpression") {
4147
4362
  throw new OxcNotImplementedError(
@@ -4380,4 +4595,4 @@ function mergeOptions(options = {}) {
4380
4595
  };
4381
4596
  }
4382
4597
 
4383
- 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@csszyx/compiler",
3
- "version": "0.10.8",
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.8"
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": {