@bamboocss/vite 1.23.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -881,7 +881,7 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
881
881
  * an unresolved variant does not merely omit a class, it can change which of several the
882
882
  * recipe applies — so a partially-known selection is not foldable at all.
883
883
  */
884
- const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
884
+ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
885
885
  if (!entry || entry === AMBIGUOUS) return {
886
886
  kind: "decline",
887
887
  reason: "unknown-recipe"
@@ -907,6 +907,13 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
907
907
  const selection = {};
908
908
  /** Variant → the source expression selecting it, for axes that stay runtime decisions. */
909
909
  const dynamicAxes = /* @__PURE__ */ new Map();
910
+ /**
911
+ * Variants whose expression could run something, in the order the source evaluates them.
912
+ *
913
+ * The text is kept, not just the key: a later property writing the same key replaces the
914
+ * entry in `dynamicAxes`, and the expression recorded here would then never be emitted.
915
+ */
916
+ const effectful = [];
910
917
  if (args.length === 1) {
911
918
  const arg = args[0];
912
919
  if (!arg || !ts_morph.Node.isObjectLiteralExpression(arg)) return {
@@ -937,14 +944,27 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
937
944
  kind: "decline",
938
945
  reason: "dynamic"
939
946
  };
940
- const literal = literalValue(property.getInitializer());
947
+ const initializer = property.getInitializer();
948
+ if (initializer && !isInert(initializer)) {
949
+ if (!Object.hasOwn(config.variants ?? {}, key)) return {
950
+ kind: "decline",
951
+ reason: "dynamic"
952
+ };
953
+ effectful.push({
954
+ key,
955
+ text: initializer.getText()
956
+ });
957
+ dynamicAxes.set(key, initializer.getText());
958
+ delete selection[key];
959
+ continue;
960
+ }
961
+ const literal = literalValue(initializer);
941
962
  if (literal !== void 0) {
942
963
  selection[key] = literal;
943
964
  dynamicAxes.delete(key);
944
965
  continue;
945
966
  }
946
967
  if (!resolvedSelection || !Object.hasOwn(resolvedSelection, key)) {
947
- const initializer = property.getInitializer();
948
968
  if (!initializer) return {
949
969
  kind: "decline",
950
970
  reason: "dynamic"
@@ -962,24 +982,47 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
962
982
  dynamicAxes.delete(key);
963
983
  }
964
984
  }
985
+ /**
986
+ * Every expression that could run something has to reach the output carrying its own text.
987
+ *
988
+ * A later property writing the same key replaces it in `dynamicAxes` — `badge({ tone: a(),
989
+ * tone: 'b' })` is last-wins for the *value*, but `a()` still runs, and emitting only the
990
+ * literal would delete it. Duplicate keys are a type error in TypeScript; the fold does not
991
+ * typecheck and does transform `.js`, so this is reachable.
992
+ */
993
+ const everyEffectSurvives = () => effectful.every(({ key, text }) => dynamicAxes.get(key) === text);
965
994
  const merged = {
966
995
  ...config.defaultVariants ?? {},
967
996
  ...(0, _bamboocss_shared.compact)(selection)
968
997
  };
969
998
  const format = (0, _bamboocss_core.classFormatter)(ctx);
970
- if (dynamicAxes.size === 0) return {
971
- kind: "class",
972
- className: (0, _bamboocss_shared.getRecipeClassNames)(name, config.variants, merged, ctx.utility.separator, format)
973
- };
974
- for (const key of [...dynamicAxes.keys()]) if (!config.variants?.[key]) dynamicAxes.delete(key);
975
999
  if (dynamicAxes.size === 0) {
976
- const staticOnly = { ...merged };
977
- for (const key of dynamicAxes.keys()) delete staticOnly[key];
1000
+ if (!everyEffectSurvives()) return {
1001
+ kind: "decline",
1002
+ reason: "dynamic"
1003
+ };
978
1004
  return {
979
1005
  kind: "class",
980
- className: (0, _bamboocss_shared.getRecipeClassNames)(name, config.variants, staticOnly, ctx.utility.separator, format)
1006
+ className: (0, _bamboocss_shared.getRecipeClassNames)(name, config.variants, merged, ctx.utility.separator, format)
1007
+ };
1008
+ }
1009
+ if (!everyEffectSurvives()) return {
1010
+ kind: "decline",
1011
+ reason: "dynamic"
1012
+ };
1013
+ if (effectful.length > 1) {
1014
+ const variantOrder = Object.keys(config.variants ?? {});
1015
+ const keys = effectful.map((entry) => entry.key);
1016
+ if ([...keys].sort((a, b) => variantOrder.indexOf(a) - variantOrder.indexOf(b)).join("\0") !== keys.join("\0")) return {
1017
+ kind: "decline",
1018
+ reason: "dynamic"
981
1019
  };
982
1020
  }
1021
+ for (const key of [...dynamicAxes.keys()]) if (!Object.hasOwn(config.variants ?? {}, key)) dynamicAxes.delete(key);
1022
+ if (dynamicAxes.size === 0) return {
1023
+ kind: "class",
1024
+ className: (0, _bamboocss_shared.getRecipeClassNames)(name, config.variants, merged, ctx.utility.separator, format)
1025
+ };
983
1026
  const ownClass = format(name);
984
1027
  const parts = [JSON.stringify(ownClass)];
985
1028
  const classNames = [ownClass];
@@ -988,7 +1031,8 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
988
1031
  if (expression === void 0) {
989
1032
  const value = merged[key];
990
1033
  if (value == null) continue;
991
- if (config.variants?.[key]?.[value] == null) continue;
1034
+ const declared = config.variants?.[key];
1035
+ if (!declared || !Object.hasOwn(declared, value) || declared[value] == null) continue;
992
1036
  const className = format(`${name}--${key}${ctx.utility.separator}${(0, _bamboocss_shared.withoutSpace)(value)}`);
993
1037
  parts.push(JSON.stringify(` ${className}`));
994
1038
  classNames.push(className);
@@ -1635,10 +1679,7 @@ const foldSource = (options) => {
1635
1679
  recipeCalls.set(name, tally);
1636
1680
  const resolvedSelection = item.data?.length === 1 ? item.data[0] : void 0;
1637
1681
  const entry = recipeConfigs.get(name);
1638
- const lowered = ts_morph.Node.isCallExpression(call) && call.getArguments().every(isInertExpression) ? lowerRecipeCall(call, entry, ctx, resolvedSelection) : {
1639
- kind: "decline",
1640
- reason: "dynamic"
1641
- };
1682
+ const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
1642
1683
  if (lowered.kind === "expression") {
1643
1684
  const helper = ensureRecipeHelperImport(call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1644
1685
  if (helper) {
package/dist/index.d.cts CHANGED
@@ -2,7 +2,6 @@ import { Plugin } from "vite";
2
2
  import { Context } from "@bamboocss/core";
3
3
  import { Dict, ParserResultInterface } from "@bamboocss/types";
4
4
  import MagicString from "magic-string";
5
-
6
5
  //#region src/css.d.ts
7
6
  /**
8
7
  * What a project imports to get the stylesheet.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import MagicString from "magic-string";
2
+ import { Node } from "ts-morph";
2
3
  import { Context } from "@bamboocss/core";
3
4
  import { Plugin } from "vite";
4
5
  import { Dict, ParserResultInterface } from "@bamboocss/types";
package/dist/index.mjs CHANGED
@@ -854,7 +854,7 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
854
854
  * an unresolved variant does not merely omit a class, it can change which of several the
855
855
  * recipe applies — so a partially-known selection is not foldable at all.
856
856
  */
857
- const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
857
+ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
858
858
  if (!entry || entry === AMBIGUOUS) return {
859
859
  kind: "decline",
860
860
  reason: "unknown-recipe"
@@ -880,6 +880,13 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
880
880
  const selection = {};
881
881
  /** Variant → the source expression selecting it, for axes that stay runtime decisions. */
882
882
  const dynamicAxes = /* @__PURE__ */ new Map();
883
+ /**
884
+ * Variants whose expression could run something, in the order the source evaluates them.
885
+ *
886
+ * The text is kept, not just the key: a later property writing the same key replaces the
887
+ * entry in `dynamicAxes`, and the expression recorded here would then never be emitted.
888
+ */
889
+ const effectful = [];
883
890
  if (args.length === 1) {
884
891
  const arg = args[0];
885
892
  if (!arg || !Node.isObjectLiteralExpression(arg)) return {
@@ -910,14 +917,27 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
910
917
  kind: "decline",
911
918
  reason: "dynamic"
912
919
  };
913
- const literal = literalValue(property.getInitializer());
920
+ const initializer = property.getInitializer();
921
+ if (initializer && !isInert(initializer)) {
922
+ if (!Object.hasOwn(config.variants ?? {}, key)) return {
923
+ kind: "decline",
924
+ reason: "dynamic"
925
+ };
926
+ effectful.push({
927
+ key,
928
+ text: initializer.getText()
929
+ });
930
+ dynamicAxes.set(key, initializer.getText());
931
+ delete selection[key];
932
+ continue;
933
+ }
934
+ const literal = literalValue(initializer);
914
935
  if (literal !== void 0) {
915
936
  selection[key] = literal;
916
937
  dynamicAxes.delete(key);
917
938
  continue;
918
939
  }
919
940
  if (!resolvedSelection || !Object.hasOwn(resolvedSelection, key)) {
920
- const initializer = property.getInitializer();
921
941
  if (!initializer) return {
922
942
  kind: "decline",
923
943
  reason: "dynamic"
@@ -935,24 +955,47 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
935
955
  dynamicAxes.delete(key);
936
956
  }
937
957
  }
958
+ /**
959
+ * Every expression that could run something has to reach the output carrying its own text.
960
+ *
961
+ * A later property writing the same key replaces it in `dynamicAxes` — `badge({ tone: a(),
962
+ * tone: 'b' })` is last-wins for the *value*, but `a()` still runs, and emitting only the
963
+ * literal would delete it. Duplicate keys are a type error in TypeScript; the fold does not
964
+ * typecheck and does transform `.js`, so this is reachable.
965
+ */
966
+ const everyEffectSurvives = () => effectful.every(({ key, text }) => dynamicAxes.get(key) === text);
938
967
  const merged = {
939
968
  ...config.defaultVariants ?? {},
940
969
  ...compact(selection)
941
970
  };
942
971
  const format = classFormatter(ctx);
943
- if (dynamicAxes.size === 0) return {
944
- kind: "class",
945
- className: getRecipeClassNames(name, config.variants, merged, ctx.utility.separator, format)
946
- };
947
- for (const key of [...dynamicAxes.keys()]) if (!config.variants?.[key]) dynamicAxes.delete(key);
948
972
  if (dynamicAxes.size === 0) {
949
- const staticOnly = { ...merged };
950
- for (const key of dynamicAxes.keys()) delete staticOnly[key];
973
+ if (!everyEffectSurvives()) return {
974
+ kind: "decline",
975
+ reason: "dynamic"
976
+ };
951
977
  return {
952
978
  kind: "class",
953
- className: getRecipeClassNames(name, config.variants, staticOnly, ctx.utility.separator, format)
979
+ className: getRecipeClassNames(name, config.variants, merged, ctx.utility.separator, format)
980
+ };
981
+ }
982
+ if (!everyEffectSurvives()) return {
983
+ kind: "decline",
984
+ reason: "dynamic"
985
+ };
986
+ if (effectful.length > 1) {
987
+ const variantOrder = Object.keys(config.variants ?? {});
988
+ const keys = effectful.map((entry) => entry.key);
989
+ if ([...keys].sort((a, b) => variantOrder.indexOf(a) - variantOrder.indexOf(b)).join("\0") !== keys.join("\0")) return {
990
+ kind: "decline",
991
+ reason: "dynamic"
954
992
  };
955
993
  }
994
+ for (const key of [...dynamicAxes.keys()]) if (!Object.hasOwn(config.variants ?? {}, key)) dynamicAxes.delete(key);
995
+ if (dynamicAxes.size === 0) return {
996
+ kind: "class",
997
+ className: getRecipeClassNames(name, config.variants, merged, ctx.utility.separator, format)
998
+ };
956
999
  const ownClass = format(name);
957
1000
  const parts = [JSON.stringify(ownClass)];
958
1001
  const classNames = [ownClass];
@@ -961,7 +1004,8 @@ const lowerRecipeCall = (call, entry, ctx, resolvedSelection) => {
961
1004
  if (expression === void 0) {
962
1005
  const value = merged[key];
963
1006
  if (value == null) continue;
964
- if (config.variants?.[key]?.[value] == null) continue;
1007
+ const declared = config.variants?.[key];
1008
+ if (!declared || !Object.hasOwn(declared, value) || declared[value] == null) continue;
965
1009
  const className = format(`${name}--${key}${ctx.utility.separator}${withoutSpace(value)}`);
966
1010
  parts.push(JSON.stringify(` ${className}`));
967
1011
  classNames.push(className);
@@ -1608,10 +1652,7 @@ const foldSource = (options) => {
1608
1652
  recipeCalls.set(name, tally);
1609
1653
  const resolvedSelection = item.data?.length === 1 ? item.data[0] : void 0;
1610
1654
  const entry = recipeConfigs.get(name);
1611
- const lowered = Node.isCallExpression(call) && call.getArguments().every(isInertExpression) ? lowerRecipeCall(call, entry, ctx, resolvedSelection) : {
1612
- kind: "decline",
1613
- reason: "dynamic"
1614
- };
1655
+ const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
1615
1656
  if (lowered.kind === "expression") {
1616
1657
  const helper = ensureRecipeHelperImport(call, isBambooCssModule, isGeneratedCssModule, isShadowed);
1617
1658
  if (helper) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "Vite integration for Bamboo CSS",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -37,18 +37,18 @@
37
37
  "dependencies": {
38
38
  "magic-string": "0.30.21",
39
39
  "ts-morph": "28.0.0",
40
- "@bamboocss/core": "1.23.0",
41
- "@bamboocss/extractor": "1.23.0",
42
- "@bamboocss/config": "1.23.0",
43
- "@bamboocss/node": "1.23.0",
44
- "@bamboocss/logger": "1.23.0",
45
- "@bamboocss/shared": "1.23.0",
46
- "@bamboocss/types": "1.23.0"
40
+ "@bamboocss/config": "1.24.0",
41
+ "@bamboocss/core": "1.24.0",
42
+ "@bamboocss/extractor": "1.24.0",
43
+ "@bamboocss/logger": "1.24.0",
44
+ "@bamboocss/node": "1.24.0",
45
+ "@bamboocss/shared": "1.24.0",
46
+ "@bamboocss/types": "1.24.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@jridgewell/trace-mapping": "^0.3.31",
50
50
  "vite": "7.2.6",
51
- "@bamboocss/fixture": "1.23.0"
51
+ "@bamboocss/fixture": "1.24.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "vite": ">=5"